INTERNAL / TRAINING Assets/_Game/Home/HomeRoot.cs
2026-07-17
Script Analysis

HomeRoot.cs

Home 场景的启动引导脚本,负责设置相机背景色、确保 UI 事件系统存在、加载并实例化主菜单预制体。 从零开始逐行拆解,类比如前端 Vue / React 组件生命周期与 DOM 操作。

基类 MonoBehaviour
生命周期 Awake()
目标读者 前端转型 Unity
场景 Home.unity
CH 01

完整源码

先整体看一眼,建立全局印象,然后逐行拆开讲。

HomeRoot.cs 36 lines
using UnityEngine;
using UnityEngine.EventSystems;

// 主页场景引导(放在 Home 场景里)。对应 Main 场景的 GameRoot:
// 负责设好相机、EventSystem,并实例化主页界面预制体 HomeScreen.prefab。
public sealed class HomeRoot : MonoBehaviour
{
    void Awake()
    {
        var cam = Camera.main;
        if (cam != null)
        {
            cam.clearFlags = CameraClearFlags.SolidColor;
            cam.backgroundColor = Theme.MainBackgroundColor;
        }

        EnsureEventSystem();

        var prefab = Resources.Load<GameObject>("Prefabs/HomeScreen");
        if (prefab != null)
            Instantiate(prefab);
        else
            Debug.LogError("主页预制体缺失...");
    }

    static void EnsureEventSystem()
    {
        if (Object.FindObjectOfType<EventSystem>() == null)
        {
            var es = new GameObject("EventSystem");
            es.AddComponent<EventSystem>();
            es.AddComponent<StandaloneInputModule>();
        }
    }
}
CH 02

逐行拆解

每一行的 API 来源、C# 语法、前端等价写法。不留坑。

02.1

using — C# 的 import

第 1~2 行,告诉编译器我要用哪些命名空间里的东西。

1
using UnityEngine;

C# 的 命名空间导入语句。using = 前端的 import。 写了这一行后,下面的 MonoBehaviourCameraGameObjectDebug 才能直接用短名字。 不写的话得写全限定名 UnityEngine.MonoBehaviour

JS 类比 import * as UnityEngine from 'unity-engine';
2
using UnityEngine.EventSystems;

导入 EventSystems 命名空间,这样下面才能用 EventSystemStandaloneInputModule 这两个类(UGUI 点击事件所需)。

JS 类比 import { EventSystem, StandaloneInputModule } from 'unity-engine/event-systems';
02.2

类声明 — public sealed class HomeRoot : MonoBehaviour

第 6 行,定义这个脚本的类型。

6
public sealed class HomeRoot : MonoBehaviour

四个关键字,逐个解释:

关键字含义JS 类比
public任何地方都能访问export class
sealed禁止被继承Object.freeze() 对类的效果
class HomeRoot定义一个类class HomeRoot
: MonoBehaviour继承 Unity 内置基类extends MonoBehaviour
02.3

MonoBehaviour 是什么?

为什么所有 Unity 脚本都要继承它?

MonoBehaviour 是 Unity 引擎内置的基类(UnityEngine.MonoBehaviour),所有挂到 GameObject 上的脚本 必须继承它。继承之后,你的类才能拥有 Awake()Start()Update() 等生命周期方法。

名字来源
Why "Mono" + "Behaviour"?

Mono = 来自 Mono 框架(Unity 早期的 C# 运行时,跨平台 .NET 实现)。
Behaviour = 行为/组件,表示可以附加到 GameObject 上的"行为脚本"。

生命周期
Lifecycle

Unity 每帧自动遍历场景里所有 MonoBehaviour 子类,按顺序调用:
Awake()Start()Update()Update() → ... → OnDestroy()

Vue: created → mounted → updated → unmounted
02.4

void Awake() — 生命周期钩子

第 8 行,Unity 自动调用的入口方法。

8
void Awake()

void 表示不返回任何值(= TypeScript 的 void)。
Awake 是 Unity 规定的钩子名(类似 Vue 的 created() / mounted())。
没有写访问修饰符,C# 类成员默认是 private,等价于 private void Awake()

JS 类比 // Vue 组件中
mounted() { /* Unity 会自动调用 */ }
02.5

Camera.main — 获取主摄像机

第 10 行。

10
var cam = Camera.main;

var = C# 的编译时类型推断。编译器看到右边返回 Camera 类型,自动推断 camCamera。 注意:它不是 JS 的 var(JS 是运行时),C# 的 var 在编译后类型就固定了,是静态类型
Camera = Unity 内置类,代表场景里的摄像机组件(玩家看到的画面由它渲染)。
.main = Camera 的静态属性,返回场景里 tag 为 "MainCamera" 的第一个摄像机。
如果场景里没有 MainCamera,返回 null

JS 类比 const cam = document.querySelector('[tag="MainCamera"]') as Camera;
02.6

相机属性设置 — clearFlags / backgroundColor

第 11~15 行。

11
if (cam != null)

null = C# 的空引用,等同于 JS 的 null / undefined
判空防御:如果场景里没有主摄像机,后面代码不执行,防止 NullReferenceException(= JS 的 Cannot read property of undefined)。

13
cam.clearFlags = CameraClearFlags.SolidColor;

clearFlags = Camera 的实例属性,控制每帧渲染前怎么清屏
CameraClearFlags = Unity 内置枚举,选项集合:
  • Skybox — 显示天空盒(默认)
  • SolidColor — 纯色背景(本项目选这个)
  • Depth — 只清深度缓冲
  • Nothing — 不清屏

JS 类比 // 枚举 = 有名字的常量集合
enum CameraClearFlags { Skybox=1, SolidColor=2, Depth=3, Nothing=4 }
14
cam.backgroundColor = Theme.MainBackgroundColor;

backgroundColor = Camera 的实例属性,纯色清屏时用什么颜色。
Theme = 本项目手写的静态类Assets/_Game/Themes/Theme.cs),集中管理所有颜色常量。
MainBackgroundColor = new Color(1f, 1f, 1f, 1f),即白色。

JS 类比 // Theme.cs 就相当于:
export const Theme = {
  MainBackgroundColor: 'rgba(255,255,255,1)',
  ArrowColor: 'rgba(17,20,51,1)',
} as const;
02.7

EnsureEventSystem() — 确保 UI 事件系统存在

第 17 行,调用自定义静态工具方法。

17
EnsureEventSystem();

调用下面定义的静态方法。方法名带 Ensure 是编码习惯:确保某东西存在,没有就创建。

02.8

Resources.Load — 动态加载资源

第 19~23 行。

19
var prefab = Resources.Load<GameObject>("Prefabs/HomeScreen");

Resources = Unity 内置静态类,从 Resources/ 文件夹动态加载资源。
.Load<T>() = 泛型方法<GameObject> 告诉它返回类型是 GameObject。
"Prefabs/HomeScreen" = 相对路径。实际对应的文件:

文件实际位置Load 的路径
Assets/_Game/Home/Resources/Prefabs/HomeScreen.prefabPrefabs/HomeScreen

规则:去掉 Resources/ 前缀,去掉文件扩展名。

JS 类比 const prefab = await import('@/prefabs/HomeScreen');
20~23
if (prefab != null)
    Instantiate(prefab);
else
    Debug.LogError("主页预制体缺失...");

Instantiate(prefab) = Unity 最重要的方法之一,克隆一个 GameObject 放到场景里
Debug.LogError("...") = 红色错误日志,输出到 Console 窗口(= JS 的 console.error())。

JS 类比 const instance = prefab.cloneNode(true);
document.body.appendChild(instance);
02.9

EnsureEventSystem 方法体

第 26~34 行。

26
static void EnsureEventSystem()

static = 静态方法,属于类本身,不需要 new HomeRoot() 就可以调。 同类的静态方法可以省略类名直接调 EnsureEventSystem(),不同类要用 HomeRoot.EnsureEventSystem()

JS 类比 static ensureEventSystem() { ... } // Foo.ensureEventSystem()
28
if (Object.FindObjectOfType<EventSystem>() == null)

Object = UnityEngine.Object,所有 Unity 对象的基类(⚠️ 不是 C# 的 object)。
FindObjectOfType<T>() = 全局搜索,在场景中遍历所有 GameObject,找第一个挂有 EventSystem 组件的。
性能警告:这个方法很慢,只能在初始化时偶尔用,绝不能在 Update 里每帧调用

JS 类比 if (!document.querySelector('EventSystem')) { ... }
30
var es = new GameObject("EventSystem");

new = C# 的 new,创建一个空的 GameObject,控制台里显示名为 "EventSystem"。
GameObject = Unity 中一切场景对象的基类。空 GameObject = 一个只有 Transform 的空容器。

JS 类比 const es = document.createElement('div');
容易混:两个 "EventSystem" 不是同一个东西
写法性质作用
"EventSystem"(引号字符串) 随便取的 显示名 Hierarchy 窗口里看到的 GameObject 名字,写 "foo" 也行
EventSystem(无引号,第 31 行) Unity 内置类 真正的 UGUI 事件调度引擎,来自 UnityEngine.EventSystems

第 30 行只是创建了一个空容器并取名叫 "EventSystem"。真正让 UI 能点击的,是第 31~32 行通过 AddComponent 挂上去的两个内置组件。

31~32
es.AddComponent<EventSystem>();
es.AddComponent<StandaloneInputModule>();

AddComponent<T>() = 给 GameObject挂一个组件
这里挂了两个组件:

  • EventSystem — UGUI 事件的调度中心,负责把输入分发给正确的 UI 元素(射线检测)
  • StandaloneInputModule — 把鼠标/触摸的原始输入翻译为 UGUI 能处理的事件(PointerEnter、PointerClick 等)
JS 类比 es.setAttribute('data-component', 'EventSystem');
es.setAttribute('data-component', 'StandaloneInputModule');
CH 03

完整执行流程

从场景加载到主菜单显示,HomeRoot 做了三件事。

Home.unity 加载 → Awake() 执行流程
场景 Home.unity 加载完毕,Unity 发现场景里的 GameObject 上挂了一个 HomeRoot 脚本。
找到 MainCamera,设背景为白色。 Camera.main 取 tag="MainCamera" 的摄像机 → 设为纯色清屏模式 → 背景色 = Theme.MainBackgroundColor(白色)。
确保 UI 事件系统存在。 FindObjectOfType 全局搜索 EventSystem → 如果没找到 → new GameObject + AddComponent 创建。
加载 HomeScreen 预制体并实例化。 Resources.Load 读取 .prefab 模板 → Instantiate 克隆一个放到场景里 → 用户看到主菜单。
一句话总结

HomeRoot 是一个极简的场景启动器,只做三件事:设相机背景、确保 UGUI 能响应点击、加载主菜单 UI。 对比 Main 场景的 GameRoot,它非常轻量 —— 因为主菜单是一个静态 UI,不需要 ECS 世界、关卡系统、作弊面板等复杂逻辑。

CH 04

知识点速查表

本文件中出现的所有 Unity/C# 概念及其前端等价物。

Unity / C#含义前端类比
using导入命名空间import
class A : BA 继承 Bclass A extends B
MonoBehaviourUnity 脚本基类React.Component / Vue 组件
Awake()生命周期钩子,最早调用created() / mounted()
var编译时类型推断(静态)const(但概念不完全相同,C# var 是静态)
null空引用null / undefined
static属于类,不属于实例static
enum枚举类型enum
GameObject场景中的一切对象DOM 元素 <div>, <button>
Component挂到 GameObject 上的功能模块HTML 属性 / 指令
Prefab可复用的 GameObject 模板React 组件模板
Resources.Load()动态加载资源import() 动态导入
Instantiate()克隆对象到场景cloneNode(true) + appendChild
Debug.Log()普通日志console.log()
Debug.LogError()错误日志console.error()
<T> 泛型类型参数<T> in TypeScript
Color(r,g,b,a)RGBA 颜色(值范围 0~1)rgba(r,g,b,a)(值范围 0~255)
CH 05

补充详解 — 给前端工程师的 C# 速成

CH 01~04 已逐行拆解了所有代码逻辑。这里补充几个对前端转型者来说「一眼看不出来」的关键差异。

05.1

为什么用 sealed?

第 6 行 public sealed class HomeRoot 中的 sealed。

6
public sealed class HomeRoot : MonoBehaviour

sealed 阻止继承:别的类不能写 class Xxx : HomeRoot。Unity 里大多数场景脚本都标记 sealed,原因有三:

  • 性能:JIT 编译器看到 sealed 可以做「去虚拟化」(devirtualization),直接调用方法而不是走虚函数表,更快。
  • 安全:场景启动脚本不应被继承。如果有人继承并重写 Awake,会破坏启动逻辑。
  • 惯例:这是这个项目代码库的风格约定。你去看 GameRoot.cs、ArrowView.cs,也都 sealed。
JS 类比 // JS 没有 sealed,最接近的是:
Object.freeze(HomeRoot.prototype); // 但只能冻结原型,不能阻止继承
// 或者用 Symbol 做私有标记阻止子类化(但不完全等同)
05.2

C# 花括号 {} — 与 JS 的关键差异

代码中每一对大括号的含义和 C# 特有的风格。

7, 35
{ ... } // 类体

第 7 行 { 和第 35 行 } 包裹整个类体。C# 的花括号和 JS 一样表示「作用域块」,但有几个差异:

差异点C#JS
换行风格 花括号另起一行(Allman 风格) 花括号放在同一行末尾(K&R 风格)
作用域 { } 内声明的变量离开 {} 就销毁 var 会提升,let/const 块级作用域
单行 if 可以省略花括号但本项目不允许 可以省略但 ESLint 通常禁止
表达式 vs 语句 { } 永远是语句块,不返回值 对象字面量 {} 是表达式,会混淆
代码风格对比 // C# (Allman 风格):
if (x != null)
{
    DoSomething();
}

// JS (K&R 风格):
if (x != null) {
  doSomething();
}
05.3

1f — float 字面量的 f 后缀

Color(1f, 1f, 1f, 1f) 中的 f 是什么意思?

-
new Color(1f, 1f, 1f, 1f)

C# 的Color 构造函数接受 float 类型参数(范围 0.0 ~ 1.0)。

在 C# 中,带小数点的数字字面量默认是 double 类型(双精度浮点),不是 float。写 1.0 = double,传给 float 参数需要显式转换。

f 后缀告诉编译器:「这是 float 类型,不是 double」。

写法C# 类型说明
1int整数
1.0double双精度(64位)
1f1.0ffloat单精度(32位),Unity 常用
1d1.0ddouble显式双精度(不常用)
JS 类比 // JS 只有一种数字类型 Number(IEEE 754 double)
// 没有 float/double 之分,也没有 f 后缀

// C#: Color(1f, 1f, 1f, 1f) = RGBA(255, 255, 255, 255)
// JS: { r: 1, g: 1, b: 1, a: 1 } 或 'rgba(255, 255, 255, 1)'
05.4

C# 默认访问修饰符 — 不写 = private

为什么 Awake() 前面没有 public/private?

8, 26
void Awake() // 没有 public
static void EnsureEventSystem() // 也没有 public

C# 中,类成员(方法、字段、属性)不写访问修饰符时默认是 private

场景C# 默认JS 默认
类成员(方法/字段)privatepublic(无原生 private,用 # 前缀)
类本身internalexport 才公开,否则模块私有
接口/枚举成员publicpublic

⚠️ 重要:即使不写 public,Unity 引擎依然能找到并调用 Awake()Start()Update() 等生命周期方法。这是 Unity 使用反射(Reflection)做到的 —— 不管方法是 public 还是 private,引擎都能调用。

JS 类比 // C#
void Awake() { } // 没写修饰符 = private(但 Unity 反射仍能调)

// JS
#awake() { } // 私有方法
// 但 JS 私有方法外部完全无法访问,跟 C# 反射不同
05.5

前端 → C# 代码风格速查

一些看起来不一样但其实只是习惯差异的地方。

命名风格
Naming Conventions

C#:PascalCase(类/方法/属性)、camelCase(局部变量)
JS:camelCase(全部)
本例:HomeRoot(类)、Awake(方法)、cam(局部变量)

没有 function 关键字
No "function" keyword

C# 方法直接写:void MethodName() { }
不需要 function 关键字。
返回类型写在前面(void / int / string 等)。

JS: function foo() {}
C#: void Foo() {}
new 的两种用法
new keyword

new GameObject() — 实例化对象(同 JS)
sealed class 配合 new 可以隐藏父类成员(但本项目未使用此用法)

分号必须
Semicolons required

C# 每条语句必须; 结尾。
JS 有 ASI(自动分号插入),C# 没有。

花括号行号速查(本文件所有 {} 的作用)
行号符号作用
7{类 HomeRoot 体开始
9{Awake() 方法体开始
12{if (cam != null) 块开始
15}if (cam != null) 块结束
24}Awake() 方法体结束
27{EnsureEventSystem() 方法体开始
29{if (FindObjectOfType == null) 块开始
33}if 块结束
34}EnsureEventSystem() 方法体结束
35}类 HomeRoot 体结束

C# 中每对 { } 都定义一个作用域。花括号内声明的变量离开作用域就失效。这和 JS 的 let/const 块级作用域是一样的。

CH 06

双栏对照 · 源码 + 逐行注释

左栏:高亮源码 | 右栏:解释、来源、JS 类比。标签:C# 语言 / JS 类比 / src 来源 / ! 注意

HomeRoot.cs (Assets/_Game/Home/)
解释 / 来源 / 类比
1using UnityEngine;
导入 Unity 引擎核心命名空间(GameObject、MonoBehaviour、Debug 等才能用)。C#
为什么用 using 而不是 import?
C# 的 using ≈ JS 的 import,但它是命名空间级别的,不导入具体文件而是导入整个命名空间。Unity 把核心 API 全放在 UnityEngine 命名空间里,一行 using 就能用 MonoBehaviour、GameObject、Debug 等几百个类。
2using UnityEngine.EventSystems;
导入 UGUI 事件系统(EventSystem、StandaloneInputModule 等)。C#
6public sealed class HomeRoot : MonoBehaviour
sealed = 不可被继承,标记后别人不能 extend 这个类。MonoBehaviour = Unity 脚本基类,挂 GameObject 上才有生命周期。C#js: class HomeRoot extends Component
为什么加 sealed?不加行不行?
可以不加,但推荐加。三个原因:① 这个类是场景入口脚本,没人会去继承它,标记 sealed 告诉同事「别继承」;② .NET JIT 看到 sealed 可做虚方法去虚拟化优化(虽微乎其微);③ Unity 里继承链越深越难维护,sealed 强制组合优于继承。
为什么继承 MonoBehaviour?不继承会怎样?
不继承就不能挂到 GameObject 上。MonoBehaviour 提供了 Awake/Start/Update 等生命周期钩子。纯 C# 类(如 Theme.cs、GameEnv.cs)不能在 Inspector 里看到,不能挂到场景物体上,只能作为数据类存在。
8 void Awake()
Unity 生命周期第一个回调。场景加载后执行一次,Start()之前。C#js: mounted() / useEffect([], [])
为什么用 Awake 而不是构造函数?
Unity 里不要用构造函数做初始化!GameObject 被 Instantiate 时构造函数在序列化数据还原之前就执行了,此时字段可能都是 null。Awake 在所有序列化完成之后才执行,所有引用都有效。类比:构造函数 = class 的 constructor(),Awake = Vue 的 mounted()。
Awake 和 Start 有什么区别?
Awake 先执行(所有脚本的 Awake 跑完再跑 Start)。Awake 做内部初始化(设自己字段),Start 做依赖他人的初始化(需要别的脚本 Awake 已完成)。这个项目只用 Awake——因为只需要设相机+加载预制体,不依赖其他脚本。
10 var cam = Camera.main;
Camera.main = 返回场景中 tag="MainCamera" 的第一个摄像机。C#
为什么用 Camera.main 而不是 new Camera()?
Home.scene 场景文件里已经放了一个摄像机(在 Unity 编辑器里拖进去的)。Camera.main 的作用是在代码里找到它,不是创建新的。HomeRoot 职责是「找已有摄像机并设属性」,不是「创建摄像机」。对比 GameRoot 会 new 一个。
Camera.main 性能好吗?
不太好,它内部调了 FindGameObjectsWithTag,会遍历所有 GameObject。但这里只在 Awake 调用一次,不是每帧调用,所以没问题。在 Update 里频繁用摄像机应该缓存引用。
11 if (cam != null)
防御判空:没摄像机就跳过。和 GameRoot 不同——GameRoot 会自己建。!
为什么 HomeRoot 不自己建而要信任场景?
职责分离原则。Home 场景的摄像机是美术/关卡设计师在编辑器里手动放置的,可能调了位置参数。代码只负责改背景色,不负责「有没有摄像机」——那是场景文件保证的。如果真没有,静默跳过比建一个位置可能不对的摄像机更安全。
13 cam.clearFlags = CameraClearFlags.SolidColor;
清除标志 = 纯色填充。不清会显示上一帧残留画面。C#
SolidColor 和其他选项的区别?
Skybox(默认)= 渲染天空盒,3D 游戏用。SolidColor = 纯色填充,2D 游戏标配,最省性能。DepthOnly = 只清深度,多相机叠加时用。DontClear = 不清,会拖影。
14 cam.backgroundColor = Theme.MainBackgroundColor;
背景色 = 白色Theme.MainBackgroundColor 是项目自定义常量,定义在 Theme.cssrc: Theme.csjs: cam.style.backgroundColor = Theme.mainBg
为什么不直接写 Color.white 而用 Theme?
统一管理颜色。如果以后要把白底换成米色,改 Theme.cs 一处就行,不用满项目找 Color.white。这和前端用 CSS 变量(var(--bg))不用硬编码颜色值的道理一样。
17 EnsureEventSystem();
调用本类自定义静态方法(L26-34),确保 UGUI 事件系统存在。src: 本文件 L26-34
为什么需要手动确保 EventSystem?
Unity 新建场景时不会自动创建 EventSystem。如果场景里没有 EventSystem 对象,所有 Button.onClick 都不会触发——用户点什么都没反应。这个方法就是「检查有没有,没有就建一个」的保险。
19 var prefab = Resources.Load<GameObject>("Prefabs/HomeScreen");
从 Resources/ 加载预制体"Prefabs/HomeScreen" = 对应 Resources/Prefabs/HomeScreen.prefabsrc: Resources/Prefabs/HomeScreen.prefabjs: const prefab = await import('Prefabs/HomeScreen')
为什么用 Resources.Load 而不是拖到 Inspector?
动态加载 vs 静态引用。拖到 Inspector(public字段+序列化)适合少量固定引用。Resources.Load 适合「代码决定加载什么」。这个项目选 Resources.Load 因为:① 预制体路径是固定字符串;② 保持场景文件干净,不用在 Inspector 里拖来拖去;③ 和前端 import() 心智模型一致。
Resources 目录有什么特殊?
只有 Resources 文件夹下的资源能通过 Resources.Load 动态加载。不管用不用都会打进包体——适合少量核心资源,不适合所有资源。这个项目预制体很少(4个),用 Resources 完全没问题。
20 if (prefab != null)
加载成功才继续。
21 Instantiate(prefab);
克隆预制体到场景中。不像 GameRoot 那样链式调 GetComponent+Bind。!js: document.body.appendChild(prefab.cloneNode(true))
为什么 HomeRoot 不需要 GetComponent+Bind?
HomeScreenView 自己处理初始化。它没有需要注入的 ECS 上下文(Home 场景没有 ECS 世界),也不需要外部回调。它自己读存档文件、绑定按钮事件。对比 GameRoot 里的 HUD 需要 .Bind(_contexts) 注入 ECS 上下文。
23 Debug.LogError("主页预制体缺失…");
预制体缺失时打印红色错误。js: console.error('...')
26 static void EnsureEventSystem()
类级别方法(static),不需要实例就能调用。src: 本文件C#
为什么用 static?
这个方法不依赖任何实例字段(不读 _env、不读 _contexts),纯粹是工具函数。标为 static:① 语义清晰——「我不依赖实例状态」;② 同类实例方法可直接调用,外部也能 HomeRoot.EnsureEventSystem() 调用;③ 编译器可做内联优化。
28 if (Object.FindObjectOfType<EventSystem>() == null)
全局搜索 EventSystem 组件,找不到才创建(幂等)。性能开销大但只调一次。!
FindObjectOfType 为什么性能差?
它会遍历当前场景里所有 GameObject 的所有 Component来找第一个匹配的。场景物体越多越慢。但这里只在 Awake 调用一次,影响可忽略。在 Update 里频繁用应该缓存查找结果。
30 var es = new GameObject("EventSystem");
"EventSystem" 只是显示名。详见 CH 02.9 的 EventSystem 双名分析。!
31 es.AddComponent<EventSystem>();
挂上 Unity 内置 EventSystem 类,负责 UGUI 事件分发。C#src: UnityEngine.EventSystems
32 es.AddComponent<StandaloneInputModule>();
挂上输入驱动,处理鼠标/触摸/键盘。C#src: UnityEngine.EventSystems
35}
HomeRoot 类结束。全部 35 行。2 个方法。无字段、无 Update。js: export class HomeRoot {}