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