using MalbersAnimations.Scriptables; using System.Collections.Generic; using UnityEngine; using UnityEngine.Serialization; using MalbersAnimations.Utilities; #if UNITY_EDITOR using UnityEditor; #endif namespace MalbersAnimations.Controller { public abstract class State : ScriptableObject { /// Name that will be represented on the creation State List public abstract string StateName { get; } /// You can enable/disable temporarly the State [HideInInspector] public bool Active = true; /// Reference for the Animal that Holds this State internal MAnimal animal; /// Height from the ground to the hip multiplied for the Scale Factor protected float Height =>animal.Height; #region Animal Shortcuts /// (Z), horizontal (X) and Vertical (Y) Raw Movement Input internal Vector3 MovementRaw => animal.MovementAxisRaw; /// Forward (Z), horizontal (X) and Vertical (Y) Smoothed Movement Input AFTER aplied Speeds Multipliers (THIS GOES TO THE ANIMATOR) internal Vector3 MovementSmooth => animal.MovementAxisSmoothed; /// Direction of the Gravity protected Vector3 Gravity => animal.Gravity.normalized; /// Reference for the Animal Transform protected Transform transform; /// Layers the Animal considers ground protected LayerMask GroundLayer => animal.GroundLayer; /// Up Vector is the Opposite direction of the Gravity dir protected Vector3 UpVector => animal.UpVector; /// Animal Transform.Forward (Stored) protected Vector3 Forward => animal.Forward; /// Animal Transform.UP (Stored) protected Vector3 Up => animal.Up; /// Animal Transform.Right (Stored) protected Vector3 Right => animal.Right; /// Difference from the Last Frame and the Current Frame protected Vector3 DeltaPos => animal.DeltaPos; /// The Scale Factor of the Animal.. if the animal has being scaled this is the multiplier for the raycasting things protected float ScaleFactor => animal.ScaleFactor; #endregion [Space, Tooltip("Input to Activate the State, leave empty for automatic states")] /// Input to Activate the State public string Input; [Tooltip("Input to Exit the State, leave empty for automatic states")] /// Input to Activate the State public StringReference ExitInput; [Tooltip("Priority of the State. Higher value -> more priority to be activated")] /// Priority of the State. Higher value more priority public int Priority; [Tooltip("Main/Core Modifier. When the Animal enters the Main Animation, it will change the core parameters of the Animal")] public AnimalModifier General; [Tooltip("Main/Core Animation Messages. When the Animal enters the Main Animation,It will send messages to the Animal Components")] public List GeneralMessage; public List TagModifiers = new List(); [Tooltip("When Sending messages, it will use Unity: SendMessage, instead of the IAnimatorListener Interface")] public bool UseSendMessage = false; [Tooltip("When Sending messages, it will send the messages to all the Animal Children gameobjects")] public bool IncludeChildren = true; /// Override to multiply on the Movement Axis when this state is active (By default is 1,1,1) internal Vector3 MovementAxisMult; [Tooltip("[Experimental*] To Allow to Exit the state, the Animations need to use the [Allow Exit Behaviour] on the Animator.")] public bool AllowExitFromAnim = false; [Tooltip("Sleep from state check if the Active State is on this list. Set this value to false to invert the list")] public bool IncludeSleepState = true; // [Space] [Tooltip("If the Active State is one of one on the List, the state cannot be activated")] public List SleepFromState = new List(); [Tooltip(" If A mode is Enabled and is one of one on the List ...the state cannot be activated")] public List SleepFromMode = new List(); //[Tooltip("Which Modes are allowed during this State. Leave empty to include all")] //public List modes = new List(); [Tooltip("If The state is trying to be active but the active State is on this list, " + "the State will be queued until the Active State is not inlcuded on the queue list")] public List QueueFrom = new List(); [Tooltip(" If A Stance is active, and is one of one on the List ...the state cannot be activated")] public List SleepFromStance = new List(); [Tooltip("Which stances are allowed during this State. Leave empty to include all")] public List stances = new List(); /// The State can play only in stances public bool HasStances => stances != null && stances.Count > 0; //[Space] [Tooltip("Try States will try to activate every X frames")] public IntReference TryLoop = new IntReference(1); //[Space] [Tooltip("Tag to Identify Entering Animations on a State.\nE.g. (TakeOff) in Fly, EnterWater on Swim")] public StringReference EnterTag = new StringReference(); [Tooltip("Tag to Identify Exiting Animations on a State.\nE.g. (Land) in Fall, or SwimClimb in Swim")] public StringReference ExitTag = new StringReference(); [Tooltip("if True, the state will execute another frame of logic while entering the other state ")] public bool ExitFrame = true; [Tooltip("Try Exit State on Main State Animation. E.g. The Fall Animation can try to exit only when is on the Fall Animation")] public bool ExitOnMain = true; [Tooltip("Time needed to activate this state again after exit")] public FloatReference EnterCooldown = new FloatReference(0); [Tooltip("Time needed to exit this state after being activated")] public FloatReference ExitCooldown = new FloatReference(0); //[Space] [Tooltip("Can straffing be used with this State?")] public bool CanStrafe; [Tooltip("Strafe Multiplier when movement is detected. This will make the Character be aligned to the Strafe Direction Quickly")] [Range(0, 1)] public float MovementStrafe = 1f; internal bool ValidStance(StanceID currentStance) { if (!HasStances) return true; return stances.Contains(currentStance); } [Tooltip("Strafe Multiplier when there's no movement. This will make the Character be aligned to the Strafe Direction Quickly")] [Range(0, 1)] public float IdleStrafe = 1f; // [Space] public bool debug = true; [HideInInspector] public int Editor_Tabs1; #region Properties protected QueryTriggerInteraction IgnoreTrigger => QueryTriggerInteraction.Ignore; /// Unique ID used on performance public int UniqueID { get; private set; } /// Reference for the Animal Animator protected Animator Anim => animal.Anim; /// Store the OnEnterOnExit Event internal OnEnterExitState EnterExitEvent; /// Check all the Rules to see if the state can be activated public bool CanBeActivated { get { //Debug.Log($"CurrentActiveState != Null [{(CurrentActiveState == null)}]" + // $"animal.JustActivateState [{animal.JustActivateState}]" + // $"IsSleep [{IsSleep}]" + // $"IsActiveState [{IsActiveState}]" + // $"OnEnterCoolDown [{OnEnterCoolDown}]"); if ((CurrentActiveState == null) //Means there's no active State (First Creation) //|| (animal.m_IsAnimatorTransitioning) //Cannot be activated while is on transition //|| OnActiveQueue //The State is not queued || animal.JustActivateState //AnotherState was just activated || (!Active || IsSleep) //if the New state is disabled or is sleep or the Input is Locked: Ignore Activation || (CurrentActiveState.Priority > Priority && CurrentActiveState.IgnoreLowerStates) //if the Active state is set to ignoring lower States skip || (CurrentActiveState.IsPersistent) //if the Active state is persitent: Ignore the Activation || IsActiveState //We are already on this state: Ignore the Activation || OnEnterCoolDown //This state is still in cooldown ) return false; return true; } } /// Has completed the Exit Cooldown so it can be activated again public bool OnEnterCoolDown => EnterCooldown > 0 && !MTools.ElapsedTime(CurrentExitTime, EnterCooldown.Value/* + 0.01f*/); /// Main Tag of the Animation State which it will rule the State the ID name Converted to Hash public int MainTagHash { get; private set; } /// Hash of the Exit Tag Animation protected int ExitTagHash { get; private set; } /// Hash of the Tag of an Enter Animation protected int EnterTagHash { get; private set; } /// The State is on an Exit Animation public bool InExitAnimation => ExitTagHash != 0 && ExitTagHash == CurrentAnimTag; /// The State is on an Enter Animation(TAG) public bool InEnterAnimation => EnterTagHash != 0 && EnterTagHash == CurrentAnimTag; /// Current Time the state exited internal float CurrentExitTime { get; set; } /// Current Time the state was Activated internal float CurrentEnterTime { get; set; } /// Returns the Active Animation State tag Hash on the Base Layer protected int CurrentAnimTag => animal.AnimStateTag; /// Animal Current Active State protected State CurrentActiveState => animal.ActiveState; /// Can the State use the TryExitMethod public bool CanExit { get; internal set; } public bool AllowingExit => !IgnoreLowerStates && !IsPersistent; /// True if this state is the Animal Active State public bool IsActiveState => animal.ActiveState == this; /// Input Value for a State (Some states can by activated by inputs public virtual bool InputValue { get; set; } /// Exit Input Value for a State public virtual bool ExitInputValue { get; set; } /// Put a state to sleep it works with the Avoid States list public virtual bool IsSleepFromState { get; internal set; } /// Put a state to sleep When Certaing Mode is Enable public virtual bool IsSleepFromMode { get; internal set; } /// Put a state to sleep When Certaing Mode is Enable public virtual bool IsSleepFromStance { get; internal set; } /// The State is Sleep (From Mode, State or Stance) public virtual bool IsSleep => IsSleepFromMode || IsSleepFromState || IsSleepFromStance; /// is this state on queue? public virtual bool OnQueue { get; internal set; } /// The State wants to be activated but is on QUEUE! public bool OnActiveQueue { get; internal set; } /// The State is on the Main State Animation public bool InCoreAnimation => CurrentAnimTag == MainTagHash; /// Quick Access to Animal.currentSpeedModifier.position public float CurrentSpeedPos { get => animal.CurrentSpeedModifier.position; set => animal.currentSpeedModifier.position = value; } public MSpeed CurrentSpeed => animal.CurrentSpeedModifier; /// If True this state cannot be interrupted by other States public bool IsPersistent { get; set; } /// If true the states below it will not try to Activate themselves public bool IgnoreLowerStates { get; set; } //{ // get => ignoreLowerStates; // set // { // ignoreLowerStates = value; // Debug.Log($"ignoreLowerStates: {ignoreLowerStates} "); // } //} //bool ignoreLowerStates; /// Means that is already activated but is Still exiting the Last State and it does not have entered any of the Active State Animations public bool IsPending { get; set; } /// The Last State still has animations to exit public bool PendingExit { get; set; } /// Speed Sets this State may has... Locomotion, Sneak etc public List SpeedSets { get; internal set; } #endregion [Tooltip("ID to Identify the State. The name of the ID is the Core Tag used on the Animator")] /// ID Asset Reference public StateID ID; private IAnimatorListener[] listeners; #region Methods /// Return if this state have a current Tag used on the animal protected bool StateAnimationTags(int MainTag) { if (MainTagHash == MainTag) return true; var Foundit = TagModifiers.Find(tag => tag.TagHash == MainTag); return Foundit != null; } /// Set all the values for all the States on Awake public void AwakeState(MAnimal mAnimal) { animal = mAnimal; transform = animal.transform; AwakeState(); } /// Called on Awake public virtual void AwakeState() { if (ID == null) Debug.LogError($"State {name} is missing its ID",this); MainTagHash = Animator.StringToHash(ID.name); //Store the Main Tag at Awake ExitTagHash = Animator.StringToHash(ExitTag.Value); //Store the Main Tag at Awake EnterTagHash = Animator.StringToHash(EnterTag.Value); //Store the Main Tag at Awake foreach (var mod in TagModifiers) mod.TagHash = Animator.StringToHash(mod.AnimationTag); //Convert all the Tags to HashTags SpeedSets = new List(); foreach (var set in animal.speedSets) //Find if this state has a Speed Set if (set.states.Contains(ID)) SpeedSets.Add(set); if (SpeedSets.Count > 0) SpeedSets.Sort(); //IMPORTANT! EnterExitEvent = animal.OnEnterExitStates.Find(st => st.ID == ID); InputValue = false; ExitInputValue = false; ResetState(); ResetStateValues(); CurrentExitTime = -EnterCooldown*5; //DirectionalVelocity = transform.forward; //As default the Directional is the Transform.forward if (TryLoop < 1) TryLoop = 1; UniqueID = UnityEngine.Random.Range(0, 99999); //Fin all the IAnimator Listener if (!UseSendMessage) { if (IncludeChildren) listeners = animal.GetComponentsInChildren(); else listeners = animal.GetComponents(); } } /// Current Direction Speed Applied to the Additional Speed, by default is the Animal Forward Direction public virtual Vector3 Speed_Direction() => animal.Forward * Mathf.Abs(animal.VerticalSmooth); /// Check if the State is Queued public bool CheckQueuedState() { // Debug.Log("Queued = " + OnQueue, this); if (OnQueue) { OnActiveQueue = true; //meaning is waiting for bee activated Debugging($"[Active*Queued]. Allow Exit to Active State: [{animal.ActiveState.ID.name}]"); animal.ActiveState.AllowExit(); //Force Allow Exit animal.QueueState = this; return true; } return false; } /// Connects the State with the External Inputs Source internal void ConnectInput(IInputSource InputSource, bool connect) { if (!string.IsNullOrEmpty(Input)) //If a State has an Input { var input = InputSource.GetInput(Input); if (input != null) { if (connect) input.InputChanged.AddListener(ActivatebyInput); else input.InputChanged.RemoveListener(ActivatebyInput); } } if (!string.IsNullOrEmpty(ExitInput.Value)) //If a State has an Input { var exitInput = InputSource.GetInput(ExitInput.Value); if (exitInput != null) { if (connect) exitInput.InputChanged.AddListener(ExitByInput); else exitInput.InputChanged.RemoveListener(ExitByInput); } } ExtraInputs(InputSource, connect); } /// Use this to connect extra inputs the State may have public virtual void ExtraInputs(IInputSource inputSource, bool connect) {} /// Activate the State. Code is Applied on base.Activate() public virtual void Activate() { if (CheckQueuedState()) { return; } animal.LastState = animal.ActiveState; //Set a new Last State Release the Old States animal.Check_Queue_States(ID); //Check if a queue State was released //Wake UP the State that is no longer on QUEUE and it was activated! (PRIORITY FOR THE QUEDED STATES)! if (animal.QueueReleased) { animal.QueueState.ActivateQueued(); return; } if (animal.JustActivateState) { return; } //Do not activate any state if a new state has being activated already. //animal.LastState.IsActiveState = false; Debugging("Activated"); animal.ActiveState = this; //Update to the Current State SetSpeed(); //Set the Speed on the New State MovementAxisMult = Vector3.one; // IsActiveState = true; //Set this state as the Active State CanExit = false; CurrentEnterTime = Time.time; if (animal.LastState != animal.ActiveState) { IsPending = true; //We need to set is as pending since we have not enter this states animations yet IMPORTANT IF we are not activating outselves PendingExit = true; } EnterExitEvent?.OnEnter.Invoke(); } public virtual void ForceActivate() { Debugging("Force Activated"); animal.LastState = animal.ActiveState; //Set a new Last State animal.ActiveState = this; //Update to the Current State SetSpeed(); //Set the Speed on the New State // IsActiveState = true; //Set this state as the Active State CanExit = false; CurrentEnterTime = Time.time; if (animal.LastState != animal.ActiveState) { IsPending = true; //We need to set is as pending since we have not enter this states animations yet IMPORTANT IF we are not activating outselves PendingExit = true; } EnterExitEvent?.OnEnter.Invoke(); } /// Search on the Internal Speed Set which one it can be used internal virtual void SetSpeed() { animal.CustomSpeed = false; foreach (var set in SpeedSets) { if (animal.Stance == 0 && !set.HasStances || animal.Stance != 0 && set.HasStance(animal.Stance)) { animal.CurrentSpeedSet = set; //Set a new Speed Set // animal.CurrentSpeedIndex = set.CurrentIndex; //Set a new Speed Set return; } } var speedSet = new MSpeedSet() { name = this.name, Speeds = new List(1) { new MSpeed(this.name, animal.CurrentSpeedModifier.Vertical.Value, 4, 4) } }; animal.CustomSpeed = true; // Debug.Log("animal.CurrentSpeedModifier.Vertical.Value = " + animal.CurrentSpeedModifier.Vertical.Value); animal.CurrentSpeedSet = speedSet; //Use Default instead animal.CurrentSpeedModifier = speedSet[0]; //Use Default instead } //internal MSpeedSet defaultSpeedSet = new MSpeedSet() //{ name = "Default Set", Speeds = new List(1) { new MSpeed("Default", 1, 4, 4) } }; //Create a Default Speed at Awake ///// Set a Default //public abstract void InternalSpeedSet(); /// Reset a State values to its first Awakening public virtual void ResetState() { IgnoreLowerStates = false; IsPersistent = false; IsPending = false; // IsActiveState = false; CanExit = false; IsSleepFromMode = false; IsSleepFromState = false; IsSleepFromStance = false; OnQueue = false; OnActiveQueue = false; CurrentExitTime = Time.time; MovementAxisMult = Vector3.one; } /// Restore some of the Animal Parameters when the State exits public virtual void RestoreAnimalOnExit() { } public virtual void ExitState() { ResetStateValues(); ResetState(); RestoreAnimalOnExit(); // Debugging("Exit State"); } /// Invoke the Exit State for the Laset State and Execute the Exit State method /// Status Value of the State public void SetEnterStatus(int value) => animal.State_SetStatus(value); public void SetStatus(int value) => SetEnterStatus(value); public void SetFloat(float value) => animal.State_SetFloat(value); public void SetFloatSmooth(float value, float time) { if (animal.State_Float != 0f) animal.State_SetFloat(Mathf.MoveTowards(animal.State_Float, 0, time)); } /// Exit Status Value of the State public void SetExitStatus(int value) => animal.State_SetExitStatus(value); public virtual void ActivateQueued() { OnQueue = false; OnActiveQueue = false; animal.QueueState = null; // animal.lastState = animal.activeState; Debugging("[No Longer on Queue]"); Activate(); } /// Send Messages to the Animal when entering Animations private void SendMessagesTags(List msgs) { if (msgs != null && msgs.Count > 0) { if (UseSendMessage) { foreach (var item in msgs) item.DeliverMessage(animal, IncludeChildren, animal.debugStates && debug); } else { if (listeners != null && listeners.Length > 0) { foreach (var animListeners in listeners) { foreach (var item in msgs) item.DeliverAnimListener(animListeners, animal.debugStates && debug); } } } } } /// When a Tag Changes apply this modifications public void AnimationTagEnter(int animatorTagHash) { // Debug.Log("animatorTagHash"+ animatorTagHash); if (!IsActiveState) return;// this need to be ignored if the State has not being Started yet //Check Tags on the State Animations if ( MainTagHash == animatorTagHash) { General.Modify(animal); CheckPendingExit(); EnterCoreAnimation(); SetExitStatus(0); //Reset the Exit status.!! SetEnterStatus(0); //Reset the Enter status.!! animal.SprintUpdate(); SendMessagesTags(GeneralMessage); //Send the Messages to the Animal Controller if (IsPending) { IsPending = false; animal.OnStateChange.Invoke(ID);//Invoke the Event only when the State is no longer Pending } } else { TagModifier ActiveTag = TagModifiers.Find(tag => tag.TagHash == animatorTagHash); if (ActiveTag != null) { ActiveTag.modifier.Modify(animal); CheckPendingExit(); EnterTagAnimation(); animal.SprintUpdate(); SendMessagesTags(ActiveTag.tagMessages); //Send the Messages to the Animal Controller when entering tags if (IsPending) { IsPending = false; animal.OnStateChange.Invoke(ID);//Invoke the Event only when the State is no longer Pending } } } } /// /// Used on Pending States from the Last State exiting /// private void CheckPendingExit() { if (IsPending && PendingExit) { animal.LastState?.PendingAnimationState(); PendingExit = false; } } public void SetInput(bool value) => InputValue = value; /// Receive messages from the Animator Controller public void ReceiveMessages(string message, object value) => this.Invoke(message, value); /// Enable the State using an Input. Example :Fly, Jump internal void ActivatebyInput(bool InputValue) { this.InputValue = InputValue; // Debug.Log("InputValue = " + InputValue); if (IsSleep) { this.InputValue = false; animal.InputSource?.SetInput(Input, false); //Hack to reset the toggle when it exit on Grounded } if (animal.LockInput) return; //All Inputs are locked so Ignore Activation by input if (animal.JustActivateState) return; //Do Not Enable if a state was activated if (CanBeActivated) { StatebyInput(); } } internal void ExitByInput(bool exitValue) { ExitInputValue = exitValue; if (IsActiveState == this && CanExit) { StateExitByInput(); } } internal void SetCanExit() { if (!CanExit && !IsPending && !animal.m_IsAnimatorTransitioning) { if (MTools.ElapsedTime(CurrentEnterTime, ExitCooldown)) { if (ExitOnMain) { if (InCoreAnimation) CanExit = true; } else { CanExit = true; } } } } /// Notifies all the States that a new state has started public virtual void NewActiveState(StateID newState) { } /// Notifies all the States the Speed Have Changed public virtual void SpeedModifierChanged(MSpeed speed, int SpeedIndex) { } /// Allow the State to be Replaced by lower States public bool AllowExit() { // if (!AllowExitFromAnim && CanExit) if (CanExit) { IgnoreLowerStates = false; IsPersistent = false; AllowStateExit(); // Debugging("[Allow Exit]"); } return CanExit; } /// Allow the State add logic to the Allow Exit public virtual void AllowStateExit(){} /// Allow the State to Exit. It forces the Next state to be activated public void AllowExit(int nextState) { if (!AllowExitFromAnim && AllowExit()) { if (nextState != -1) animal.State_Activate(nextState); } } /// Allow the State to Exit. It forces the Next state to be activated. Set a value for the Exit Status public void AllowExit(int nextState, int StateExitStatus) { SetExitStatus(StateExitStatus); if (!AllowExitFromAnim && AllowExit()) { if (nextState != -1) animal.State_Activate(nextState); } } public void Debugging(string value) { #if UNITY_EDITOR if (debug && animal.debugStates) Debug.Log($"[{animal.name}][{this.GetType().Name}]{value}", animal); #endif } #endregion #region Empty Methods /// Reset a State values to its first Awakening public void Enable(bool value) => Active = value; /// This will be called on the Last State before the Active state enters Core animations public virtual void PendingAnimationState() { } /// Set all the values for all the States on Start of the Animal... NOT THE START(ACTIVATION OF THE STATE) OF THE STATE public virtual void InitializeState() { } /// When Entering Core Animation of the State (the one tagged like the State) public virtual void EnterCoreAnimation() { } /// When Entering a new animation State do this public virtual void EnterTagAnimation() { } /// Logic to Try exiting to Lower Priority States public virtual void TryExitState(float DeltaTime) { } ///// Called when Sleep is false //public virtual void JustWakeUp() { } /// Logic Needed to Try to Activate the State, By Default is the Input Value for the State public virtual bool TryActivate() => InputValue && CanBeActivated; public virtual void StatebyInput() { if (IsSleep) InputValue = false; //Debug.Log($"InputValue = {name}" + InputValue); if (InputValue && TryActivate()) //Enable the State if is not already active Activate(); } /// Check if the state Exit Input is valid public virtual void StateExitByInput() { if (ExitInputValue) AllowExit(); } /// Restore Internal values on the State (DO NOT INLCUDE Animal Methods or Parameter calls here public virtual void ResetStateValues() { } /// Restore Internal values on the State (DO NOT INLCUDE Animal Methods or Parameter calls here public virtual void OnStateMove(float deltatime) { } /// Called before Adding Additive Position and Rotation public virtual void OnStatePreMove(float deltatime) { } /// Called when a Mode Start Playing and This is the Active State public virtual void OnModeStart(Mode mode) { } /// Called when a Mode Ends Playing and This is the Active State public virtual void OnModeEnd(Mode mode) { } public virtual void StateGizmos(MAnimal animal) { } /// Use this method to draw a custom inspector on the States public virtual bool CustomStateInspector() => false; #endregion } /// When an new Animation State Enters and it have a tag = "AnimationTag" [System.Serializable] public class TagModifier { /// Animation State with the Tag to apply the modifiers public string AnimationTag; public AnimalModifier modifier; /// "Animation Tag" Converted to TagHash public int TagHash { get; set; } public List tagMessages; //Store messages to send it when Enter the animation State //public bool UseSendMessage = true; //public bool SendToChildren = true; } /// Modifier for the Animals [System.Serializable] public struct AnimalModifier { ///// Animation State with the Tag to apply the modifiers //public string AnimationTag; [Utilities.Flag] public modifier modify; /// Enable/Disable the Root Motion on the Animator public bool RootMotion; /// Enable/Disable the Sprint on the Animal public bool Sprint; /// Enable/Disable the Gravity on the Animal, only used when the animal is on the air, falling, jumping ..etc public bool Gravity; /// Enable/Disable the if the Animal is Grounded (Align|Snap to ground position) public bool Grounded; /// Enable/Disable the Rotation Alignment while grounded public bool OrientToGround; /// Enable/Disable the Custom Rotations (Used in Fly, Climb, UnderWater Swimming, etc) public bool CustomRotation; /// Enable/Disable the Free Movement... This allow to Use the Pitch direction vector public bool FreeMovement; /// Enable/Disable Additive Position use on the Speed Modifiers public bool AdditivePosition; /// Enable/Disable Additive Rotation use on the Speed Modifiers public bool AdditiveRotation; /// Enable/Disable is Persistent on the Active State ... meaning it cannot activate any other states whatsoever public bool Persistent; /// Enable/Disable is Persistent on the Active State ... meaning it cannot activate any other states whatsoever public bool IgnoreLowerStates; /// Enable/Disable the movement on the Animal public bool LockMovement; /// Enable/Disable is AllInputs on the Animal public bool LockInput; public void Modify(MAnimal animal) { if ((int)modify == 0) return; //Means that the animal have no modification if (Modify(modifier.IgnoreLowerStates)) { animal.ActiveState.IgnoreLowerStates = IgnoreLowerStates; } if (Modify(modifier.AdditivePositionSpeed)) { animal.UseAdditivePos = AdditivePosition; } if (Modify(modifier.AdditiveRotationSpeed)) { animal.UseAdditiveRot = AdditiveRotation; } if (Modify(modifier.RootMotion)) { animal.RootMotion = RootMotion; } if (Modify(modifier.Gravity)) { animal.UseGravity = Gravity; } if (Modify(modifier.Sprint)) { animal.UseSprintState = Sprint; } if (Modify(modifier.Grounded)) {animal.Grounded = Grounded;} if (Modify(modifier.OrientToGround)){ animal.UseOrientToGround = OrientToGround; } if (Modify(modifier.CustomRotation)){ animal.UseCustomAlign = CustomRotation;} if (Modify(modifier.Persistent)) { animal.ActiveState.IsPersistent = Persistent; /*Debug.Log($"{animal.ActiveState.name} + Pers{Persistent}");*/} if (Modify(modifier.LockInput)) {animal.LockInput = LockInput;} if (Modify(modifier.LockMovement)){ animal.LockMovement = LockMovement;} if (Modify(modifier.FreeMovement)) {animal.FreeMovement = FreeMovement;} } private bool Modify(modifier modifier) => ((modify & modifier) == modifier); } public enum modifier { RootMotion = 1, Sprint = 2, Gravity = 4, Grounded = 8, OrientToGround = 16, CustomRotation = 32, IgnoreLowerStates = 64, Persistent = 128, LockMovement = 256, LockInput = 512, AdditiveRotationSpeed = 1024, AdditivePositionSpeed = 2048, FreeMovement = 4096, } /// Struct to Apply a Multiplier to anything whenever the Animal is on a State [System.Serializable] public class StateMultiplier { public StateID ID; public float Multiplier; } #region Inspector #if UNITY_EDITOR [CustomEditor(typeof(State), true)] public class StateEd : Editor { SerializedProperty ID, Input, ExitInput, Priority, General, GeneralMessage, TryLoop, EnterTag, ExitTag, ExitFrame, ExitOnMain, ExitCooldown, EnterCooldown, CanStrafe, MovementStrafe, IdleStrafe, debug, UseSendMessage, IncludeChildren, AllowExitAnimation, IncludeSleepState, SleepFromState, SleepFromMode, TagModifiers, QueueFrom, Editor_Tabs1, stances, SleepFromStance; State M; string[] Tabs = new string[4] { "General", "Tags", "Limits", "" }; GUIStyle GreatLabel; private void OnEnable() { M = (State)target; Tabs[3] = M.ID ? M.ID.name : "Missing ID***"; Editor_Tabs1 = serializedObject.FindProperty("Editor_Tabs1"); ID = serializedObject.FindProperty("ID"); Input = serializedObject.FindProperty("Input"); ExitInput = serializedObject.FindProperty("ExitInput"); Priority = serializedObject.FindProperty("Priority"); TryLoop = serializedObject.FindProperty("TryLoop"); AllowExitAnimation = serializedObject.FindProperty("AllowExitFromAnim"); EnterTag = serializedObject.FindProperty("EnterTag"); ExitTag = serializedObject.FindProperty("ExitTag"); TagModifiers = serializedObject.FindProperty("TagModifiers"); General = serializedObject.FindProperty("General"); GeneralMessage = serializedObject.FindProperty("GeneralMessage"); UseSendMessage = serializedObject.FindProperty("UseSendMessage"); IncludeChildren = serializedObject.FindProperty("IncludeChildren"); ExitFrame = serializedObject.FindProperty("ExitFrame"); ExitOnMain = serializedObject.FindProperty("ExitOnMain"); ExitCooldown = serializedObject.FindProperty("ExitCooldown"); EnterCooldown = serializedObject.FindProperty("EnterCooldown"); CanStrafe = serializedObject.FindProperty("CanStrafe"); MovementStrafe = serializedObject.FindProperty("MovementStrafe"); IdleStrafe = serializedObject.FindProperty("IdleStrafe"); debug = serializedObject.FindProperty("debug"); IncludeSleepState = serializedObject.FindProperty("IncludeSleepState"); SleepFromState = serializedObject.FindProperty("SleepFromState"); SleepFromMode = serializedObject.FindProperty("SleepFromMode"); QueueFrom = serializedObject.FindProperty("QueueFrom"); stances = serializedObject.FindProperty("stances"); SleepFromStance = serializedObject.FindProperty("SleepFromStance"); } public GUIContent Deb; public override void OnInspectorGUI() { serializedObject.Update(); if (GreatLabel == null) GreatLabel = new GUIStyle(EditorStyles.largeLabel) { fontStyle = FontStyle.Bold, fontSize = 14 }; Editor_Tabs1.intValue = GUILayout.Toolbar(Editor_Tabs1.intValue, Tabs); int Selection = Editor_Tabs1.intValue; if (Selection == 0) ShowGeneral(); else if (Selection == 1) ShowTags(); else if (Selection == 2) ShowLimits(); else if (Selection == 3) ShowState(); serializedObject.ApplyModifiedProperties(); Deb = new GUIContent((Texture)(AssetDatabase.LoadAssetAtPath("Assets/Malbers Animations/Common/Scripts/Editor/Icons/Debug_Icon.png", typeof(Texture))), "Debug"); // base.OnInspectorGUI(); } private void ShowGeneral() { MalbersEditor.DrawDescription($"Common parameters of the State"); EditorGUILayout.BeginVertical(EditorStyles.helpBox); { EditorGUILayout.BeginHorizontal(); EditorGUILayout.PropertyField(ID); // MTools.DrawDebugIcon(debug); var currentGUIColor = GUI.color; GUI.color = debug.boolValue ? Color.red : currentGUIColor; if (Deb == null) Deb = new GUIContent((Texture) (AssetDatabase.LoadAssetAtPath("Assets/Malbers Animations/Common/Scripts/Editor/Icons/Debug_Icon.png", typeof(Texture))), "Debug"); debug.boolValue = GUILayout.Toggle(debug.boolValue, Deb, EditorStyles.miniButton, GUILayout.Width(25)); GUI.color = currentGUIColor; EditorGUILayout.EndHorizontal(); EditorGUILayout.PropertyField(Input, new GUIContent("Enter Input")); EditorGUILayout.PropertyField(ExitInput); EditorGUILayout.PropertyField(Priority); EditorGUILayout.PropertyField(AllowExitAnimation); } EditorGUILayout.EndVertical(); EditorGUILayout.BeginVertical(EditorStyles.helpBox); { EditorGUILayout.PropertyField(ExitFrame); EditorGUILayout.PropertyField(ExitOnMain); EditorGUILayout.PropertyField(EnterCooldown); EditorGUILayout.PropertyField(ExitCooldown); EditorGUILayout.PropertyField(TryLoop); } EditorGUILayout.EndVertical(); EditorGUILayout.BeginVertical(EditorStyles.helpBox); { EditorGUILayout.PropertyField(CanStrafe); if (M.CanStrafe) { EditorGUILayout.PropertyField(MovementStrafe); EditorGUILayout.PropertyField(IdleStrafe); } } EditorGUILayout.EndVertical(); ShowDebug(); } private void ShowTags() { MalbersEditor.DrawDescription($"Animator Tags will modify the core parameters on the Animal.\nThe core tag value is the name of the ID - [{Tabs[3]}]"); EditorGUILayout.BeginVertical(EditorStyles.helpBox); { EditorGUILayout.PropertyField(EnterTag); EditorGUILayout.PropertyField(ExitTag); } EditorGUILayout.EndVertical(); EditorGUILayout.BeginVertical(EditorStyles.helpBox); { EditorGUILayout.PropertyField(General, new GUIContent("Tag [" + Tabs[3] + "]"), true); var st = new GUIStyle(EditorStyles.boldLabel); st.fontSize += 1; EditorGUILayout.Space(); EditorGUILayout.LabelField("Messages", st); EditorGUILayout.BeginHorizontal(EditorStyles.helpBox); EditorGUIUtility.labelWidth = 85; EditorGUILayout.PropertyField(UseSendMessage, new GUIContent("Use SendMsg")); EditorGUIUtility.labelWidth = 55; EditorGUILayout.PropertyField(IncludeChildren, new GUIContent("Children")); EditorGUIUtility.labelWidth = 0; EditorGUILayout.EndHorizontal(); EditorGUI.indentLevel++; EditorGUILayout.PropertyField(GeneralMessage, new GUIContent("Messages [" + Tabs[3] + "]"), true); EditorGUI.indentLevel--; EditorGUILayout.Space(); EditorGUILayout.LabelField("Animation Tags", st); EditorGUI.indentLevel++; EditorGUILayout.PropertyField(TagModifiers, new GUIContent(TagModifiers.displayName + " [" + TagModifiers.arraySize + "]"), true); EditorGUI.indentLevel--; } EditorGUILayout.EndVertical(); } private void ShowDebug() { if (M.debug && Application.isPlaying && M.animal) { using (new EditorGUI.DisabledGroupScope(true)) { using (new GUILayout.VerticalScope(EditorStyles.helpBox)) { EditorGUILayout.Toggle("Enabled", M.Active); EditorGUILayout.Toggle("Is Active State", M.IsActiveState); EditorGUILayout.Toggle("Can Exit", M.CanExit); EditorGUILayout.Toggle("OnQueue", M.OnQueue); EditorGUILayout.Toggle("On Active Queue", M.OnActiveQueue); EditorGUILayout.Toggle("Pending", M.IsPending); EditorGUILayout.Toggle("Pending Exit", M.PendingExit); EditorGUILayout.Toggle("Sleep From State", M.IsSleepFromState); EditorGUILayout.Toggle("Sleep From Mode", M.IsSleepFromMode); EditorGUILayout.Toggle("Sleep From Stance", M.IsSleepFromStance); EditorGUILayout.Toggle("In Core Animation", M.InCoreAnimation); EditorGUILayout.Toggle("Ignore Lower States", M.IgnoreLowerStates); EditorGUILayout.Toggle("Is Persistent", M.IsPersistent); } Repaint(); } } } private void ShowLimits() { MalbersEditor.DrawDescription($"Set Limitations to the States when another State, Mode or Stance is playing"); using (new GUILayout.VerticalScope(EditorStyles.helpBox)) { var incl = IncludeSleepState.boolValue; var AcSleep = incl ? "INCLUDE" : "EXCLUDE"; var AcSleepList = incl ? "" : "EXCEPT"; var dC = GUI.color; using (new GUILayout.HorizontalScope()) { EditorGUILayout.LabelField("States", GreatLabel); GUI.color = incl ? GUI.color : Color.red; IncludeSleepState.boolValue = GUILayout.Toggle(incl, new GUIContent(AcSleep), EditorStyles.miniButton, GUILayout.Width(90)); } GUI.color = dC; EditorGUI.indentLevel++; EditorGUILayout.PropertyField(SleepFromState, new GUIContent($"Sleep from States {AcSleepList}"), true); EditorGUILayout.PropertyField(QueueFrom, true); EditorGUI.indentLevel--; } using (new GUILayout.VerticalScope(EditorStyles.helpBox)) { EditorGUILayout.LabelField("Modes", GreatLabel); EditorGUILayout.Space(); EditorGUI.indentLevel++; EditorGUILayout.PropertyField(SleepFromMode, true); EditorGUI.indentLevel--; } using (new GUILayout.VerticalScope(EditorStyles.helpBox)) { EditorGUILayout.LabelField("Stances", GreatLabel); EditorGUILayout.Space(); EditorGUI.indentLevel++; EditorGUILayout.PropertyField(SleepFromStance, true); EditorGUILayout.PropertyField(stances, true); EditorGUI.indentLevel--; } } protected virtual void ShowState() { MalbersEditor.DrawDescription($"{Tabs[3]} Parameters"); if (!M.CustomStateInspector()) { var skip = 27; var property = serializedObject.GetIterator(); property.NextVisible(true); for (int i = 0; i < skip; i++) property.NextVisible(false); do EditorGUILayout.PropertyField(property, true); while (property.NextVisible(false)); } } } [UnityEditor.CustomPropertyDrawer(typeof(AnimalModifier))] public class AnimalModifierDrawer : UnityEditor.PropertyDrawer { private float Division; int activeProperties; public override void OnGUI(Rect position, UnityEditor.SerializedProperty property, GUIContent label) { UnityEditor.EditorGUI.BeginProperty(position, label, property); GUI.Box(position, GUIContent.none, UnityEditor.EditorStyles.helpBox); position.x += 2; position.width -= 2; position.y += 2; position.height -= 2; var indent = UnityEditor.EditorGUI.indentLevel; UnityEditor.EditorGUI.indentLevel = 0; var height = UnityEditor.EditorGUIUtility.singleLineHeight; #region Serialized Properties var modify = property.FindPropertyRelative("modify"); var Colliders = property.FindPropertyRelative("Colliders"); var RootMotion = property.FindPropertyRelative("RootMotion"); var Sprint = property.FindPropertyRelative("Sprint"); var Gravity = property.FindPropertyRelative("Gravity"); var OrientToGround = property.FindPropertyRelative("OrientToGround"); var CustomRotation = property.FindPropertyRelative("CustomRotation"); var IgnoreLowerStates = property.FindPropertyRelative("IgnoreLowerStates"); var AdditivePositionSpeed = property.FindPropertyRelative("AdditivePosition"); var AdditiveRotation = property.FindPropertyRelative("AdditiveRotation"); var Grounded = property.FindPropertyRelative("Grounded"); var FreeMovement = property.FindPropertyRelative("FreeMovement"); var Persistent = property.FindPropertyRelative("Persistent"); var LockInput = property.FindPropertyRelative("LockInput"); var LockMovement = property.FindPropertyRelative("LockMovement"); #endregion var line = position; var lineLabel = line; line.height = height; var foldout = lineLabel; foldout.width = 10; foldout.x += 10; UnityEditor.EditorGUIUtility.labelWidth = 16; UnityEditor.EditorGUIUtility.labelWidth = 0; modify.intValue = (int)(modifier)UnityEditor.EditorGUI.EnumFlagsField(line, label, (modifier)(modify.intValue)); line.y += height + 2; Division = line.width / 3; activeProperties = 0; int ModifyValue = modify.intValue; if (Modify(ModifyValue, modifier.RootMotion)) DrawProperty(ref line, RootMotion, new GUIContent("RootMotion", "Root Motion:\nEnable/Disable the Root Motion on the Animator")); if (Modify(ModifyValue, modifier.Sprint)) DrawProperty(ref line, Sprint, new GUIContent("Sprint", "Sprint:\nEnable/Disable Sprinting on the Animal")); if (Modify(ModifyValue, modifier.Gravity)) DrawProperty(ref line, Gravity, new GUIContent("Gravity", "Gravity:\nEnable/Disable the Gravity on the Animal. Used when is falling or jumping")); if (Modify(ModifyValue, modifier.Grounded)) DrawProperty(ref line, Grounded, new GUIContent("Grounded", "Grounded\nEnable/Disable if the Animal is Grounded (If True it will calculate the Alignment for Position with the ground ). If False: Orient to Ground is also disabled.")); if (Modify(ModifyValue, modifier.CustomRotation)) DrawProperty(ref line, CustomRotation, new GUIContent("Custom Rot", "Custom Rotation: \nEnable/Disable the Custom Rotations (Used in Fly, Climb, UnderWater, Swim), This will disable Orient to Ground")); UnityEditor.EditorGUI.BeginDisabledGroup(CustomRotation.boolValue || !Grounded.boolValue); if (Modify(ModifyValue, modifier.OrientToGround)) DrawProperty(ref line, OrientToGround, new GUIContent("Orient Ground", "Orient to Ground:\nEnable/Disable the Rotation Alignment while grounded. (If False the Animal will be aligned with the Up Vector)")); UnityEditor.EditorGUI.EndDisabledGroup(); if (Modify(ModifyValue, modifier.IgnoreLowerStates)) DrawProperty(ref line, IgnoreLowerStates, new GUIContent("Ignore Lower States", "States below will not be able to try to activate themselves")); if (Modify(ModifyValue, modifier.Persistent)) DrawProperty(ref line, Persistent, new GUIContent("Persistent", "Persistent:\nEnable/Disable is Persistent on the Active State ... meaning the Animal will not Try to activate any States")); if (Modify(ModifyValue, modifier.LockMovement)) DrawProperty(ref line, LockMovement, new GUIContent("Lock Move", "Lock Movement:\nLock the Movement on the Animal, does not include Action Inputs for Attack, Jump, Action, etc")); if (Modify(ModifyValue, modifier.LockInput)) DrawProperty(ref line, LockInput, new GUIContent("Lock Input", "Lock Input:\nLock the Inputs, (Jump, Attack, etc) does not include Movement Input (WASD or Axis Inputs)")); if (Modify(ModifyValue, modifier.AdditiveRotationSpeed)) DrawProperty(ref line, AdditiveRotation, new GUIContent("+ Rot Speed", "Additive Rotation Speed:\nEnable/Disable Additive Rotation used on the Speed Modifier")); if (Modify(ModifyValue, modifier.AdditivePositionSpeed)) DrawProperty(ref line, AdditivePositionSpeed, new GUIContent("+ Pos Speed", "Additive Position Speed:\nEnable/Disable Additive Position used on the Speed Modifiers")); if (Modify(ModifyValue, modifier.FreeMovement)) DrawProperty(ref line, FreeMovement, new GUIContent("Free Move", "Free Movement:\nEnable/Disable the Free Movement... This allow to Use the Pitch direction vector and the Rotator Transform")); UnityEditor.EditorGUI.indentLevel = indent; UnityEditor.EditorGUI.EndProperty(); } private void DrawProperty(ref Rect rect, UnityEditor.SerializedProperty property, GUIContent content) { Rect splittedLine = rect; splittedLine.width = Division - 1; splittedLine.x += (Division * (activeProperties % 3)) + 1; // property.boolValue = GUI.Toggle(splittedLine, property.boolValue, content, EditorStyles.miniButton); property.boolValue = UnityEditor.EditorGUI.ToggleLeft(splittedLine, content, property.boolValue); activeProperties++; if (activeProperties % 3 == 0) { rect.y += UnityEditor.EditorGUIUtility.singleLineHeight + 2; } } private bool Modify(int modify, modifier modifier) { return ((modify & (int)modifier) == (int)modifier); } public override float GetPropertyHeight(UnityEditor.SerializedProperty property, GUIContent label) { int activeProperties = 0; var modify = property.FindPropertyRelative("modify"); int ModifyValue = modify.intValue; if (Modify(ModifyValue, modifier.RootMotion)) activeProperties++; if (Modify(ModifyValue, modifier.Sprint)) activeProperties++; if (Modify(ModifyValue, modifier.Gravity)) activeProperties++; if (Modify(ModifyValue, modifier.Grounded)) activeProperties++; if (Modify(ModifyValue, modifier.CustomRotation)) activeProperties++; if (Modify(ModifyValue, modifier.OrientToGround)) activeProperties++; if (Modify(ModifyValue, modifier.IgnoreLowerStates)) activeProperties++; if (Modify(ModifyValue, modifier.AdditivePositionSpeed)) activeProperties++; if (Modify(ModifyValue, modifier.AdditiveRotationSpeed)) activeProperties++; if (Modify(ModifyValue, modifier.Persistent)) activeProperties++; if (Modify(ModifyValue, modifier.FreeMovement)) activeProperties++; if (Modify(ModifyValue, modifier.LockMovement)) activeProperties++; if (Modify(ModifyValue, modifier.LockInput)) activeProperties++; // if (Modify(ModifyValue, modifier.Colliders)) activeProperties++; float lines = (int)((activeProperties + 2) / 3) + 1; return base.GetPropertyHeight(property, label) * lines + (2 * lines); } } #endif #endregion }