Unity PlayableDirector(Timeline) 正播倒播处理

控制Timeline的播放状态,官方API中只有正播方法,没有倒播的接口。
自己原本有一个使用协程的方式,每一帧中进行更新,但是协程在处理中途暂停继续播放上比较难处理。所以使用Dotween的方式重新编辑一个。

TimelineHelper.cs :主要是提供了两个静态接口,用来实现挂TimelineDirector组件

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Playables;
 
namespace Tools
{
    public static class PlayableExpansion
    {
        /// <summary>
        /// 为director添加自定义控制器
        /// </summary>
        /// <param name="director"></param>
        /// <returns></returns>
        public static PlayableController AddPlayableController(this PlayableDirector director)
        {
            return director.gameObject.AddComponent<TimelineDirector>();
        }
 
        /// <summary>
        /// 为obj添加自定义控制器
        /// </summary>
        /// <param name="director"></param>
        /// <returns></returns>
        public static PlayableController AddPlayableController(this GameObject obj)
        {
            return obj.AddComponent<TimelineDirector>();
        }
    }
 
 
    public class TimelineHelper
    {
        /// <summary>
        /// 创建Timeline控制器
        /// </summary>
        /// <param name="director">PlayableDirector 组件</param>
        public static TimelineDirector CreateTimelineDirector(PlayableDirector director)
        {
            Debug.Assert(null != director, "null is director");
            return director.GetComponent<TimelineDirector>() ?? director.gameObject.AddComponent<TimelineDirector>();
        }
 
        /// <summary>
        /// 创建Timeline控制器
        /// </summary>
        /// <param name="directorPath">PlayableDirector 路径</param>
        public static TimelineDirector CreateTimelineDirector(string directorPath)
        {
            var director = GameObject.Find(directorPath);
            Debug.Assert(null != director, "null is directorPath");
            return director.GetComponent<TimelineDirector>() ?? director.gameObject.AddComponent<TimelineDirector>();
        }
    }
}

TimelineDirector.cs :Timeline的相关控制封装,没有用官方播放的API,只用到了PlayableDirector时间和采样(PlayableDirector.time和PlayableDirector.Evaluate())

using System;
using DG.Tweening;
using UnityEngine;
using UnityEngine.Playables;



[RequireComponent(typeof(PlayableDirector))]
public class TimelineDirector : MonoBehaviour
{
    #region ENUM
    public enum Status
    {
        NULL,
        PLAYING,
        PAUSED,
        STOPPED,
    }

    public enum Direction
    {
        NULL,
        FORWARD,
        BACKWARD
    }

    #endregion

    [SerializeField]
    private PlayableDirector m_playableDirector;

    [Range(0f, 1f)]
    public float PlaySpeed = 1f;

    /// <summary>
    /// 播放模式
    /// </summary>
    public WrapMode WrapMode = WrapMode.Once;

    /// <summary>
    /// 开始播放事件, 返回时 时间点,和触发时方向
    /// </summary>
    public Action<double, Direction> OnPlay;

    /// <summary>
    /// 暂停播放事件, 返回时 时间点,和触发时方向
    /// </summary>
    public Action<double, Direction> OnPause;

    /// <summary>
    /// 停止播放事件, 返回时 时间点,和触发时方向
    /// </summary>
    public Action<double, Direction> OnStop;

    /// <summary>
    /// 继续播放事件, 返回时 时间点,和触发时方向
    /// </summary>
    public Action<double, Direction> OnContinue;

    /// <summary>
    /// Timeline长度
    /// </summary>
    public double Duration { get; private set; } = -1f;

    /// <summary>
    /// 当前播放状态(如果用不到可是删除,现在这个字段只是一个状态的记录)
    /// </summary>
    public Status CurrentPlayStatus { get; private set; } = Status.NULL;

    /// <summary>
    /// 当前播放方向(如果用不到可是删除,现在这个字段只是一个状态的记录)
    /// </summary>
    public Direction CurrentPlayDirection { get; private set; } = Direction.NULL;

    /// <summary>
    /// 当前播放进度
    /// </summary>
    public double CurrentTime { get; private set; } = 0d;

    private Tweener m_timeTween;

    [Header("自设播放时间")]
    public Vector2 timeV2;
    private void Awake()
    {
        if (m_playableDirector == null)
        {

            m_playableDirector = GetComponent<PlayableDirector>();
        }
        m_playableDirector.playOnAwake = false;
        Duration = m_playableDirector.duration;
        CurrentPlayStatus = Status.STOPPED;
    }

    /// <summary>
    /// 继续播放
    /// </summary>
    public void Continue()
    {
        OnContinue?.Invoke(CurrentTime, CurrentPlayDirection);
        if (m_timeTween.IsActive()) m_timeTween.Play();
    }

    /// <summary>
    /// 从暂停时间点正向播放, 应用在倒播中途暂停后切换为正播
    /// </summary>
    public void ContinuePlayForwardByPausePoint()
    {
        OnContinue?.Invoke(CurrentTime, CurrentPlayDirection);
        CurrentPlayStatus = Status.PLAYING;
        CurrentPlayDirection = Direction.FORWARD;
        m_timeTween.Kill();
        RatioExecute(Duration);

    }

    /// <summary>
    /// 从暂停时间点反向播放, 应用在正播中途暂停后切换为倒播
    /// </summary>
    public void ContinuePlayBackwardByPausePoint()
    {
        OnContinue?.Invoke(CurrentTime, CurrentPlayDirection);
        CurrentPlayStatus = Status.PLAYING;
        CurrentPlayDirection = Direction.BACKWARD;
        m_timeTween.Kill();
        RatioExecute(0);
    }

    /// <summary>
    /// 从开始播放
    /// </summary>
    public void PlayForward()
    {
        OnPlay?.Invoke(CurrentTime, CurrentPlayDirection);

        m_timeTween.Kill();
        CurrentTime = 0d;
        RatioExecute(Duration);
    }

    /// <summary>
    /// 从结尾倒放
    /// </summary>
    public void PlayBackward()
    {
        OnPlay?.Invoke(CurrentTime, CurrentPlayDirection);

        m_timeTween.Kill();
        CurrentTime = Duration;
        RatioExecute(0);
    }

    /// <summary>
    /// 暂停播放
    /// </summary>
    public void Pause()
    {
        OnPause?.Invoke(CurrentTime, CurrentPlayDirection);

        m_timeTween.Pause();

    }

    /// <summary>
    /// 停止播放
    /// </summary>
    public void Stop()
    {
        OnStop?.Invoke(CurrentTime, CurrentPlayDirection);

        m_timeTween.Kill();
        CurrentTime = 0d;
        m_playableDirector.time = CurrentTime;
        m_playableDirector.Evaluate();
    }


    private void RatioExecute(double target)
    {
        // 使用DoTween最当前时间进行线性过渡
        m_timeTween = DOTween.To(() => CurrentTime, x => CurrentTime = x, target, PlaySpeed).SetSpeedBased().SetEase(Ease.Linear);
        // 做出限制避免bug
        CurrentTime = Clamp(CurrentTime, 0d, Duration);

        m_timeTween.OnUpdate(() =>
        {
            // 直接取样
            m_playableDirector.time = CurrentTime;
            m_playableDirector.Evaluate();
        });
        m_timeTween.Play();
    }

    /// <summary>
    /// 针对Double的Clamp
    /// </summary>
    public static double Clamp(double value, double min, double max)
    {
        if (value < min)
            value = min;
        else if (value > max)
            value = max;
        return value;
    }

    [ContextMenu("设置播放时间")]
    public void SetStartEnd() 
    {
        //关闭当前DoTween动画
        m_timeTween.Kill();
        //设置开始时间
        CurrentTime = timeV2.x;
        //设置结束时间
        RatioExecute(timeV2.y);
    }
}

TimelineController.cs 测试类创建一个场景自己制作一个Timeline, 绑定一些按钮。

using System.Collections;
using System.Collections.Generic;
using Tools;
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.UI;
 
public class TimelineController : MonoBehaviour
{
 
    public PlayableDirector PlayableDirector;
 
    public Button PlayForward;
 
    public Button PlayBackward;
 
    public Button Pause;
 
    public Button Stop;
 
    public Button Continue;
 
    public Button ContinueForward;
 
    public Button ContinueBackward;
 
    public TimelineDirector Director;
 
    public void Start()
    {
        Director = TimelineHelper.CreateTimelineDirector(PlayableDirector);
 
        Director.OnPlay = (t, d) =>
        {
            Debug.Log($"OnPlay time {t} dir {d}");
        };
 
        Director.OnPause = (t, d) =>
        {
            Debug.Log($"OnPause time {t} dir {d}");
        };
 
        Director.OnContinue = (t, d) =>
        {
            Debug.Log($"OnContinue time {t} dir {d}");
        };
 
        Director.OnStop = (t, d) =>
        {
            Debug.Log($"OnStop time {t} dir {d}");
        };
 
        PlayForward.onClick.AddListener(() =>
        {
            Director.PlayForward();
        });
 
        PlayBackward.onClick.AddListener(() =>
        {
            Director.PlayBackward();
        });
 
        Pause.onClick.AddListener(() =>
        {
            Director.Pause();
        });
 
        Stop.onClick.AddListener(() =>
        {
            Director.Stop();
        });
 
        Continue.onClick.AddListener(() =>
        {
            Director.Continue();
        });
 
        ContinueForward.onClick.AddListener(() =>
        {
            Director.ContinuePlayForwardByPausePoint();
        });
 
        ContinueBackward.onClick.AddListener(() =>
        {
            Director.ContinuePlayBackwardByPausePoint();
        });
    }
}

下面是采用协程的方式写的功能:


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;

public class TimelineTool : MonoBehaviour
{ 
    public PlayableDirector Director;
    public float PD_Speed = 1;
    public bool isPause=false;
    public float StartTime;
    public float EndTime;


   
    private float time;
    private float temp_time;
    private bool isActive = true;
    public  static bool isPlaying  = false;

    public Vector2[] ChaiTime;
    private void Start()
    {

    }

    void Update()
    {
       
        time += Time.deltaTime;

    }


    /// <summary>
    /// 倒放播放
    /// </summary>
    /// <returns></returns>
    public IEnumerator BackSeedingPlay()
    {
        yield return new WaitForSeconds(0.001f * Time.deltaTime);
        Director.time -= PD_Speed * Time.deltaTime;  //PD_Speed是倒帶速度
        Director.Evaluate();
        if (Director.time < StartTime)
        {
            Director.time = StartTime;
            Director.Evaluate();
            isPlaying = false;
        }
        else
        {
            if (!isPause) {
                StartCoroutine("BackSeedingPlay");
                isPlaying = true;
            }
            
        }
    }
   
    /// <summary>
    /// 正向播放
    /// </summary>
    /// <returns></returns>
    public IEnumerator ForwardSeedingPlay()
    {
        
        yield return new WaitForSeconds(0.01f * Time.deltaTime);
        Director.time += PD_Speed * Time.deltaTime;
        Director.Evaluate();
        if (Director.time > EndTime)
        {
            Director.time = EndTime;
            Director.Evaluate();
            isPlaying = false;
        }
        else
        {
            if (!isPause) {
                StartCoroutine("ForwardSeedingPlay");
                isPlaying = true;
            }
            
        }
    }


   
    public void PlayAnimator() {

        if (time-temp_time>0.5f) {
            if (isActive)
            {
                Director.time = StartTime;
                isPause = false;
                StopCoroutine("BackSeedingPlay");
                Director.Pause();         
                StartCoroutine("ForwardSeedingPlay"); 
                isActive = false;
               
            }
            else {
                Director.time = EndTime;
                isPause = false;
                StopCoroutine("ForwardSeedingPlay");
                Director.Pause();           
                StartCoroutine("BackSeedingPlay"); 
                isActive = true;
            }
            
            temp_time = time;
        }
    }  

    public void ForwardSeeding()
    {

        if (time - temp_time > 0.5f )
        {
            Director.time = StartTime;
            isPause = false;
            StopAllCoroutines();
            Director.Pause();          
            StartCoroutine("ForwardSeedingPlay"); 
        }
    }
    public void BackSeeding()
    {
        if (time - temp_time > 0.5f&&isPlaying== false)
        {
            Director.time = EndTime;
            isPause = false;
            StopCoroutine("ForwardSeedingPlay");
            Director.Pause();         
            StartCoroutine("BackSeedingPlay");
        }
    }

    public void StopPlay() 
    {
        Director.Pause();
        StopAllCoroutines();
        Director.Pause();
    }
}
  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值