GuGu拼音游戏 (GuGu PinYin)
一句话总结:一款用“Q弹交互”和“森林探险”包装的,让孩子在点点按按中不知不觉学完21天拼音课程的数字化学习手账。
🌟 产品定位:沉浸式·游戏化拼音启蒙
“不只是学拼音,更是一场森林探险。”
我们正在打造一款对标 Duolingo(多邻国) 体验的,高互动、高反馈的儿童拼音启蒙 App。核心在于将枯燥的“跟读”转化为有趣的“点击与交互”。
🎨 1. 视觉主题:森林探险 & 贴纸手账
- 世界观:以 “热带雨林” 为舞台,动物伙伴伴学。
- 首页面交互:采用 独立卡片左右轮播 (Independent Card Horizontal Carousel) 设计,无限循环展示课程卡片。
- 进度展示:点击用户头像弹出 普通弹框 (Standard Popup),清晰展示学习数据。
- UI 风格:“贴纸风” (Sticker UI),配合白色描边和圆润导角,营造“想去撕下来”的触感。
🎮 2. 交互体验:Juicy (多汁/Q弹)
拒绝生硬的 UI,追求**“像果冻一样”**的反馈:
- 触感反馈:
JuicyButton带来的挤压变形、回弹效果。 - 视觉反馈:点击后的水波纹 (Ripple)、粒子爆发。
- 流畅度:场景切换采用 无缝黑屏淡入淡出 (Seamless Fade Transition),拒绝生硬截断。
📚 3. 学习架构:21天卡片流
- 微课模式:将课程打散为一张张独立的 “卡片” (Lesson Card),降低认知负荷。
- 故事板驱动:可视化 Storyboard 配置流程,像导演一样安排学习剧本。
- 成长路径:明确的 21天课程地图。
📊 数据架构:本地化存储
- JSON 存档:基于
AppSave系统的本地化存储,记录firstLoginDate、currentStreak、totalLearningSeconds等核心指标。 - 数据埋点:自动统计活跃天数与学习时长,为未来的成就系统打下基础。
📱 UI 适配方案:UIRootAdapter
针对“手机宽屏”与“iPad方屏”差异,采用高度锚定缩放策略:
- iPad 原生保留:在 iPad 比例下不修改任何数据,保持预制体原始显示效果。
- 手机动态缩放:检测到宽屏时,通过
localScale强行匹配屏幕高度,并反向延展sizeDelta.x扩充逻辑画布。 - 内容自适应:利用横向逻辑空间的增加,自动展示更多卡片或侧边元素,解决超宽屏“顶天立地”导致的画面被切问题。
功能特点
🎮 核心游戏机制
- 萤火虫互动系统:21只萤火虫对应21个学习单元
- 触摸交互:支持点击和拖拽操作
- 实时预览:触摸萤火虫时显示对应课程内容
- 进度跟踪:记录学习进度,已学习的萤火虫保持点亮状态
📚 学习内容
- 拼音基础:单韵母、声母、复韵母、前后鼻韵母
- 系统学习:按照标准拼音教学顺序安排
- 巩固复习:定期复习已学内容
🎯 学习进度
项目包含完整的21课拼音学习体系:
- aoe - 单韵母学习
- iuü - 单韵母扩展
- 巩固复习6个单韵母
- bpmf - 声母学习
- dtnl - 声母扩展
- 巩固复习8个声母
- gkh - 声母进阶
- jqx - 特殊拼读规则
- 巩固复习14个声母
- zcs - 平舌音
- zh ch sh r - 翘舌音
- yw - 半元音
- 巩固复习23个声母
- ai ei ui - 复韵母
- ao ou iu - 复韵母扩展
- ie üe er - 特殊复韵母
- 巩固复习9个复韵母
- an en in un ün - 前鼻韵母
- ang eng ing ong - 后鼻韵母
- 巩固复习前、后鼻韵母
- 总复习
🔧 技术特点
- Unity 3D引擎:跨平台游戏开发
- 性能优化:场景性能监控系统
- 异步加载:分帧处理避免卡顿
- 资源管理:高效的资源加载和释放机制
技术架构
🏗️ 新架构特性(2024年重构)
本项目采用流水线式模块化架构,专为AI协作和快速迭代设计:
流水线式页面管理
- LessonFlowController:总控管理器,统一调度页面流转
- ILessonPage接口:标准化页面生命周期(Initialize → StartPage → StopPage → ResetPage)
- 模块独立性:每个页面完全独立,互不耦合
- 资源统一管理:AudioSource、UI按钮等共用资源集中分配
AI协作友好设计
- 设计钩子:代码中嵌入清晰的架构说明注释
- 一叶知秋:看任何一个文件都能理解整体架构
- 标准化接口:统一的命名规范和开发模式
- 快速测试:支持从任意课程页面开始调试(
startFromPage参数)
核心组件
新架构组件
- LessonFlowController:流水线总控管理器
- Lesson01Page1/2/3:独立的课程页面模块
- LessonFlowSetupWizard:一键配置工具
- VoiceRecorder:跨平台录音组件
- AudioWaveDisplay:实时音波图显示
原有组件
- FireflyLessonManager:萤火虫课程管理器
- ScenePerformanceMonitor:场景性能监控
- AllConst:游戏常量和配置管理
- AudioManager:音频统一管理
主要系统
- 流水线课程系统:模块化页面管理,支持快速切换和测试
- 智能录音系统:支持iOS/Android/PC平台的语音录制和回放
- 实时音波图:超高灵敏度音量检测和可视化显示
- 交互系统:触摸事件处理和手势识别
- 进度系统:学习进度跟踪和状态管理
开发环境
- Unity版本:Unity 2022.3 LTS或更高版本
- 平台支持:iOS、Android
- 开发语言:C#
- 第三方插件:
- Sirenix Odin Inspector
- DOTween动画库
- 各类性能优化插件
项目结构
Assets/
├── Scripts/ # 核心脚本
│ ├── Manager/ # 管理器类
│ │ ├── LessonFlowController.cs # 📋 流水线总控(架构核心)
│ │ ├── AudioManager.cs # 🔊 音频统一管理
│ │ └── ...
│ ├── Interfaces/ # 接口定义
│ │ └── ILessonPage.cs # 📜 页面标准接口(必读)
│ ├── Lessons/Pages/ # 课程页面模块
│ │ ├── Lesson01Page1.cs # 📄 第一页(句子+拼音)
│ │ ├── Lesson01Page2.cs # 📄 第二页(AOE卡片)
│ │ └── Lesson01Page3.cs # 📄 第三页(跟读录音)
│ ├── Audio/Manager/ # 音频系统
│ │ └── VoiceRecorder.cs # 🎤 跨平台录音组件
│ ├── UI/ # UI组件
│ │ └── AudioWaveDisplay.cs # 🌊 实时音波图
│ ├── Setup/ # 配置工具
│ │ └── LessonFlowSetupWizard.cs # 🛠️ 一键配置向导
│ ├── Const/ # 常量定义
│ └── ARCHITECTURE_PATTERN.md # 🎯 架构设计文档(AI协作关键)
├── Images/ # 游戏图片资源
├── Fonts/ # 字体资源
├── Resources/ # 可加载资源
│ └── Audio/ # 音频资源
│ ├── lesson01/ # 第一课音频
│ └── 拼音表/ # 拼音音频库
├── Scenes/ # 游戏场景
└── Plugins/ # 第三方插件🚀 快速开始
开发者快速上手
- 理解架构:先阅读
Assets/Scripts/ARCHITECTURE_PATTERN.md - 配置环境:使用Unity菜单
Tools → GuGu拼音 → 设置课程流水线 - 快速测试:设置
startFromPage = 3直接测试录音功能 - 开始开发:参考现有页面实现,遵循ILessonPage接口
AI模型协作指南
- 架构理解:阅读架构文档理解流水线模式
- 接口学习:掌握ILessonPage的4个标准方法
- 模式复制:按照现有页面模式创建新功能
- 钩子识别:寻找代码中的"设计钩子"注释
安装与运行
- 使用Unity Hub打开项目
- 确保Unity版本为2022.3 LTS或更高
- 导入必要的第三方插件
- 在Unity中打开
Assets/Scenes/00Agree.unity场景 - 点击播放按钮开始游戏
贡献指南
- Fork本项目
- 创建功能分支 (
git checkout -b feature/AmazingFeature) - 提交更改 (
git commit -m 'Add some AmazingFeature') - 推送到分支 (
git push origin feature/AmazingFeature) - 开启Pull Request
许可证
本项目采用私有许可证,仅供内部开发使用。
联系方式
- 开发团队:南京蒲雷坦网络科技有限公司
- 微信公众号:古古识字
- 邮箱:[email protected]
🔍 MVC架构详解:初始化流程和题目呈现机制
MVC架构概览
本项目采用严格的MVC(Model-View-Controller)模式,实现完全的职责分离和单向数据流。这个架构特别适合多邻国式的题型驱动学习应用。
┌─────────────────┐ 事件通知 ┌─────────────────┐
│ │ ──────────────> │ │
│ View Layer │ │ Controller Layer│
│ (UI显示) │ <────────────── │ (流程控制) │
│ │ UI更新指令 │ │
└─────────────────┘ └────────┬───────┘
│
│ 数据读写
▼
┌─────────────────┐
│ │
│ Model Layer │
│ (纯数据) │
│ │
└─────────────────┘核心架构层级
| 层级 | 职责 | 依赖关系 | 关键文件 |
|---|---|---|---|
| Model | 纯数据+业务规则 | 不依赖任何层 | LessonModel.cs, ProgressModel.cs, QuestionModel.cs |
| View | 纯显示+用户输入 | 不依赖Model/Controller | QuestionView.cs, ListenAndChooseView.cs, LessonUIView.cs |
| Controller | 流程控制 | 依赖Model,控制View | LessonController.cs |
| Service | 工具服务 | 被所有层使用 | AudioService.cs |
🚀 初始化流程详解
1. 系统启动阶段 (Scene Load)
入口点:LessonController.cs:62
void Start()
{
InitializeLesson();
}启动序列:
单例初始化 (
LessonController.cs:46-59)- 创建或获取LessonController单例
- 查找并绑定LessonUIView引用
- 保证全局只有一个控制器实例
课程数据创建 (
LessonController.cs:77-107)csharpprivate void CreateTestLesson() { lessonModel = new LessonModel(1, "拼音第一课"); // 创建测试题目1: "bā" var question1 = new QuestionModel(QuestionType.ListenAndChoose, "bā"); question1.options.Add(new OptionModel("bā")); question1.options.Add(new OptionModel("pá")); // ... 更多选项 lessonModel.questions.Add(question1); }进度模型初始化 (
LessonController.cs:72)csharpprogressModel = new ProgressModel(lessonModel.GetTotalQuestions());
2. 数据组合阶段
Model层数据结构:
LessonModel: 课程容器
lessonId: 课程编号lessonName: 课程名称questions: 题目列表- 业务方法:
GetTotalQuestions(),GetQuestion(index)
QuestionModel: 单题数据
questionType: 题型枚举(听音选字、配对等)correctAnswer: 正确答案options: 选项列表audioClip: 对应音频- 验证方法:
ValidateAnswer()
ProgressModel: 进度跟踪
currentQuestionIndex: 当前题目索引correctCount: 正确计数totalQuestions: 总题数- 计算方法:
GetProgress(),GetAccuracy(),IsComplete()
🎯 第一道题呈现流程
1. 题目显示准备 (LessonController.cs:131-161)
private void ShowCurrentQuestion()
{
// 1. 检查是否完成
if (progressModel.IsComplete()) {
CompleteLesson();
return;
}
// 2. 清理上一题
ClearCurrentQuestion();
// 3. 从Model获取当前题目数据
var questionModel = lessonModel.GetQuestion(progressModel.currentQuestionIndex);
// 4. 根据题型创建对应View
var questionGO = InstantiateQuestionByType(questionModel.questionType);
currentQuestionView = questionGO.GetComponent<QuestionView>();
// 5. 初始化View并绑定事件
currentQuestionView.Initialize(questionModel);
currentQuestionView.OnAnswerSelected += OnAnswerSelected;
currentQuestionView.OnAnswerSubmitted += OnAnswerSubmitted;
// 6. 更新UI进度
UpdateProgress();
}2. View初始化过程 (ListenAndChooseView.cs:56-87)
public override void Initialize(QuestionModel model)
{
base.Initialize(model);
currentAudioClip = model.audioClip;
StartCoroutine(AutoPlayAudioOnStart()); // 自动播放音频
}
protected override void SetupUI()
{
correctAnswer = questionModel.correctAnswer;
// 遍历选项,设置UI内容
for (int i = 0; i < optionCards.Count && i < questionModel.options.Count; i++)
{
var option = questionModel.options[i];
var card = optionCards[i];
card.label.text = option.text; // 设置选项文字
card.cardObject.SetActive(true);
if (option.text == correctAnswer) {
correctIndex = i; // 记录正确答案索引
}
ResetCardVisual(card); // 重置视觉状态
}
}3. 用户交互响应链
事件流程:用户点击选项 → 显示CHECK按钮 → 点击CHECK → 验证答案 → 显示反馈
选项点击 (
ListenAndChooseView.cs:117-134)csharpprivate void OnOptionSelected(int index) { if (isAnswerLocked) return; selectedIndex = index; ShowSelectedEffect(optionCards[index]); // 视觉反馈 NotifyAnswerSelected(); // 通知Controller }Controller响应 (
LessonController.cs:191-197)csharpprivate void OnAnswerSelected() { if (uiView != null) { uiView.ShowCheckState(OnCheckButtonClicked); // 显示CHECK按钮 } }CHECK按钮处理 (
LessonController.cs:199-205)csharpprivate void OnCheckButtonClicked() { if (currentQuestionView != null) { currentQuestionView.SubmitAnswer(); // 触发验证 } }答案验证 (
ListenAndChooseView.cs:147-150)csharpprotected override bool ValidateAnswer() { return selectedIndex == correctIndex; // 纯业务逻辑验证 }结果处理 (
LessonController.cs:207-227)csharpprivate void OnAnswerSubmitted(bool isCorrect) { progressModel.AnswerQuestion(isCorrect); // 更新Model if (isCorrect) { uiView.ShowCorrectState(OnContinueButtonClicked); // 正确UI } else { uiView.ShowIncorrectState(OnRetryButtonClicked); // 错误UI } OnQuestionComplete?.Invoke(isCorrect); // 事件通知 }
🔄 状态流转机制
按钮状态管理 (LessonUIView.cs:72-88)
系统通过统一的按钮状态机制管理用户界面:
public enum ButtonState
{
NormalNext, // 普通下一步
Unselected, // 未选择状态
Selected, // 已选择(显示CHECK)
Incorrect, // 答错(显示RETRY)
Correct, // 答对(显示CONTINUE)
Disabled // 禁用状态
}状态转换流程:
Disabled → Unselected → Selected → (Correct|Incorrect) → Unselected进度计算和更新
进度模型 (ProgressModel.cs:36-51)
public float GetProgress()
{
if (totalQuestions == 0) return 0f;
return (float)currentQuestionIndex / totalQuestions;
}
public float GetAccuracy()
{
if (currentQuestionIndex == 0) return 0f;
return (float)correctCount / currentQuestionIndex;
}🏗️ 架构优势
- 严格分离:Model/View/Controller职责清晰,互不依赖
- 易于测试:每层可独立单元测试
- 易于扩展:新题型只需继承基类,遵循协议
- 统一流程:所有题型都走相同的数据流
- 可维护性:修改UI不影响逻辑,修改逻辑不影响UI
🚀 快速扩展新题型
要添加新题型(如拖拽配对),只需:
- 扩展Model:在
QuestionType枚举中添加新类型 - 创建View:继承
QuestionView基类,实现抽象方法 - 配置Controller:在
InstantiateQuestionByType()中添加case分支 - 创建预制体:设计UI并配置到LessonController
整个过程遵循相同的MVC模式,保证代码一致性和可维护性。
让孩子在游戏中快乐学习拼音!
Unity Duolingo 风格交互复刻开发白皮书
1. 核心设计宗旨 (Design Philosophy)
本项目的最高准则是 "Juicy UI" (多汁的 UI)。所有可交互元素必须具备物理反馈。
- 拒绝对话框式弹窗:尽量使用底部抽屉 (Bottom Sheet) 或全屏推入。
- 拒绝生硬切换:所有状态变化必须有过渡动画 (Tween)。
- 视觉欺骗:通过 2D 图层的 Y 轴位移模拟 3D 按压感。
2. 美术资源需求清单 (Asset Requirements)
为了达到 1:1 的效果,美术资源必须严格按照**“三明治结构”**拆分。请将以下需求发给你的 UI 设计师。
2.1 核心控件:通用果冻按钮 (The Jelly Button)
Duolingo 的按钮不是一张图,而是由两部分组成的。
- 资源 A:顶层表面 (Face)
- 格式:PNG (带透明通道)
- 形状:圆角矩形,圆角半径极大(通常是高度的 50%)。
- 颜色:亮色(如亮绿 #58CC02)。
- 高光:顶部边缘有微妙的亮白色内描边(增加塑料质感)。
- 资源 B:底层阴影/厚度 (Edge/Shadow)
- 格式:PNG
- 形状:同上,完全一致。
- 颜色:深色(如深绿 #2B8500)。
- 用途:垫在 A 的下面,用于在 A 抬起时露出“侧边厚度”。
【关键要求】:所有 UI 面板、卡片、按钮,必须全部切成 9-Slice (九宫格) 格式,确保在 Unity 中任意拉伸圆角不失真。
2.2 图标与插画
- 格式:SVG (推荐使用 Vector Graphics 包导入) 或 高清 PNG。
- 风格:粗描边 (Stroke width = 3px~5px),扁平化,无渐变或微渐变。
2.3 字体
- 主要字体:寻找类似 Feather Bold 或 DIN Round 的圆体字。
- 备选:Nunito (Google Fonts, Bold & ExtraBold)。
3. 交互技术细节 (Interaction Mechanics)
3.1 核心:3D-in-2D 按钮逻辑 (DoTween 实现)
这是多邻国的灵魂。按钮点击不是缩放,而是 Y 轴下沉。
- 组件结构:
[Button_Root] (空物体,挂载 Button 脚本, Height = 60) ├── [Shadow_Layer] (Image, Color = Dark, Anchor = Stretch) └── [Face_Layer] (Image, Color = Light, Anchor = Stretch, Pivot Y=0.5) └── [Content] (Text/Icon) - 初始状态设定:
Face_Layer的AnchoredPosition.y设置为 12 (即厚度)。Button_Root的高度要包含这额外的 12px。 - 交互脚本 (DoTween):csharp
// 必须引用的命名空间 using DG.Tweening; public class JuicyButton : MonoBehaviour, IPointerDownHandler, IPointerUpHandler { [Header("References")] public RectTransform faceLayer; public Image faceImage; // 用于改色 [Header("Settings")] public float pressDepth = 12f; // 下压深度,必须等于初始 Y 偏移量 public float duration = 0.1f; // 状态机,防止动画冲突 private bool _isPressed = false; public void OnPointerDown(PointerEventData eventData) { if (!interactable) return; _isPressed = true; // 1. 瞬间下压 (或极快) - 模拟受力 faceLayer.DOKill(); // 清除之前的动画 faceLayer.DOAnchorPosY(0f, 0.05f).SetEase(Ease.OutQuad); // 2. 颜色变暗 (模拟被遮挡光线,可选) // faceImage.DOColor(pressedColor, 0.05f); // 3. 触觉反馈 (必做!) HapticFeedback.Light(); } public void OnPointerUp(PointerEventData eventData) { if (!_isPressed) return; _isPressed = false; // 1. 弹性回弹 (这是精髓) // OutElastic 带来“果冻”般的抖动感 faceLayer.DOKill(); faceLayer.DOAnchorPosY(pressDepth, 0.3f).SetEase(Ease.OutElastic, 0.8f); // 2. 触发点击逻辑 // 建议延迟 0.1s 执行逻辑,让用户看清回弹动画 DOVirtual.DelayedCall(0.1f, () => onClick.Invoke()); } }
3.2 列表选项卡 (Selection Card)
针对你上传的截图,选项卡有三种状态。
- Normal (未选中):
- Face: 白色
- Shadow: 浅灰 (#E5E5E5)
- Border: 浅灰 (#E5E5E5) 2px 宽
- Selected (选中):
- Face: 极淡蓝 (#DDF4FF)
- Shadow: 亮蓝 (#1CB0F6)
- Border: 亮蓝 (#1CB0F6) 2px 宽
- 文本: 变蓝
- Wrong (错误 - 答题模式用):
- Face: 极淡红
- Shadow: 红色
- Border: 红色
切换动画:当用户点击另一个选项时,不要瞬间切换。
// 选中动画
transform.DOPunchScale(new Vector3(0.05f, 0.05f, 0), 0.2f, 10, 1); // 轻微缩放震动
borderImage.DOColor(blueColor, 0.2f);
faceImage.DOColor(lightBlueColor, 0.2f);3.3 进度条连贯性 (Progress Bar)
逻辑:进度条增长不能是线性的,要有“刹车感”。 DoTween 写法:
// value 是 0 到 1
progressBarFill.DOFillAmount(targetValue, 0.5f).SetEase(Ease.OutBack);注意:OutBack 会让进度条稍微冲过头一点点再缩回来,这是非常细腻的“多邻国味”。
4. 衔接与转场 (Transitions & Flow)
多邻国没有“黑屏加载”。所有界面都是滑进来或弹出来的。
4.1 页面入场 (Page Enter)
假设进入一个新的答题卡片。
- 动画:从屏幕右侧滑入。
- DoTween:csharp
rectTransform.anchoredPosition = new Vector2(Screen.width, 0); // 初始在右侧外 rectTransform.DOAnchorPosX(0, 0.4f).SetEase(Ease.OutCubic); // 减速滑入
4.2 底部抽屉 (Bottom Sheet / Feedback)
当用户点击 "CHECK" (检查) 后,底部会弹出一个结果栏(正确是绿色,错误是红色)。
- 动画:从屏幕底部迅速升起。
- 连贯性要求:
- 点击 CHECK 按钮 -> 按钮下压。
- 播放音效 ("Ding" or "Womp").
- 底部抽屉
DOAnchorPosY升起。 - 同时,角色 (Duo) 播放对应动画(开心跳跃 或 失望哭泣)。
5. 角色系统联动 (Character System)
为了让“壳”不仅是 UI,你需要一个 CharacterReactionController。
- 状态定义:
Idle(眨眼,轻微呼吸)Happy(回答正确)Sad(回答错误)Talking(对话泡泡出现时)
- 实现:使用 Spine 最好,如果没有,使用序列帧或简单的 DoTween 缩放/旋转。
- 例子:回答正确时,让猫头鹰跳一下。csharp
characterRect.DOJump(characterRect.anchoredPosition, 30f, 1, 0.5f);
6. 音效与震动 (Audio & Haptics) - 灵魂所在
没有声音的多邻国是也是没有灵魂的。
- SFX 清单:
Click_Normal: 短促的木头敲击声。Click_Toggle: 类似于气泡破裂的轻响。Success_Chime: 标志性的上行三和弦。Error_Buzz: 沉闷的低音。
- 震动 (Haptics):使用
CandyCoded.Haptic或 iOS 原生插件。Light: 普通按钮点击。Medium: 选中答案。Heavy/Double: 答错时的惩罚反馈。
7. 总结:开发验收标准
在你的 Unity 工程 Review 时,请检查以下几点:
- 按压感:点击任何可点击物体,必须看到它“凹”下去了。
- 松手回弹:松开手指时,必须看到它弹回原位,且带有一点点 Q 弹。
- 声音同步:按下瞬间必须有声音,不能有延迟。
- 无死板动画:所有位移、缩放,都必须使用 Ease Function (推荐
OutBack,OutElastic,OutQuad),严禁使用Linear。
8. Lottie 动画集成指南 (Vector Animation)
本项目集成了 unity-rlottie 插件以支持高质量的矢量动画。
✅ 最佳实践配置
为了确保 Lottie 动画在 Unity 中 高清 且 方向正确,请遵循以下规范:
- 组件使用:使用
AnimatedImage(Namespace:LottiePlugin.UI)。 - 纹理设置 (关键):
- Texture Width/Height:必须设置为 1024 或 2048 (默认为128会导致模糊)。
- 即使是矢量文件,插件也是通过渲染纹理(RenderTexture)来实现的,因此分辨率决定了清晰度。
- 坐标修正 (关键):
- Unity (左下原点) 与 Lottie (左上原点) Y轴相反。
- 必须将 RectTransform 的 Scale Y 设置为 -1 以修正倒置问题。
- 预制体:
- 推荐使用
Assets/Prefabs/Lottie/LottiePlayer预制体,已预设好上述参数。
- 推荐使用
