Skip to content

Lesson 010|Stage01 阶段复习:从 API 到引擎数据流 ​

Tags: #Creator2.x #Stage01 #Node #Component #Transform #Matrix #LifecycleDifficulty: ⭐⭐⭐☆☆


🎯 本课目标 ​

  • 串联 Lesson001~Lesson009 的核心知识
  • 从 API 使用思维转向引擎数据流思维
  • 理解 Node、Transform、Matrix、Component、Lifecycle 之间的关系
  • 能分析 node.x、node.active、component.enabled、destroy() 背后的影响
  • 完成 Stage01 第一阶段闭环

🧩 本章核心问题 ​

Stage01 一直围绕一个问题:

你在代码里改了一个东西,引擎内部到底发生了什么?

例如:

ts
this.node.x = 100;
this.node.active = false;
this.getComponent(cc.Button).enabled = false;
this.node.destroy();

普通开发只知道“效果变了”。

高级开发要知道:

text
数据改了哪里
引擎什么时候处理
影响哪些组件
会不会触发生命周期
会不会影响 update
会不会影响渲染
会不会影响事件
会不会造成性能问题

🧠 Stage01 核心知识树 ​

这一章可以拆成五条主线:

text
1. Node 是什么
2. Transform 是什么
3. Matrix 是什么
4. Component 是什么
5. active / enabled / destroy / 生命周期如何影响运行状态

它们合起来就是:

text
Node 提供结构和位置
Transform 描述位置、旋转、缩放
Matrix 负责底层变换计算
Component 提供能力
Lifecycle 控制能力什么时候运行

🔍 从 this.node.x = 100 开始复盘 ​

你写:

ts
this.node.x = 100;

表面看只是改了 x。

但引擎内部大概是:

text
this.node.x = 100
    ↓
调用 setter
    ↓
修改 Node 的 position 数据
    ↓
标记 Transform Dirty
    ↓
等待本帧合适时机统一更新
    ↓
重新计算 Local Matrix
    ↓
根据父节点 World Matrix 计算自己的 World Matrix
    ↓
影响子节点 World Matrix
    ↓
渲染组件或 Renderer 消费新的 Transform / RenderData
    ↓
Batch / DrawCall
    ↓
GPU 显示到屏幕

所以:

改 node.x 不是直接操作屏幕,也不是直接操作 GPU,而是先改 Node 的 Transform 数据。

这是一条稳定的职责模型,不代表 Web 与 Native 都经过相同类和函数,也不保证每次修改都会重建顶点或只产生一个 DrawCall。实际成本还取决于父子树、组件类型、Camera、Material、RenderState 和 Pass。


🧠 为什么有 DirtyFlag? ​

如果一帧里写:

ts
this.node.x = 100;
this.node.y = 200;
this.node.scale = 2;
this.node.angle = 45;

如果每次修改都马上重算矩阵,就可能重算 4 次。

引擎更希望:

text
你改了很多属性
    ↓
我先记下来:这个节点脏了
    ↓
这一帧真正需要用的时候
    ↓
统一算一次

这就是 DirtyFlag 的意义:

避免频繁重复计算,把多次修改合并成一次更新。


🧠 Local 和 World ​

Node 的位置有两种理解:

text
Local:相对父节点
World:结合父节点后的世界坐标结果
View / Clip / Screen:经过 Camera、投影和视口后的观察、裁剪与屏幕结果

例如:

text
Root.x = 0
Panel.x = 100
Button.x = 30

那么:

text
Button.worldX = 130

如果:

text
Panel.x = 200

那么:

text
Button.worldX = 230

Button 自己的 x 没变,但显示位置变了。

更底层一点:

text
Button.worldMatrix = Panel.worldMatrix * Button.localMatrix

🧠 Transform 和 Matrix ​

你平时操作的是:

ts
node.x
node.y
node.angle
node.scale

这对开发者直观。

但引擎底层更适合用 Matrix。

所以 Creator 给开发者暴露:

text
position
rotation
scale

底层会合成:

text
Local Matrix
World Matrix

记住:

text
开发者操作 Transform
引擎使用 Matrix
GPU 最终吃顶点数据

🧠 Node 到底是什么? ​

这一章最重要的一句话:

text
Node 不是图片,不是按钮,不是文字。
Node 是场景树里的空间对象和组件容器。

Node 负责:

text
位置
旋转
缩放
父子关系
active
事件
层级
组件挂载

但 Node 不负责具体显示。

它能显示,是因为挂了:

text
Sprite / Label / Graphics / Spine

它能点击,是因为挂了:

text
Button

它能跑业务,是因为挂了:

text
自定义脚本 Component

🧠 Component 是什么? ​

Component 是:

text
挂在 Node 上的能力

例如:

text
StartButton
├── Sprite
├── Button
└── StartGameScript

职责:

text
Sprite:显示图片
Button:处理交互
StartGameScript:处理业务逻辑

所以:

text
Node 决定在哪里
Component 决定做什么

🧠 Node + Component 为什么比继承灵活? ​

如果用继承,很容易变成:

text
ShopRedPointAnimatedButtonWithTimerNode

这很难维护。

组件化可以写成:

text
ShopButton
├── Sprite
├── Button
├── Animation
├── RedPointScript
├── CountDownScript
└── ShopButtonScript

优势是:

text
按能力组合
职责清晰
组件可复用
编辑器可配置
避免继承爆炸

代价是:

text
组件多了会增加生命周期管理成本
引用关系可能变复杂
update 调度可能变多
组件之间可能强耦合

所以原则是:

text
按职责合理拆分,不是越碎越好

🧠 生命周期复盘 ​

常见生命周期顺序:

text
onLoad
↓
onEnable
↓
start
↓
update
↓
onDisable
↓
onDestroy

记法:

text
onLoad:初始化自身
onEnable:进入启用状态
start:第一次 update 前
update:每帧执行
onDisable:退出启用状态
onDestroy:销毁清理

工程习惯:

text
onEnable 注册事件,优先在 onDisable 取消。
不用 update,就不要写空 update。

🧠 update 是否执行,看哪些条件? ​

一个组件的 update 能执行,通常要满足:

text
1. 组件实现了 update
2. component.enabled = true
3. node.activeInHierarchy = true
4. 对象没有被 destroy

所以父节点 active=false 时,子节点虽然:

text
child.active = true

但:

text
child.activeInHierarchy = false

因此子节点组件不会 update。

关键点:

不是父节点 update 不调用导致子节点 update 不调用,而是子节点最终激活状态 activeInHierarchy=false,所以它不进入 update 调度。


🧠 active / enabled / destroy 复盘 ​

这三个是不同层级:

text
active:Node 层级
enabled:Component 层级
destroy:对象生命周期终止

active ​

ts
node.active = false;

影响:

text
activeInHierarchy 变 false
组件触发 onDisable
update 停止
事件停止
渲染停止
子节点也受影响
节点仍然存在

enabled ​

ts
component.enabled = false;

影响:

text
这个组件停止工作
这个组件不 update
通常触发 onDisable
其他组件不一定受影响
Node 仍然 active

destroy ​

ts
node.destroy();

影响:

text
对象标记为销毁
后续由引擎清理
触发 onDestroy
之后不应该继续使用

💼 实际项目怎么选? ​

临时隐藏界面 ​

用:

ts
panel.active = false;

适合:

text
背包
商城
任务
活动页
反复打开关闭的弹窗

只是关闭某个功能 ​

用:

ts
component.enabled = false;

例如:

text
头像还显示,但不旋转
界面还显示,但倒计时暂停
节点还显示,但某个脚本不执行

以后不用了 ​

用:

ts
node.destroy();

适合:

text
一次性特效
确定不复用的节点
切场景清理

但频繁出现的对象不建议频繁 destroy / instantiate,更适合对象池。


🔍 从 API 到引擎数据流:完整例子 ​

假设有一个活动按钮:

text
ActivityButton
├── Sprite
├── Button
├── RedPoint
└── ActivityButtonScript

你写:

ts
ActivityButton.active = false;

发生的事情大概是:

text
ActivityButton.active 变 false
    ↓
ActivityButton.activeInHierarchy 变 false
    ↓
RedPoint 等子节点 activeInHierarchy 也变 false
    ↓
ActivityButtonScript 触发 onDisable
    ↓
Button 组件停止响应
    ↓
Sprite / RedPoint 不再显示
    ↓
ActivityButtonScript.update 不再执行
    ↓
节点仍然存在,可以 active=true 恢复

你写:

ts
ActivityButton.getComponent(cc.Button).interactable = false;

发生的是:

text
Button 组件变成不可交互
    ↓
按钮通常仍显示
    ↓
节点仍 active
    ↓
其他组件仍工作
    ↓
只是不能点击或进入不可交互状态

你写:

ts
ActivityButton.destroy();

发生的是:

text
节点被标记销毁
    ↓
子节点和组件也会进入销毁流程
    ↓
onDestroy 触发
    ↓
后续不应该继续使用这个节点

🚀 高级开发怎么用这些知识? ​

初级开发看到问题:

text
按钮点不了

可能只会说:

text
是不是没绑定事件?

高级开发会按层排查:

text
1. Node active 是否为 true?
2. 父节点 activeInHierarchy 是否为 true?
3. Button Component enabled 是否为 true?
4. Button interactable 是否为 true?
5. 是否有遮挡节点?
6. 是否注册了点击事件?
7. 是否事件被父节点拦截?
8. 是否节点尺寸 / hit area 不对?
9. 是否脚本生命周期没走到?

这就是从 API 使用者到引擎理解者的区别。


📝 阶段综合练习参考答案 ​

题目结构:

text
Root
└── Panel
    ├── StartButton
    │   ├── Sprite
    │   ├── Button
    │   └── StartButtonScript
    └── TimeLabel
        ├── Label
        └── CountDownScript

当前状态:

text
Root.active = true
Panel.active = false
StartButton.active = true
TimeLabel.active = true
StartButtonScript.enabled = true
CountDownScript.enabled = true

答案:

  1. StartButton.active = true。父节点不会改掉子节点自己的 active。
  2. StartButton.activeInHierarchy = false。因为父节点 Panel.active=false。
  3. StartButtonScript.update 不会执行,因为节点 activeInHierarchy=false。
  4. TimeLabel 不会显示,因为它的 activeInHierarchy=false。
  5. 如果 Panel.active = true,并且其他状态不变:
    • Panel.activeInHierarchy = true
    • StartButton.activeInHierarchy = true
    • TimeLabel.activeInHierarchy = true
    • StartButton 显示
    • TimeLabel 显示
    • Button 可以响应
    • StartButtonScript.update 恢复
    • CountDownScript.update 恢复
    • 相关组件可能触发 onEnable
  6. 如果只想让 TimeLabel 显示但倒计时不更新:
ts
this.countDownScript.enabled = false;
  1. 如果 Panel 后续会反复打开关闭,应该用:
ts
Panel.active = false;

而不是频繁 destroy / instantiate。


🎤 Stage01 面试题整理 ​

1. Node 是什么? ​

Node 是场景树里的空间对象和组件容器,负责 Transform、层级、active、事件和组件挂载。Node 本身不是图片、按钮或文字。

2. Component 是什么? ​

Component 是挂在 Node 上的能力,比如 Sprite 负责显示,Button 负责交互,自定义脚本负责业务逻辑。

3. Local 坐标和 World 坐标有什么区别? ​

Local 是相对父节点的坐标,World 是结合父节点变换后的世界坐标;它还要经过 Camera、投影和视口,才会成为 Screen 坐标。

4. DirtyFlag 的意义是什么? ​

多次修改 Transform 时不立即重复计算,而是标记为脏,在需要时统一更新,减少重复计算。

5. active 和 activeInHierarchy 有什么区别? ​

active 是节点自身是否想激活;activeInHierarchy 是结合父节点后最终是否处于激活状态。

6. active 和 enabled 有什么区别? ​

active 控制 Node 及其子树;enabled 控制某个 Component。

7. active=false 和 destroy 有什么区别? ​

active=false 是暂时关闭,节点仍然存在;destroy 是销毁,后续不应该继续使用。

8. 为什么空 update 也有成本? ​

只要实现了 update,引擎就可能加入每帧调度,即使函数为空也有调度、遍历、判断和函数调用成本。


✅ 本课总结 ​

Stage01 你应该形成这条主线:

text
API 调用
    ↓
Node / Component 数据变化
    ↓
DirtyFlag / activeInHierarchy / enabled 状态变化
    ↓
生命周期触发
    ↓
update / 事件 / 渲染受到影响
    ↓
最终表现到屏幕和业务逻辑上

记住最终结论:

text
Node 是结构和位置。
Transform 是位置、旋转、缩放。
Matrix 是底层变换计算。
Component 是能力。
Lifecycle 决定组件什么时候运行。
active 控制节点树。
enabled 控制组件。
destroy 结束对象生命周期。

Stage01 完成。下一阶段:Stage02|Engine Loop。