MeteorCat / QFramework结合DoTween

Created Thu, 07 Nov 2024 11:54:06 +0800 Modified Wed, 29 Oct 2025 23:25:05 +0800
1088 Words

DoTween动画

众所周知 Unity 本身具有动画系统, 但是因为其不成熟的体现后续都采用第三方库来处理, 其中最知名的就是 DoTween 插件.

DoTween官方网站

DoTween 极可能把功能封装完成简单函数调用, 提供了包含且不限于 线性插值、贝塞尔曲线插值、弹簧插值 等动画插值处理方法.

官方提供的样例也简单, 直接 下载插件 解压放入项目即可进行商业化使用.

插件移动过去之后一键点击配置, 最多需要点击设置 ASMDEF 处理下, 之后就可以开始直接代码配置.

这里集合之前 QFramework 追加事件即可:

using System.Collections.Generic;
using QFramework;
using UnityEngine;
using DG.Tweening; // 引入动画系统



// 项目文件: QGame/ViewController/RevolveController.cs
namespace QGame.ViewController
{
    
    /// <summary>
    /// 旋转事件
    /// </summary>
    public struct RevolveEvent
    {
        public bool Stop;
        public GameObject[] Objects;
        public float Duration;
    }
    
    
    /// <summary>
    /// 旋转控制器
    /// </summary>
    public class RevolveController : MonoBehaviour, IController,IOnEvent<RevolveEvent>
    {
        /// <summary>
        /// 返回引用句柄
        /// </summary>
        /// <returns>IArchitecture</returns>
        public IArchitecture GetArchitecture()
        {
            return QGameApp.Interface;
        }
        
        
        /// <summary>
        /// 暴露给编辑器绑定的旋转对象
        /// </summary>
        public GameObject target;

        
        /// <summary>
        /// 确定事件绑定
        /// </summary>
        private List<Tweener> tweeners = new List<Tweener>();
        
        /// <summary>
        /// 旋转角度
        /// </summary>
        private Vector3 targetRevolveVec3 = new Vector3(0,0,360f);
        
        /// <summary>
        /// 事件初始化
        /// </summary>
        private void Start()
        {
            this.RegisterEvent<RevolveEvent>().UnRegisterWhenGameObjectDestroyed(gameObject);
        }


        /// <summary>
        /// 事件回调
        /// </summary>
        public void OnEvent(RevolveEvent e)
        {
            Debug.Log($"RevolveEvent: {e.Stop},{e.Objects.Length},{e.Duration}");
            
            // 注册旋转事件
            if (tweeners.Count <= 0)
            {
                foreach (var go in e.Objects)
                {
                    tweeners.Add(go.transform.DOLocalRotate(
                        targetRevolveVec3, 
                        e.Duration,
                        RotateMode.FastBeyond360
                        ).SetEase(Ease.Linear)
                        .SetLoops(-1, LoopType.Restart)
                    );
                }
            }
            
            // 判断是否为停止
            if (e.Stop)
            {
                tweeners.ForEach(t=>t.Pause());
            }
            else
            {
                tweeners.ForEach(t=>t.Play());
            }
        }


        /// <summary>
        /// 逻辑帧执行
        /// </summary>
        private void Update()
        {
            // 鼠标左键运行
            if (Input.GetMouseButtonDown(0))
            {
                TypeEventSystem.Global.Send(new RevolveEvent
                {
                    Stop = false,
                    Objects = new []{target},
                    Duration = 2f
                });
            }

            // 鼠标右键停止
            if (Input.GetMouseButtonUp(1))
            {
                TypeEventSystem.Global.Send(new RevolveEvent
                {
                    Stop = true,
                    Objects = new []{target}
                });
            }
        }
    }   
}

最后呈现下来的效果如下:

image

可以看到 QFrameworkDoTween 的调用衔接, 虽然不需要依赖框架就能实现这类功能, 但是在项目工程上尽可能把功能做好解耦才能更好对游戏内部做好掌控.

但是! 这里所说的动画仅仅作为 简单效果动画, 比如 进度加载|卡牌抽取|简单掉落等情况; 这种游戏人物动作功能涉及到外部另外的动画系统, 比如 Spine|龙骨|Live2D等 外部动画编辑器导入 Unity.

动画事件

这里模拟背包功能, 为了 UI 不至于让菜单直接隐藏界面, 从而营造好看灵动都会做侧边移动弹出弹入效果:

using DG.Tweening;
using QFramework;
using UnityEngine;

namespace QGame.ViewController
{
    /// <summary>
    /// 背包状态
    /// </summary>
    public struct BagState
    {
        public Tweener Animation;
        public bool Closed;
    }

    public class UIController : MonoSingleton<UIController>, IController, IOnEvent<BagState>
    {
        /// <summary>
        /// 分配所属对象
        /// </summary>
        /// <returns></returns>
        public IArchitecture GetArchitecture()
        {
            return QGame.QGameApp.Interface;
        }

        /// <summary>
        /// 背包对象
        /// </summary>
        public GameObject bagTarget;


        /// <summary>
        /// 背包状态
        /// </summary>
        private BagState mBagState = new BagState();

        /// <summary>
        /// 初始化
        /// </summary>
        private void Start()
        {
            if (bagTarget != null)
            {
                // 置为宽度位移屏幕外等待移动
                var pos = bagTarget.transform.position;
                var offset = pos.x + Screen.width; // 偏移界面外等待触发移动到视角内
                bagTarget.transform.position = new Vector3(offset, pos.y, pos.z);

                // 注册弹出播放事件
                mBagState.Animation = bagTarget.transform.DOMoveX(
                        pos.x, 0.5f)
                    .SetEase(Ease.Linear)
                    .Pause()
                    .SetAutoKill(false);
            }


            // 注册事件
            this.RegisterEvent().UnRegisterWhenGameObjectDestroyed(gameObject);
        }


        /// <summary>
        /// 关闭背包
        /// </summary>
        private bool mClosed = true;

        /// <summary>
        /// 模拟GUI界面
        /// </summary>
        private void OnGUI()
        {
            GUILayout.BeginHorizontal();

            if (GUILayout.Button("打开|关闭背包"))
            {
                mClosed = !mClosed;
                mBagState.Closed = mClosed;
                TypeEventSystem.Global.Send(mBagState);
            }

            GUILayout.EndHorizontal();
        }


        /// <summary>
        /// 事件回调
        /// </summary>
        /// <param name="e"></param>
        public void OnEvent(BagState e)
        {
            Debug.Log($"初始化状态:${e.Closed}");
            if (e.Closed)
            {
                e.Animation.PlayBackwards();// 正向播放
            }
            else
            {
                e.Animation.PlayForward();// 倒向播放
            }
        }
    }
}

这里最后展示事件调度效果如下:

image

这样就能结合起来做出个虚拟的背包功能, 后续就是美术设计方面的处理.