外观
Anim模块相关文件
约 1172 字大约 4 分钟
2026-03-17
场景挂载
在Prefab/StageCore中,能看见BuildingScope和UnitScope两个预制件,这是关卡中所有view端building和unit的管理器
所有building和unit的动画应该挂载至这两个预制件或其子物体中,注入已由管理器负责
重要
你可以注意到,我们是按照所有建筑和所有单位的范围定义动画的;因此对于不同单位和建筑,若需要不同的动画表现,应当通过注入的上下文处理
如何实现动画
依然是MessagePipe方案,如果你看过了VFX模块的内容,你会发现这两者非常相似,只不过这次的事件包trigger了一整段动画包括其中的特效和音效
下列代码给出了一个标准的接收方行为,即定义需要接收的事件包,然后写一个函数去处理这个事件包,最终再丢给基类的Play方法
public class UnitMoveAnim : UnitAnimBase
{
public override Symbol<AnimId> AnimId { get; } = UnitAnims.UnitMove;
//这里是一些可配置项
[Inject]
private void Construct(ISubscriber<UnitMoveAnimMsg> moveSubscriber)
{
moveSubscriber.Subscribe(Invoke).AddTo(this);
}
private void Invoke(UnitMoveAnimMsg moveAnimMsg)
{
if (!TryGetUnitView(moveAnimMsg.UnitId, out var unitView))
return;
//这里是具体的动画,VFX,SFX排布
ForcePlay(unitView, seq);
}
}我能拿到什么
显然你会关心,在这个事件包的处理函数中,我能拿到哪些东西
以下以UnitAnim举例(~~BuildingAnim留作课后练习。? ~~)
除了传入的位于StaticData的事件参数包之外,我们还提供了
public abstract class UnitAnimBase : MonoBehaviour, IEntityAnim
{
[Inject] private readonly UnitViewPresenter _unitViewPresenter;
protected bool TryGetUnitView(long unitId, out IUnitAnimView unitView)
{
unitView = _unitViewPresenter.GetUnitAnimView(unitId);
return unitView != null;
}
[field:Inject] protected IUnitViewProjection UnitViewProjection { get; private set; }
[field:Inject] protected IVfxService VfxService { get; private set; }
}IUnitAnimView- 一般事件包中均会带有一个
UnitId字段,用于获取在本次处理中需要操作的GameObject - 而
IUnitAnimView即为你可以操纵的Unit预制件上的Transform,CanvasGroup,以及未来会用到的Material或其它控件(由于Unit预制件是通用的,因此这种手段是成立的)
- 一般事件包中均会带有一个
IUnitViewProjection- 如果你希望获取某个
Unit的运行时状态,应当通过这个接口和ID获取(而不是从presenter或viewModel拿二手信息) - 未来这个接口会继续补充其它需要用到的东西
- 如果你希望获取某个
IVfxService- 老熟人了
- 对比VFX模块,相当于这一整个接收方承担了
Subscriber的职能 - 思考:在哪些特效的触发中,依然是走原先的VFX模块中提到的设计的
以及其它可以注入的服务,不过一般来说,你不会需要这个的。
动画如何执行
protected void ForcePlay(IUnitAnimView unitView, Sequence sequence)
{
unitView.ForcePlay(sequence).Forget();
}
protected void WaitAndPlay(IUnitAnimView unitView, Sequence sequence)
{
unitView.WaitAndPlay(sequence).Forget();
}在基类中,提供了两个方法,它所涵盖的简单动画机能够满足该项目的需求(应该,希望如此)
ForcePlay- 强制执行当前动画,将当前正在执行和所有等待队列中的动画设置为完成(e.g.
BreachAnim)
- 强制执行当前动画,将当前正在执行和所有等待队列中的动画设置为完成(e.g.
WaitAndPlay- 将该动画加入等待队列,等到前面的动画全执行完再动(e.g.
DeathAnim)
- 将该动画加入等待队列,等到前面的动画全执行完再动(e.g.
如何触发动画
重要
顺带一提,世界空间的特效啊,音效啊之类的触发也大概会遵循第二,三类的模式;可以类比思考一下
这就有很多方式了,不过框架上大抵会遵循以下三种模式
Presenter内部触发
这一类动画非常有限,一般会是单位的生成和死亡动画
SystemViewer触发
简单来讲,项目的Scheduler是针对ECS System的一种特化,它将一个逻辑帧的所有System有序排列后依次执行
因此我们可以发现,只要我定义一些UI程序集中的System,让它在tick流程中从整个ECS world中抓取信息,然后再转换为显示事件就ok了
比如说单位的移动,只需要找到所有本tick触发了移动的单位,然后抛出事件包;比如说单位的侵入建筑,只需要捕获所有在world中放出的BreachEvent(不过BreachEvent的vibe实现目前不是最优,但不影响SystemViewer进行的转换)
泛用System触发
这就很难说了,比如某个建筑的能力触发时抛个事件出来;或者建筑掉血的时候抛个事件出来;但总之你可以发现:
所有的通信都是通过MessagePipe 和StaticData中定义的数据包进行的,因此logic和view端不会产生任何形式的耦合,所以大可以放心改;
不过要是注入了某些不该注入的服务就另当别论了
更新日志
2026/4/16 11:07
查看所有更新日志
e4055-Merge pull request #12 from azaneNH37/doc于