经常会碰到这种Tick 方法
经常会碰到这样的方法,继承| 实现来自 ITickable
//Tick 方法
public void Tick()
{
if (++_tick % _config.CheckInterval != 0) //实现:每几秒执行一次的循环
return;
var cam = _cameraModel.CurrentCamera;
if (cam == null)
return;
GeometryUtility.CalculateFrustumPlanes(cam, _frustumPlanes);
Vector3 camPos = cam.transform.position;
var items = _registry.Items;
//
Unity Zenject ITickable 完整触发原理与执行流程
一、基础概念
1. 核心接口
public interface ITickable
{
void Tick();
}
配套还有:
IFixedTickable:对应FixedUpdateILateTickable:对应LateUpdate
作用:替代 MonoBehaviour 的 Update,实现非 Mono 逻辑解耦、依赖注入、统一生命周期管理。
2. 核心管理类:TickManager
Zenject 所有 Tick 逻辑的总调度器,是整个流程的核心,属于场景容器 DiContainer 内置自动绑定单例,无需手动注册。 职责:
- 存储所有注册的
ITickable/IFixedTickable/ILateTickable对象 - 在 Unity 原生 Update/FixedUpdate/LateUpdate 驱动下,批量调用所有 Tick 方法
- 支持执行优先级、动态增删 Tick 对象
二、底层挂载载体:ZenjectMonoBehaviour(场景自动生成)
- 加载 Zenject 场景时,容器会自动创建一个隐藏 GameObject,挂载
SceneKernel(继承 ZenjectMonoBehaviour) SceneKernel内部持有TickManager引用,监听 Unity 原生生命周期:void Update() { _tickManager.Tick(); } void FixedUpdate() { _tickManager.FixedTick(); } void LateUpdate() { _tickManager.LateTick(); }
关键:ITickable 不是自己挂脚本,是由一个全局 Mono 统一驱动所有非 Mono 对象。
三、完整执行全流程(分 4 大阶段)
阶段 1:对象注册(绑定阶段)
两种注册 ITickable 的方式,最终都会加入 TickManager 的列表:
方式 1:Bind 后自动注册(最常用)
// Installer中绑定
Container.BindInterfacesTo<PlayerLogic>().AsSingle();
BindInterfacesTo会自动识别该类实现了ITickable- 绑定完成后,自动调用
TickManager.AddTickable(实例, 优先级)
方式 2:手动添加(动态运行时新增)
// 运行时拿到TickManager手动注册
var tickMgr = Container.Resolve<TickManager>();
tickMgr.AddTickable(new EnemyLogic(), 100);
存储结构
TickManager 内部维护三个有序列表:
List<TickableInfo> _tickables; // ITickable
List<TickableInfo> _fixedTickables; // IFixedTickable
List<TickableInfo> _lateTickables; // ILateTickable
TickableInfo = 实例对象 + 执行优先级 int,列表会按优先级从小到大排序,数值越小越先执行。
阶段 2:Unity 原生帧驱动(SceneKernel Mono 回调)
每帧 Unity 执行顺序:
FixedUpdate→ SceneKernel.FixedUpdate() →TickManager.FixedTick()Update→ SceneKernel.Update() →TickManager.Tick()LateUpdate→ SceneKernel.LateUpdate() →TickManager.LateTick()
阶段 3:TickManager 批量分发(核心触发逻辑)
以普通 ITickable.Tick() 举例,TickManager.Tick() 内部逻辑:
- 处理上一帧标记为移除的 Tick 对象(安全移除,防止遍历时删列表报错)
- 遍历排序后的
_tickables列表 - 逐个调用
tickInfo.Tickable.Tick() - 执行完成,等待下一帧
伪代码简化实现:
public void Tick()
{
// 先清理待删除对象
ProcessRemoveQueue();
// 按优先级顺序遍历所有ITickable
foreach (var info in _tickables)
{
if (info.IsActive)
{
info.Tickable.Tick();
}
}
}
优先级规则
- 绑定时指定优先级:
Container.BindInterfacesTo<GameLoop>().AsSingle().WithArguments(200); - 默认优先级:0
- 数字越小,越早执行;负数优先,大数后置
- 同优先级:注册顺序不确定
阶段 4:销毁 / 移除流程
- 对象销毁、容器销毁、手动移除都会触发注销
_tickManager.RemoveTickable(tickObj); - 不会立刻从列表删除,加入移除队列,当前帧遍历结束后统一清理,避免遍历中集合修改报错
- 场景卸载时,
SceneKernel销毁,TickManager 清空所有列表,停止所有 Tick
四、关键特性与底层细节
1. 和 Mono Update 本质区别
| 维度 | MonoBehaviour Update | Zenject ITickable |
|---|---|---|
| 驱动载体 | 每个脚本自身 Mono | 全局唯一 SceneKernel 统一驱动 |
| 对象类型 | 必须挂载 GameObject | 纯 C# 非 Mono 类也可使用 |
| 依赖管理 | 无原生注入,需手动 GetComponent | 完全由 DI 容器管理依赖 |
| 执行排序 | 依赖脚本挂载顺序 / Execution Order | 自定义数值优先级,可控 |
| 批量性能 | 大量 Mono 有 GC 开销 | 单 Mono 遍历列表,更少 GC |
2. 线程与执行时机
- 完全在主线程执行,和 Update 同步,不能异步
ITickable= Update 时机;IFixedTickable= FixedUpdate(不受帧率影响,固定时间步);ILateTickable= LateUpdate(渲染前最后逻辑)
3. 激活 / 暂停控制
TickableInfo 包含 IsActive 标记:
- 调用
tickManager.PauseTickable(obj)会标记为不执行,不删除 - 适合临时暂停逻辑(如游戏暂停),无需反复 Add/Remove
4. 生命周期绑定自动解绑
通过 BindInterfaces 绑定的对象,当对象被容器销毁时,Zenject 会自动调用 RemoveTickable,无需手动清理,防止空引用报错。
5. 多容器隔离
- ProjectContext(全局容器)、SceneContext(场景容器)各自拥有独立
TickManager - 全局注册的 ITickable 跨场景常驻;场景内注册的切换场景自动销毁停止 Tick,互不干扰
五、完整时序链路总结
Unity引擎帧循环
↓
SceneKernel(Mono).Update()
↓
TickManager.Tick()
↓
1. 清理待删除Tick对象队列
2. 按优先级升序遍历全部ITickable实例
↓
循环调用每个实例的 Tick() 方法
↓
本帧Tick执行完毕,等待下一帧Update再次触发
FixedTick / LateTick 流程完全一致,仅触发入口为 FixedUpdate/LateUpdate,对应不同接口。
六、常见踩坑底层原因
- Tick 不执行
- 类没有通过
BindInterfaces绑定,只是 Bind <类名>(不会自动识别 ITickable) - 对象提前被销毁,已从 Tick 列表移除
- 调用了 PauseTickable 暂停执行
- 类没有通过
- 遍历时报集合修改异常 Zenject 设计了延迟删除队列,只要不用反射强行直接操作内部列表,不会报错;禁止在 Tick 内部直接 Remove
- 执行顺序混乱 未指定优先级,默认 0,同优先级注册顺序不保证,复杂业务必须手动传入优先级数值排
课内,生命周期,解释链接
Unity高性能依赖注入框架Extenject(Zenject)-----进阶教程 - 灰信网(软件开发博客聚合)

完整生命周期如上,只是
ProjectContext
SceneContext
这和普通英文翻译和理解不同,ProjectContext 是在SceneContext 下触发的,因为Unity必然是以Scene 场景为基础单位

Fixed 修复循环引用问题recycle dependence(可能能修复)
https://www.youtube.com/watch?v=v22y-gNnk8I
Zneject +Editor 例子
ZenjectEditorWindowの紹介 | VirtualCast Blog
课外,实现 MenuBar (Group by 页签) Zenject框架是否OK?
研究中。。。



创建
显示Label
点击事件
//参考代码,不能直接拷贝,依赖多个自定义类
var item = GameObject.Instantiate(m_Toggle3,m_ToggleRoot,true);
item.transform.position += new Vector3(210, 0);
var binder = item.GetComponent<UIDataBinder>();
var col = new CommonDataCollection();
col["label"] = "储物柜";
binder.Args = col;

CommndBar
Billboard
- 课外3- Zenject Tick方法和全局生命周期&spm=1001.2101.3001.5002&articleId=126595409&d=1&t=3&u=b44d09337b1f4c0e970a65389d447054)
2万+

被折叠的 条评论
为什么被折叠?



