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 变体时,改数据不改表现或反之,边界清楚。
建议实现顺序
- 按面板清单搭 BasePanel 派生类与 Prefab,先把开始场景几条线的 Show、Hide、跳转跑通。
- 建立 GameDataMgr 与五类 Data 的实例与读写接口,面板只通过管理器取数,顺便统一存档键或文件路径。
- 游戏场景内再挂 PlayerObject、FireObject、BulletObject,严格按数据脚本与行为脚本分工实现移动、开火、命中。
- 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 的 EventDelegate、UIButton.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,内部调用 GameDataMgr 的 SetMusicValue、SetSoundValue、SetMusicIsOpen、SetSoundIsOpen;ShowMe 里用 musicData 回填控件;HideMe 末尾 SaveMusicData,把内存数据交给 XmlDataMgr 落盘。
- MusicData 字段:musicValue / soundValue 取 0~1;musicIsOpen;SoundIsOpen(注意与音效开关对应,别和音乐开关混在注释里)。
// ShowMe:从管理器取数刷新控件
MusicData data = GameDataMgr.Instance.musicData;
togMusic.value = data.musicIsOpen;
sliderMusic.value = data.musicValue;
// …
// HideMe:关闭面板时保存
GameDataMgr.Instance.SaveMusicData();
BKMusic 与 GameDataMgr 联动
- 场景里单独空物体挂 AudioSource 与 BKMusic:Awake 取 AudioSource,用 GameDataMgr 里已加载的 musicData 做一次静音与音量初始化。
- SetBKMusicIsOpen:
mute = !isOpen;SetBKMusicValue:写 volume。在 GameDataMgr.SetMusicIsOpen / SetMusicValue 中除了改 musicData 字段,再调用 BKMusic.Instance 的上述方法,才能把设置面板的操作变成「听得见」的变化;音效大小与开关则留到真正播放音效处再消费 musicData。
排行榜数据与列表 UI
- RankData 含 rankList;RankInfo 含 name、time(秒),字段标 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")实例化,设父为 UIScrollView 的 transform,用固定步长摆 localPosition。课中假设列表条数只增不减,故不回收条目。 - RankItem.InitInfo:把总秒数拆成 h / m / s 拼字符串显示;BeginPanel 排行按钮打开 RankPanel.Instance.ShowMe。
选角:配置、资源与交互
- RoleInfo:hp、speed、volume、resName、scale 等标 XmlAttribute;RoleData 内 roleList。与 RankData 不同:RoleData 来自 StreamingAssets 下配死的 RoleData.xml,属版本内平衡配置;RankData 随玩家长期读写,属存档。
- 飞机 Prefab 放进 Resources 路径下,与 resName 对齐;GameDataMgr 增加 nowSelHeroIndex 与 **GetNowSelHeroInfo()**。
- ChoosePanel:左右按钮在 0 … Count-1 间循环;ChangeNowHero 先 Destroy 旧飞机,再 Resources.Load 实例化、父到 heroPos、localScale = Vector3.one * scale、layer 设为 UI;下方十条小格用 SetActive 表示血量、速度、体积档位。
- ShowMe 把索引归零并刷新模型;HideMe 销毁展示用飞机。开始游戏 调 **SceneManager.LoadScene(“GameScene”)**;关闭回到 BeginPanel.ShowMe。
- Update:对 heroPos 用 Mathf.Sin 做轻微上下浮动,位移用世界空间,避免父节点带倾角时本地坐标飘移;射线用 NGUI 所用摄像机 ScreenPointToRay,Layer 选 UI,才能点在飞机碰撞体上拖动旋转,而不被错误摄像机或层挡住。
组间实现顺序建议
- Resources / StreamingAssets 与 BasePanel 就绪 → 搭 BeginPanel 主流程按钮。
- SettingPanel 拼层与事件 → MusicData + GameDataMgr 存取 → BKMusic 实机出声。
- RankData / AddRankData → RankItem Prefab + RankPanel.ShowMe 动态行。
- RoleData.xml + Resources 飞机 → ChoosePanel 切换模型与进 GameScene。
- GameScene:GamePanel 计时与血量、QuitPanel 二次确认、GameOverPanel 提交排行;Main 动态实例化玩家并挂 PlayerObject。
- BulletData / FireData XML,FireObject 八宫格开火与 BulletObject 移动;最后补子弹层、鼠标点消弹、BGM。
游戏内 HUD 与流程面板
- GamePanel:继承
GamePanel : BasePanel<GamePanel>。nowTime 在 Update 里累加 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。
结束界面与排行榜写入
- GameOverPanel:ShowMe 时 endTime = (int)GamePanel.Instance.nowTime(存秒数),labTime.text 复用 GamePanel.labTime 的展示字符串,保证「看见的和存的一致」。
- 确定:**AddRankData(inputName.value, endTime)**,再回 BeginScene。姓名来自 UIInput。
多摄像机与玩家移动
- 新增特效层:主摄像机可只渲特效;再建摄像机渲默认层游戏物体;深度顺序一般为特效 → 世界物体 → NGUI。原主摄像机标签可改为普通,新摄像机标为 MainCamera,便于 Camera.main 与边界检测一致。
- PlayerObject:Horizontal/Vertical 输入;左右倾角用 Quaternion.AngleAxis(±20°, Vector3.forward) 与 Slerp;前后 Translate( forward ),左右 Translate( right, Space.World ) 减轻「旋转后斜向漂移」。WorldToScreenPoint 做屏边夹紧,溢出时恢复 frontPos 对应轴。
- Dead 打开 GameOverPanel;Wound 扣血并 GamePanel.Instance.ChangeHp。跟踪弹等需 PlayerObject.Instance 时,在 Awake 设静态实例。
子弹数据与 BulletObject
- BulletInfo:type 1~5 对应直线、正弦横向偏移、右/左抛物线旋转、追踪 Slerp 朝向玩家;forwardSpeed、rightSpeed、roundSpeed、resName、deadEffRes、lifeTime 等 Xml 配置。
- InitInfo 后
Invoke("DelayDestroy", lifeTime);DelayDestroy 里可改为调 Dead 以播放销毁特效。OnTriggerEnter 对 Player 标签调用 Wound 再 Dead。预制体父节点 Collider 设触发器;玩家 Collider + Rigidbody,关重力。 - 课末 DelayDestroy 直接 **Dead()**,超时销毁也带特效;子弹单独 Layer,玩家 Camera.main 射线只打子弹层可点消。
开火点 FireObject 与 Main
- FireInfo:type 1 顺序朝玩家、2 散弹;ids 如
"1,10"表随机子弹 ID 区间;cd 颗间间隔、delay 组间休息、num 条数。 - FireObject:E_Pos_Type 八宫格;UpdatePos 用 ScreenToWorldPoint,screenPos.z 与玩家 WorldToScreenPoint 的 z 对齐(课中示例常数如 351,需按项目实测),保证与玩家同深度截面。ResetFireInfo 在 nowCD、nowNum 均为 0 时抽下一组数据;UpdateFire 扣 CD、按类型生成子弹并
AddComponent<BulletObject>()。 - Main.Start:GetNowSelHeroInfo,Resources.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 统一管理 RoleData、RankData、MusicData 等,而不是让每个面板自己读写 PlayerPrefs 或静态变量。这样做的好处和要注意的边界是什么?
深入解析
集中管理的收益:读写路径单一,存档键名、JSON 字段、加密或云同步以后只改管理器;依赖方向清晰,UI 只依赖管理器接口,不互相引用对方的数据结构;测试与重置时可以在一处 mock 或清空数据。
边界:管理器不要变成上帝类,把「各类 Data 的内部规则」仍放在各自类里,管理器只做聚合、加载顺序、持久化调度;否则所有业务都会挤进 GameDataMgr,比分散静态更难维护。
答题示例
集中管理等于单一数据源,存档键、加载顺序、以后换本地或云端,只动一条管线。
UI 问管理器要数据,面板之间不用互相引用。注意别把业务逻辑全写进管理器,各 Data 类仍要封装自己的字段含义和校验。
参考文章
- 1.需求分析
3. SettingPanel 打开拉数与关闭存档
题目
SettingPanel 在 ShowMe 里用 GameDataMgr 的 musicData 回填 Slider 和 Toggle,在 HideMe 末尾调用 SaveMusicData。为什么不在每次 onChange 都立刻写 XML?若玩家改了音量后直接杀进程,可能丢什么?
深入解析
滑块 onChange 已经通过 SetMusicValue 等改了内存里的 musicData,并驱动 BKMusic 即时出声;HideMe 再 SaveMusicData 是把当前内存一次性交给 XmlDataMgr,减少磁盘写入次数。若进程在未触发 HideMe 时退出,最后一次内存状态可能未落盘——可补 OnApplicationPause / OnApplicationQuit 再保存,或对关键操作节流写盘。
答题示例
ShowMe 回填是让控件和上次存盘一致,避免打开面板时滑块和实际音量脱节。
拖动时内存和听感已经更新,HideMe 再保存是省 IO。强杀进程可能丢未经过 HideMe 的改动,上线项目一般会 quit 时再存一道。
参考文章
- 5.开始场景-设置界面-音效数据
4. XmlAttribute 用在 RankInfo 上的取舍
题目
RankInfo 的 name、time 使用了 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 延时销毁子弹
题目
BulletObject 用 Invoke("DelayDestroy", info.lifeTime) 到点销毁。课里为什么强调「脚本被删了延迟函数就不会执行」,和 Destroy(gameObject, lifeTime) 比各适合什么场景?
深入解析
Invoke 挂在 MonoBehaviour 上,GameObject 被 Destroy 后未执行的 Invoke 会取消,避免对已销毁对象回调。Destroy(go, t) 由引擎调度延迟销毁;课中 DelayDestroy 改为调 Dead 播特效,用字符串 Invoke 便于改实现。
答题示例
对象没了,Invoke 不会执行,不会空引用。
需要到点先播特效再销毁,就写在 DelayDestroy 里统一走 Dead。
参考文章
- 18.游戏场景-游戏主逻辑-子弹相关-逻辑处理
- 21.游戏场景-游戏主逻辑-小功能补充
7. 为什么用 QuitPanel 而不是直接切场景
题目
游戏内返回按钮不直接 LoadScene("BeginScene"),而是先 QuitPanel.Instance.ShowMe()。这样设计解决什么问题?
深入解析
防止误触一次就退出当前局;取消只关面板,GamePanel 计时与状态保持。确定后再加载开始场景,属于常见二次确认模式。
答题示例
防止手滑退回主菜单白打,确认再切场景。
取消只隐藏面板,游戏还在。
参考文章
- 14.游戏场景-确定退出界面
8. PlayerObject 左右移动为何用世界空间 Translate
题目
PlayerObject 在 Update 里先 Translate(Vector3.forward * vValue * ...),再 Translate(Vector3.right * hValue * ..., Space.World)。课里为何左右用世界右轴?
深入解析
飞机左右倾斜后,本地 right 会随滚转偏斜,若左右沿本地右移动易与旋转耦合成斜向漂移;世界右 保持屏幕左右平移手感,前后仍用机头 forward 对应 WS。边界用 WorldToScreenPoint 与 Screen 宽高比较做夹紧。
答题示例
左右用世界右,避免飞机已经倾斜了再沿本地右飞,越飞越斜。
前后还是沿机头 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.time 则 a 排前面;条数超过 20 时 RemoveAt(20)。若 time 表示通关用时(秒),「越快名次越靠前」通常应如何比较?当前 RemoveAt(20) 删掉的是排序后第几名?
深入解析
「越快越好」应对 time 升序,让最小值在下标 0。课中实现是 time 大者在前,更偏展示或与其它规则一致,接业务时要先定榜的语义再写比较器。RemoveAt(20) 删除的是 下标 20 的元素,即第 21 条(从 1 数);若排序是升序且要保留前 20 名最快,应确认删掉的是「最慢的一条」——需与排序方向一致,否则删错人。
答题示例
越快越好就按 time 从小到大排,第一名在最前。
RemoveAt(20) 删的是第 21 个元素,具体是谁取决于排序。课里是时间大的靠前,和常见竞速榜相反,接项目要先对齐策划规则再改比较器和截断。
参考文章
- 7.开始场景-排行榜界面-排行榜数据
3. 选角界面射线相机与浮动坐标空间
题目
ChoosePanel 用 uiCamera.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 向下取整;labTime 在 Update 里按 h/m/s 拼接,与 int 秒在临界秒数附近可能对不齐。稳妥做法是以同一浮点秒数生成「展示字符串」和「存档整数」,或在 ShowMe 只读一次 nowTime 再本地格式化与转 int。
答题示例
浮点转 int 和 Label 里拼出来的字符串可能差一秒,最好都用同一个 nowTime 算。
ShowMe 时取 GamePanel 单例,要保证游戏结束流程里 GamePanel 还活着。
参考文章
- 15.游戏场景-结束界面
5. FireObject 里屏幕坐标 z 与分辨率
题目
FireObject.UpdatePos 把 screenPos 的 x、y 按八宫格设到屏边或中部,再把 z 设成与玩家 WorldToScreenPoint 一致的值,最后 Camera.main.ScreenToWorldPoint。为什么缺了正确的 z,开火点会和飞机不在同一深度?
深入解析
ScreenToWorldPoint 在透视相机下会把屏幕点还原到给定深度对应的世界点;z 与玩家不一致时,算出的世界位置在前后方向错位,子弹从「飘在空中」或「在玩家身后」发射。分辨率变时屏上像素变,但深度标定仍依赖相机与玩家相对关系,故换机或改相机应用同样方法重测 z。
答题示例
z 决定从屏幕点往前多远打射线进世界,z 不对开火点就和你飞机不在一个截面上。
课里用玩家位置转屏幕看 z,就是让开火点和玩家同深度。
参考文章
- 20.游戏场景-游戏主逻辑-开火点相关-逻辑处理
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com