大家好!我叫Threeyes,是全世界最懒的程序员之一,开发这款可视化插件的唯一原因就是:减少重复工作!消除加班时间!可通过以下渠道获取:

开发痛点
使用Unity作法时,我经常遇到以下问题:
-
需要频繁调用系统组件或第三方插件的公开方法,或需要组合使用多个插件的接口
-
脚本所在物体被隐藏,但需要调用其协程方法
-
基于时间(如延迟或间歇)调用方法(如:子弹射出后延迟销毁,或程序化控制灯泡闪烁)
虽然代码就那么几行,但本人作为洁癖狂就是不想写鸭!因为:
-
这么简单的功能也需要写个不通用脚本?
-
编写脚本不需要时间?
-
编译不需要时间?
-
摸鱼不需要时间?
Unity在2015年提供了
UnityEvent
特性,开发者得以可视化编辑回调方法。这虽然能减少上述部分代码工作量,然而仍有以下缺点:
-
无法方便地更改回调方法的调用顺序,如从[唱→跳→Rap]改为[跳→Rap→唱](Unity2020版增加了编辑器回调方法列表重排的功能,但只能同时在Inspector窗口中编辑单个UnityEvent)
-
无法禁用某个回调方法
-
无法混用、重用多组回调方法
-
无通用的组件载体,开发者需要在代码声明UnityEvent及其子类的字段,或者是使用与Unity其他功能模块耦合的组件(如Button或EventTrigger)
为了解决以上问题,我为项目组写了一个EventPlayer插件用于管理UnityEvent,不但减少了新手的学习成本,还能够将代码复用率提高数十倍。项目组在此插件基础上配合基础通用库可开发多个不同逻辑的项目,并且大部分时间不需要写任何额外的脚本,目前在数十个项目中运行良好。
EventPlayer核心功能
名词解释
-
子EventPlayer:子物体中的EventPlayer或其子类
EventPlayer组件
每个项目都能细分为模块及基础功能,而大部分基础功能都是通过一小段方法组合而成,我们暂时称之为“元素方法”。本插件通过将“元素方法”与EventPlayer及其子类的实例绑定,将
编程的文本增删改操作
变为
编辑器中的可视化物体摆放
。
EventPlayer组件是整个插件的核心,用于管理UnityEvent,可挂在任意游戏物体上。它有以下功能:
-
可在Hierarchy直接调用事件,以及查看当前的配置信息
-
当Play/Stop方法被调用时,执行对应UnityEvent(onPlayStop/onPlay/onStop)
-
可设置激活状态(IsActive)
-
可设置自动执行事件(IsPlayOnAwake)
-
可设置事件是否仅能执行一次(IsPlayOnce)
-
可设置Play/Stop相关事件是否翻转(IsReverse)
-
可设置为组长(IsGroup),管理子物体中的EventPlayer或其子类
-
可向监听者广播事件(listListener)
-
可指定ID,便于匿名或远程调用
-
支持网络同步

视频演示【0 BasicSetting.unity】:
EventPlayer子类(时间相关)
以下组件继承于EventPlayer,提供了与时间相关的事件控制功能:
-
DelayEventPlayer:延迟执行事件,如延迟销毁
-
RepeatEventPlayer:重复执行事件,如重复播放提示音
-
TempEventPlayer:临时调用Play事件,并在指定时间后调用Stop事件,如临时亮灯

视频演示【3 Coroutine.unity】:
EventPlayer子类(参数相关)
以下组件调用事件时,可接收或向子EventPlayer传递特定的参数,并且支持仅当参数匹配才会执行事件。EventPlayer提供了常用的组件,开发者也可以创建其他参数的子类:
-
EventPlayer_Int/Float/String/...:传递基础的参数
-
EventPlayer_SOAction:将执行方法存储在ScriptableObject的子类实例中,并通过EventPlayer调用,可以实现跨场景共享行为、持久化行为,让调用更加灵活。(注意:该功能尚未集成到当前版本中,有兴趣的可以下载本人另一个Github库 AliveCursorSDK ,通过PackageManager窗口导入Action案例进行测试)
-
EventPlayer_PlayableInfo:实时接收Timeline Clip的相关信息,后面详叙
-
EventPlayer_Video:扩充VideoPlayer组件的功能,提供播放/暂停/音量控制等基础功能,可传入指定的VideoClip参数来播放视频

EventPlayer_Video视频演示,其中提前展示了后面提到的EventPlayer组及EventPlayer序列组件功能【31 Extend_VideoPlayer.unity】:
EventPlayer组
当EventPlayer为组长时(IsGroup字段为true),该组件能够管理特定范围子物体所挂载的EventPlayer,当其Play/Stop等方法被调用时,会按照子EventPlayer在物体层级的顺序调用同名方法,该特性能够极大地提升事件组织能力。可以把一个组理解为一个方法体,把每个子EventPlayer理解为方法体中的一行代码。
Hierarchy中各EventPlayer的关系如下:

EventPlayer成组有以下特点:
-
所有EventPlayer及其子类都能设置为组长,并保留原功能特性,如DelayEventPlayer成为组长后可以让所有子EventPlayer延后执行
-
可将参数传到带参子EventPlayer中,从而实现参数传递
-
隐藏的子EventPlayer也能够被父EventPlayer管理及调用
-
可快速直观地改变子EventPlayer的执行顺序。因为EventPlayer与游戏物体绑定,因此通过改变游戏物体在层级中的顺序,就能影响对应EventPlayer实例的执行顺序(类似于代码段重排,但是避免了代码的重编译耗时)
-
不同类型的子EventPlayer组件放在同一组中,可以轻松组合出复杂的逻辑。如在程序开始后立即播放背景音乐,并在第2帧后初始化UI,然后临时显示3D指示路标并在5秒后消失。
-
常见的方法序列可以单独变成一个通用组(如下面的EventPlayerGroup(Common)),方便开发人员在任意地方调用:

EventPlayer序列
使用EventPlayerSequence的子类,能够按指定顺序调用某个子EP,适用于按特定顺序调用EP的需求。其中子EP被禁用时,序列会无法执行下一步,适用于仅当条件满足才能继续的任务类逻辑。
视频展示【10 Sequence.unity】:
EventPlayer扩展功能
Timeline
此类组件用于监听事件及接收参数,可用于Timeline进度同步等复杂操作。
Unity提供了优秀的
Timeline
库,能用于创建影片内容、游戏序列、音频序列和复杂的粒子效果,然而其提供的Signals功能并不完善。在开发时难免有在特定进度调用事件的需求,比如动画运行到某一帧时调用指定方法(虽然可通过AnimationEvent或TimelineSignals实现,但是耦合度高且不灵活),或者通过UI显示动画的播放进度。
此类需求的关键在于需要在特定时间/时间段内获取Timeline的信息,正好EventPlayer的功能之一就是传参。插件将Timeline片段的运行时数据(开始、结束、时长等)封装成PlayableInfo实例,然后通过EventPlayerClip将相关信息实时传递给场景的EventPlayer实例,之后就又回归到熟悉的EventPlayer可视化编辑流程。
开发者只需要在Timeline Track中创建一段EventPlayerClip,并为其指定场景中的某一个EventPlayer实例作为目标,当Timeline进入/退出该EventPlayerClip片段时,EventPlayer实例的Play/Stop方法会被调用。如果需要获取Timeline片段的PlayableInfo数据,可以将上述的目标换成EventPlayer_PlayableInfo,并将其listListener字段添加对应的TLEPListener子类即可。程序流程如下:

Timeline中使用EventPlayer的优点:
-
仅需要写少量代码即可使第三方插件适配Timeline,(仅需处理PlayableInfo数据,减少开发者写Timeline、Playable相关代码的痛苦)
-
可获取Clip的实时信息,进行实时预览及相应同步操作
-
配合DoTweenPro等插件可实现程序化动画,减少手搓动画的痛苦
Timeline基础视频展示【21 Extend_Timeline.unity】:
Timeline+BezierSolution插件视频展示【21_1 Extend_Timeline_BezierSolution.unity】:
Timeline+DoTweenPro插件视频展示【21_2 Extend_Timeline_DoTweenPro.unity】:
编辑器绘制工具
为了数值可视化及方便开发者自行编写子类,插件提供了编辑器界面信息的复写方法,只需要在方法体中提供自定义信息,就能在Hierarchy窗口实时显示。


整体类图

所以EventPlayer有什么特点
既然已经有那么多成熟的可视化插件(VisualScripting、PlayerMaker、Bolt),这个插件的存在意义在哪? 如下:
-
没有新增窗口,一切都在Unity原窗口中显示,避免使用VisualScripting等插件需要来回切换窗口的麻烦
-
零学习成本,物体摆放及编辑器属性设置操作都是初学者必修技能
-
下完即用,小巧(核心代码不到100KB的容量,增加的项目负担可忽略不计)
-
模块化设计,支持自定义代码
-
EventPlayer的信息保存在场景或预制物的资源文件中,因此将EventPlayer挂载在预制物上,可以实现方法重用或跨场景使用
-
仅使用Unity内置接口,不涉及Reflection、Inject等高级特性,兼容各平台,与Unity各版本兼容性良好(经测试,Unity2018.4~2022都能使用)。只要Unity不更改UnityEvent的接口,本插件基本不出大问题(紧跟Unity官方更新节奏的同学们应该能理解那种接口刚用习惯,然后下一版啪一下就弃用的痛苦囧)
-
最重要的一点是,他还提供中文支持!

当然,任何事物都有优缺点,强如一拳超人也只是个光头。本插件与UnityEvent的缺点类似,你需要注意:
-
EventPlayer如果分组不当或随意引用,可能会导致调用混乱。(无规矩不成方圆,好的组织能够避免此问题。后面会介绍推荐的分组)
-
EventPlayer与GameObject绑定,而GameObject与预制物或场景文件绑定,因此注意回调方法不能跨场景链接,否则会出现引用丢失的问题(可以通过ID调用)
-
UnityEvent通过方法名称进行事件绑定,因此一旦方法被删除/重命名/改变参数数量或类型、类所在的命名空间有变动、使用 Assembly definitions 封装Dll后,可能会导致UnityEvent无法找到对应方法。(如下图,原回调方法是用户充值后即可增加运行帧率,后来增加了一个充值价格的参数,会导致方法栏显示“Missing”的错误)

预告
目前Github及AssetStore上的插件为稳定版,后续预计会进行重构,涉及修改如下:
-
优化库之间的依赖关系,增加可选Asmdef
-
通过 package 的形式发布新版,方便通过PackageManager集成
注意
-
部分视频录制时间有点久远,有些组件名字发生变动(如IntEventPlayer→EventPlayer_Int,EventPlayerSequence→Sequence_EventPlayer),但核心功能不变。
写在结尾
希望各位看官试用完插件后提供更多建议,你们的挑刺就是对我最大的支持,谢谢朋友们!