/// --------------------------------------------- /// Ultimate Character Controller /// Copyright (c) Opsive. All Rights Reserved. /// https://www.opsive.com /// --------------------------------------------- namespace Opsive.UltimateCharacterController.StateSystem { using Opsive.Shared.Utility; using System; using System.Collections.Generic; using UnityEngine; /// /// The StateConfiguration class contains an array of profiles with prespecified states that can be added to an object. /// public class StateConfiguration : ScriptableObject { [Tooltip("An array of profiles which map a name to a list of states.")] [SerializeField] protected Profile[] m_Profiles; public Profile[] Profiles { get { return m_Profiles; } set { m_Profiles = value; ResetInitialization(); } } private Dictionary> m_ProfileStateMap; /// /// The Profile class contains an array of states that should be added. /// [Serializable] public class Profile { /// /// Specifies the type of object that represents the profile. /// public enum ProfileType { Character, Item, Camera } /// /// A representation of the StateSystem.State object, used to restore states on an object. /// [Serializable] public class StateElement { [Tooltip("The name of the state.")] [SerializeField] protected string m_Name; [Tooltip("The preset which the state belongs to.")] [SerializeField] protected PersistablePreset m_Preset; [Tooltip("Any other states that the current state can block.")] [SerializeField] protected string[] m_BlockList; [Tooltip("Is the state the default state? Only one state can be the default for each object type.")] [SerializeField] protected bool m_Default; public string Name { get { return m_Name; } set { m_Name = value; } } public PersistablePreset Preset { get { return m_Preset; } set { m_Preset = value; } } public string[] BlockList { get { return m_BlockList; } set { m_BlockList = value; } } public bool Default { get { return m_Default; } set { m_Default = value; } } /// /// Default constructor. /// public StateElement() { } /// /// Three parameter constructor. /// /// The name of the state. /// The preset used by the state. /// The list of states that the current state should block. /// Is the state a default state? public StateElement(string name, PersistablePreset preset, string[] blockList, bool defaultState) { m_Name = name; m_Preset = preset; m_BlockList = blockList; m_Default = defaultState; } } [Tooltip("The name of the profile.")] [SerializeField] protected string m_Name; [Tooltip("The type of object the profile represents.")] [SerializeField] protected ProfileType m_Type; [Tooltip("The states which belong to the profile.")] [SerializeField] protected StateElement[] m_StateElements; public string Name { get { return m_Name; } set { m_Name = value; } } public ProfileType Type { get { return m_Type; } set { m_Type = value; } } public StateElement[] StateElements { get { return m_StateElements; } set { m_StateElements = value; } } } /// /// Creates a mapping for all of the profiles. /// private void Initialize() { // The mapping may have already been initialized. if (m_ProfileStateMap != null) { return; } m_ProfileStateMap = new Dictionary>(); if (m_Profiles == null) { return; } for (int i = 0; i < m_Profiles.Length; ++i) { var profileStateElements = m_Profiles[i].StateElements; var typeStateMap = new Dictionary(); m_ProfileStateMap.Add(m_Profiles[i].Name, typeStateMap); for (int j = 0; j < profileStateElements.Length; ++j) { // The state must be valid. if (string.IsNullOrEmpty(profileStateElements[j].Name) || profileStateElements[j].Preset == null) { continue; } var objType = Utility.UnityEngineUtility.GetType(profileStateElements[j].Preset.Data.ObjectType); if (objType == null) { continue; } Profile.StateElement[] stateElements; if (!typeStateMap.TryGetValue(objType, out stateElements)) { stateElements = new Profile.StateElement[0]; typeStateMap.Add(objType, stateElements); } // Add the state to the array that is specific for the object type. Array.Resize(ref stateElements, stateElements.Length + 1); stateElements[stateElements.Length - 1] = profileStateElements[j]; // Allocating a new array requires the dictionary element to be updated. typeStateMap[objType] = stateElements; } } } /// /// After a change the state map must be cleared so it can be initialized again. /// public void ResetInitialization() { m_ProfileStateMap = null; } /// /// Returns a list of profiles that have been added to the specified GameObject. /// /// The GameObject to retrieve the profiles for. /// The type of profiles that should be shown. /// A list of profiles that have been added to the specified GameObject. public List GetProfilesForGameObject(GameObject gameObject, Profile.ProfileType type) { Initialize(); var profileNames = new List(); if (gameObject == null) { // All of the profiles for the specfied type should be shown if the GameObject is null. if (m_Profiles != null) { for (int i = 0; i < m_Profiles.Length; ++i) { if (type != m_Profiles[i].Type) { continue; } profileNames.Add(m_Profiles[i].Name); } } return profileNames; } var stateBehaviors = gameObject.GetComponents(); for (int i = 0; i < stateBehaviors.Length; ++i) { GetProfilesForType(stateBehaviors[i].GetType(), type, profileNames); // The GameObject may contain the UltimateCharacterLocomotion component where the // movement types, abilities, item abilities, and effects also need to be searched. if (stateBehaviors[i] is Character.UltimateCharacterLocomotion) { var characterLocomotion = stateBehaviors[i] as Character.UltimateCharacterLocomotion; characterLocomotion.DeserializeMovementTypes(); for (int j = 0; j < characterLocomotion.MovementTypes.Length; ++j) { GetProfilesForType(characterLocomotion.MovementTypes[j].GetType(), type, profileNames); } characterLocomotion.DeserializeAbilities(); if (characterLocomotion.Abilities != null) { for (int j = 0; j < characterLocomotion.Abilities.Length; ++j) { GetProfilesForType(characterLocomotion.Abilities[j].GetType(), type, profileNames); } } characterLocomotion.DeserializeItemAbilities(); if (characterLocomotion.ItemAbilities != null) { for (int j = 0; j < characterLocomotion.ItemAbilities.Length; ++j) { GetProfilesForType(characterLocomotion.ItemAbilities[j].GetType(), type, profileNames); } } characterLocomotion.DeserializeEffects(); if (characterLocomotion.Effects != null) { for (int j = 0; j < characterLocomotion.Effects.Length; ++j) { GetProfilesForType(characterLocomotion.Effects[j].GetType(), type, profileNames); } } } // The GameObject may contain the CameraController component where the view types also need to be searched. if (stateBehaviors[i] is UltimateCharacterController.Camera.CameraController) { var cameraController = stateBehaviors[i] as UltimateCharacterController.Camera.CameraController; cameraController.DeserializeViewTypes(); for (int j = 0; j < cameraController.ViewTypes.Length; ++j) { GetProfilesForType(cameraController.ViewTypes[j].GetType(), type, profileNames); } } } return profileNames; } /// /// Adds to a list of profiles that contain the specified type. /// /// The type of object to retrieve the profiles for. /// A list of profiles that contain the specifeid type. private void GetProfilesForType(Type objType, Profile.ProfileType profileType, List profileNames) { for (int i = 0; i < m_Profiles.Length; ++i) { if (m_Profiles[i].Type != profileType) { continue; } Dictionary typeStateMap; if (!m_ProfileStateMap.TryGetValue(m_Profiles[i].Name, out typeStateMap)) { continue; } // Add the profile name if the type exists. if (typeStateMap.ContainsKey(objType) && !profileNames.Contains(m_Profiles[i].Name)) { profileNames.Add(m_Profiles[i].Name); } } } /// /// Add the states to the GameObject that have been added to the specified profile name. /// /// The name of the profile to retrieve the states from. /// The type of profiles that should be shown. /// The GameObject to add the states to. public void AddStatesToGameObject(string profileName, GameObject gameObject) { Initialize(); var stateBehaviors = gameObject.GetComponents(); for (int i = 0; i < stateBehaviors.Length; ++i) { AddStatesToObject(profileName, stateBehaviors[i]); // The GameObject may contain the UltimateCharacterLocomotion component where the // movement types, abilities, item abilities, and effects also need to be searched. if (stateBehaviors[i] is Character.UltimateCharacterLocomotion) { var characterLocomotion = stateBehaviors[i] as Character.UltimateCharacterLocomotion; characterLocomotion.DeserializeMovementTypes(); if (characterLocomotion.MovementTypes != null) { for (int j = 0; j < characterLocomotion.MovementTypes.Length; ++j) { AddStatesToObject(profileName, characterLocomotion.MovementTypes[j]); } var movementTypes = new List(characterLocomotion.MovementTypes); characterLocomotion.MovementTypeData = Shared.Utility.Serialization.Serialize(movementTypes); characterLocomotion.MovementTypes = movementTypes.ToArray(); } characterLocomotion.DeserializeAbilities(); if (characterLocomotion.Abilities != null) { for (int j = 0; j < characterLocomotion.Abilities.Length; ++j) { AddStatesToObject(profileName, characterLocomotion.Abilities[j]); } Utility.Builders.AbilityBuilder.SerializeAbilities(characterLocomotion); } characterLocomotion.DeserializeItemAbilities(); if (characterLocomotion.ItemAbilities != null) { for (int j = 0; j < characterLocomotion.ItemAbilities.Length; ++j) { AddStatesToObject(profileName, characterLocomotion.ItemAbilities[j]); } Utility.Builders.AbilityBuilder.SerializeItemAbilities(characterLocomotion); } characterLocomotion.DeserializeEffects(); if (characterLocomotion.Effects != null) { for (int j = 0; j < characterLocomotion.Effects.Length; ++j) { AddStatesToObject(profileName, characterLocomotion.Effects[j]); } var effects = new List(characterLocomotion.Effects); characterLocomotion.EffectData = Shared.Utility.Serialization.Serialize(effects); characterLocomotion.Effects = effects.ToArray(); } } // The GameObject may contain the CameraController component where the view types also need to be searched. if (stateBehaviors[i] is UltimateCharacterController.Camera.CameraController) { var cameraController = stateBehaviors[i] as UltimateCharacterController.Camera.CameraController; cameraController.DeserializeViewTypes(); if (cameraController.ViewTypes != null) { for (int j = 0; j < cameraController.ViewTypes.Length; ++j) { AddStatesToObject(profileName, cameraController.ViewTypes[j]); } Utility.Builders.ViewTypeBuilder.SerializeViewTypes(cameraController); } } } } /// /// Add the states to the object that have been added to the specified profile name. /// /// The name of the profile to retrieve the states from. /// The object to add the states to. private void AddStatesToObject(string profileName, object obj) { Dictionary typeStateMap; if (!m_ProfileStateMap.TryGetValue(profileName, out typeStateMap)) { return; } Profile.StateElement[] stateElements; if (!typeStateMap.TryGetValue(obj.GetType(), out stateElements)) { return; } var defaultIndex = -1; for (int i = 0; i < stateElements.Length; ++i) { if (stateElements[i].Default) { stateElements[i].Preset.Initialize(obj, MemberVisibility.Public); stateElements[i].Preset.ApplyValues(); defaultIndex = i; break; } } // The profile may contain states from more than one of the same object type. Only one set of states should be applied. if (defaultIndex != -1 && defaultIndex < stateElements.Length - 1) { Array.Resize(ref stateElements, defaultIndex + 1); } var states = new State[stateElements.Length + (defaultIndex != -1 ? 0 : 1)]; // One element is reserved for the default state. for (int i = 0; i < stateElements.Length; ++i) { if (stateElements[i].Default) { continue; } states[i] = new State(stateElements[i].Name, stateElements[i].Preset, stateElements[i].BlockList); } if (obj is StateBehavior) { var stateBehavior = obj as StateBehavior; states[states.Length - 1] = stateBehavior.States[stateBehavior.States.Length - 1]; stateBehavior.States = states; } else { // StateObject. var stateObject = obj as StateObject; states[states.Length - 1] = stateObject.States[stateObject.States.Length - 1]; stateObject.States = states; } } } }