Skip to content

Unity-天命6源码

求职用的,主要扒游戏管理和UI部分。想要可以找我要,我有翻译韩语完的。
游戏中最好不要使用魔法值,所有的值都应该用常量定义出来,放到Scripts/GameDefine下面。
游戏从Logo场景开始,一个简单的动画控制,播放对应商店的Logo Sprite,Logo播放完毕的时候加载Certification场景。

这个系统的UI用的是NGUI,每个面板用不同的摄像机然后叠加culling。把组件都挂到Animation下面,便于控制动画和效果

从Logo场景进入,Logo场景很简单,就是一个logo,判断了一个莫名其妙的宏,加载动画,然后进入Certification场景

Certification场景目前来的功能是验证AB包+验证身份登录(? UI部分重要的结构

C
UStartPackage_Launcher: UStartPackage

Certification下
加载脚本的顺序是:
- UStartPackage_Launcher : UStartPackage
    - 这里面塞了三个全局prefab管理器
        - GlobalFontManager 字体
        - BGM Module 背景音乐
        - UI Root Dialog 全局的UI提示弹窗
     - 单例触发ULocalPlayer
- ULoginCtrl
- ULauncherCtrl
- 表驱动
- 网络
- 搞定两个UI

UStartPackage 设置UI缩放和分辨率,读取预制体,加载三个模块的预制体,中间重置本地客户端的旧数据 如果PlayerPrefs中没有保存数据,则从旧文件中读取信息并保存到PlayerPrefs(我好像没怎么弄懂这东西的数据持久化是怎么管理的 Lancher完事了,会调用PostAwake自定义生命周期函数

ULocalPlayer 是“全局玩家与系统服务的聚合单例”,涵盖资源、网络、设置、前后台行为等。没有全看完,ULocalPlayer.Instance.LocalPushMgr.CancelLocalPush();里面包含了一个服务通知推送管理模块,底层推送使用的是NMSDK 聚合单例使用partial关键字分段编写,降低合作编写难度和耦合。 LobbyMenuData是大厅(战斗外)的核心数据单例,战斗时的核心数据单例使用的是另一个上下文。

此时已经初始化好了UI缩放适配,音乐,游戏设置,AB包的资源,全局字体管理器。

接下来使用的是,ULoginCtrl,

仅针对非战斗部分 首先是基础架构和业务使用分开 基础架构看 网络 数据

跨场景管理

换大厅的时候使用单例里GetLobbySkinSenceName获取皮肤按需加载不同场景,因为不同大厅皮肤里的灯光效果等等,都是不同的 换场景的时候统一抽象中转出GameLoading场景,这个场景下会清理资源AB包,等等,最后进去要进的场景,UI上也能统一加载

感觉只看不太够,得自己古法把这些坑踩一遍才有从头构造的能力。

复习一遍登录的全过程

1. 启动与生命周期:从冷启动到进入大厅

游戏从 Logo 场景开始,播放完 Logo 动画后加载 Certification 场景。Certification 里先走 UStartPackage_Launcher.Awake():设置帧率、写入基础 PlayerPrefs、注册并加载客户端 Config,取消本地推送;随后调用父类 UStartPackage.Awake(),由父类统一做 UI 缩放计算、实例化 startPrefabs,并通知 BGM 模块和全局 Dialog 执行 PostAwake。如果实例化出的 prefab 带有 GlobalFontManager,这里也会顺手设置默认语言。

UStartPackage_Launcher 自己的 Awake 收尾时会初始化游戏系统设置,包括画质、帧率、BGM 音量和音效音量,然后通过 SendMessage("PostAwake") 通知同一个场景里的业务脚本。这里主要进入 ULoginCtrl.PostAwake():禁用系统软键、激活日志查看器、初始化 ULocalPlayer、加载 Launcher 阶段 CSV、播放登录 BGM,并先把状态切到 SDKSignIn。之后启动 StartingDestinyConfig 协程,资源包模式下会先从 CDN 拉最新 DestinyConfig,然后初始化 Netmarble SDK,最后进入 Launcher 流程。

ULoginCtrl 虽然继承自 LoginController,但父类的 SetState() 基本是空实现,实际 UI 状态切换都在 ULoginCtrl.SetState() 里。Launcher 阶段会 SetState(LoginState.Launcher),显示启动弹窗,然后调用 ULauncherCtrl.OnStartLauncher(OnSuccessLauncher)。真正的版本检查、维护检查、补丁检查/下载、表格加载和最终 HandShake 都在 ULauncherCtrl.ProcessLauncher() 中完成。Launcher 完成后回调 OnSuccessLauncher(),关闭系统弹窗,并切换到 LocalLogin 场景。

2. 资源管理:AB 包、版本检查与缓存机制

静态资源通过 AssetBundle(AB 包)进行管理,启动时进行版本检查,对比本地与 CDN 上的版本差异,按需下载补丁并更新本地缓存。

3. 配置与表驱动:CSV、本地化与加载策略

游戏里大量静态配置都不是直接写死在代码里,而是通过 CSV 表驱动。也就是说,代码里只保存一个表 ID,然后通过 UGameTableManager 查到对应的配置对象,再根据配置决定 UI 文本、角色数值、资源路径、功能开关、关卡信息、技能参数等。

3.1 UGameTableManager:全游戏表集合

UGameTableManager 可以理解为客户端侧的“表管理总入口”。它继承自 Singleton<UGameTableManager>,所以业务代码通常通过:

csharp
UGameTableManager.Instance.GetUnitTable(unitId)

来查询表数据。

它里面主要有三类内容:

  1. 泛型表声明
csharp
protected CSVDataTable<UTable_Unit> unitTable = new CSVDataTable<UTable_Unit>();
protected CSVDataTable<UTable_Item> itemTable = new CSVDataTable<UTable_Item>();
protected CSVDataTable<UTable_Define> defineTable = new CSVDataTable<UTable_Define>();
protected CSVDataTable<UTable_GameStrings> gameStringTable = new CSVDataTable<UTable_GameStrings>();

CSVDataTable<T> 表示“一张 CSV 表”,泛型 T 表示“每一行解析出来是什么数据类”。比如 unit.csv 的每行会解析成 UTable_Unititem.csv 的每行会解析成 UTable_Item

  1. 路径注册

UGameTableManager.Awake() 里会给每张表注册资源路径:

csharp
unitTable.SetFullPathName(TablePath + "unit");
itemTable.SetFullPathName(TablePath + "item");
defineTable.SetFullPathName(TablePath + "define");
gameImgTable.SetFullPathName(TablePath + "resource_game_image");

这里的 TablePath 是:

csharp
protected readonly string TablePath = "CSVTable/";

所以 unitTable 实际对应的是 CSVTable/unit 这个资源路径。

  1. Getter 查询函数

表加载完以后,业务侧通过 getter 访问:

csharp
public UTable_Unit GetUnitTable(int ID) { return unitTable.GetData(ID); }
public UTable_Item GetItemTable(int ID) { return itemTable.GetData(ID); }
public int GetDefineTableValue(int ID) { ... }
public string GetGameStringTableText(int ID) { ... }

这就是表驱动在业务层的使用方式:业务代码不关心 CSV 怎么读,只拿 ID 查配置。

3.2 CSVData:所有表行数据的基类

CSVData 是大部分 UTable_xxx 的基类,提供每张表都通用的字段:

csharp
public int id;
public string regionKey;

id 是表 ID,也是运行时查表的 key。
regionKey 是地区标记,用于区分默认数据和地区覆盖数据。

它的核心解析函数是:

csharp
public virtual void InitializeValue(string[] elements, ref int fieldIdx)
{
    id = int.Parse(elements[fieldIdx++]);
    regionKey = elements[fieldIdx++].ToLower();
}

这表示默认情况下,CSV 每行的前两列是 idregionKey。子类会先调用 base.InitializeValue(...),再继续解析自己的业务字段。

比如 UTable_Unit 会继续解析角色星级、觉醒状态、势力、战斗类型、图标 ID、文本 ID 等:

csharp
public override void InitializeValue(string[] elements, ref int fieldIdx)
{
    base.InitializeValue(elements, ref fieldIdx);

    grade = int.Parse(elements[fieldIdx++]);
    ultimate = int.Parse(elements[fieldIdx++]);
    unit_group = (UGameDefine.ERealmType)int.Parse(elements[fieldIdx++]);
    battle_type = (UGameDefine.EUnitBattleStyleType)int.Parse(elements[fieldIdx++]);
    ...
}

CSVData 里几个函数的职责:

  • GetId():返回 id,给 CSVDataTable<T> 存字典时使用。
  • GetTableID():也是返回 id,给解析器统一获取表 ID。
  • GetRegionKey():返回地区 key。
  • IsIgnoreRegionKey():默认返回 false,表示这张表参与地区覆盖。有些表不需要地区覆盖,会在子类里 override 成 true
  • AfterParse():解析完成后的钩子,子类可以在这里做二次处理。
  • BooleanParse():兼容 0/1true/false 两种布尔写法。
  • CollectionAssetData():给资源收集用,表里如果配置了 prefab、贴图、音频路径,可以通过它把依赖资源收集出来。
  • PreLoad():给表数据预加载资源时扩展用。

所以 CSVData 不是一张具体表,而是“表行数据”的统一协议和默认行为。

3.3 CSVDataTable<T>一张表的运行时容器

CSVDataTable<T> 内部最重要的是两个字典:

csharp
private Dictionary<int, System.Object> table = new Dictionary<int, System.Object>();
private Dictionary<string, RegionDataTable> regionDataTable = new Dictionary<string, RegionDataTable>();

table 是当前生效的数据,按 id 查询。
regionDataTable 是按地区暂存的覆盖数据,后面由 UpdateRegionData() 合并进去。

查询时就是:

csharp
public T GetData(int id)
{
    return CSVDataTable<T>.GetData(id, table);
}

如果 id <= 0 或者字典里找不到,会返回 default(T),也就是引用类型里的 null

3.4 Load 和 LoadFromFile 的区别

这两个函数都会清空旧数据,然后读取 TextAsset,最后调用:

csharp
CSVParser<T>.ParseToData(text.text, ref table, ref regionDataTable);

区别在于资源来源不同。

Load() 使用项目自己的资源委托层:

csharp
TextAsset text = UResourceDelegator.Instance.LoadResource<UnityEngine.TextAsset>(fullPathName);

UResourceDelegator 会根据当前工程模式去资源管理系统里找资源。在 AssetBundle 模式下,它会走 AssetBundle 相关的加载流程;在编辑器或非 AB 模式下,则可能走模拟加载。所以主游戏表通常使用 Load()

LoadFromFile(string filename) 使用 Unity 原生 Resources.Load

csharp
TextAsset text = Resources.Load(filename, typeof(TextAsset)) as TextAsset;

这要求资源位于 Resources 目录下。启动器表就在:

txt
Assets/Launcher/Resources/Table/

所以 LoadLauncherCSV() 会使用:

csharp
appStringTable.LoadFromFile(launcherTablePath + "app_string_" + language.ToLower());
languageFontSizeTable.LoadFromFile(launcherTablePath + "language_font_size");
cdnthemaTable.LoadFromFile(launcherTablePath + "cdn_theme");
cdnThemaSequenceTable.LoadFromFile(launcherTablePath + "cdn_theme_sequence");

一句话总结:

txt
Load()         = 走 UResourceDelegator,适合主游戏表,AB 模式下可从 AB/资源管理器取
LoadFromFile() = 走 Resources.Load,适合 Launcher 阶段内置在 Resources 下的小表

3.5 启动器表和完整表加载

UGameTableManager.Instance.LoadLauncherCSV() 只加载启动器阶段必须用到的少量表,比如登录/补丁界面的本地化字符串、字体大小、CDN 主题信息。它不是完整加载所有游戏表。

完整加载在 LoadFullCSV() 或 AssetBundle 流程的 LoadTableToAssetBundle() 里。

LoadTableToAssetBundle() 的流程是:

csharp
for (int nIndex = 0; nIndex < allTablePaths.Count; ++nIndex)
{
    AssetBundleUtil.FLoadAssetOperation operation =
        AssetBundleUtil.UAssetBundleManager.LoadAssetOp(allTablePaths[nIndex], typeof(UnityEngine.TextAsset), false);

    if (operation != null)
    {
        yield return StartCoroutine(operation);
    }
}

LoadFullCSV();

也就是先把所有表资源通过 AB 管理器加载/缓存好,再调用 LoadFullCSV() 把 CSV 内容解析进内存字典。

3.6 CSVParser:CSV 行如何变成 UTable 对象

CSVParser<T>.ParseToData() 会把 CSV 文本按行切开:

csharp
string[] lines = inSource.Split('\n');

然后从第 3 行开始解析:

csharp
for (int nIndex = 2; nIndex < lines.Length; ++nIndex)

这说明 CSV 前两行大概率是表头、类型说明或注释,真正的数据从第 3 行开始。

每行按分号切字段:

csharp
string[] elements = lines[nIndex].Split(';');

然后创建泛型对象:

csharp
T obj = new T();
obj.InitializeValue(elements, ref fieldIdx);
obj.AfterParse();

这里没有使用反射自动映射字段,而是每个 UTable_xxx 自己手写 InitializeValue()。这种方式比较原始,但性能和可控性更好,也容易处理枚举、数组、特殊字段。

最后根据 regionKey 判断数据放哪里:

  • 默认地区数据进入主表 table
  • 非默认地区数据进入 regionDataTable

3.7 UpdateRegionData:地区覆盖机制

UpdateRegionData() 是这套表驱动里很关键的一层,它负责把当前地区的配置覆盖到默认配置上。

解析时,如果某行的 regionKey 不是默认地区,解析器不会直接把它加入主表,而是放进:

csharp
regionDataTable[regionKey]

等确定当前地区后,UGameTableManager.UpdateRegionData() 会获取当前地区:

csharp
string netmarbleRegionKey = UGameStatices.GetRegion().ToLower();

然后对所有表调用:

csharp
unitTable.UpdateRegionData(netmarbleRegionKey);
itemTable.UpdateRegionData(netmarbleRegionKey);
defineTable.UpdateRegionData(netmarbleRegionKey);
...

单张表的覆盖逻辑是:

csharp
if (table.ContainsKey(dicEnum.Current.Key))
    table.Remove(dicEnum.Current.Key);

table.Add(dicEnum.Current.Key, dicEnum.Current.Value);

也就是说,如果当前地区有同 ID 的配置,就删除默认数据,换成地区数据。

例子:

txt
id=100; region=default; value=10
id=100; region=japan;   value=12

如果当前地区是 japan,那么最终:

csharp
GetData(100)

拿到的是 value=12。如果当前地区没有覆盖行,就继续使用 default 数据。

这个设计的好处是:大部分地区共用默认配置,只有需要差异化的 ID 才单独写地区覆盖行。

3.8 表驱动整体链路

完整链路可以概括为:

txt
CSV 文件
    -> UGameTableManager 注册表路径
    -> Load / LoadFromFile 读取 TextAsset
    -> CSVParser<T>.ParseToData
    -> 每行生成 UTable_xxx 对象
    -> 默认数据进入 table
    -> 地区数据进入 regionDataTable
    -> UpdateRegionData 按当前地区覆盖
    -> 业务代码通过 UGameTableManager getter 按 ID 查询

所以这个游戏的表驱动核心不是“动态脚本”或者“规则引擎”,而是很朴素的静态配置系统:策划通过 CSV 配数据,客户端启动时解析成强类型对象,业务代码通过 ID 查询使用。

4. 账号与网络:渠道登录、握手校验与数据拉取

支持多渠道 SDK 登录,登录后与游戏服进行握手校验,校验通过后拉取用户数据,确保账号安全与数据一致性。

5. UI 系统:NGUI 分层、摄像机与 Culling 组织

UI 采用 NGUI 分层设计,合理组织页面、弹窗、全局提示与加载中界面;通过摄像机 Depth 与 Culling Mask 控制渲染层级与可见性。

6. 全局服务:音频、字体、推送与单例管理

全局服务包括音频播放、字体管理、推送通知、日志记录、设备适配等,通常通过全局单例模式统一管理,确保跨场景一致性。

7. 场景与流程编排:场景跳转与状态控制

由流程控制器决定下一个场景,如从登录跳转至大厅、从大厅进入剧情或教程,支持场景恢复与分支跳转逻辑。

8. 本地持久化:PlayerPrefs、数据迁移与缓存

使用 PlayerPrefs 或本地文件持久化客户端设置、登录痕迹与缓存数据;支持旧数据迁移机制,确保版本升级后数据兼容。

9. 网络模块:请求发送、数据拉取与分层设计

网络模块负责请求发送与响应处理,登录后自动拉取用户数据;采用分层设计,分离网络层、业务层与 UI 层,提升可维护性。

10. 表驱动与本地持久化:配置组织与数据落盘

静态配置的表驱动细节见第 3 节。这里需要区分“表数据”和“本地持久化数据”:CSV 表在客户端侧基本是只读配置,主要负责加载、解析、地区覆盖和查询;真正会保存到本地的更多是客户端设置、玩家偏好、登录痕迹、教程状态、队伍编辑缓存等运行时数据。

11. 场景管理:启动链路、登录跳转与恢复逻辑

管理从启动到登录、登录到大厅、教程/剧情恢复等完整链路;通过状态机或流程控制器决定场景跳转时机与恢复策略。

12. UI 架构:NGUI 分层、摄像机与弹窗组织

UI 架构基于 NGUI 实现分层渲染,通过摄像机 Depth 与 Culling Mask 控制层级;统一管理页面、弹窗、全局提示与加载界面。

13. MVC 业务写法:控制器、视图与数据交互

采用 MVC 模式拆分业务逻辑,控制器处理按钮事件与状态刷新,视图负责 UI 表现,数据层读取表配置与网络回包,实现 UI 与逻辑解耦。

14. 资源管理:AB 包、版本检查与启动器设计

资源管理以 AB 包为核心,启动器负责版本检查与补丁下载;优先完成补丁更新再进入登录流程,确保资源一致性。

15. 全局单例与基础设施:大总管类的职责划分

全局单例如 ULocalPlayer、UGameStatics、UGameTableManager 等承担全局能力,包括玩家状态、游戏配置、表数据管理等,作为基础设施支撑各模块运行。

最近更新: