22.Unity基础实践项目总结

  1. 22.总结
    1. 22.1 知识点
      1. 学习的主要内容
      2. 总结目的
      3. 记住几句话
      4. 商业游戏中的配置表
      5. 商业游戏制作套路
      6. 如何和策划配合设计配置表
    2. 22.2 核心要点速览
      1. 需求与模块边界
      2. UI 面板架构
      3. GameDataMgr 与数据类
      4. 场景中核心对象
      5. 建议实现顺序
      6. 工程准备与面板基类
      7. 开始界面与 DrawCall
      8. 设置界面与音效数据管线
      9. BKMusic 与 GameDataMgr 联动
      10. 排行榜数据与列表 UI
      11. 选角:配置、资源与交互
      12. 组间实现顺序建议
      13. 游戏内 HUD 与流程面板
      14. 确定退出与场景回退
      15. 结束界面与排行榜写入
      16. 多摄像机与玩家移动
      17. 子弹数据与 BulletObject
      18. 开火点 FireObject 与 Main
      19. 小功能补充
    3. 22.3 面试题精选
      1. 进阶题
        1. 1. 为什么用 BasePanel 统一 Show、Hide、Init
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        2. 2. GameDataMgr 集中管理多类 Data 的意义
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        3. 3. SettingPanel 打开拉数与关闭存档
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        4. 4. XmlAttribute 用在 RankInfo 上的取舍
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        5. 5. BasePanel 静态 Instance 与多实例
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        6. 6. Invoke 延时销毁子弹
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        7. 7. 为什么用 QuitPanel 而不是直接切场景
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        8. 8. PlayerObject 左右移动为何用世界空间 Translate
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
      2. 深度题
        1. 1. RankPanel 与 RankItem 拆分的工程含义
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        2. 2. AddRankData 的排序方向与 RemoveAt
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        3. 3. 选角界面射线相机与浮动坐标空间
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        4. 4. GameOverPanel 记录时间与界面展示
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        5. 5. FireObject 里屏幕坐标 z 与分辨率
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章

22.总结


22.1 知识点

学习的主要内容

总结目的

记住几句话

商业游戏中的配置表

商业游戏制作套路

如何和策划配合设计配置表


22.2 核心要点速览

需求与模块边界

飞机小游戏按界面与流程拆块:开始场景里有多块业务面板,进入战斗后有游戏内 UI,结束时需要结算与返回入口,退出前还要单独确认。数据侧则要覆盖角色、排行、音量、子弹与炮台等配置,后面课程会按这个地图逐个落地。

要交付什么
开始场景面板族 开始、选角、排行榜、设置等,各自独立但共用同一套面板基类约定
游戏内 UI 战斗中 HUD 与信息展示,和开始/结束场景面板分开生命周期
收尾与确认 游戏结束面板 + 确定退出面板,避免误触直接关进程
数据与存档 角色、排行、音效设置、子弹与开火参数等,由统一数据管理器收口,避免脚本各写各的持久化

UI 面板架构

  • BasePanel 泛型基类:所有业务面板走同一套对外能力,图中给出显示、隐藏、初始化,以及静态侧可复用的入口;子类只填具体资源引用和交互逻辑。
  • 子类固定两块面板控件管 Inspector 里拖的 Button、Text、Slider 等;面板逻辑处理管点击、刷新、和 GameDataMgr 或场景管理器通信。这样改布局不必动业务判断,改流程不必在场景里满世界找引用。
  • RankPanel 与 RankItem:排行榜是动态列表,单行抽成 RankItem 组合控件,面板负责列表数据源与显隐,条目负责这一行的控件赋值和样式,后续加滚动、对象池或换皮肤都集中改条目 Prefab。
面板 在结构图里的角色
BeginPanel 开始界面
ChoosePanel 选择角色
RankPanel 排行榜,内含 RankItem
SettingPanel 设置
游戏 HUD / 结束 / 确定退出 与战斗流程、流程收尾对应

GameDataMgr 与数据类

  • GameDataMgr:单点持有各 数据对象,并承担「从哪里读、写到哪里、何时刷新」的协调;面板和业务脚本优先问管理器拿数据,而不是互相直接引用对方的内部字段。
  • 五类数据对象RoleData 角色信息,RankData 排行,MusicData 玩家音效相关设置,BulletData 子弹参数,FireData 开火炮台参数。玩法扩展时先想清楚新字段落在哪一类,再接到管理器,避免散落的 static 或 PlayerPrefs 键名失控。

场景中核心对象

  • BKMusic:背景音乐单独成管理对象,与战斗实体解耦,音量、暂停、切场景不断音等逻辑有明确归属。
  • BulletObject / FireObject / PlayerObject:三类都按「数据相关 + 行为处理」划分。数据侧放速度、伤害、冷却、血量等可配置项;行为侧放移动、碰撞、输入、生成子弹等帧更新逻辑。后面接配置表或 Prefab 变体时,改数据不改表现或反之,边界清楚。

建议实现顺序

  1. 按面板清单搭 BasePanel 派生类与 Prefab,先把开始场景几条线的 Show、Hide、跳转跑通。
  2. 建立 GameDataMgr 与五类 Data 的实例与读写接口,面板只通过管理器取数,顺便统一存档键或文件路径。
  3. 游戏场景内再挂 PlayerObject、FireObject、BulletObject,严格按数据脚本与行为脚本分工实现移动、开火、命中。
  4. BKMusic 与全局音量设置联动,设置面板改 MusicData 后立刻反映到音频组件。

工程准备与面板基类

  • 工程侧:创建 Unity 工程,导入美术资源、NGUI、以及课程用的 XML 数据管理器;目录上习惯保留 Resources(运行时 Resources.Load)与 StreamingAssets(可读原始配置或首包数据)。
  • BasePanel<T> 约定:T 约束为 class;静态只读 Instance,在 Awake 里把 this 转成 T 赋给单例;Start 里调用抽象方法 Init,子类在 Init 里只做控件引用与事件绑定;ShowMe / HideMe 默认切换 GameObject 显隐。
  • 子类写法形如 BeginPanel : BasePanel<BeginPanel>,外部通过 BeginPanel.Instance 打开面板,和 NGUI 的 EventDelegateUIButton.onClick 等配合。
成员 作用
Instance 取当前场景里该面板脚本挂载对象对应的单例引用
Init 绑按钮、Slider、Toggle;初始有的面板会先 HideMe
ShowMe / HideMe 默认 SetActive(true/false),子类可重写夹带读档、存档

注意:静态 Instance 只保留「最后一个执行 Awake 的同类组件」。本系列按「每种面板场景里只放一个实例」用,若同类型多实例会互相覆盖引用,需要改用管理器字典或取消泛型单例模式。

开始界面与 DrawCall

  • 开始场景:摄像机 Solid Color 黑底,可叠粒子做背景氛围。
  • 因选角界面要摆 3D 飞机模型,使用 3D NGUI 根;主摄像机 Culling Mask 去掉 UI 层,仅 UI 摄像机渲染 UI,避免重复绘制与深度错乱。拼控件前定好 UIRoot 缩放与参考分辨率,后面选角、排行等面板都复用这套根。
  • BeginPanel:标题 Label + 开始、排行、设置、退出四个 UIButton;锚点按多分辨率适配。开始按钮先占位打印「显示选角」,后续改为打开 ChoosePanel 并隐藏自己;退出调用 Application.Quit
  • DrawCall:若字体与图集、按钮图片在深度上穿插,合批会被打断。把文字控件的深度整体调高,让同材质、同图集的控件连续绘制,从而降低 DrawCall。

设置界面与音效数据管线

  • 面板层:半透明全屏底图需带 Collider,避免点到背后界面;关闭、双 UISlider(音乐/音效)、双 UIToggle(开关),标题与说明文字同样做分辨率适配。
  • SettingPanel 典型节奏:Init 里给 Slider/Toggle 注册 onChange,内部调用 GameDataMgrSetMusicValueSetSoundValueSetMusicIsOpenSetSoundIsOpenShowMe 里用 musicData 回填控件;HideMe 末尾 SaveMusicData,把内存数据交给 XmlDataMgr 落盘。
  • MusicData 字段:musicValue / soundValue 取 0~1;musicIsOpenSoundIsOpen(注意与音效开关对应,别和音乐开关混在注释里)。
// ShowMe:从管理器取数刷新控件
MusicData data = GameDataMgr.Instance.musicData;
togMusic.value = data.musicIsOpen;
sliderMusic.value = data.musicValue;
// …

// HideMe:关闭面板时保存
GameDataMgr.Instance.SaveMusicData();

BKMusic 与 GameDataMgr 联动

  • 场景里单独空物体挂 AudioSourceBKMusicAwakeAudioSource,用 GameDataMgr 里已加载的 musicData 做一次静音与音量初始化。
  • SetBKMusicIsOpenmute = !isOpenSetBKMusicValue:写 volume。在 GameDataMgr.SetMusicIsOpen / SetMusicValue 中除了改 musicData 字段,再调用 BKMusic.Instance 的上述方法,才能把设置面板的操作变成「听得见」的变化;音效大小与开关则留到真正播放音效处再消费 musicData

排行榜数据与列表 UI

  • RankDatarankListRankInfonametime(秒),字段标 XmlAttribute,序列化成 XML 时名字与成绩写在标签属性里,文件更短、人工 diff 更直观。
  • GameDataMgr 构造中 LoadData(typeof(RankData), “RankData”)AddRankData 追加一条、按 time 排序(课中实现为较大时间排前)、超过 20 条删下标 20 的一条,再 SaveData
  • RankPanel:维护 List<RankItem>ShowMe 时取 rankData.rankList,索引已有则 InitInfo 刷新,不够则 Resources.Load<GameObject>("UI/RankItem") 实例化,设父为 UIScrollViewtransform,用固定步长摆 localPosition。课中假设列表条数只增不减,故不回收条目。
  • RankItem.InitInfo:把总秒数拆成 h / m / s 拼字符串显示;BeginPanel 排行按钮打开 RankPanel.Instance.ShowMe

选角:配置、资源与交互

  • RoleInfohpspeedvolumeresNamescale 等标 XmlAttributeRoleDataroleList。与 RankData 不同:RoleData 来自 StreamingAssets 下配死的 RoleData.xml,属版本内平衡配置;RankData 随玩家长期读写,属存档。
  • 飞机 Prefab 放进 Resources 路径下,与 resName 对齐;GameDataMgr 增加 nowSelHeroIndex 与 **GetNowSelHeroInfo()**。
  • ChoosePanel:左右按钮在 0 … Count-1 间循环;ChangeNowHeroDestroy 旧飞机,再 Resources.Load 实例化、父到 heroPoslocalScale = Vector3.one * scalelayer 设为 UI;下方十条小格用 SetActive 表示血量、速度、体积档位。
  • ShowMe 把索引归零并刷新模型;HideMe 销毁展示用飞机。开始游戏 调 **SceneManager.LoadScene(“GameScene”)**;关闭回到 BeginPanel.ShowMe
  • Update:对 heroPosMathf.Sin 做轻微上下浮动,位移用世界空间,避免父节点带倾角时本地坐标飘移;射线用 NGUI 所用摄像机 ScreenPointToRay,Layer 选 UI,才能点在飞机碰撞体上拖动旋转,而不被错误摄像机或层挡住。

组间实现顺序建议

  1. Resources / StreamingAssetsBasePanel 就绪 → 搭 BeginPanel 主流程按钮。
  2. SettingPanel 拼层与事件 → MusicData + GameDataMgr 存取 → BKMusic 实机出声。
  3. RankData / AddRankDataRankItem Prefab + RankPanel.ShowMe 动态行。
  4. RoleData.xml + Resources 飞机 → ChoosePanel 切换模型与进 GameScene
  5. GameSceneGamePanel 计时与血量、QuitPanel 二次确认、GameOverPanel 提交排行;Main 动态实例化玩家并挂 PlayerObject
  6. BulletData / FireData XML,FireObject 八宫格开火与 BulletObject 移动;最后补子弹层、鼠标点消弹、BGM。

游戏内 HUD 与流程面板

  • GamePanel:继承 GamePanel : BasePanel<GamePanel>nowTimeUpdate 里累加 Time.deltaTime,按 h/m/s 拼到 labTime(写法与排行 RankItem 时间展示同套路)。ChangeHp 遍历 hpObjs,用 i < hp 控制显隐,与选角面板属性块一致。
  • 返回键先占位,后接 **QuitPanel.Instance.ShowMe()**;课中可用 ChangeHp(5) 自测,正式流程由 Main 按角色血量刷新。

确定退出与场景回退

  • QuitPanel:确认 **SceneManager.LoadScene(“BeginScene”)**;取消只 HideMe。初始化 HideMe,由 GamePanel 返回键弹出。
  • 避免无确认直接退:先叠确认层,再决定是否卸载 GameScene

结束界面与排行榜写入

  • GameOverPanelShowMeendTime = (int)GamePanel.Instance.nowTime(存秒数),labTime.text 复用 GamePanel.labTime 的展示字符串,保证「看见的和存的一致」。
  • 确定:**AddRankData(inputName.value, endTime)**,再回 BeginScene。姓名来自 UIInput

多摄像机与玩家移动

  • 新增特效层:主摄像机可只渲特效;再建摄像机渲默认层游戏物体;深度顺序一般为特效 → 世界物体 → NGUI。原主摄像机标签可改为普通,新摄像机标为 MainCamera,便于 Camera.main 与边界检测一致。
  • PlayerObjectHorizontal/Vertical 输入;左右倾角用 Quaternion.AngleAxis(±20°, Vector3.forward)Slerp;前后 Translate( forward ),左右 Translate( right, Space.World ) 减轻「旋转后斜向漂移」。WorldToScreenPoint 做屏边夹紧,溢出时恢复 frontPos 对应轴。
  • Dead 打开 GameOverPanelWound 扣血并 GamePanel.Instance.ChangeHp。跟踪弹等需 PlayerObject.Instance 时,在 Awake 设静态实例。

子弹数据与 BulletObject

  • BulletInfotype 1~5 对应直线、正弦横向偏移、右/左抛物线旋转、追踪 Slerp 朝向玩家;forwardSpeedrightSpeedroundSpeedresNamedeadEffReslifeTime 等 Xml 配置。
  • InitInfoInvoke("DelayDestroy", lifeTime)DelayDestroy 里可改为调 Dead 以播放销毁特效。OnTriggerEnterPlayer 标签调用 WoundDead。预制体父节点 Collider 设触发器;玩家 Collider + Rigidbody,关重力。
  • 课末 DelayDestroy 直接 **Dead()**,超时销毁也带特效;子弹单独 Layer,玩家 Camera.main 射线只打子弹层可点消。

开火点 FireObject 与 Main

  • FireInfotype 1 顺序朝玩家、2 散弹;ids"1,10" 表随机子弹 ID 区间;cd 颗间间隔、delay 组间休息、num 条数。
  • FireObjectE_Pos_Type 八宫格;UpdatePosScreenToWorldPointscreenPos.z 与玩家 WorldToScreenPoint 的 z 对齐(课中示例常数如 351,需按项目实测),保证与玩家同深度截面。ResetFireInfonowCD、nowNum 均为 0 时抽下一组数据;UpdateFire 扣 CD、按类型生成子弹并 AddComponent<BulletObject>()
  • Main.StartGetNowSelHeroInfoResources.Load 飞机,AddComponent<PlayerObject>()speed 等用配置乘系数;GamePanel.Instance.ChangeHp 同步 HUD。

小功能补充

  • NGUI EventSystem 默认藏光标,需要鼠标点子弹时取消勾选相关选项。游戏场景可加 BGM 对象延续开始场景音量逻辑。

22.3 面试题精选

进阶题

1. 为什么用 BasePanel 统一 Show、Hide、Init

题目

需求里所有 UI 面板都继承 BasePanel,并统一提供显示、隐藏,以及在子类实现的 Init 里做初始化。这样设计主要解决什么问题?子类里「面板控件」和「面板逻辑处理」拆开又是在规避什么坑?

深入解析

统一基类的直接收益是生命周期一致:UIManager 或场景控制器可以只认 BasePanel,按名字或类型打开、关闭、预加载,不用为每个面板写一套 if-else。初始化集中走子类实现的 Init,可以在进界面时一次性绑事件、拉数据,避免在 Awake 里抢顺序或重复绑定。

子类内拆 控件逻辑:控件层对应序列化引用,动 Prefab 时主要在 Inspector 修;逻辑层处理业务流程和数据回调。若不拆,常见问题是改一个按钮位置导致脚本里路径全断,或逻辑类里既算分又直接操作深层子节点,后期改 UI 必回归测试整块功能。

答题示例

基类统一 Show、Hide、Init,外面管理 UI 时不用每个面板各写一套打开关闭约定,预加载和调试也方便。

子类里控件是 View,逻辑是事件和数据流,拆开以后改 Prefab 布局不容易把业务代码搅成一团,也方便多人分工。

参考文章
  • 1.需求分析

2. GameDataMgr 集中管理多类 Data 的意义

题目

结构图里用 GameDataMgr 统一管理 RoleDataRankDataMusicData 等,而不是让每个面板自己读写 PlayerPrefs 或静态变量。这样做的好处和要注意的边界是什么?

深入解析

集中管理的收益:读写路径单一,存档键名、JSON 字段、加密或云同步以后只改管理器;依赖方向清晰,UI 只依赖管理器接口,不互相引用对方的数据结构;测试与重置时可以在一处 mock 或清空数据。

边界:管理器不要变成上帝类,把「各类 Data 的内部规则」仍放在各自类里,管理器只做聚合、加载顺序、持久化调度;否则所有业务都会挤进 GameDataMgr,比分散静态更难维护。

答题示例

集中管理等于单一数据源,存档键、加载顺序、以后换本地或云端,只动一条管线。

UI 问管理器要数据,面板之间不用互相引用。注意别把业务逻辑全写进管理器,各 Data 类仍要封装自己的字段含义和校验。

参考文章
  • 1.需求分析

3. SettingPanel 打开拉数与关闭存档

题目

SettingPanelShowMe 里用 GameDataMgrmusicData 回填 Slider 和 Toggle,在 HideMe 末尾调用 SaveMusicData。为什么不在每次 onChange 都立刻写 XML?若玩家改了音量后直接杀进程,可能丢什么?

深入解析

滑块 onChange 已经通过 SetMusicValue 等改了内存里的 musicData,并驱动 BKMusic 即时出声;HideMeSaveMusicData 是把当前内存一次性交给 XmlDataMgr,减少磁盘写入次数。若进程在未触发 HideMe 时退出,最后一次内存状态可能未落盘——可补 OnApplicationPause / OnApplicationQuit 再保存,或对关键操作节流写盘。

答题示例

ShowMe 回填是让控件和上次存盘一致,避免打开面板时滑块和实际音量脱节。

拖动时内存和听感已经更新,HideMe 再保存是省 IO。强杀进程可能丢未经过 HideMe 的改动,上线项目一般会 quit 时再存一道。

参考文章
  • 5.开始场景-设置界面-音效数据

4. XmlAttribute 用在 RankInfo 上的取舍

题目

RankInfonametime 使用了 XmlAttribute。和全部用子元素相比,序列化出来的 XML 有什么特点?什么情况下不适合用属性?

深入解析

属性把字段写在元素标签上,XML 行数更少,单条记录一眼扫完;课中也提到「用属性比用很多尖括号更方便」。若字段是长文本、需要换行或嵌套子结构,再用子元素或 CDATA 更合适。

答题示例

属性把名字和成绩写在标签上,文件短,人工 diff 好看。

内容特别长或要嵌套多层时别硬塞属性,改用子节点。

参考文章
  • 7.开始场景-排行榜界面-排行榜数据

5. BasePanel 静态 Instance 与多实例

题目

BasePanel<T>Awake 里执行 instance = this as T。若场景里不小心放了两个 BeginPanel,对外用 BeginPanel.Instance 会得到哪一个?会有什么问题?

深入解析

静态字段只有一份,后执行 Awake 的实例会覆盖先赋的值Instance 指向后挂载的那块。先实例化的对象若仍留在层级里,可能出现事件绑在旧实例、外部却调新实例的 ShowMe,表现为重复或失效。本系列按「每种面板场景里只挂一个」使用。

答题示例

后 Awake 的覆盖前面的,Instance 只认最后一个。

两个面板会乱套,按钮可能绑在老物体上。这课是一种面板一个实例。

参考文章
  • 2.准备工作

6. Invoke 延时销毁子弹

题目

BulletObjectInvoke("DelayDestroy", info.lifeTime) 到点销毁。课里为什么强调「脚本被删了延迟函数就不会执行」,和 Destroy(gameObject, lifeTime) 比各适合什么场景?

深入解析

Invoke 挂在 MonoBehaviour 上,GameObjectDestroy 后未执行的 Invoke 会取消,避免对已销毁对象回调。Destroy(go, t) 由引擎调度延迟销毁;课中 DelayDestroy 改为调 Dead 播特效,用字符串 Invoke 便于改实现。

答题示例

对象没了,Invoke 不会执行,不会空引用。

需要到点先播特效再销毁,就写在 DelayDestroy 里统一走 Dead。

参考文章
  • 18.游戏场景-游戏主逻辑-子弹相关-逻辑处理
  • 21.游戏场景-游戏主逻辑-小功能补充

7. 为什么用 QuitPanel 而不是直接切场景

题目

游戏内返回按钮不直接 LoadScene("BeginScene"),而是先 QuitPanel.Instance.ShowMe()。这样设计解决什么问题?

深入解析

防止误触一次就退出当前局;取消只关面板,GamePanel 计时与状态保持。确定后再加载开始场景,属于常见二次确认模式。

答题示例

防止手滑退回主菜单白打,确认再切场景。

取消只隐藏面板,游戏还在。

参考文章
  • 14.游戏场景-确定退出界面

8. PlayerObject 左右移动为何用世界空间 Translate

题目

PlayerObjectUpdate 里先 Translate(Vector3.forward * vValue * ...),再 Translate(Vector3.right * hValue * ..., Space.World)。课里为何左右用世界右轴?

深入解析

飞机左右倾斜后,本地 right 会随滚转偏斜,若左右沿本地右移动易与旋转耦合成斜向漂移;世界右 保持屏幕左右平移手感,前后仍用机头 forward 对应 WS。边界用 WorldToScreenPointScreen 宽高比较做夹紧。

答题示例

左右用世界右,避免飞机已经倾斜了再沿本地右飞,越飞越斜。

前后还是沿机头 forward,WS 手感分开。

参考文章
  • 16.游戏场景-游戏主逻辑-主玩家相关

深度题

1. RankPanel 与 RankItem 拆分的工程含义

题目

排行榜面板单独做 RankItem 组合控件,而不是在 RankPanel 里手写多组 Text。结合动态列表和后期维护,说明这样拆的利弊。

深入解析

:单行 UI 与单行数据一一对应,刷新列表时对每条数据实例化或复用 RankItem 即可,和无限滚动、对象池的接法标准;换行样式、头像框、段位图标只在条目 Prefab 上改一次。:多一层组件通信,需要约定条目如何接收 RankData 的一条记录、如何回调点击事件,比「一个脚本里十个 SerializeField」上手稍慢。

若排行榜长度固定且极短,合并写在一处也能交付;一旦行数变化或要滚动,RankItem 几乎是必选项,否则复制粘贴控件在 Hierarchy 里不可维护。

答题示例

条目拆出去后,一行 UI 对应一条数据,列表变长只要生成或复用条目,也方便以后做滚动和对象池。

代价是多一层数据绑定和事件约定,但排行这种动态列表不拆条目,后期改样式或加列会很难维护。

参考文章
  • 1.需求分析

2. AddRankData 的排序方向与 RemoveAt

题目

AddRankData 里对 rankList 排序时,若 a.time > b.timea 排前面;条数超过 20 时 RemoveAt(20)。若 time 表示通关用时(秒),「越快名次越靠前」通常应如何比较?当前 RemoveAt(20) 删掉的是排序后第几名?

深入解析

「越快越好」应对 time 升序,让最小值在下标 0。课中实现是 time 大者在前,更偏展示或与其它规则一致,接业务时要先定榜的语义再写比较器。RemoveAt(20) 删除的是 下标 20 的元素,即第 21 条(从 1 数);若排序是升序且要保留前 20 名最快,应确认删掉的是「最慢的一条」——需与排序方向一致,否则删错人。

答题示例

越快越好就按 time 从小到大排,第一名在最前。

RemoveAt(20) 删的是第 21 个元素,具体是谁取决于排序。课里是时间大的靠前,和常见竞速榜相反,接项目要先对齐策划规则再改比较器和截断。

参考文章
  • 7.开始场景-排行榜界面-排行榜数据

3. 选角界面射线相机与浮动坐标空间

题目

ChoosePaneluiCamera.ScreenPointToRay 做点击检测,用 heroPos.Translate(..., Space.World) 做上下浮动。为什么射线要用 NGUI 用的那台 UI 相机?浮动为何用世界空间而不是本地空间?

深入解析

飞机挂在带旋转或倾斜的父节点下时,本地 Up 不等于世界竖直方向,用 Space.World 沿 Vector3.up 微动,视觉上才是「上下飘」。射线若用主摄像机,Layer、深度或与 UI 不同步,容易打不到挂在 UI 层、由 UI 相机渲染的飞机碰撞体;用实际参与渲染与输入的那台 Camera,并配合 UI 的 LayerMask,才能与屏幕点击位置一致。

答题示例

射线从真正画 UI 和飞机的相机发,层和屏幕坐标才对得上,主相机可能挡或层不对。

父物体有倾角时本地 Y 不是竖直,用世界空间沿真正上下动,浮动才自然。

参考文章
  • 12.开始场景-选角界面-逻辑处理

4. GameOverPanel 记录时间与界面展示

题目

GameOverPanel.ShowMe 里用 (int)GamePanel.Instance.nowTime 作为提交排行的秒数,又用 GamePanel.Instance.labTime.text 填结束界面的时间文案。两者来源不同格式,可能出现什么不一致?工程上更稳妥的做法是什么?

深入解析

nowTime 为浮点累计秒,强转 int 向下取整;labTimeUpdate 里按 h/m/s 拼接,与 int 秒在临界秒数附近可能对不齐。稳妥做法是以同一浮点秒数生成「展示字符串」和「存档整数」,或在 ShowMe 只读一次 nowTime 再本地格式化与转 int。

答题示例

浮点转 int 和 Label 里拼出来的字符串可能差一秒,最好都用同一个 nowTime 算。

ShowMe 时取 GamePanel 单例,要保证游戏结束流程里 GamePanel 还活着。

参考文章
  • 15.游戏场景-结束界面

5. FireObject 里屏幕坐标 z 与分辨率

题目

FireObject.UpdatePosscreenPos 的 x、y 按八宫格设到屏边或中部,再把 z 设成与玩家 WorldToScreenPoint 一致的值,最后 Camera.main.ScreenToWorldPoint。为什么缺了正确的 z,开火点会和飞机不在同一深度?

深入解析

ScreenToWorldPoint 在透视相机下会把屏幕点还原到给定深度对应的世界点;z 与玩家不一致时,算出的世界位置在前后方向错位,子弹从「飘在空中」或「在玩家身后」发射。分辨率变时屏上像素变,但深度标定仍依赖相机与玩家相对关系,故换机或改相机应用同样方法重测 z

答题示例

z 决定从屏幕点往前多远打射线进世界,z 不对开火点就和你飞机不在一个截面上。

课里用玩家位置转屏幕看 z,就是让开火点和玩家同深度。

参考文章
  • 20.游戏场景-游戏主逻辑-开火点相关-逻辑处理


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏