【Unity博客节选】Timeline 内部结构 IntervalTree 分析

article/2025/7/20 5:51:04

注:软件版本Unity 6.0 + Timeline 1.8.7

作者:CSDN @ RingleaderWang
原文:《Unity第25期——Timeline结构及其源码浅析》
文章首发Github👍:《Timeline结构及其源码浅析》

Bilibili 视频版👍👍:《Timeline结构及其源码解析》https://www.bilibili.com/video/BV1bHjYzNE35

Unity的 Timeline 本质是一个包含很多可播放片段(Playable Clip)的区间树(IntervalTree),这个区间树可以排序、搜索、以及控制所有 Clip的激活与停止,最后利用底层的Playable系统,运行所激活的Clips,就是这么个东西。

(当然这么定义不够准确,IntervalTree不控制clip激活,只是排序和搜索clip。IntervalTree本质是装clip的容器,不考虑性能的话用数组也行。这么定义只是方便理解Timeline的底层结构罢了)

下面便详细解析下 IntervalTree 的结构。

TimelinePlayable利用IntervalTree来管理RuntimeClip,UML类图如下:

IntervalTree UML类图
  • m_Entries:记录所有 RuntimeClip 和其左右边界的List
  • m_Nodes:IntervalTree中的节点
    • node first/last: 表示存储在当前节点中的所有Runtimeclip中的首和尾index
    • center:排序前比较重要,表示待排序Runtimeclip左右边界中点,用于后续递归地进行二分排序
    • left/right:左右子节点

Build IntervalTree 逻辑:

private void Rebuild()
{m_Nodes.Clear();m_Nodes.Capacity = m_Entries.Capacity;Rebuild(0, m_Entries.Count - 1);
}private int Rebuild(int start, int end)
{IntervalTreeNode intervalTreeNode = new IntervalTreeNode();// minimum size, don't subdivideint count = end - start + 1;if (count < kMinNodeSize){intervalTreeNode = new IntervalTreeNode() { center = kCenterUnknown, first = start, last = end, left = kInvalidNode, right = kInvalidNode };m_Nodes.Add(intervalTreeNode);return m_Nodes.Count - 1;}var min = Int64.MaxValue;var max = Int64.MinValue;for (int i = start; i <= end; i++){var o = m_Entries[i];min = Math.Min(min, o.intervalStart);max = Math.Max(max, o.intervalEnd);}var center = (max + min) / 2;intervalTreeNode.center = center;// first pass, put every thing left of center, leftint x = start;int y = end;while (true){while (x <= end && m_Entries[x].intervalEnd < center)x++;while (y >= start && m_Entries[y].intervalEnd >= center)y--;if (x > y)break;var nodeX = m_Entries[x];var nodeY = m_Entries[y];m_Entries[y] = nodeX;m_Entries[x] = nodeY;}intervalTreeNode.first = x;// second pass, put every start passed the center righty = end;while (true){while (x <= end && m_Entries[x].intervalStart <= center)x++;while (y >= start && m_Entries[y].intervalStart > center)y--;if (x > y)break;var nodeX = m_Entries[x];var nodeY = m_Entries[y];m_Entries[y] = nodeX;m_Entries[x] = nodeY;}intervalTreeNode.last = y;// reserve a placem_Nodes.Add(new IntervalTreeNode());int index = m_Nodes.Count - 1;intervalTreeNode.left = kInvalidNode;intervalTreeNode.right = kInvalidNode;if (start < intervalTreeNode.first)intervalTreeNode.left = Rebuild(start, intervalTreeNode.first - 1);if (end > intervalTreeNode.last)intervalTreeNode.right = Rebuild(intervalTreeNode.last + 1, end);m_Nodes[index] = intervalTreeNode;return index;
}

构建思路非常简单:

  • 排序开始时,遍历所有Runtimeclip,确定最左端和最右端边界,然后取边界的中点作为根节点的center,这样构建的tree不至于一边短一边长。
  • 然后把所有Runtimeclip完全在中间值center左侧的放到m_Entries靠下位置,把所有Runtimeclip完全在中间值center右侧的放到m_Entries靠上位置,被center贯穿的Runtimeclip保留在当前节点中。
  • 这样就完成了初步排序,然后再递归地排序靠下的这坨,并作为左子节点;再递归地排序靠上的这坨,并作为右子节点。这样整棵IntervalTree就构建完成了。

最终结构如下图所示:

IntervalTree内部结构示例

RuntimeClip 结构

下面就以 AnimationClip 为例,讲解IntervalTreeNode中RuntimeClip的创建逻辑:

  1. 创建TimelineClip (主逻辑在TrackAsset)

    1. 根据TrackClipTypeAttribute的定义获取限定的ClipTypeGetType().GetCustomAttributes(typeof(TrackClipTypeAttribute))
    2. 创建限定ClipType的 TimelineClip 容器newClip = CreateNewClipContainerInternal()
    3. 把特定类型 ClipPlayableAsset 比如AnimationPlayableAsset塞进TimelineClip中asset参数中。(此时AnimationPlayableAsset中clip为null)
  2. 将具体AnimationClip塞进AnimationPlayableAsset的clip变量中AddClipOnTrack(newClip, parentTrack, candidateTime, assignableObject, state)

  3. 创建RuntimeClip (主逻辑在TrackAsset 的 CompileClips方法)

    1. 根据timelineClip和clip对应的Playable创建RuntimeClip
    2. 将新建的RuntimeClip加入区间树IntervalTree
  4. 重新排序区间树节点(可延迟执行)

最终,TimelineClip 与 RuntimeClip结构如下:

TimelineClip 与 RuntimeClip结构

运行时 IntervalTree

我们使用区间树 IntervalTree 最主要目的就是能快速搜索RuntimeClip,为什么呢?

因为每帧Timeline都会执行PrepareFrame方法,指明哪些clip在当前时间激活,哪些clip在当前时间停止,如果只是用List结构,搜索时间复杂度 O(n),而使用IntervalTree,时间复杂度便降到 O(logn)了。

TimelinePlayable PrepareFrame 方法执行逻辑:

  1. 利用IntervalTree获取当前帧所有激活的RuntimeClip
  2. disable上一帧激活,这一帧未激活的clip(会执行Playable的Pause()方法 )
  3. enable这一帧激活的clip(会执行Playable的Play()方法 )
  4. 根据mixin/mixout curve设置此clip所在的mixer input weight权重


http://www.hkcw.cn/article/fdmaOLober.shtml

相关文章

太原一路虎车横冲直撞 路人纷纷避让 危险驾驶引热议

今天刷手机时看到一段让人揪心的视频。6月2日凌晨,太原南中环花海酒吧门口,一辆路虎越野车失控冲撞。车辆先是猛烈撞击酒吧大门,停顿片刻后又朝人群冲去,吓得周围人四处躲避。有个穿黑衣服的小伙子试图上前拉开车门,结果被晃得一个趔趄。据现场网友说,事发时间大约是凌晨…

吃不起的玉米蛋挞到底谁在买 轻奢甜品引争议

“我亲手种的苞谷,终究成了我吃不起的玉米蛋挞。”这几天,许多短视频展示了从玉米地到高价烘焙品玉米蛋挞的转场画面,台词表达了消费者对高价烘焙品的不满。一块名为玉米蛋挞的网红甜品引发了广泛关注。在产地,一斤玉米的收购价大约在一元上下,但在烘焙店里,一枚玉米蛋挞…

泡泡玛特市值首超三丽鸥 登顶亚洲角色经济榜首

2025年6月2日,中国潮玩巨头泡泡玛特迎来重要时刻。截至当日收盘,公司总市值突破2500亿港元,首次超越日本IP巨头三丽鸥,登顶亚洲角色经济市值榜首。这一成就主要归功于泡泡玛特以自有IP Labubu为核心的全球爆火以及海外市场同比超475%的爆发式增长。公司计划年内将海外门店数…

哈佛演讲的中国女生否认走后门入学 回应争议自述经历

哈佛大学毕业典礼上,中国学生蒋雨融的演讲引起了广泛关注。6月2日凌晨,一个名为“哈佛蒋雨融Luanna”的账号发文回应了争议。她提到自己从小父母离婚分居,跟随母亲四处搬家转学。在初中时曾遭受霸凌,只能通过阅读书籍来寻找安慰和答案。在美国求学期间,她的推荐信分别来自…

PostgreSQL 性能问题诊断:锁等待、索引失效与查询计划分析

在高并发、大数据量的 PostgreSQL 应用场景中,锁等待、索引失效与查询计划异常是导致性能下降的三大核心问题。 本文将系统解析如何通过 pg_locks、pg_stat_activity 和 EXPLAIN ANALYZE 等工具快速诊断问题,并结合实战案例与代码示例,帮助开发者构建完整的性能调优知识体系…

3D可视化/元宇宙方向前端岗位30道Three.js高频面试题及解析

文章目录 一、核心概念二、对象与材质三、动画与交互四、性能优化五、高级渲染六、加载与资源七、工程实践八、特效实现九、调试与问题十、综合应用 一、核心概念 Three.js三大核心组件及作用 解析&#xff1a; 场景&#xff08;Scene&#xff09;&#xff1a;容器&#xff0c;…

食养有方:进行性核上性麻痹患者的健康饮食指南

进行性核上性麻痹&#xff08;PSP&#xff09;是一种罕见的神经系统变性疾病&#xff0c;患者常出现吞咽困难、肌肉强直等症状&#xff0c;因此合理的饮食对于维持患者营养状况、延缓病情发展至关重要。 患者饮食应遵循 “均衡、易消化、高营养” 原则。主食方面&#xff0c;可…

瘦了不少!姚明进行恢复训练曝光 一脸笑容被调侃“亚洲杯复出”

北京时间6月2日,有博主爆料,给儿时的偶像训练引发外界的热议,特别是姚明身型变化相对比较明显,几乎是瘦了一圈,身穿运动套装一脸笑容,在过去几年极为罕见,姚明几乎每一次公开场合,或者是私下的活动都是身穿正装为主。有球迷调侃说道:姚明要复出吗,这不是亚洲杯马上要…

CodeTop100 Day19

55、从前序与中序遍历序列构造二叉树 前序是根左右&#xff0c;中序是左根右 写一个递归函数&#xff0c;build&#xff08;preorder,0,preorder.length-1,inorder,0,inorder.length-1&#xff09;;接收的参数是先序遍历数组&#xff0c;起始位置&#xff0c;长度&#xff0c;…

又要热热热回来了 多地将迎持续高温

华北和黄淮地区本周将迎来更多高温天气。明天,河北南部和河南北部将出现零星高温,随后高温范围会逐渐扩大。石家庄、济南和郑州等地预计本周会有连续3到5天的高温天气。从5日起,随着副热带高压加强并向北移动,南方地区的高温也将增加。武汉、杭州和长沙在6日预报了高温,如…

如何利用自动生成文档工具打造出色的技术文档

文章目录 每日一句正能量前言一、自动生成文档工具的优势&#xff08;一&#xff09;提高效率&#xff08;二&#xff09;保持一致性&#xff08;三&#xff09;实时更新 二、常见的自动生成文档工具&#xff08;一&#xff09;Sphinx&#xff08;二&#xff09;Javadoc&#x…

张雪峰称或告别直播:动了太多人蛋糕 真情流露引共鸣

5月31日晚,有网友发布视频称张雪峰2025届高考志愿填报季直播结束。直播中,张雪峰提到:“做这行不容易,触动了太多人的利益,有些话不能说得太直白。我们计划8月份恢复直播,希望8月1日能和大家见面,如果不行的话,9月1日也可以,但也有可能这是你最后一次在网上见到我,我…

[蓝桥杯]剪格子

剪格子 题目描述 如下图所示&#xff0c;3 x 3 的格子中填写了一些整数。 我们沿着图中的红色线剪开&#xff0c;得到两个部分&#xff0c;每个部分的数字和都是 60。 本题的要求就是请你编程判定&#xff1a;对给定的 mnmn 的格子中的整数&#xff0c;是否可以分割为两个部…

中国援喀麦隆医疗队开展义诊活动 情暖六一关爱健康

当地时间5月30日至6月1日,中国(山西)第24批援喀麦隆姆巴尔马尤医疗队在喀麦隆杜阿拉开展了一系列健康公益活动,通过专业医疗服务和人文关怀为中非友谊注入新活力。5月30日,医疗队在中企杜阿拉项目园区进行了健康知识宣讲。医生们通过理论讲解与实操演示,系统培训了AED(自…

【连载22】基础智能体的进展与挑战综述-超对齐

21. 超对齐与人工智能智能体中的安全扩展法则 21.1 超对齐&#xff1a;面向目标的人工智能智能体对齐 随着大规模语言模型&#xff08;LLMs&#xff09;越来越多地成为自主智能体决策的核心&#xff0c;确保它们的输出保持安全、伦理&#xff0c;并始终与人类目标一致&#x…

【技术追踪】InverseSR:使用潜在扩散模型进行三维脑部 MRI 超分辨率重建(MICCAI-2023)

LDM 实现三维超分辨率~ 论文&#xff1a;InverseSR: 3D Brain MRI Super-Resolution Using a Latent Diffusion Model 代码&#xff1a;https://github.com/BioMedAI-UCSC/InverseSR 0、摘要 从研究级医疗机构获得的高分辨率&#xff08;HR&#xff09;MRI 扫描能够提供关于成像…

美科罗拉多州恐袭嫌疑人曝光 FBI定性恐袭

当地时间6月1日,美国科罗拉多州博尔德市一名男子向人群投掷燃烧瓶,造成6人烧伤。受害者年龄在67至88岁之间,均已送医。嫌疑人确认是45岁的穆罕默德苏莱曼,案发后亦因伤入院。FBI局长卡什帕特尔称该事件为“有针对性的恐怖袭击”,并指出FBI已将此案按恐袭处理。副局长丹邦吉…

成都90后小伙让刀剑重获新生 十年磨一剑

在成都邛崃郊外的一间工作室里,一把锈迹斑斑的古刀静静地躺在工作台上。王一凯戴上手套,拿起磨石,开始了又一个漫长的工作日。磨石与刀身接触,发出“嗤嗤”的摩擦声。褐色锈层缓缓剥落,千年前的钢铁本色渐渐显露。这把沉睡已久的古刀,在他手中慢慢苏醒。1991年出生的王一…

白俄罗斯媒体:卢卡申科将访华 深化双边合作

白俄罗斯总统卢卡申科计划于本月2日至4日访问中国。此前,卢卡申科曾表示,白中关系是互利共赢的典范。白方将坚定恪守一个中国原则,并愿与中方共同推进“一带一路”重大项目,发展新质生产力,助力各自的发展振兴。责任编辑:zhangxiaohua

气象部门回应西藏现红色精灵闪电 罕见现象揭秘

5月31日至6月1日,中国摄影师在西藏山南拍到红色精灵闪电。精灵闪电形成于大气中间层,是雷击产生电磁波的结果。据中国天气科普,它在空中存在几十分之一秒,如鬼魅一般难以捉摸。责任编辑:zhangxiaohua