Unity中实现动画回放功能

在制作游戏中,我们有时候会播放过场动画或者剧情动画,有时候会需要有动画重新看,或者拖动进度条看每一帧信息的需求,那么怎么办呢,我们需要实现一个动画重放系统,实现逻辑主要是依靠Unity自带的动画曲线类(AnimationCurve),储存游戏物体从动画开始始末的运动轨迹。然后我们用一个重播管理器去管理各项数据,像播放视频一样控制每帧的位置信息,实现重放。以下是核心的两个脚本:

ReplayEntity.cs

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
using UnityEngine.AI;


namespace Replay
{

	[Serializable]
	public class TimelinedVector3
	{
		public AnimationCurve x;
		public AnimationCurve y;
		public AnimationCurve z;

		public void Add (Vector3 v)
		{
			float time = ReplayManager.Singleton.GetCurrentTime ();
			x.AddKey (time, v.x);
			y.AddKey (time, v.y);
			z.AddKey (time, v.z);
		}

		public Vector3 Get (float _time)
		{
			return new Vector3 (x.Evaluate (_time), y.Evaluate (_time), z.Evaluate (_time));
		}
	}

	[Serializable]
	public class TimelinedQuaternion
	{
		public AnimationCurve x;
		public AnimationCurve y;
		public AnimationCurve z;
		public AnimationCurve w;

		public void Add (Quaternion v)
		{
			float time = ReplayManager.Singleton.GetCurrentTime ();
			x.AddKey (time, v.x);
			y.AddKey (time, v.y);
			z.AddKey (time, v.z);
			w.AddKey (time, v.w);
		}

		public Quaternion Get (float _time)
		{
			return new Quaternion (x.Evaluate (_time), y.Evaluate (_time), z.Evaluate (_time), w.Evaluate (_time));
		}
	}

	[Serializable]
	public class RecordData
	{
		public TimelinedVector3 position;
		public TimelinedQuaternion rotation;
		public TimelinedVector3 scale;

		public void Add (Transform t)
		{
			position.Add (t.position);
			rotation.Add (t.rotation);
			scale.Add (t.localScale);
		}

		public void Set (float _time, Transform _transform)
		{
			_transform.position = position.Get (_time);
			_transform.rotation = rotation.Get (_time);
			_transform.localScale = scale.Get (_time);
		}
	}

	public class ReplayEntity : MonoBehaviour
	{
		public RecordData data = new RecordData ();

		private Rigidbody rigidbody;
		private NavMeshAgent agent;
		private Animator animator;

		protected virtual void Start ()
		{
			StartCoroutine (Recording ());
			ReplayManager.Singleton.OnReplayTimeChange += Replay;
			ReplayManager.Singleton.OnReplayStart += OnReplayStart;

			rigidbody = GetComponent ();
			agent = GetComponent ();
			animator = GetComponent ();
		}

		IEnumerator Recording ()
		{
			while (true) {
				yield return new WaitForSeconds (1 / ReplayManager.Singleton.recordRate);
				if (ReplayManager.Singleton.isRecording) {
					data.Add (transform);
				}
				
			}
		}

		public void OnReplayStart ()
		{
			if (rigidbody != null)
				rigidbody.isKinematic = true;

			if (agent)
				agent.enabled = false;

			if (animator)
				animator.enabled = false;	
		}

		public void Replay (float t)
		{
			data.Set (t, transform);
		}
	}
}

ReplayManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System;

namespace Replay
{
	public class ReplayManager : MonoBehaviour
	{
		public int recordRate = 120;
		public bool isRecording = false;
		public bool isPlaying = false;
		public static ReplayManager Singleton;
		public Action OnReplayTimeChange;
		public Action OnReplayStart;

		private bool wasPlaying = true;

		private bool replayReplayAvailable = false;

		#region UI

		public Slider _slide;
		public Image _play;
		public Image _replay;
		public Image _pause;
		public Text _timestamp;
		public GameObject _replayCanvas;

		#endregion

		#region Time

		private float _startTime;
		private float _endTime;

		#endregion


		void Awake ()
		{
			if (ReplayManager.Singleton == null) {
				ReplayManager.Singleton = this;
			} else {
				Destroy (gameObject);
			}
		}


		public float GetCurrentTime ()
		{
			return Time.time - _startTime;
		}

		void StartReplay ()
		{
			_endTime = Time.time;
			_replayCanvas.SetActive (true);
			isPlaying = false;
			_replayCanvas.GetComponent ().alpha = 1;
			_slide.maxValue = _endTime - _startTime;
			OnReplayTimeChange (0);
			RefreshTimer ();

			if (OnReplayStart != null) {
				// You can remove this log if you don't care
				#if UNITY_EDITOR
				Debug.Log ("There's " + OnReplayStart.GetInvocationList ().Length + " objects affected by the replay.");
				#endif

				OnReplayStart ();
			}
		}
		// Use this for initialization
		void Start ()
		{
			// This line call the replay to start after 3 seconds. You can remove this line and call StartReplay when you want.
			Invoke ("StartReplay", 3f);

			isRecording = true;
			_startTime = Time.time;

			_slide = _replayCanvas.GetComponentInChildren ();


			_play.GetComponent

接着,我们打开Unity,开始制作动画播放器预设体,预设体层级如下图:

Unity中实现动画回放功能_第1张图片

我们给预设体加上ReplayManager.cs脚本,然后依次把Slider,Play等游戏物体拖上去,Inspector面板设置如下图:

Unity中实现动画回放功能_第2张图片

接着,我们新建几个游戏物体在空中,给他们挂上刚体和ReplayEntity.cs脚本,如下图:

Unity中实现动画回放功能_第3张图片

可以看到,运行之前,运动曲线的值是空的,接着我们点击运行,Cube开始下落动画,动画曲线开始赋值,运行后的动画曲线如下图:

Unity中实现动画回放功能_第4张图片

然后我们可以看到,动画播放器的进度条出来了,我们可以拖动Slider观看每帧画面,也可以点击播放按钮,重放动画。

Unity中实现动画回放功能_第5张图片

 

资源来源:https://github.com/FeNo/InGameReplay

你可能感兴趣的:(Unity)