一、环境
Unity 5.5.2 、Cinema Director v1.4.5.4
二、问题
美术用 Director 做CutScene动画时, 某一个角色动作时间线上,播放多个动作, 预览模式,拖动线的时候,Unity基本会100%Crash
如上图拖到类似位置就会闪退(如果一个Track只有一个动作 则不会)
有2中Crash:
========== OUTPUTING STACK TRACE ==================
0x000000014073DCB9 (Unity) AnimationClipPlayable::SetClip
0x0000000141650663 (Unity) mecanim::statemachine::StartState
0x0000000141651118 (Unity) mecanim::statemachine::EvaluateStateMachine
0x0000000140740D9D (Unity) AnimatorControllerPlayable::UpdateGraph
0x0000000140684F61 (Unity) Animator::EvaluateController
0x00000001406E12BD (Unity) Animator::UpdateAvatars
0x00000001406E207C (Unity) Animator::UpdateWithDelta
0x000000004F0EF2A5 (Mono JIT Code) (wrapper managed-to-native) UnityEngine.Animator:Update (single)
0x000000004F0F9885 (Mono JIT Code) [C:\work\mmo3d_client\mmo3d\Assets\Cinema Suite\Cinema Director\System\Runtime\TrackGroups\CharacterTrackGroup.cs:154] CinemaDirector.CharacterTrackGroup:Update ()
0x000000004A527370 (Mono JIT Code) (wrapper delegate-invoke) UnityEditor.EditorApplication/CallbackFunction:invoke_void__this__ ()
0x000000004A526FA3 (Mono JIT Code) [C:\buildslave\unity\build\artifacts\generated\common\editor\EditorApplicationBindings.gen.cs:207]
========== OUTPUTING STACK TRACE ==================
0x000000014164E5FC (Unity) mecanim::statemachine::EvaluateState
0x000000014165121E (Unity) mecanim::statemachine::EvaluateStateMachine
0x0000000140740D9D (Unity) AnimatorControllerPlayable::UpdateGraph
0x0000000140684F61 (Unity) Animator::EvaluateController
0x00000001406E12BD (Unity) Animator::UpdateAvatars
0x00000001406E207C (Unity) Animator::UpdateWithDelta
0x00000000361ECC45 (Mono JIT Code) (wrapper managed-to-native) UnityEngine.Animator:Update (single)
0x000000002F7F06AA (Mono JIT Code) [C:\work\mmo3d_client\mmo3d\Assets\Cinema Suite\Cinema Director\System\Runtime\TrackGroups\CharacterTrackGroup.cs:175] CinemaDirector.CharacterTrackGroup:UpdateTrackGroup (single,single)
0x00000000360F6B3A (Mono JIT Code) [C:\work\mmo3d_client\mmo3d\Assets\Cinema Suite\Cinema Director\System\Runtime\Cutscene.cs:212] CinemaDirector.Cutscene:UpdateCutscene (single)
0x00000000361022BC (Mono JIT Code) [C:\work\mmo3d_client\mmo3d\Assets\Cinema Suite\Cinema Director\System\Runtime\Cutscene.cs:266] CinemaDirector.Cutscene:ScrubToTime (single)
0x0000000036101E4C (Mono JIT Code) [C:\work\mmo3d_client\mmo3d\Assets\Cinema Suite\Cinema Director\System\Editor\DirectorWindow.cs:256] DirectorWindow:directorControl_ScrubCutscene (object,CinemaDirectorArgs)
0x0000000031995D93 (Mono JIT Code) DirectorControl:updateTimelineHeader (UnityEngine.Rect,UnityEngine.Rect)
0x0000000031988094 (Mono JIT Code) DirectorControl:OnGUI (UnityEngine.Rect,CutsceneWrapper)
三、编辑模式实现动作预览并播放指定时间方式1
类似这种方式:
http://blog.csdn.net/akof1314/article/details/45226945
Cinema Director里也是用这种方式,控制一个动作这个方案没问题。
包括这个:http://bbs.cgwell.com/thread-34313-1-2.html 也是一样实现原理。
Cinema Director 是在 CharacterTrackGroup.cs实现 编辑模式动作拖动预览的。核心就是用Animator的录制 回放
录制
float frameRate = 30;
int frameCount = (int)((Cutscene.Duration * frameRate) + 2);
animator.StopPlayback();
animator.recorderStartTime = 0;
animator.StartRecording(frameCount);
base.SetRunningTime(0);
for (int i = 0; i < frameCount-1; i++)
{
TimelineTrack[] tracks = GetTracks();
for (int j = 0; j < tracks.Length; j++)
{
if (!(tracks[j] is DialogueTrack))
{
tracks[j].UpdateTrack(i * (1.0f / frameRate), (1.0f / frameRate));
}
}
animator.Update(1.0f / frameRate);
}
animator.recorderStopTime = frameCount * (1.0f / frameRate);
animator.StopRecording();
animator.StartPlayback();
更新时:
animator.playbackTime = time;
animator.Update(0);
播放指定时间动作。
目前关键就是多个动作时,播放到后面几个动作 animator.Update(0)很容易挂掉。
四、编辑模式实现动作预览并播放指定时间方式2
参考了:https://www.cnblogs.com/chiguozi/p/6862571.html
就是用 AnimationMode.SampleAnimationClip
就是找到对用的 AnimationClip 然后让其sample指定时间的动作。
五、优化了
Cinema Director的代码, 换成用 方式2 预览动作, 实测没有发生Crash
1. CharacterTrackGroup.cs
/* */ 部分是shan
#if UNITY_EDITOR 部分新增的
// Cinema Suite
using CinemaDirector.Helpers;
using System;
using System.Collections.Generic;
using UnityEngine;
namespace CinemaDirector
{
/// <summary>
/// The character track group is a type of actor group, specialized for humanoid characters.
/// </summary>
[TrackGroupAttribute("Character Track Group", TimelineTrackGenre.CharacterTrack)]
public class CharacterTrackGroup : ActorTrackGroup, IRevertable, IBakeable
{
// Options for reverting in editor.
[SerializeField]
private RevertMode editorRevertMode = RevertMode.Revert;
// Options for reverting during runtime.
[SerializeField]
private RevertMode runtimeRevertMode = RevertMode.Revert;
// Has a bake been called on this track group?
private bool hasBeenBaked = false;
/// <summary>
/// Bake the Mecanim preview data.
/// </summary>
public void Bake()
{
if (Actor == null || Application.isPlaying) return;
Animator animator = Actor.GetComponent<Animator>();
if (animator == null)
{ return; }
AnimatorCullingMode cullingData = animator.cullingMode;
animator.cullingMode = AnimatorCullingMode.AlwaysAnimate;
List<RevertInfo> revertCache = new List<RevertInfo>();
// Build the cache of revert info.
MonoBehaviour[] mb = this.GetComponentsInChildren<MonoBehaviour>();
for (int i = 0; i < mb.Length; i++)
{
IRevertable revertable = mb[i] as IRevertable;
if (revertable != null)
{
revertCache.AddRange(revertable.CacheState());
}
}
Vector3 position = Actor.transform.localPosition;
Quaternion rotation = Actor.transform.localRotation;
Vector3 scale = Actor.transform.localScale;
/*
float frameRate = 30;
int frameCount = (int)((Cutscene.Duration * frameRate) + 2);
animator.StopPlayback();
animator.recorderStartTime = 0;
animator.StartRecording(frameCount);
*/
base.SetRunningTime(0);
/*
for (int i = 0; i < frameCount-1; i++)
{
TimelineTrack[] tracks = GetTracks();
for (int j = 0; j < tracks.Length; j++)
{
if (!(tracks[j] is DialogueTrack))
{
tracks[j].UpdateTrack(i * (1.0f / frameRate), (1.0f / frameRate));
}
}
animator.Update(1.0f / frameRate);
}
animator.recorderStopTime = frameCount * (1.0f / frameRate);
animator.StopRecording();
animator.StartPlayback();
*/
#if UNITY_EDITOR
UnityEditor.AnimationMode.StartAnimationMode();
#endif
hasBeenBaked = true;
// Return the Actor to his initial position.
Actor.transform.localPosition = position;
Actor.transform.localRotation = rotation;
Actor.transform.localScale = scale;
for (int i = 0; i < revertCache.Count; i++)
{
RevertInfo revertable = revertCache[i];
if (revertable != null)
{
if ((revertable.EditorRevert == RevertMode.Revert && !Application.isPlaying) ||
(revertable.RuntimeRevert == RevertMode.Revert && Application.isPlaying))
{
revertable.Revert();
}
}
}
animator.cullingMode = cullingData;
base.Initialize();
}
/// <summary>
/// Cache the Actor Transform.
/// </summary>
/// <returns>The revert info for the Actor's transform.</returns>
public RevertInfo[] CacheState()
{
RevertInfo[] reverts = new RevertInfo[3];
if (Actor == null) return new RevertInfo[0];
reverts[0] = new RevertInfo(this, Actor.transform, "localPosition", Actor.transform.localPosition);
reverts[1] = new RevertInfo(this, Actor.transform, "localRotation", Actor.transform.localRotation);
reverts[2] = new RevertInfo(this, Actor.transform, "localScale", Actor.transform.localScale);
return reverts;
}
/// <summary>
/// Initialize the Track Group as normal and initialize the Animator if in Editor Mode.
/// </summary>
public override void Initialize()
{
base.Initialize();
if (!Application.isPlaying)
{
if (Actor == null) return;
Animator animator = Actor.GetComponent<Animator>();
if (animator == null)
{
return;
}
//animator.StartPlayback();
}
}
/// <summary>
/// Update the Track Group over time. If in editor mode, play the baked animator data.
/// </summary>
/// <param name="time">The new running time.</param>
/// <param name="deltaTime">the deltaTime since last update.</param>
public override void UpdateTrackGroup(float time, float deltaTime)
{
if (Application.isPlaying)
{
base.UpdateTrackGroup(time, deltaTime);
}
else
{
TimelineTrack[] tracks = GetTracks();
for (int i = 0; i < tracks.Length; i++)
{
//if (!(tracks[i] is MecanimTrack))
{
tracks[i].UpdateTrack(time, deltaTime);
}
}
/*
if (Actor == null) return;
Animator animator = Actor.GetComponent<Animator>();
if (animator == null)
{
return;
}
if (Actor.gameObject.activeInHierarchy)
{
#if UNITY_5 && !UNITY_5_0 && !UNITY_5_1
if (animator.isInitialized)
animator.playbackTime = time;
#else
// if (animator.)
animator.playbackTime = time;
#endif
animator.Update(0);
}
*/
}
}
public override void SetRunningTime(float time)
{
if (Application.isPlaying)
{
TimelineTrack[] tracks = GetTracks();
for (int i = 0; i < tracks.Length; i++)
{
tracks[i].SetTime(time);
}
}
else
{
TimelineTrack[] tracks = GetTracks();
for (int i = 0; i < tracks.Length; i++)
{
//if (!(tracks[i] is MecanimTrack))
{
tracks[i].SetTime(time);
}
}
/*
if (Actor == null) return;
Animator animator = Actor.GetComponent<Animator>();
if (animator == null)
{
return;
}
if (Actor.gameObject.activeInHierarchy)
{
animator.playbackTime = time;
animator.Update(0);
}
*/
}
}
/// <summary>
/// Stop this track group and stop playback on animator.
/// </summary>
public override void Stop()
{
base.Stop();
if (!Application.isPlaying)
{
if (hasBeenBaked)
{
hasBeenBaked = false;
Animator animator = Actor.GetComponent<Animator>();
if (animator == null)
{
return;
}
if (animator.recorderStopTime > 0)
{
if (Actor.gameObject.activeInHierarchy)
{
/*animator.StartPlayback();
animator.playbackTime = 0;
animator.Update(0);
animator.StopPlayback();*/
#if UNITY_EDITOR
UnityEditor.AnimationMode.StopAnimationMode();
#endif
animator.Rebind();
}
}
}
}
}
/// <summary>
/// Option for choosing when this Event will Revert to initial state in Editor.
/// </summary>
public RevertMode EditorRevertMode
{
get { return editorRevertMode; }
set { editorRevertMode = value; }
}
/// <summary>
/// Option for choosing when this Event will Revert to initial state in Runtime.
/// </summary>
public RevertMode RuntimeRevertMode
{
get { return runtimeRevertMode; }
set { runtimeRevertMode = value; }
}
}
}
2.ActorItemTrack.cs
#if UNITY_EDITOR 部分新增的
using System;
// Cinema Suite
using System.Collections.Generic;
using UnityEngine;
namespace CinemaDirector
{
/// <summary>
/// A track which maintains all timeline items marked for actor tracks and multi actor tracks.
/// </summary>
[TimelineTrackAttribute("Actor Track", new TimelineTrackGenre[] { TimelineTrackGenre.ActorTrack, TimelineTrackGenre.MultiActorTrack }, CutsceneItemGenre.ActorItem)]
public class ActorItemTrack : TimelineTrack, IActorTrack, IMultiActorTrack
{
/// <summary>
/// Initialize this Track and all the timeline items contained within.
/// </summary>
public override void Initialize()
{
base.Initialize();
for (int i = 0; i < this.ActorEvents.Length; i++)
{
for (int j = 0; j < Actors.Count; j++)
{
if (Actors[j] != null)
{
this.ActorEvents[i].Initialize(Actors[j].gameObject);
}
}
}
}
/// <summary>
/// The cutscene has been set to an arbitrary time by the user.
/// Processing must take place to catch up to the new time.
/// </summary>
/// <param name="time">The new cutscene running time</param>
public override void SetTime(float time)
{
float previousTime = elapsedTime;
base.SetTime(time);
TimelineItem[] items = GetTimelineItems();
#if UNITY_EDITOR
int cur_PAE_index = -1;
#endif
for (int i = 0; i < items.Length; i++)
{
// Check if it is an actor event.
CinemaActorEvent cinemaEvent = items[i] as CinemaActorEvent;
if (cinemaEvent != null)
{
if ((previousTime < cinemaEvent.Firetime && time >= cinemaEvent.Firetime) || (cinemaEvent.Firetime == 0f && previousTime <= cinemaEvent.Firetime && time > cinemaEvent.Firetime))
{
for (int j = 0; j < Actors.Count; j++)
{
if (Actors[j] != null)
{
cinemaEvent.Trigger(Actors[j].gameObject);
}
}
}
else if (previousTime > cinemaEvent.Firetime && time <= cinemaEvent.Firetime)
{
for (int j = 0; j < Actors.Count; j++)
{
if (Actors[j] != null)
cinemaEvent.Reverse(Actors[j].gameObject);
}
}
#if UNITY_EDITOR
if(cinemaEvent is PlayAnimatorEvent && time >= cinemaEvent.Firetime)
{
cur_PAE_index = i;
}
#endif
}
// Check if it is an actor action.
CinemaActorAction action = items[i] as CinemaActorAction;
if (action != null)
{
for (int j = 0; j < Actors.Count; j++)
{
if (Actors[j] != null)
{
action.SetTime(Actors[j].gameObject, (time - action.Firetime), time - previousTime);
}
}
}
}
#if UNITY_EDITOR
if(cur_PAE_index >=0)
{
PlayAnimatorEvent playaniEvent = items[cur_PAE_index] as PlayAnimatorEvent;
if(playaniEvent!= null)
{
for (int j = 0; j < Actors.Count; j++)
{
if (Actors[j] != null)
{
playaniEvent.SamleTime(Actors[j].gameObject, (time - playaniEvent.Firetime));
}
}
}
}
#endif
}
/// <summary>
/// Update this track since the last frame.
/// </summary>
/// <param name="time">The new running time.</param>
/// <param name="deltaTime">The deltaTime since last update.</param>
public override void UpdateTrack(float time, float deltaTime)
{
float previousTime = base.elapsedTime;
base.UpdateTrack(time, deltaTime);
TimelineItem[] items = GetTimelineItems();
#if UNITY_EDITOR
int cur_PAE_index = -1;
#endif
for (int i = 0; i < items.Length; i++)
{
// Check if it is an actor event.
CinemaActorEvent cinemaEvent = items[i] as CinemaActorEvent;
if (cinemaEvent != null)
{
if ((previousTime < cinemaEvent.Firetime && time >= cinemaEvent.Firetime) || (cinemaEvent.Firetime == 0f && previousTime <= cinemaEvent.Firetime && time > cinemaEvent.Firetime))
{
for (int j = 0; j < Actors.Count; j++)
{
if (Actors[j] != null)
cinemaEvent.Trigger(Actors[j].gameObject);
}
}
else if (previousTime >= cinemaEvent.Firetime && base.elapsedTime <= cinemaEvent.Firetime)
{
for (int j = 0; j < Actors.Count; j++)
{
if (Actors[j] != null)
cinemaEvent.Reverse(Actors[j].gameObject);
}
}
#if UNITY_EDITOR
if (cinemaEvent is PlayAnimatorEvent && time >= cinemaEvent.Firetime)
{
cur_PAE_index = i;
}
#endif
}
CinemaActorAction action = items[i] as CinemaActorAction;
if (action != null)
{
if (((previousTime < action.Firetime || previousTime <= 0f) && base.elapsedTime >= action.Firetime) && base.elapsedTime < action.EndTime)
{
for (int j = 0; j < Actors.Count; j++)
{
if (Actors[j] != null)
{
action.Trigger(Actors[j].gameObject);
}
}
}
else if (previousTime < action.EndTime && base.elapsedTime >= action.EndTime)
{
for (int j = 0; j < Actors.Count; j++)
{
if (Actors[j] != null)
{
action.End(Actors[j].gameObject);
}
}
}
else if (previousTime >= action.Firetime && previousTime < action.EndTime && base.elapsedTime <= action.Firetime)
{
for (int j = 0; j < Actors.Count; j++)
{
if (Actors[j] != null)
{
action.ReverseTrigger(Actors[j].gameObject);
}
}
}
else if (((previousTime > action.EndTime || previousTime >= action.Cutscene.Duration) && (base.elapsedTime > action.Firetime) && (base.elapsedTime <= action.EndTime)))
{
for (int j = 0; j < Actors.Count; j++)
{
if (Actors[j] != null)
{
action.ReverseEnd(Actors[j].gameObject);
}
}
}
else if ((base.elapsedTime > action.Firetime) && (base.elapsedTime <= action.EndTime))
{
for (int j = 0; j < Actors.Count; j++)
{
if (Actors[j] != null)
{
float runningTime = time - action.Firetime;
action.UpdateTime(Actors[j].gameObject, runningTime, deltaTime);
}
}
}
}
}
#if UNITY_EDITOR
if (cur_PAE_index >= 0)
{
PlayAnimatorEvent playaniEvent = items[cur_PAE_index] as PlayAnimatorEvent;
if (playaniEvent != null)
{
for (int j = 0; j < Actors.Count; j++)
{
if (Actors[j] != null)
{
playaniEvent.SamleTime(Actors[j].gameObject, (time - playaniEvent.Firetime));
}
}
}
}
#endif
}
/// <summary>
/// Pause playback while being played.
/// </summary>
public override void Pause()
{
base.Pause();
TimelineItem[] items = GetTimelineItems();
for (int i = 0; i < items.Length; i++)
{
CinemaActorAction action = items[i] as CinemaActorAction;
if (action != null)
{
if (((elapsedTime > action.Firetime)) && (elapsedTime < (action.Firetime + action.Duration)))
{
for (int j = 0; j < Actors.Count; j++)
{
if (Actors[j] != null)
{
action.Pause(Actors[j].gameObject);
}
}
}
}
}
}
/// <summary>
/// Resume playback after being paused.
/// </summary>
public override void Resume()
{
base.Resume();
TimelineItem[] items = GetTimelineItems();
for (int i = 0; i < items.Length; i++)
{
CinemaActorAction action = items[i] as CinemaActorAction;
if (action != null)
{
if (((elapsedTime > action.Firetime)) && (elapsedTime < (action.Firetime + action.Duration)))
{
for (int j = 0; j < Actors.Count; j++)
{
if (Actors[j] != null)
{
action.Resume(Actors[j].gameObject);
}
}
}
}
}
}
/// <summary>
/// Stop the playback of this track.
/// </summary>
public override void Stop()
{
base.Stop();
base.elapsedTime = 0f;
TimelineItem[] items = GetTimelineItems();
for (int i = 0; i < items.Length; i++)
{
CinemaActorEvent cinemaEvent = items[i] as CinemaActorEvent;
if (cinemaEvent != null)
{
for (int j = 0; j < Actors.Count; j++)
{
if (Actors[j] != null)
cinemaEvent.Stop(Actors[j].gameObject);
}
}
CinemaActorAction action = items[i] as CinemaActorAction;
if (action != null)
{
for (int j = 0; j < Actors.Count; j++)
{
if (Actors[j] != null)
action.Stop(Actors[j].gameObject);
}
}
}
}
/// <summary>
/// Get the Actor associated with this track. Can return null.
/// </summary>
public Transform Actor
{
get
{
ActorTrackGroup atg = this.TrackGroup as ActorTrackGroup;
if (atg == null)
{
Debug.LogError("No ActorTrackGroup found on parent.", this);
return null;
}
return atg.Actor;
}
}
/// <summary>
/// Get the Actors associated with this track. Can return null.
/// In the case of MultiActors it will return the full list.
/// </summary>
public List<Transform> Actors
{
get
{
ActorTrackGroup trackGroup = TrackGroup as ActorTrackGroup;
if (trackGroup != null)
{
List<Transform> actors = new List<Transform>() { };
actors.Add(trackGroup.Actor);
return actors;
}
MultiActorTrackGroup multiActorTrackGroup = TrackGroup as MultiActorTrackGroup;
if (multiActorTrackGroup != null)
{
return multiActorTrackGroup.Actors;
}
return null;
}
}
public CinemaActorEvent[] ActorEvents
{
get
{
return base.GetComponentsInChildren<CinemaActorEvent>();
}
}
public CinemaActorAction[] ActorActions
{
get
{
return base.GetComponentsInChildren<CinemaActorAction>();
}
}
}
}
3.PlayAnimatorEvent.cs
#if UNITY_EDITOR 部分新增的
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
namespace CinemaDirector
{
[CutsceneItemAttribute("Animator", "Play Mecanim Animation", CutsceneItemGenre.ActorItem, CutsceneItemGenre.MecanimItem)]
public class PlayAnimatorEvent : CinemaActorEvent
{
public List<string> StateName = new List<string>();
#if UNITY_EDITOR
Dictionary<string,AnimationClip> animClips = new Dictionary<string, AnimationClip>();
#endif
public int Layer = -1;
public float Normalizedtime = 0.0f;
public float speed = 1.0f;
[SerializeField]
private bool israndom = true;
public bool IsRandom
{
get { return israndom; }
set { israndom = value; }
}
public override void Trigger(GameObject actor)
{
Animator animator = actor.GetComponent<Animator>();
if (animator == null)
{
return;
}
if (StateName.Count == 0)
{
return;
}
int i = Random.Range(0, StateName.Count);
animator.speed = speed;
if (Application.isPlaying)
{
if (IsRandom)
{
animator.Play(StateName[i], Layer, Random.Range(0, (int)(Normalizedtime * 10)) / 10.0f);
}
else
{
animator.Play(StateName[i], Layer, Normalizedtime);
}
}
}
public void SamleTime(GameObject actor, float time)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
Animator animator = actor.GetComponent<Animator>();
if (animator == null)
{
return;
}
if (StateName.Count == 0)
{
return;
}
int i = Random.Range(0, StateName.Count);
animator.speed = speed;
AnimationClip _clip = null;
if (!animClips.ContainsKey(StateName[i]))
{
AnimationClip[] tAnimationClips = animator.runtimeAnimatorController.animationClips;
if (tAnimationClips != null)
{
for (int j = 0; j < tAnimationClips.Length; ++j)
{
if (tAnimationClips[j] && tAnimationClips[j].name == StateName[i])
{
_clip = tAnimationClips[j];
animClips[StateName[i]] = _clip;
break;
}
}
}
}
else
{
_clip = animClips[StateName[i]];
}
if (_clip) //换种方式预览角色动作
{
//Debug.Log(string.Format("{0} SamleTime {1} {2} {3}", this.name, actor.name, StateName[i], time));
UnityEditor.AnimationMode.SampleAnimationClip(actor, _clip, time);
}
}
#endif
}
}
}