using System; using System.Collections.Generic; using UnityEngine; using Framework.Services; namespace Framework.Actors { /// Controls the animation state of the Mecanim animator public class ActorPresenter : ScriptComponent { #region struct TaggedState /// State with its assigned tag private struct TaggedState { /// Tag that has been assigned to the state public string Tag; /// State ID, usually taken from an actor-specific enumation public int State; /// Layer the state switch will happen on public int Layer; } #endregion // struct TaggedState /// State the character is currently in /// /// This is usually backed by an enumeration of possible states that is kept /// in sync with your Mecanim animation controller (animator). /// public int State; /// Name of the variable used to feed random numbers to the animator /// /// If this string is filled with a name, a variable of that name (of type integer) /// will be set to a random number from 0 to 99 once per second, allowing /// the animator to randomly select between multiple transitions. /// public string RandomProvider; /// Initializes a new actor presenter public ActorPresenter() { this.queuedStateSwitches = new Queue(); } /// Mecanim animator that the presenter is feeding public Animator Animator { get { return this.animator; } } /// Selects the state showing the character idling public virtual void SetIdleState() { } /// Removes all queued states public void ClearStateQueue() { this.queuedStateSwitches.Clear(); } /// Switches a state and waits for the specific tag to become active /// State to switch to /// Tag that should become active when the state was set /// Layer for which the state switch will bequeued /// /// If the presenter is already waiting for another state to be reached, this method /// will queue the state switch until the awaited state has been reached. /// public void SwitchState(int newState, string tagToWaitFor, int layer) { var taggedState = new TaggedState() { State = newState, Tag = tagToWaitFor, Layer = layer }; this.queuedStateSwitches.Enqueue(taggedState); } /// Forces an immediate state switch, ignoring transitions /// New state the animator should switch to /// Name of the new state to switch to public void ForceState(int newState, string newStateName) { ClearStateQueue(); State = state; // Force the presenter to assign the state to Mecanim so there's no risk of // Mecanim jumping to any other animation between the call to Play() and // the next update. Update(); Animator.Play(Animator.StringToHash(newStateName)); } /// Forces an immediate state switch, ignoring transitions /// New state the animator should switch to /// Name of the new state to switch to /// Layer on which the state switch will happen public void ForceState(int newState, string newStateName, int layer) { ClearStateQueue(); State = state; // Force the presenter to assign the state to Mecanim so there's no risk of // Mecanim jumping to any other animation between the call to Play() and // the next update. Update(); Animator.Play(Animator.StringToHash(newStateName), layer); } /// Called when the script instance is being loaded /// /// Awake is used to initialize any variables or game state before /// the game starts. Awake is called only once during the lifetime of /// the script instance. Awake is called after all objects are initialized /// so you can safely speak to other objects. /// protected override void Awake() { base.Awake(); this.animator = gameObject.GetComponentInChildren(); if(this.animator == null) { Debug.LogWarning( "Could not locate Mecanim animator. The presenter attached to the game object '" + gameObject.name + "' either doesn't contain an animated model, has no animator " + "assigned or is using the legacy animation system. Animations will not work!" ); } } /// Called once per frame protected virtual void Update() { if(this.animator == null) { return; } // If state switches have been queued, keep the state the queue is currently // waiting for set until the tag is reached (also preventing any manual state // switches that might confuse the state machine). if(this.queuedStateSwitches.Count > 0) { TaggedState top = this.queuedStateSwitches.Peek(); this.State = top.State; if(this.animator.GetCurrentAnimatorStateInfo(top.Layer).IsTag(top.Tag)) { this.queuedStateSwitches.Dequeue(); } } // If a manual or queued state switch resulted in a different target state, // send the state to the animator (we try to avoid hitting the animator as little // as possible - string lookups, internal processing and all) if(this.State != this.state) { this.state = this.State; this.animator.SetInteger("State", this.State); } // Provide the animator with a random value if desired. This can be used to // select random animations. if(!string.IsNullOrEmpty(this.RandomProvider)) { float time = Time.time; if(time > this.nextRandomUpdateTime) { this.nextRandomUpdateTime = time + 1.0f; this.animator.SetInteger(this.RandomProvider, UnityEngine.Random.Range(0, 100)); } } } /// Mecanim animator that the presenter is feeding private Animator animator; /// Next time the random variable is updated private float nextRandomUpdateTime; /// Currently established state of the character private int state; /// State switches that are waiting for a specific tag to become set private Queue queuedStateSwitches; } } // namespace Framework.Actors