控制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();
}
}