/// --------------------------------------------- /// Ultimate Character Controller /// Copyright (c) Opsive. All Rights Reserved. /// https://www.opsive.com /// --------------------------------------------- namespace Opsive.UltimateCharacterController.Character { using Opsive.Shared.Events; using Opsive.Shared.Game; using Opsive.Shared.Utility; using Opsive.UltimateCharacterController.Events; using Opsive.UltimateCharacterController.Game; using Opsive.UltimateCharacterController.Character.MovementTypes; using Opsive.UltimateCharacterController.Character.Abilities; using Opsive.UltimateCharacterController.Character.Abilities.Items; using Opsive.UltimateCharacterController.Character.Effects; #if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER using Opsive.UltimateCharacterController.Networking; using Opsive.UltimateCharacterController.Networking.Character; #endif using Opsive.UltimateCharacterController.StateSystem; using Opsive.UltimateCharacterController.Utility; using System.Collections.Generic; using UnityEngine; /// /// The UltimateCharacterLocomotion component extends the CharacterLocomotion functionality by handling the following features: /// - Movement Types /// - Abilities /// - Effects /// - Animator Knowledge /// public class UltimateCharacterLocomotion : CharacterLocomotion { [Tooltip("Specifies the location that the object should be updated.")] [SerializeField] protected KinematicObjectManager.UpdateLocation m_UpdateLocation = KinematicObjectManager.UpdateLocation.FixedUpdate; [Tooltip("The name of the state that should be activated when the character is in a first person perspective.")] [SerializeField] protected string m_FirstPersonStateName = "FirstPerson"; [Tooltip("The name of the state that should be activated when the character is in a third person perspective.")] [SerializeField] protected string m_ThirdPersonStateName = "ThirdPerson"; [Tooltip("The name of the state that should be activated when the character is moving.")] [SerializeField] protected string m_MovingStateName = "Moving"; [Tooltip("The name of the state that should be activated when the character is airborne.")] [SerializeField] protected string m_AirborneStateName = "Airborne"; [Tooltip("The full name of the active movement type.")] [SerializeField] protected string m_MovementTypeFullName; [Tooltip("The name of the active first person movement type.")] [SerializeField] protected string m_FirstPersonMovementTypeFullName; [Tooltip("The name of the active third person movement type.")] [SerializeField] protected string m_ThirdPersonMovementTypeFullName; [Tooltip("Specifies how much to multiply the yaw parameter by when turning in place.")] [SerializeField] protected float m_YawMultiplier = 7; [Tooltip("Specifies the value of the Speed Parameter when the character is moving.")] [SerializeField] protected float m_MovingSpeedParameterValue = 1; [Tooltip("The serialization data for the MovementTypes.")] [SerializeField] protected Serialization[] m_MovementTypeData; [Tooltip("The serialization data for the Abilities.")] [SerializeField] protected Serialization[] m_AbilityData; [Tooltip("The serialization data for the Item Abilities.")] [SerializeField] protected Serialization[] m_ItemAbilityData; [Tooltip("The serialization data for the Effects.")] [SerializeField] protected Serialization[] m_EffectData; [Tooltip("Unity event invoked when an ability has been started or stopped.")] [SerializeField] protected UnityMovementTypeBoolEvent m_OnMovementTypeActiveEvent; [Tooltip("Unity event invoked when an movement type has been started or stopped.")] [SerializeField] protected UnityAbilityBoolEvent m_OnAbilityActiveEvent; [Tooltip("Unity event invoked when an item ability has been started or stopped.")] [SerializeField] protected UnityItemAbilityBoolEvent m_OnItemAbilityActiveEvent; [Tooltip("Unity event invoked when the character has changed grounded state.")] [SerializeField] protected UnityBoolEvent m_OnGroundedEvent; [Tooltip("Unity event invoked when the character has landed on the ground.")] [SerializeField] protected UnityFloatEvent m_OnLandEvent; [Tooltip("Unity event invoked when the time scale has changed.")] [SerializeField] protected UnityFloatEvent m_OnChangeTimeScaleEvent; [Tooltip("Unity event invoked when the moving platforms have changed.")] [SerializeField] protected UnityTransformEvent m_OnChangeMovingPlatformsEvent; public string FirstPersonStateName { get { return m_FirstPersonStateName; } set { m_FirstPersonStateName = value; } } public string ThirdPersonStateName { get { return m_ThirdPersonStateName; } set { m_ThirdPersonStateName = value; } } public string MovingStateName { get { return m_MovingStateName; } set { m_MovingStateName = value; } } public string AirborneStateName { get { return m_AirborneStateName; } set { m_AirborneStateName = value; } } public string MovementTypeFullName { get { return m_MovementTypeFullName; } set { SetMovementType(value); } } public string FirstPersonMovementTypeFullName { get { return m_FirstPersonMovementTypeFullName; } set { if (m_FirstPersonMovementTypeFullName != value) { if (!string.IsNullOrEmpty(value) && Application.isPlaying && m_FirstPersonPerspective) { SetMovementType(value); } else { m_FirstPersonMovementTypeFullName = value; } } } } public string ThirdPersonMovementTypeFullName { get { return m_ThirdPersonMovementTypeFullName; } set { if (m_ThirdPersonMovementTypeFullName != value) { if (!string.IsNullOrEmpty(value) && Application.isPlaying && !m_FirstPersonPerspective) { SetMovementType(value); } else { m_ThirdPersonMovementTypeFullName = value; } } } } public float YawMultiplier { get { return m_YawMultiplier; } set { m_YawMultiplier = value; } } public float MovingSpeedParameterValue { get { return m_MovingSpeedParameterValue; } set { m_MovingSpeedParameterValue = value; } } public float YawAngle { get { return m_YawAngle; } } public UnityMovementTypeBoolEvent OnMovementTypeActiveEvent { get { return m_OnMovementTypeActiveEvent; } set { m_OnMovementTypeActiveEvent = value; } } public UnityAbilityBoolEvent OnAbilityActiveEvent { get { return m_OnAbilityActiveEvent; } set { m_OnAbilityActiveEvent = value; } } public UnityItemAbilityBoolEvent OnItemAbilityActiveEvent { get { return m_OnItemAbilityActiveEvent; } set { m_OnItemAbilityActiveEvent = value; } } public UnityBoolEvent OnGroundedEvent { get { return m_OnGroundedEvent; } set { m_OnGroundedEvent = value; } } public UnityFloatEvent OnLandEvent { get { return m_OnLandEvent; } set { m_OnLandEvent = value; } } public UnityFloatEvent OnChangeTimeScaleEvent { get { return m_OnChangeTimeScaleEvent; } set { m_OnChangeTimeScaleEvent = value; } } public UnityTransformEvent OnChangeMovingPlatformsEvent { get { return m_OnChangeMovingPlatformsEvent; } set { m_OnChangeMovingPlatformsEvent = value; } } private GameObject m_GameObject; private Animator m_Animator; private int m_KinematicObjectIndex = -1; #if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER private INetworkInfo m_NetworkInfo; private INetworkCharacter m_NetworkCharacter; #endif private float m_MaxHeight; private Vector3 m_MaxHeightPosition; private float m_YawAngle; private Vector2 m_RawInputVector; private bool m_Moving; private bool m_MovingParameter; private bool m_SpeedParameterOverride; private ILookSource m_LookSource; private MovementType[] m_MovementTypes; private Ability[] m_Abilities; private Ability[] m_ActiveAbilities; private int m_ActiveAbilityCount; private bool m_DirtyAbilityParameter; private MoveTowards m_MoveTowardsAbility; private ItemEquipVerifier m_ItemEquipVerifierAbility; private ItemAbility[] m_ItemAbilities; private ItemAbility[] m_ActiveItemAbilities; private int m_ActiveItemAbilityCount; private bool m_DirtyItemAbilityParameter; private Effect[] m_Effects; private Effect[] m_ActiveEffects; private int m_ActiveEffectCount; private Dictionary m_MovementTypeNameMap = new Dictionary(); private MovementType m_MovementType; private bool m_FirstPersonPerspective; private bool m_Alive; private bool m_Aiming; private Vector3 m_AbilityMotor; private int[] m_ItemSlotStateIndex; private int[] m_ItemSlotSubstateIndex; [NonSerialized] public int KinematicObjectIndex { get { return m_KinematicObjectIndex; } set { m_KinematicObjectIndex = value; } } public KinematicObjectManager.UpdateLocation UpdateLocation { get { return m_UpdateLocation; } set { if (m_UpdateLocation == value) { return; } m_UpdateLocation = value; EventHandler.ExecuteEvent(m_GameObject, "OnCharacterChangeUpdateLocation", m_UpdateLocation == KinematicObjectManager.UpdateLocation.FixedUpdate); } } public ILookSource LookSource { get { return m_LookSource; } } public MovementType[] MovementTypes { get { return m_MovementTypes; } set { m_MovementTypes = value; m_MovementTypeNameMap.Clear(); for (int i = 0; i < m_MovementTypes.Length; ++i) { m_MovementTypeNameMap.Add(m_MovementTypes[i].GetType().FullName, i); } } } public Serialization[] MovementTypeData { get { return m_MovementTypeData; } set { m_MovementTypeData = value; } } public Ability[] Abilities { get { return m_Abilities; } set { m_Abilities = value; if (Application.isPlaying && m_Abilities != null) { if (m_ActiveAbilities == null) { m_ActiveAbilities = new Ability[m_Abilities.Length]; } else { System.Array.Resize(ref m_ActiveAbilities, m_Abilities.Length); } // The ability can be added after the character has already been initialized. for (int i = 0; i < m_Abilities.Length; ++i) { if (m_Abilities[i].Index == -1) { m_Abilities[i].Initialize(this, i); m_Abilities[i].Awake(); } } } } } public Serialization[] AbilityData { get { return m_AbilityData; } set { m_AbilityData = value; } } public ItemAbility[] ItemAbilities { get { return m_ItemAbilities; } set { m_ItemAbilities = value; if (Application.isPlaying && m_ItemAbilities != null) { if (m_ActiveItemAbilities == null) { m_ActiveItemAbilities = new ItemAbility[m_ItemAbilities.Length]; } else { System.Array.Resize(ref m_ActiveItemAbilities, m_ItemAbilities.Length); } // The ability can be added after the character has already been initialized. for (int i = 0; i < m_ItemAbilities.Length; ++i) { if (m_ItemAbilities[i].Index == -1) { m_ItemAbilities[i].Initialize(this, i); m_ItemAbilities[i].Awake(); } } } } } public Serialization[] ItemAbilityData { get { return m_ItemAbilityData; } set { m_ItemAbilityData = value; } } [Snapshot] public Ability[] ActiveAbilities { get { return m_ActiveAbilities; } protected set { m_ActiveAbilities = value; } } [Snapshot] public int ActiveAbilityCount { get { return m_ActiveAbilityCount; } protected set { m_ActiveAbilityCount = value; } } [Snapshot] public bool DirtyAbilityParameter { get { return m_DirtyAbilityParameter; } protected set { m_DirtyAbilityParameter = value; } } [Snapshot] public ItemAbility[] ActiveItemAbilities { get { return m_ActiveItemAbilities; } protected set { m_ActiveItemAbilities = value; } } [Snapshot] public int ActiveItemAbilityCount { get { return m_ActiveItemAbilityCount; } protected set { m_ActiveItemAbilityCount = value; } } [Snapshot] public bool DirtyItemAbilityParameter { get { return m_DirtyItemAbilityParameter; } protected set { m_DirtyItemAbilityParameter = value; } } public Effect[] Effects { get { return m_Effects; } set { m_Effects = value; } } public Serialization[] EffectData { get { return m_EffectData; } set { m_EffectData = value; } } public MovementType ActiveMovementType { get { return m_MovementType; } } public MoveTowards MoveTowardsAbility { get { return m_MoveTowardsAbility; } } public ItemEquipVerifier ItemEquipVerifierAbility { get { return m_ItemEquipVerifierAbility; } } public override float TimeScale { get { return base.TimeScale; } set { // Override the TimeScale setter to allow an event to be sent when the time scale changes. if (base.TimeScale != value) { if (base.TimeScale == 0 && value != 0) { EventHandler.ExecuteEvent(m_GameObject, "OnEnableGameplayInput", true); } else if (base.TimeScale != 0 && value == 0) { EventHandler.ExecuteEvent(m_GameObject, "OnEnableGameplayInput", false); } EventHandler.ExecuteEvent(m_GameObject, "OnCharacterChangeTimeScale", value); if (m_OnChangeTimeScaleEvent != null) { m_OnChangeTimeScaleEvent.Invoke(value); } } base.TimeScale = value; } } [NonSerialized] public Vector2 RawInputVector { get { return m_RawInputVector; } set { m_RawInputVector = value; } } [NonSerialized] public Vector2 InputVector { get { return m_InputVector; } set { m_InputVector = value; } } [NonSerialized] public Vector3 DeltaRotation { get { return m_DeltaRotation; } set { m_DeltaRotation = value; } } [NonSerialized] public bool Moving { get { return m_Moving || m_InputVector.sqrMagnitude > 0.001f; } set { if (m_Moving != value) { m_Moving = value; EventHandler.ExecuteEvent(m_GameObject, "OnCharacterMoving", m_Moving); if (!string.IsNullOrEmpty(m_MovingStateName)) { StateManager.SetState(m_GameObject, m_MovingStateName, m_Moving); } } } } [NonSerialized] public bool SpeedParameterOverride { get { return m_SpeedParameterOverride; } set { m_SpeedParameterOverride = value; } } [NonSerialized] public float SpeedParameterValue { get { return Moving ? m_MovingSpeedParameterValue : 0; } } [NonSerialized] public Vector3 AbilityMotor { get { return m_AbilityMotor; } set { m_AbilityMotor = value; } } [NonSerialized] public bool FirstPersonPerspective { get { return m_FirstPersonPerspective; } set { m_FirstPersonPerspective = value; } } public bool Alive { get { return m_Alive; } } /// /// Cache the component references and initialize the default values. /// protected override void Awake() { m_GameObject = gameObject; m_Animator = m_GameObject.GetCachedComponent(); #if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER m_NetworkInfo = m_GameObject.GetCachedComponent(); m_NetworkCharacter = m_GameObject.GetCachedComponent(); #endif // Create any movement types, abilities, and effects from the serialized data. DeserializeMovementTypes(true); DeserializeAbilities(true); DeserializeItemAbilities(true); DeserializeEffects(true); base.Awake(); // Use the local y position in determining the max height. m_MaxHeight = float.NegativeInfinity; m_MaxHeightPosition = m_Transform.position; var forwardDirection = m_Transform.rotation * m_Transform.forward; m_YawAngle = Mathf.Atan2(forwardDirection.x, forwardDirection.z) * Mathf.Rad2Deg; m_Alive = true; EventHandler.RegisterEvent(m_GameObject, "OnCharacterAttachLookSource", OnAttachLookSource); EventHandler.RegisterEvent(m_GameObject, "OnCharacterChangePerspectives", OnChangePerspectives); EventHandler.RegisterEvent(m_GameObject, "OnAimAbilityAim", OnAiming); EventHandler.RegisterEvent(m_GameObject, "OnDeath", OnDeath); EventHandler.RegisterEvent(m_GameObject, "OnRespawn", OnRespawn); EventHandler.RegisterEvent(m_GameObject, "OnCharacterImmediateTransformChange", OnImmediateTransformChange); EventHandler.RegisterEvent(m_GameObject, "OnAnimatorSnapped", AnimatorSnapped); // Call Awake on all of the deserialized objects after the character controller's Awake method is complete. if (m_MovementTypes != null) { for (int i = 0; i < m_MovementTypes.Length; ++i) { m_MovementTypes[i].Awake(); } } if (m_Abilities != null) { m_ActiveAbilities = new Ability[m_Abilities.Length]; for (int i = 0; i < m_Abilities.Length; ++i) { m_Abilities[i].Awake(); } } if (m_ItemAbilities != null) { m_ActiveItemAbilities = new ItemAbility[m_ItemAbilities.Length]; for (int i = 0; i < m_ItemAbilities.Length; ++i) { m_ItemAbilities[i].Awake(); } } if (m_Effects != null) { m_ActiveEffects = new Effect[m_Effects.Length]; for (int i = 0; i < m_Effects.Length; ++i) { m_Effects[i].Awake(); } } // The controller needs to start with a movement type. SetMovementType(m_MovementTypeFullName); if (m_MovementType != null) { m_FirstPersonPerspective = m_MovementType.FirstPersonPerspective; } } /// /// Deserialize the movement types. /// /// Were any movement types removed? public bool DeserializeMovementTypes() { return DeserializeMovementTypes(false); } /// /// Deserialize the movement types. /// /// Should the movement types be force deserialized? /// Were any movement types removed? public bool DeserializeMovementTypes(bool forceDeserialization) { // The Movement Types only need to be deserialized once. if (m_MovementTypes != null && !forceDeserialization) { return false; } var dirty = false; if (m_MovementTypeData != null && m_MovementTypeData.Length > 0) { m_MovementTypes = new MovementType[m_MovementTypeData.Length]; m_MovementTypeNameMap.Clear(); for (int i = 0; i < m_MovementTypes.Length; ++i) { m_MovementTypes[i] = m_MovementTypeData[i].DeserializeFields(MemberVisibility.Public) as MovementType; if (m_MovementTypes[i] == null) { dirty = true; continue; } m_MovementTypeNameMap.Add(m_MovementTypes[i].GetType().FullName, i); if (Application.isPlaying) { m_MovementTypes[i].Initialize(this); } } } return dirty; } /// /// Deserialize the abilities. /// /// Were any abilities removed? public bool DeserializeAbilities() { return DeserializeAbilities(false); } /// /// Deserialize the abilities. /// /// Should the abilities be force deserialized? /// Were any abilities removed? public bool DeserializeAbilities(bool forceDeserialization) { // The abilities only need to be deserialized once. if (m_Abilities != null && !forceDeserialization) { return false; } var dirty = false; if (m_AbilityData != null && m_AbilityData.Length > 0) { m_Abilities = new Ability[m_AbilityData.Length]; for (int i = 0; i < m_AbilityData.Length; ++i) { m_Abilities[i] = m_AbilityData[i].DeserializeFields(MemberVisibility.Public) as Ability; if (m_Abilities[i] == null) { dirty = true; continue; } if (Application.isPlaying) { m_Abilities[i].Initialize(this, i); } // The MoveTowards and ItemToggleAbilityBlock abilities are a special type of ability in that it is started by the controller. if (m_Abilities[i] is MoveTowards) { m_MoveTowardsAbility = m_Abilities[i] as MoveTowards; } else if (m_Abilities[i] is ItemEquipVerifier) { m_ItemEquipVerifierAbility = m_Abilities[i] as ItemEquipVerifier; } } } return dirty; } /// /// Deserialize the item abilities. /// /// Were any item abilities removed? public bool DeserializeItemAbilities() { return DeserializeItemAbilities(false); } /// /// Deserialize the item abilities. /// /// Should the item abilities be force deserialized? /// Were any item abilities removed? public bool DeserializeItemAbilities(bool forceDeserialization) { // The Item Abilities only need to be deserialized once. if (m_ItemAbilities != null && !forceDeserialization) { return false; } var dirty = false; if (m_ItemAbilityData != null && m_ItemAbilityData.Length > 0) { m_ItemAbilities = new ItemAbility[m_ItemAbilityData.Length]; for (int i = 0; i < m_ItemAbilityData.Length; ++i) { m_ItemAbilities[i] = m_ItemAbilityData[i].DeserializeFields(MemberVisibility.Public) as ItemAbility; if (m_ItemAbilities[i] == null) { dirty = true; continue; } if (Application.isPlaying) { m_ItemAbilities[i].Initialize(this, i); } } } return dirty; } /// /// Deserialize the effects. /// /// Were any effects removed? public bool DeserializeEffects() { return DeserializeEffects(false); } /// /// Deserialize the effects. /// /// Should the effects be force deserialized? /// Were any effects removed? public bool DeserializeEffects(bool forceDeserialization) { // The Effects only need to be deserialized once. if (m_Effects != null && !forceDeserialization) { return false; } var dirty = false; if (m_EffectData != null && m_EffectData.Length > 0) { m_Effects = new Effect[m_EffectData.Length]; for (int i = 0; i < m_EffectData.Length; ++i) { m_Effects[i] = m_EffectData[i].DeserializeFields(MemberVisibility.Public) as Effect; if (m_Effects[i] == null) { dirty = true; continue; } if (Application.isPlaying) { m_Effects[i].Initialize(this, i); } } } return dirty; } /// /// Returns an array of serialized movement types. Useful for editor scripts when the movement types haven't already been deserialized at runtime. /// /// An array of serialized movement types. public MovementType[] GetSerializedMovementTypes() { if (m_MovementTypeData != null && m_MovementTypeData.Length > 0 && (m_MovementTypes == null || m_MovementTypes.Length == 0)) { DeserializeMovementTypes(); } return m_MovementTypes; } /// /// Returns an array of serialized abilities. Useful for editor scripts when the abilities haven't already been deserialized at runtime. /// /// An array of serialized abilities. public Ability[] GetSerializedAbilities() { if (m_AbilityData != null && m_AbilityData.Length > 0 && (m_Abilities == null || m_Abilities.Length == 0)) { DeserializeAbilities(); } return m_Abilities; } /// /// Returns an array of serialized item abilities. Useful for editor scripts when the item abilities haven't already been deserialized at runtime. /// /// An array of serialized item abilities. public ItemAbility[] GetSerializedItemAbilities() { if (m_ItemAbilityData != null && m_ItemAbilityData.Length > 0 && (m_ItemAbilities == null || m_ItemAbilities.Length == 0)) { DeserializeItemAbilities(); } return m_ItemAbilities; } /// /// Returns an array of serialized effects. Useful for editor scripts when the effects haven't already been deserialized at runtime. /// /// An array of serialized effects. public Effect[] GetSerializedEffects() { if (m_EffectData != null && m_EffectData.Length > 0 && (m_Effects == null || m_Effects.Length == 0)) { DeserializeEffects(); } return m_Effects; } /// /// Sets the movement type to the object with the specified type which should be set. /// /// The type name of the MovementType which should be set. private void SetMovementType(string typeName) { SetMovementType(UnityEngineUtility.GetType(typeName)); } /// /// Sets the movement type to the object with the specified type. /// /// The type of the MovementType which should be set. public void SetMovementType(System.Type type) { if (type == null || (m_MovementType != null && m_MovementType.GetType() == type)) { return; } // The MovementTypes may not be deserialized yet. if (m_MovementTypeNameMap.Count == 0) { DeserializeMovementTypes(); } int index; if (!m_MovementTypeNameMap.TryGetValue(type.FullName, out index)) { Debug.LogError($"Error: Unable to find the movement type with name {type.FullName}.", this); return; } // Notify the previous movement type that it is no longer active. if (m_MovementType != null && Application.isPlaying) { m_MovementType.ChangeMovementType(false); EventHandler.ExecuteEvent(m_GameObject, "OnCharacterChangeMovementType", m_MovementType, false); if (m_OnMovementTypeActiveEvent != null) { m_OnMovementTypeActiveEvent.Invoke(m_MovementType, false); } } m_MovementTypeFullName = type.FullName; m_MovementType = m_MovementTypes[index]; // Notify the current movement type that is now active. if (Application.isPlaying) { if (m_MovementType.FirstPersonPerspective) { m_FirstPersonMovementTypeFullName = m_MovementTypeFullName; } else { m_ThirdPersonMovementTypeFullName = m_MovementTypeFullName; } m_MovementType.ChangeMovementType(true); EventHandler.ExecuteEvent(m_GameObject, "OnCharacterChangeMovementType", m_MovementType, true); if (m_OnMovementTypeActiveEvent != null) { m_OnMovementTypeActiveEvent.Invoke(m_MovementType, true); } } } /// /// A new ILookSource object has been attached to the character. /// /// The ILookSource object attached to the character. private void OnAttachLookSource(ILookSource lookSource) { m_LookSource = lookSource; #if THIRD_PERSON_CONTROLLER var hasPerspectiveMonitor = m_GameObject.GetComponent() != null; #else var hasPerspectiveMonitor = false; #endif // If the character doesn't have the PerspectiveMonitor then the perspective depends on the look source. if (!hasPerspectiveMonitor) { if (lookSource != null) { var cameraController = lookSource as UltimateCharacterController.Camera.CameraController; if (cameraController != null) { m_FirstPersonPerspective = cameraController.ActiveViewType.FirstPersonPerspective; } else { m_FirstPersonPerspective = false; } } EventHandler.ExecuteEvent(m_GameObject, "OnCharacterChangePerspectives", m_FirstPersonPerspective); } } /// /// The character has been enabled. /// protected override void OnEnable() { // The KinematicObjectManager is responsible for calling the move method. m_KinematicObjectIndex = KinematicObjectManager.RegisterCharacter(this); // If the previous index is not -1 then the character has already been enabled. Send events so all of the components correctly reset. if (m_KinematicObjectIndex != -1) { EventHandler.ExecuteEvent(m_GameObject, "OnCharacterMoving", false); EventHandler.ExecuteEvent(m_GameObject, "OnCharacterImmediateTransformChange", true); } base.OnEnable(); } /// /// Call Start on all of the character's abilities and effects in addition to checking the TimeScale. /// public override void Start() { base.Start(); // The animator needs to know how many slots there are. var inventory = m_GameObject.GetCachedComponent(); if (inventory != null) { var slotCount = inventory.SlotCount; m_ItemSlotStateIndex = new int[slotCount]; m_ItemSlotSubstateIndex = new int[slotCount]; } // Start the abilities and effects. if (m_Abilities != null) { for (int i = 0; i < m_Abilities.Length; ++i) { m_Abilities[i].Start(); } } if (m_ItemAbilities != null) { for (int i = 0; i < m_ItemAbilities.Length; ++i) { m_ItemAbilities[i].Start(); } } if (m_Effects != null) { for (int i = 0; i < m_Effects.Length; ++i) { m_Effects[i].Start(); } } // Do a pass on trying to start any abilities in case they should be started on the first frame. UpdateAbilities(m_Abilities); UpdateAbilities(m_ItemAbilities); UpdateDirtyAbilityAnimatorParameters(); // The abilities may have updated the animator. EventHandler.ExecuteEvent(m_GameObject, "OnCharacterSnapAnimator"); // The character isn't moving at the start. EventHandler.ExecuteEvent(m_GameObject, "OnCharacterMoving", false); // Notify those interested in the time scale isn't set to 1 at the start. The TimeScale property will notify those interested of the change during runtime. if (m_TimeScale != 1) { EventHandler.ExecuteEvent(m_GameObject, "OnCharacterChangeTimeScale", m_TimeScale); if (m_OnChangeTimeScaleEvent != null) { m_OnChangeTimeScaleEvent.Invoke(m_TimeScale); } } } /// /// Callback from CharacterLocomotion.Move when the UltimateCharacterLocomotion should perform its updates (such as updating the abilities). /// protected override void UpdateUltimateLocomotion() { // The MovementType may change the InputVector. m_RawInputVector = m_InputVector; // Abilities may disallow input. bool allowPositionalInput, allowRotationalInput; AbilitiesAllowInput(out allowPositionalInput, out allowRotationalInput); if (allowPositionalInput) { // Positional input is allowed - use the movement type to determine how the character should move. m_InputVector = m_MovementType.GetInputVector(m_InputVector); } else { m_InputVector = Vector2.zero; } if (!allowRotationalInput) { m_DeltaRotation = Vector3.zero; } // Start and update the abilities. UpdateAbilities(m_Abilities); UpdateAbilities(m_ItemAbilities); // Update the effects. for (int i = 0; i < m_ActiveEffectCount; ++i) { m_ActiveEffects[i].Update(); } // The Moving value is based on the input vector. It does not include vertical movement. m_MovingParameter = Moving; if (m_Moving != (m_InputVector.sqrMagnitude > 0.001f)) { Moving = !m_Moving; } } /// /// Do the abilities allow positional and rotational input? /// /// A reference to a bool which indicates if the abilities allow positional input. /// A reference to a bool which indicates if the abilities allow rotational input. private void AbilitiesAllowInput(out bool allowPositionalInput, out bool allowRotationalInput) { allowPositionalInput = allowRotationalInput = true; // Check the abilities to see if any disallow input. for (int i = 0; i < m_ActiveAbilityCount; ++i) { if (!m_ActiveAbilities[i].AllowPositionalInput) { allowPositionalInput = false; } if (!m_ActiveAbilities[i].AllowRotationalInput) { allowRotationalInput = false; } } // If neither the positional or rotational input is allowed then the item abilities don't need to be checked. if (!allowPositionalInput && !allowRotationalInput) { return; } // Check the item abilities to see if any disallow input. for (int i = 0; i < m_ActiveItemAbilityCount; ++i) { if (!m_ActiveItemAbilities[i].AllowPositionalInput) { allowPositionalInput = false; } if (!m_ActiveItemAbilities[i].AllowRotationalInput) { allowRotationalInput = false; } } } /// /// Try to start an automatic inactive ability and also try to stop an automatic active ability. The Update or InactiveUpdate will also be called. /// /// An array of all of the abilities. private void UpdateAbilities(Ability[] abilities) { if (abilities != null) { for (int i = 0; i < abilities.Length; ++i) { if (!abilities[i].IsActive) { if (abilities[i].StartType == Ability.AbilityStartType.Automatic) { TryStartAbility(abilities[i]); } else if (!(abilities[i] is ItemAbility) && abilities[i].StartType != Ability.AbilityStartType.Manual && abilities[i].CheckForAbilityMessage && (m_MoveTowardsAbility == null || !m_MoveTowardsAbility.IsActive)) { // The ability message can show if the non-automatic/manual ability can start. abilities[i].AbilityMessageCanStart = abilities[i].Enabled && abilities[i].CanStartAbility(); } } if (abilities[i].IsActive && abilities[i].StopType == Ability.AbilityStopType.Automatic) { TryStopAbility(abilities[i]); } if (abilities[i].IsActive) { abilities[i].Update(); } else if (abilities[i].Enabled) { abilities[i].InactiveUpdate(); } } } } /// /// Update the animator parameters. /// protected override void UpdateAnimator() { // In the case of a first person view the character may not have an animator. if (m_AnimatorMonitor == null) { return; } for (int i = 0; i < m_ActiveAbilityCount; ++i) { m_ActiveAbilities[i].UpdateAnimator(); } for (int i = 0; i < m_ActiveItemAbilityCount; ++i) { m_ActiveItemAbilities[i].UpdateAnimator(); } m_AnimatorMonitor.SetHorizontalMovementParameter(m_InputVector.x, m_TimeScale); m_AnimatorMonitor.SetForwardMovementParameter(m_InputVector.y, m_TimeScale); if (m_LookSource != null) { m_AnimatorMonitor.SetPitchParameter(m_LookSource.Pitch, m_TimeScale, 0); } m_AnimatorMonitor.SetYawParameter(m_YawAngle * m_YawMultiplier, m_TimeScale); if (!m_SpeedParameterOverride) { m_AnimatorMonitor.SetSpeedParameter(SpeedParameterValue, m_TimeScale); } m_AnimatorMonitor.SetMovingParameter(m_MovingParameter); // The ability parameters should only be updated once each move call. UpdateDirtyAbilityAnimatorParameters(); m_AnimatorMonitor.UpdateItemIDParameters(); } /// /// Updates the position and rotation. This should be done after the animator has updated so the root motion is accurate for the current frame. /// protected override void UpdatePositionAndRotation() { KinematicObjectManager.BeginCharacterMovement(m_KinematicObjectIndex); base.UpdatePositionAndRotation(); LateUpdateUltimateLocomotion(); KinematicObjectManager.EndCharacterMovement(m_KinematicObjectIndex); } /// /// Updates the grounded state. /// /// Is the character grounded? /// Should the events be sent if the grounded status changes? /// True if the grounded state changed. protected override bool UpdateGroundState(bool grounded, bool sendEvents) { var groundedStatusChanged = base.UpdateGroundState(grounded, sendEvents); if (groundedStatusChanged) { // Notify interested objects of the ground change. if (sendEvents) { EventHandler.ExecuteEvent(m_GameObject, "OnCharacterGrounded", m_Grounded); if (m_OnGroundedEvent != null) { m_OnGroundedEvent.Invoke(m_Grounded); } } if (m_Grounded) { if (sendEvents && m_MaxHeight != float.NegativeInfinity) { var height = m_MaxHeight - m_Transform.InverseTransformDirection(m_Transform.position - m_MaxHeightPosition).y; EventHandler.ExecuteEvent(m_GameObject, "OnCharacterLand", height); if (m_OnLandEvent != null) { m_OnLandEvent.Invoke(height); } } } else { m_MaxHeightPosition = m_Transform.position; m_MaxHeight = float.NegativeInfinity; } } else if (!m_Grounded && UsingGravity) { // Save out the max height of the character in the air so the fall height can be calculated. var height = m_Transform.InverseTransformDirection(m_Transform.position - m_MaxHeightPosition).y; if (height > m_MaxHeight) { m_MaxHeightPosition = m_Transform.position; m_MaxHeight = height; } } // Set the airborne state if the grounded status has changed or no events are being sent. No events will be sent when the grounded status is initially checked. if ((groundedStatusChanged || !sendEvents) && !string.IsNullOrEmpty(m_AirborneStateName)) { StateManager.SetState(m_GameObject, m_AirborneStateName, !m_Grounded); } return groundedStatusChanged; } /// /// Update the rotation forces. /// protected override void UpdateRotation() { // If using root motion rotation the animation will specify the rotation. if (UsingRootMotionRotation) { m_DeltaRotation = Vector3.zero; } // Give the abilities a chance to update the rotation. for (int i = 0; i < m_ActiveAbilityCount; ++i) { m_ActiveAbilities[i].UpdateRotation(); } for (int i = 0; i < m_ActiveItemAbilityCount; ++i) { m_ActiveItemAbilities[i].UpdateRotation(); } base.UpdateRotation(); } /// /// Applies the final rotation to the transform. /// protected override void ApplyRotation() { // Give the abilities a chance to verify the rotation. for (int i = 0; i < m_ActiveAbilityCount; ++i) { m_ActiveAbilities[i].ApplyRotation(); } for (int i = 0; i < m_ActiveItemAbilityCount; ++i) { m_ActiveItemAbilities[i].ApplyRotation(); } // Save the local yaw angle before the rotation is applied so the animator knows how much the character turned. m_YawAngle = 0; if (m_Platform == null) { if (Mathf.Abs(m_Torque.eulerAngles.y) > 0.1f) { m_YawAngle = MathUtility.ClampInnerAngle(m_Torque.eulerAngles.y); } } else { var delta = m_Torque * Quaternion.Inverse(m_PlatformTorque); if (Mathf.Abs(delta.eulerAngles.y) > 0.1f) { m_YawAngle = MathUtility.ClampInnerAngle(delta.eulerAngles.y); } } base.ApplyRotation(); } /// /// Sets the moving platform to the specified transform. /// /// The platform transform that should be set. Can be null. /// Is the default moving platform logic being overridden? /// True if the platform was changed. public override bool SetPlatform(Transform platform, bool platformOverride) { var movingPlatformChanged = base.SetPlatform(platform, platformOverride); if (movingPlatformChanged) { // Notify interested objects of the platform change. EventHandler.ExecuteEvent(m_GameObject, "OnCharacterChangeMovingPlatforms", m_Platform); if (m_OnChangeMovingPlatformsEvent != null) { m_OnChangeMovingPlatformsEvent.Invoke(m_Platform); } } return movingPlatformChanged; } /// /// Move according to the forces. /// protected override void UpdatePosition() { // Give the abilities a chance to update the movement. for (int i = 0; i < m_ActiveAbilityCount; ++i) { m_ActiveAbilities[i].UpdatePosition(); } for (int i = 0; i < m_ActiveItemAbilityCount; ++i) { m_ActiveItemAbilities[i].UpdatePosition(); } base.UpdatePosition(); } /// /// Updates the motor forces. /// protected override void UpdateMotorThrottle() { base.UpdateMotorThrottle(); MotorThrottle += m_AbilityMotor; } /// /// Applies the final move direction to the transform. /// protected override void ApplyPosition() { // Give the abilities a chance to verify the movement. for (int i = 0; i < m_ActiveAbilityCount; ++i) { m_ActiveAbilities[i].ApplyPosition(); } for (int i = 0; i < m_ActiveItemAbilityCount; ++i) { m_ActiveItemAbilities[i].ApplyPosition(); } base.ApplyPosition(); } /// /// Updates the ability and item ability parameters if they are dirty. /// private void UpdateDirtyAbilityAnimatorParameters() { if (m_DirtyAbilityParameter) { UpdateAbilityAnimatorParameters(true); } if (m_DirtyItemAbilityParameter) { UpdateItemAbilityAnimatorParameters(true); } } /// /// Callback after the animator has updated and the character should perform its post movement updates (such as updating the item abilities). /// private void LateUpdateUltimateLocomotion() { // If the animator is being updated within LateUpdate then the character colliders will be enabled. var collisionLayerEnabled = CollisionLayerEnabled; EnableColliderCollisionLayer(false); // Start and update the item abilities. This is done after the controller has moved so the items will be using the latest character position/rotation (such as a melee item // for collision detection). LateUpdateActiveAbilities(m_ActiveAbilities, ref m_ActiveAbilityCount); LateUpdateActiveAbilities(m_ActiveItemAbilities, ref m_ActiveItemAbilityCount); EnableColliderCollisionLayer(collisionLayerEnabled); } /// /// Calls LateUpdate on the active abilities. /// /// An array of all of the abilities. /// The number of active abilities. private void LateUpdateActiveAbilities(Ability[] abilities, ref int abilityCount) { if (abilities != null) { for (int i = 0; i < abilityCount; ++i) { abilities[i].LateUpdate(); } } } /// /// Tries to start the specified ability. /// /// The ability to try to start. /// True if the ability was started. public bool TryStartAbility(Ability ability) { return TryStartAbility(ability, false); } /// /// Tries to start the specified ability. /// /// The ability to try to start. /// Should the ability priority be ignored? /// True if the ability was started. public bool TryStartAbility(Ability ability, bool ignorePriority) { return TryStartAbility(ability, ignorePriority, false); } /// /// Tries to start the specified ability. /// /// The ability to try to start. /// Should the ability priority be ignored? /// Should the CanStartAbility check be ignored? /// True if the ability was started. public bool TryStartAbility(Ability ability, bool ignorePriority, bool ignoreCanStartCheck) { // ItemAbilities have a different startup process than regular abilities. if (ability is ItemAbility) { return TryStartAbility(ability as ItemAbility, ignoreCanStartCheck); } // Start the ability if it is not active or can be started multiple times, enabled, and can be started. if ((!ability.IsActive || ability.CanReceiveMultipleStarts) && ability.Enabled && (ignoreCanStartCheck || ability.CanStartAbility())) { // The ability may already be active if the ability can receive multiple starts. Multiple starts are useful for item abilities that need to be active // over a period of time but can be updated with the input start type while active. A good example of this is the Toggle Equip Item ability. When // this ability starts it sets an Animator parameter to equip or unequip the item. The ability continues to run while equipping or unequipping the item // but it should trigger the reverse of the equip or unequip when another start is triggered. int index; if (!ability.IsActive) { // The priority can be ignored if the ability should be force started. if (!ignorePriority) { // If the ability is not a concurrent ability then it can only be started if it has a lower index than any other active abilities. if (!ability.IsConcurrent) { for (int i = 0; i < m_ActiveAbilityCount; ++i) { var ignoreLocalPriority = m_ActiveAbilities[i].IgnorePriority && ability.IgnorePriority; if (m_ActiveAbilities[i].IsConcurrent) { // The ability cannot be started if a concurrent ability is active and has a lower index. if (((!ignoreLocalPriority && m_ActiveAbilities[i].Index < ability.Index) || ignoreLocalPriority) && m_ActiveAbilities[i].ShouldBlockAbilityStart(ability)) { return false; } } else { // The ability cannot be started if another ability is already active and has a lower index or if the active ability says the current ability cannot be started. if ((m_ActiveAbilities[i].Index < ability.Index && !ignoreLocalPriority) || m_ActiveAbilities[i].ShouldBlockAbilityStart(ability)) { return false; } else { // Stop any abilities that have a higher index to prevent two non-concurrent abilities from running at the same time. TryStopAbility(m_ActiveAbilities[i], true); } } } } // The ability cannot be started if the active ability says the current ability cannot be started. for (int i = 0; i < m_ActiveAbilityCount; ++i) { if (m_ActiveAbilities[i].ShouldBlockAbilityStart(ability)) { return false; } } for (int i = 0; i < m_ActiveItemAbilityCount; ++i) { if (m_ActiveItemAbilities[i].ShouldBlockAbilityStart(ability)) { return false; } } } // The ability can start. Stop any currently active abilities that should not be started because the current ability has started. for (int i = m_ActiveAbilityCount - 1; i >= 0; --i) { if (ability.ShouldStopActiveAbility(m_ActiveAbilities[i])) { TryStopAbility(m_ActiveAbilities[i], true); } } for (int i = m_ActiveItemAbilityCount - 1; i >= 0; --i) { if (ability.ShouldStopActiveAbility(m_ActiveItemAbilities[i])) { TryStopAbility(m_ActiveItemAbilities[i], true); } } // Give the ability one more chance to initialize any variables or stop from starting. This is important for abilities that are being started // after a period of time such as from the Move Towards ability or the Item Equip Verifier. if (!ability.AbilityWillStart()) { return false; } var moveEquipStarted = false; // The ability may require the character to first move to a specific location before it can start. if (!(ability is MoveTowards) && m_MoveTowardsAbility != null) { // If StartMoving returns true then the MoveTowards ability has started and it will start the original // ability after the character has arrived at the destination. if (m_MoveTowardsAbility.StartMoving(ability.GetMoveTowardsLocations(), ability)) { moveEquipStarted = true; } } // The ability may first need to unequip any equipped items before it can start. if (!(ability is ItemEquipVerifier) && m_ItemEquipVerifierAbility != null) { // If TryToggleItem returns true then the ItemEquipVerifier ability has started and it will start the original ability after // the character has finished unequipping the equipped items. if (m_ItemEquipVerifierAbility.TryToggleItem(ability, true) && !ability.ImmediateStartItemVerifier) { moveEquipStarted = true; } } // Wait for the MoveTowards and ItemEquipVerifier abilities to end before starting the new ability. if (moveEquipStarted) { return true; } // Insert in the active abilities array according to priority. index = m_ActiveAbilityCount; for (int i = 0; i < m_ActiveAbilityCount; ++i) { if (m_ActiveAbilities[i].Index > ability.Index) { index = i; break; } } // Make space for the new ability. for (int i = m_ActiveAbilityCount; i > index; --i) { m_ActiveAbilities[i] = m_ActiveAbilities[i - 1]; m_ActiveAbilities[i].ActiveIndex = i; } m_ActiveAbilities[index] = ability; m_ActiveAbilityCount++; } else { // The ability is already active - start it again for a multiple start. index = ability.ActiveIndex; } // Execute the event before the ability is started in case the ability is stopped within the start. EventHandler.ExecuteEvent(m_GameObject, "OnCharacterAbilityActive", ability, true); if (m_OnAbilityActiveEvent != null) { m_OnAbilityActiveEvent.Invoke(ability, true); } ability.StartAbility(index); m_DirtyAbilityParameter = true; return true; } return false; } /// /// Tries to start the specified item ability. /// /// The item ability to try to start. /// True if the ability was started. public bool TryStartAbility(ItemAbility itemAbility) { return TryStartAbility(itemAbility, false); } /// /// Tries to start the specified item ability. /// /// The item ability to try to start. /// Should the CanStartAbility check be ignored? /// True if the ability was started. public bool TryStartAbility(ItemAbility itemAbility, bool ignoreCanStartCheck) { // Start the ability if it is not active or can be started multiple times, enabled, and can be started. if ((!itemAbility.IsActive || itemAbility.CanReceiveMultipleStarts) && itemAbility.Enabled && (ignoreCanStartCheck || itemAbility.CanStartAbility())) { // The ability cannot be started if the active ability says the current ability cannot be started. for (int i = 0; i < m_ActiveItemAbilityCount; ++i) { if (m_ActiveItemAbilities[i].ShouldBlockAbilityStart(itemAbility)) { return false; } } for (int i = 0; i < m_ActiveAbilityCount; ++i) { if (m_ActiveAbilities[i].ShouldBlockAbilityStart(itemAbility)) { return false; } } // The ability can start. Stop any currently active abilities that should not be started because the current ability has started. for (int i = m_ActiveItemAbilityCount - 1; i >= 0; --i) { if (itemAbility.ShouldStopActiveAbility(m_ActiveItemAbilities[i])) { TryStopAbility(m_ActiveItemAbilities[i], true); } } for (int i = m_ActiveAbilityCount - 1; i >= 0; --i) { if (itemAbility.ShouldStopActiveAbility(m_ActiveAbilities[i])) { TryStopAbility(m_ActiveAbilities[i], true); } } // The ability may already be active if the ability can receive multiple starts. Multiple starts are useful for item abilities that need to be active // over a period of time but can be updated with the input start type while active. A good example of this is the Toggle Equip Item ability. When // this ability starts it sets an Animator parameter to equip or unequip the item. The ability continues to run while equipping or unequipping the item // but it should trigger the reverse of the equip or unequip when another start is triggered. int index; if (!itemAbility.IsActive) { // Notify the ability that it will start. This method isn't as useful for ItemAbilities because the ability will always be immediately started after this, // but it is added for consistency with the ability system. if (!itemAbility.AbilityWillStart()) { return false; } // Insert in the active abilities array according to priority. index = m_ActiveItemAbilityCount; for (int i = 0; i < m_ActiveItemAbilityCount; ++i) { if (m_ActiveItemAbilities[i].Index > itemAbility.Index) { index = i; break; } } // Make space for the new item ability. for (int i = m_ActiveItemAbilityCount; i > index; --i) { m_ActiveItemAbilities[i] = m_ActiveItemAbilities[i - 1]; m_ActiveItemAbilities[i].ActiveIndex = i; } m_ActiveItemAbilities[index] = itemAbility; m_ActiveItemAbilityCount++; } else { // The ability is already active - start it again for a multiple start. index = itemAbility.ActiveIndex; } // Execute the event before the ability is started in case the ability is stopped within the start. EventHandler.ExecuteEvent(m_GameObject, "OnCharacterItemAbilityActive", itemAbility, true); if (m_OnItemAbilityActiveEvent != null) { m_OnItemAbilityActiveEvent.Invoke(itemAbility, true); } itemAbility.StartAbility(index); m_DirtyItemAbilityParameter = true; return true; } return false; } /// /// Sets the ability animator parameters to the ability with the highest priority. /// public void UpdateAbilityAnimatorParameters() { UpdateAbilityAnimatorParameters(false); } /// /// Sets the ability animator parameters to the ability with the highest priority. /// /// Should the parameters be updated immediately? public void UpdateAbilityAnimatorParameters(bool immediateUpdate) { if (m_AnimatorMonitor == null) { return; } // Wait to update until the proper time so the animator synchronizes properly. if (!immediateUpdate) { m_DirtyAbilityParameter = true; return; } m_DirtyAbilityParameter = false; int abilityIndex = 0, intData = 0; var floatData = 0f; bool concurrentAbilityIndex = false; bool setAbilityIndex = true, setStateIndex = true, setAbilityFloatData = true; for (int i = 0; i < m_ActiveAbilityCount; ++i) { if (setAbilityIndex && m_ActiveAbilities[i].AbilityIndexParameter != -1) { abilityIndex = m_ActiveAbilities[i].AbilityIndexParameter; concurrentAbilityIndex= m_ActiveAbilities[i].IsConcurrent; setAbilityIndex = false; } if (setStateIndex && m_ActiveAbilities[i].AbilityIntData != -1) { intData = m_ActiveAbilities[i].AbilityIntData; setStateIndex = false; } if (setAbilityFloatData && m_ActiveAbilities[i].AbilityFloatData != -1) { floatData = m_ActiveAbilities[i].AbilityFloatData; setAbilityFloatData = false; } } // A negative ability index indicates that the move towards ability is active. if (m_MoveTowardsAbility != null && m_MoveTowardsAbility.IsActive && !concurrentAbilityIndex) { abilityIndex *= -1; } m_AnimatorMonitor.SetAbilityIndexParameter(abilityIndex); m_AnimatorMonitor.SetAbilityIntDataParameter(intData); m_AnimatorMonitor.SetAbilityFloatDataParameter(floatData, m_TimeScale); } /// /// Sets the ability animator parameters to the ability with the highest priority. /// public void UpdateItemAbilityAnimatorParameters() { UpdateItemAbilityAnimatorParameters(false); } /// /// Sets the item animator parameters to the item ability with the highest priority. /// /// Should the parameters be updated immediately? public void UpdateItemAbilityAnimatorParameters(bool immediateUpdate) { if (m_AnimatorMonitor == null || m_ItemSlotStateIndex == null) { return; } // Wait to update until the proper time so the animator synchronizes properly. if (!immediateUpdate) { m_DirtyItemAbilityParameter = true; return; } m_DirtyItemAbilityParameter = false; // Reset the values. for (int i = 0; i < m_ItemSlotStateIndex.Length; ++i) { m_ItemSlotStateIndex[i] = -1; m_ItemSlotSubstateIndex[i] = -1; } // The value can only be assigned if it hasn't already been assigned. int value; for (int i = 0; i < m_ActiveItemAbilityCount; ++i) { for (int j = 0; j < m_ItemSlotSubstateIndex.Length; ++j) { if (m_ItemSlotStateIndex[j] == -1 && (value = m_ActiveItemAbilities[i].GetItemStateIndex(j)) != -1) { m_ItemSlotStateIndex[j] = value; } if (m_ItemSlotSubstateIndex[j] == -1 && (value = m_ActiveItemAbilities[i].GetItemSubstateIndex(j)) != -1) { m_ItemSlotSubstateIndex[j] = value; } } } // Assign the values to the animator. for (int i = 0; i < m_ItemSlotStateIndex.Length; ++i) { if (m_ItemSlotStateIndex[i] == -1) { m_ItemSlotStateIndex[i] = 0; } if (m_ItemSlotSubstateIndex[i] == -1) { m_ItemSlotSubstateIndex[i] = 0; } m_AnimatorMonitor.SetItemStateIndexParameter(i, m_ItemSlotStateIndex[i]); m_AnimatorMonitor.SetItemSubstateIndexParameter(i, m_ItemSlotSubstateIndex[i]); } m_AnimatorMonitor.SetAimingParameter(m_Aiming); } /// /// Tries to stop the specified ability. /// /// The ability to try to stop. /// True if the ability was stopped. public bool TryStopAbility(Ability ability) { return TryStopAbility(ability, false); } /// /// Tries to stop the specified ability. /// /// The ability to try to stop. /// Should the ability be force stopped? public bool TryStopAbility(Ability ability, bool force) { // The ability can't be stopped if it isn't active. if (!ability.IsActive) { return false; } ability.WillTryStopAbility(); // CanStopAbility and CanForceStopAbility can prevent the ability from stopping. if ((!force && !ability.CanStopAbility()) || (force && !ability.CanForceStopAbility())) { return false; } // Update the active ability array by removing the stopped ability. if (ability is ItemAbility) { for (int i = ability.ActiveIndex; i < m_ActiveItemAbilityCount - 1; ++i) { m_ActiveItemAbilities[i] = m_ActiveItemAbilities[i + 1]; m_ActiveItemAbilities[i].ActiveIndex = i; } m_ActiveItemAbilityCount--; m_ActiveItemAbilities[m_ActiveItemAbilityCount] = null; ability.StopAbility(force, true); // Let others know that the ability has stopped. var itemAbility = ability as ItemAbility; EventHandler.ExecuteEvent(m_GameObject, "OnCharacterItemAbilityActive", itemAbility, false); if (m_OnItemAbilityActiveEvent != null) { m_OnItemAbilityActiveEvent.Invoke(itemAbility, false); } m_DirtyItemAbilityParameter = true; } else { for (int i = ability.ActiveIndex; i < m_ActiveAbilityCount - 1; ++i) { m_ActiveAbilities[i] = m_ActiveAbilities[i + 1]; m_ActiveAbilities[i].ActiveIndex = i; } m_ActiveAbilityCount--; m_ActiveAbilities[m_ActiveAbilityCount] = null; ability.StopAbility(force, true); // Let others know that the ability has stopped. EventHandler.ExecuteEvent(m_GameObject, "OnCharacterAbilityActive", ability, false); if (m_OnAbilityActiveEvent != null) { m_OnAbilityActiveEvent.Invoke(ability, false); } m_DirtyAbilityParameter = true; // After the ability has stopped it may need to equip the unequipped items again. if (!(ability is ItemEquipVerifier) && !(ability is MoveTowards) && m_ItemEquipVerifierAbility != null) { m_ItemEquipVerifierAbility.TryToggleItem(ability, false); } } // If the AnimatorMonitor is enabled then the character is dead and the animator parameters should be updated immediately. if (m_AnimatorMonitor != null && m_AnimatorMonitor.enabled) { UpdateDirtyAbilityAnimatorParameters(); } return true; } /// /// Returns the ability of type T. /// /// The type of ability to return. /// The ability of type T. Can be null. public T GetAbility() where T : Ability { return GetAbility(-1); } /// /// Returns the ability of type T with the specified index. /// /// The type of ability to return. /// The index of the ability. -1 will ignore the index. /// The ability of type T with the specified index. Can be null. public T GetAbility(int index) where T : Ability { var type = typeof(T); var allAbilities = (typeof(ItemAbility).IsAssignableFrom(type) ? m_ItemAbilities : m_Abilities); if (allAbilities != null) { for (int i = 0; i < allAbilities.Length; ++i) { if (type.IsInstanceOfType(allAbilities[i]) && (index == -1 || index == allAbilities[i].Index)) { return allAbilities[i] as T; } } } return null; } /// /// Returns the abilities of type T. /// /// The type of ability to return. /// The abilities of type T. Can be null. public T[] GetAbilities() where T : Ability { return GetAbilities(-1); } /// /// Returns the abilities of type T with the specified index. /// /// The type of ability to return. /// The index of the ability. -1 will ignore the index. /// The abilities of type T with the specified index. Can be null. public T[] GetAbilities(int index) where T : Ability { if (m_Abilities == null) { DeserializeAbilities(); } if (m_ItemAbilities == null) { DeserializeItemAbilities(); } var allAbilities = (typeof(ItemAbility).IsAssignableFrom(typeof(T)) ? m_ItemAbilities : m_Abilities); var count = 0; if (allAbilities != null) { // Determine the total number of abilities first so only one allocation is made. for (int i = 0; i < allAbilities.Length; ++i) { if (allAbilities[i] is T && (index == -1 || index == allAbilities[i].Index)) { count++; } } if (count > 0) { var abilities = new T[count]; count = 0; for (int i = 0; i < allAbilities.Length; ++i) { if (allAbilities[i] is T && (index == -1 || index == allAbilities[i].Index)) { abilities[count] = allAbilities[i] as T; count++; if (count == abilities.Length) { break; } } } return abilities; } } return null; } /// /// Returns the abilities of the specified type with the specified index. /// /// The type of ability. /// The abilities of the specified type with the specified index. Can be null. public Ability[] GetAbilities(System.Type type) { return GetAbilities(type, -1); } /// /// Returns the abilities of the specified type with the specified index. /// /// The type of ability. /// The index of the ability. -1 will ignore the index. /// The abilities of the specified type with the specified index. Can be null. public Ability[] GetAbilities(System.Type type, int index) { if (type == null) { return null; } if (m_Abilities == null) { DeserializeAbilities(); } if (m_ItemAbilities == null) { DeserializeItemAbilities(); } var allAbilities = (typeof(ItemAbility).IsAssignableFrom(type) ? m_ItemAbilities : m_Abilities); var count = 0; if (allAbilities != null) { // Determine the total number of abilities first so only one allocation is made. for (int i = 0; i < allAbilities.Length; ++i) { if (type.IsInstanceOfType(allAbilities[i]) && (index == -1 || index == allAbilities[i].Index)) { count++; } } if (count > 0) { var abilities = new Ability[count]; count = 0; for (int i = 0; i < allAbilities.Length; ++i) { if (type.IsInstanceOfType(allAbilities[i]) && (index == -1 || index == allAbilities[i].Index)) { abilities[count] = allAbilities[i]; count++; if (count == abilities.Length) { break; } } } return abilities; } } return null; } /// /// Is the ability of the specified type active? /// /// The type of ability. /// True if the ability is active. public bool IsAbilityTypeActive() where T : Ability { var isItemAbility = typeof(ItemAbility).IsAssignableFrom(typeof(T)); var activeAbilities = isItemAbility ? m_ActiveItemAbilities : m_ActiveAbilities; var count = isItemAbility ? m_ActiveItemAbilityCount : m_ActiveAbilityCount; if (activeAbilities != null) { for (int i = 0; i < count; ++i) { if (typeof(T).IsInstanceOfType(activeAbilities[i])) { return true; } } } return false; } /// /// Tries to start the specified effect. /// /// The effect to try to start. /// True if the effect was started. public bool TryStartEffect(Effect effect) { // The effect can't be started if it is already active, isn't enabled, or can't be started. if (effect.IsActive || !effect.Enabled || !effect.CanStartEffect()) { return false; } m_ActiveEffects[m_ActiveEffectCount] = effect; m_ActiveEffectCount++; effect.StartEffect(m_ActiveEffectCount); return true; } /// /// Tries to stop the specified effect. /// /// The effect to try to stop. /// True if the effect was stopped. public bool TryStopEffect(Effect effect) { // The effect can't be stopped if it isn't active. if (!effect.IsActive) { return false; } // Store the active index ahead of time because StopEffect will reset the value. var index = effect.ActiveIndex; effect.StopEffect(true); // Update the active effect array by removing the stopped ability. for (int i = index; i < m_ActiveEffectCount - 1; ++i) { m_ActiveEffects[i] = m_ActiveEffects[i + 1]; } m_ActiveEffectCount--; m_ActiveEffects[m_ActiveEffectCount] = null; return true; } /// /// Returns the effect of type T. /// /// The type of effect to return. /// The index of the ability. -1 will ignore the index. /// The effect of type T. Can be null. public T GetEffect() where T : Effect { return GetEffect(-1); } /// /// Returns the effect of type T at the specified index. /// /// The type of effect to return. /// The index of the ability. -1 will ignore the index. /// The effect of type T. Can be null. public T GetEffect(int index) where T : Effect { if (m_Effects == null) { DeserializeEffects(); } if (m_Effects != null) { var type = typeof(T); for (int i = 0; i < m_Effects.Length; ++i) { if (type.IsInstanceOfType(m_Effects[i]) && (index == -1 || index == m_Effects[i].Index)) { return m_Effects[i] as T; } } } return null; } /// /// Returns the effect of the specified type. /// /// The type of effect to retrieve. /// The effect of the specified type. Can be null. public Effect GetEffect(System.Type type) { return GetEffect(type, -1); } /// /// Returns the effect of the specified type at the specified index. /// /// The type of effect to retrieve. /// The index of the ability. -1 will ignore the index. /// The effect of the specified type. Can be null. public Effect GetEffect(System.Type type, int index) { if (type == null) { return null; } if (m_Effects == null) { DeserializeEffects(); } if (m_Effects != null) { for (int i = 0; i < m_Effects.Length; ++i) { if (type.IsInstanceOfType(m_Effects[i]) && (index == -1 || index == m_Effects[i].Index)) { return m_Effects[i]; } } } return null; } /// /// Callback from the animator when root motion has updated. /// protected override void OnAnimatorMove() { // If using root motion rotation then the delta position should be retrieved from the animation. If it not being retrieved then the abilities // have an option to change the delta position based on the AnimatorMotion ScriptableObject. if (UsingRootMotionPosition) { m_AnimatorDeltaPosition += m_Animator.deltaPosition; } else { for (int i = 0; i < m_ActiveAbilityCount; ++i) { m_ActiveAbilities[i].OnAnimatorMove(true); } for (int i = 0; i < m_ActiveItemAbilityCount; ++i) { m_ActiveItemAbilities[i].OnAnimatorMove(true); } } // If using root motion rotation then the delta rotation should be retrieved from the animation. If it not being retrieved then the abilities // have an option to change the delta rotation based on the AnimatorMotion ScriptableObject. if (UsingRootMotionRotation) { m_AnimatorDeltaRotation *= m_Animator.deltaRotation; } else { for (int i = 0; i < m_ActiveAbilityCount; ++i) { m_ActiveAbilities[i].OnAnimatorMove(false); } for (int i = 0; i < m_ActiveItemAbilityCount; ++i) { m_ActiveItemAbilities[i].OnAnimatorMove(false); } } base.OnAnimatorMove(); } /// /// The character perspective between first and third person has changed. /// /// Is the character in a first person perspective? private void OnChangePerspectives(bool firstPersonPerspective) { m_FirstPersonPerspective = firstPersonPerspective; if (firstPersonPerspective) { if (!string.IsNullOrEmpty(m_ThirdPersonStateName)) { StateManager.SetState(m_GameObject, m_ThirdPersonStateName, false); } if (!string.IsNullOrEmpty(m_FirstPersonStateName)) { StateManager.SetState(m_GameObject, m_FirstPersonStateName, true); } if (!string.IsNullOrEmpty(m_FirstPersonMovementTypeFullName)) { SetMovementType(m_FirstPersonMovementTypeFullName); } } else { if (!string.IsNullOrEmpty(m_FirstPersonStateName)) { StateManager.SetState(m_GameObject, m_FirstPersonStateName, false); } if (!string.IsNullOrEmpty(m_ThirdPersonStateName)) { StateManager.SetState(m_GameObject, m_ThirdPersonStateName, true); } if (!string.IsNullOrEmpty(m_ThirdPersonMovementTypeFullName)) { SetMovementType(m_ThirdPersonMovementTypeFullName); } } if (m_MovementType == null) { SetMovementType(m_MovementTypeFullName); } } #if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER /// /// Pushes the target Rigidbody in the specified direction. /// /// The Rigidbody to push. /// The direction that the character is moving. /// The point at which to apply the push force. /// The radius of the pushing collider. /// Was the rigidbody pushed? protected override bool PushRigidbody(Rigidbody targetRigidbody, Vector3 moveDirection, Vector3 point, float radius) { var pushed = base.PushRigidbody(targetRigidbody, moveDirection, point, radius); if (pushed && m_NetworkInfo != null) { m_NetworkCharacter.PushRigidbody(targetRigidbody, (moveDirection / Time.deltaTime) * (m_Mass / targetRigidbody.mass) * 0.01f, point); } return pushed; } #endif /// /// Sets the rotation of the character. /// /// The rotation to set. public override void SetRotation(Quaternion rotation) { SetRotation(rotation, true); } /// /// Sets the rotation of the character. /// /// The rotation to set. /// Should the animator be snapped into position? public void SetRotation(Quaternion rotation, bool snapAnimator) { // If the character isn't active then only the transform needs to be set. if (m_GameObject == null) { transform.rotation = rotation; return; } base.SetRotation(rotation); if (snapAnimator) { StopAllAbilities(false); } // If the index is -1 then the character isn't registered with the Kinematic Object Manager. if (m_KinematicObjectIndex != -1) { KinematicObjectManager.SetCharacterRotation(m_KinematicObjectIndex, rotation); EventHandler.ExecuteEvent(m_GameObject, "OnCharacterImmediateTransformChange", snapAnimator); } #if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER if (m_NetworkInfo != null && m_NetworkInfo.IsLocalPlayer()) { m_NetworkCharacter.SetRotation(rotation, snapAnimator); } #endif } /// /// Sets the position of the character. /// /// The position to set. public override void SetPosition(Vector3 position) { SetPosition(position, true); } /// /// Sets the position of the character. /// /// The position to set. /// Should the animator be snapped into position? public void SetPosition(Vector3 position, bool snapAnimator) { // If the character isn't active then only the transform needs to be set. if (m_GameObject == null) { transform.position = position; return; } m_MaxHeight = float.NegativeInfinity; m_MaxHeightPosition = position; base.SetPosition(position); if (snapAnimator) { StopAllAbilities(false); } // If the index is -1 then the character isn't registered with the Kinematic Object Manager. if (m_KinematicObjectIndex != -1) { KinematicObjectManager.SetCharacterPosition(m_KinematicObjectIndex, position); EventHandler.ExecuteEvent(m_GameObject, "OnCharacterImmediateTransformChange", snapAnimator); } #if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER if (m_NetworkInfo != null && m_NetworkInfo.IsLocalPlayer()) { m_NetworkCharacter.SetPosition(position, snapAnimator); } #endif } /// /// Resets the rotation and position to their default values. /// public override void ResetRotationPosition() { if (m_GameObject == null) { return; } base.ResetRotationPosition(); Moving = false; m_MaxHeight = float.NegativeInfinity; m_MaxHeightPosition = m_Transform.position; m_AnimatorDeltaPosition = Vector3.zero; m_AnimatorDeltaRotation = Quaternion.identity; // If the index is -1 then the character isn't registered with the Kinematic Object Manager. if (m_KinematicObjectIndex == -1) { return; } KinematicObjectManager.SetCharacterRotation(m_KinematicObjectIndex, m_Transform.rotation); KinematicObjectManager.SetCharacterPosition(m_KinematicObjectIndex, m_Transform.position); #if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER if (m_NetworkInfo != null && m_NetworkInfo.IsLocalPlayer()) { m_NetworkCharacter.ResetRotationPosition(); } #endif } /// /// Sets the position and rotation of the character. /// /// The position to set. /// The rotation to set. public void SetPositionAndRotation(Vector3 position, Quaternion rotation) { SetPositionAndRotation(position, rotation, true, true); } /// /// Sets the position and rotation of the character. /// /// The position to set. /// The rotation to set. /// Should the animator be snapped into position? public void SetPositionAndRotation(Vector3 position, Quaternion rotation, bool snapAnimator) { SetPositionAndRotation(position, rotation, snapAnimator, true); } /// /// Sets the position and rotation of the character. /// /// The position to set. /// The rotation to set. /// Should the animator be snapped into position? /// Should all abilities be stopped? public void SetPositionAndRotation(Vector3 position, Quaternion rotation, bool snapAnimator, bool stopAllAbilities) { if (m_GameObject == null) { return; } if (stopAllAbilities) { StopAllAbilities(false); } m_MaxHeight = float.NegativeInfinity; m_MaxHeightPosition = position; m_AnimatorDeltaPosition = Vector3.zero; m_AnimatorDeltaRotation = Quaternion.identity; base.SetRotation(rotation); base.SetPosition(position); // If the index is -1 then the character isn't registered with the Kinematic Object Manager. if (m_KinematicObjectIndex != -1) { KinematicObjectManager.SetCharacterRotation(m_KinematicObjectIndex, rotation); KinematicObjectManager.SetCharacterPosition(m_KinematicObjectIndex, position); EventHandler.ExecuteEvent(m_GameObject, "OnCharacterImmediateTransformChange", snapAnimator); } #if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER if (m_NetworkInfo != null && m_NetworkInfo.IsLocalPlayer()) { m_NetworkCharacter.SetPositionAndRotation(position, rotation, snapAnimator); } #endif } /// /// Activates or deactivates the character. /// /// Is the character active? public void SetActive(bool active) { SetActive(active, false); } /// /// Activates or deactivates the character. /// /// Is the character active? /// Should the OnShowUI event be executed? public void SetActive(bool active, bool uiEvent) { if (m_GameObject == null) { m_GameObject = gameObject; } EventHandler.ExecuteEvent(m_GameObject, "OnCharacterActivate", active); m_GameObject.SetActive(active); if (active) { // Do a pass on trying to start any abilities in case they should be started immediately when activated. UpdateAbilities(m_Abilities); UpdateAbilities(m_ItemAbilities); UpdateDirtyAbilityAnimatorParameters(); EventHandler.ExecuteEvent(m_GameObject, "OnCharacterImmediateTransformChange", true); } if (uiEvent) { EventHandler.ExecuteEvent(m_GameObject, "OnShowUI", active); } #if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER if (m_NetworkInfo != null && m_NetworkInfo.IsLocalPlayer()) { m_NetworkCharacter.SetActive(active, uiEvent); } #endif } /// /// Casts a ray using in the specified direction. If the character has multiple colliders added then a ray will be cast from each collider. /// A CapsuleCast or SphereCast is used depending on the type of collider that has been added. /// /// The direction to perform the cast. /// The layers to perform the cast on. /// The offset of the cast. /// The hit RaycastHit. /// Did the cast hit an object? public bool SingleCast(Vector3 direction, Vector3 offset, int layers, ref RaycastHit result) { for (int i = 0; i < m_ColliderCount; ++i) { // Determine if the collider would intersect any objects. if (m_Colliders[i] is CapsuleCollider) { Vector3 startEndCap, endEndCap; var capsuleCollider = m_Colliders[i] as CapsuleCollider; MathUtility.CapsuleColliderEndCaps(capsuleCollider, capsuleCollider.transform.position, capsuleCollider.transform.rotation, out startEndCap, out endEndCap); var radius = capsuleCollider.radius * MathUtility.ColliderRadiusMultiplier(capsuleCollider) - ColliderSpacing; if (Physics.CapsuleCast(startEndCap + offset, endEndCap + offset, radius, direction.normalized, out result, direction.magnitude + ColliderSpacing, layers, QueryTriggerInteraction.Ignore)) { return true; } } else { // SphereCollider. var sphereCollider = m_Colliders[i] as SphereCollider; var radius = sphereCollider.radius * MathUtility.ColliderRadiusMultiplier(sphereCollider) - ColliderSpacing; if (Physics.SphereCast(sphereCollider.transform.TransformPoint(sphereCollider.center) + offset, radius, direction.normalized, out result, direction.magnitude + ColliderSpacing, layers, QueryTriggerInteraction.Ignore)) { return true; } } } return false; } /// /// Casts a ray using in the specified direction. If the character has multiple colliders added then a ray will be cast from each collider. /// A CapsuleCast or SphereCast is used depending on the type of collider that has been added. The result is stored in the m_CombinedRaycastHits array. /// /// The direction to perform the cast. /// Any offset to apply to the cast. /// The found raycast hits. /// A mapping between the raycast hit and collider index. /// The number of objects hit from the cast. public int Cast(Vector3 direction, Vector3 offset, ref RaycastHit[] combinedRaycastHits, ref Dictionary colliderIndexMap) { if (m_ColliderCount > 1) { if (combinedRaycastHits == null) { combinedRaycastHits = new RaycastHit[m_ColliderCount * m_RaycastHits.Length]; colliderIndexMap = new Dictionary(); } // Clear the index map to start it off fresh. colliderIndexMap.Clear(); } var hitCount = 0; for (int i = 0; i < m_ColliderCount; ++i) { int localHitCount; // Determine if the collider would intersect any objects. if (m_Colliders[i] is CapsuleCollider) { Vector3 startEndCap, endEndCap; var capsuleCollider = m_Colliders[i] as CapsuleCollider; MathUtility.CapsuleColliderEndCaps(capsuleCollider, capsuleCollider.transform.position + offset, capsuleCollider.transform.rotation, out startEndCap, out endEndCap); var radius = capsuleCollider.radius * MathUtility.ColliderRadiusMultiplier(capsuleCollider) - ColliderSpacing; localHitCount = Physics.CapsuleCastNonAlloc(startEndCap, endEndCap, radius, direction.normalized, m_RaycastHits, direction.magnitude + ColliderSpacing, m_CharacterLayerManager.SolidObjectLayers, QueryTriggerInteraction.Ignore); } else { // SphereCollider. var sphereCollider = m_Colliders[i] as SphereCollider; var radius = sphereCollider.radius * MathUtility.ColliderRadiusMultiplier(sphereCollider) - ColliderSpacing; localHitCount = Physics.SphereCastNonAlloc(sphereCollider.transform.TransformPoint(sphereCollider.center) + offset, radius, direction.normalized, m_RaycastHits, direction.magnitude + ColliderSpacing, m_CharacterLayerManager.SolidObjectLayers, QueryTriggerInteraction.Ignore); } if (localHitCount > 0) { // The mapping needs to be saved if there are multiple colliders. if (m_ColliderCount > 1) { int validHitCount = 0; for (int j = 0; j < localHitCount; ++j) { if (colliderIndexMap.ContainsKey(m_RaycastHits[j])) { continue; } // Ensure the array is large enough. if (hitCount + j >= combinedRaycastHits.Length) { Debug.LogWarning("Warning: The maximum number of collisions has been reached. Consider increasing the CharacterLocomotion MaxCollisionCount value."); continue; } colliderIndexMap.Add(m_RaycastHits[j], i); combinedRaycastHits[hitCount + j] = m_RaycastHits[j]; validHitCount += 1; } hitCount += validHitCount; } else { combinedRaycastHits = m_RaycastHits; hitCount += localHitCount; } } } return hitCount; } /// /// Returns the number of colliders that are overlapping the character's collider. /// /// The offset to apply to the character's collider position. /// The number of objects which overlap the collider. public int OverlapCount(Vector3 offset) { var count = 0; for (int i = 0; i < m_ColliderCount; ++i) { count += OverlapCount(m_Colliders[i], offset); } return count; } /// /// The character has entered a trigger. /// /// The trigger collider that the character entered. private void OnTriggerEnter(Collider other) { // Forward the enter to the abilities. if (m_Abilities != null) { for (int i = 0; i < m_Abilities.Length; ++i) { if (!m_Abilities[i].Enabled) { continue; } m_Abilities[i].OnTriggerEnter(other); } } if (m_ItemAbilities != null) { for (int i = 0; i < m_ItemAbilities.Length; ++i) { if (!m_ItemAbilities[i].Enabled) { continue; } m_ItemAbilities[i].OnTriggerEnter(other); } } } /// /// The character has exited a trigger. /// /// The trigger collider that the character exited. private void OnTriggerExit(Collider other) { // Forward the exit to the abilities. if (m_Abilities != null) { for (int i = 0; i < m_Abilities.Length; ++i) { if (!m_Abilities[i].Enabled) { continue; } m_Abilities[i].OnTriggerExit(other); } } if (m_ItemAbilities != null) { for (int i = 0; i < m_ItemAbilities.Length; ++i) { if (!m_ItemAbilities[i].Enabled) { continue; } m_ItemAbilities[i].OnTriggerExit(other); } } } /// /// The character has started or stopped aiming /// /// Has the character started to aim? private void OnAiming(bool aiming) { m_Aiming = aiming; } /// /// The character has died. /// /// The position of the force. /// The amount of force which killed the character. /// The GameObject that killed the character. private void OnDeath(Vector3 position, Vector3 force, GameObject attacker) { m_Alive = false; // All of the abilities should stop. StopAllAbilities(true); // The animator values should reset. m_YawAngle = 0; m_InputVector = Vector3.zero; Moving = false; // Remote networked characters will not be registered with the KinematicObjectManager. if (m_KinematicObjectIndex == -1) { return; } // The character is no longer moving. KinematicObjectManager.SetCharacterMovementInput(m_KinematicObjectIndex, 0, 0); KinematicObjectManager.SetCharacterDeltaYawRotation(m_KinematicObjectIndex, 0); } /// /// Stops all of the active abilities. /// /// Are the abilities being stopped from death callback? private void StopAllAbilities(bool fromDeath) { for (int i = m_ActiveAbilityCount - 1; i >= 0; --i) { if (!fromDeath || !m_ActiveAbilities[i].CanStayActivatedOnDeath) { TryStopAbility(m_ActiveAbilities[i], true); } } for (int i = m_ActiveItemAbilityCount - 1; i >= 0; --i) { if (!fromDeath || !m_ActiveItemAbilities[i].CanStayActivatedOnDeath) { TryStopAbility(m_ActiveItemAbilities[i], true); } } } /// /// The character has respawned. /// private void OnRespawn() { m_Alive = true; ResetRotationPosition(); // Do a pass on trying to start any abilities and items to ensure they are in sync. UpdateAbilities(m_Abilities); UpdateAbilities(m_ItemAbilities); if (m_AnimatorMonitor != null) { if (m_LookSource != null) { m_AnimatorMonitor.SetPitchParameter(m_LookSource.Pitch, 1, 0); } m_AnimatorMonitor.SetYawParameter(m_YawAngle, 1, 0); m_AnimatorMonitor.SetHorizontalMovementParameter(m_InputVector.x, 1, 0); m_AnimatorMonitor.SetForwardMovementParameter(m_InputVector.y, 1, 0); m_AnimatorMonitor.SetMovingParameter(m_Moving); UpdateDirtyAbilityAnimatorParameters(); } } /// /// The character's position or rotation has been teleported. /// /// Should the animator be snapped? private void OnImmediateTransformChange(bool snapAnimator) { // Do a pass on trying to start any abilities and items to ensure they are in sync. UpdateAbilities(m_Abilities); UpdateAbilities(m_ItemAbilities); UpdateAbilityAnimatorParameters(true); UpdateItemAbilityAnimatorParameters(true); // Snap the animator after the abilities have updated. if (snapAnimator) { EventHandler.ExecuteEvent(m_GameObject, "OnCharacterSnapAnimator"); } } /// /// The Animator has snapped into position. /// private void AnimatorSnapped() { ResetRotationPosition(); } /// /// The character has been disabled. /// private void OnDisable() { // The KinematicObjectManager is responsible for calling the move method. KinematicObjectManager.UnregisterCharacter(m_KinematicObjectIndex); m_KinematicObjectIndex = -1; } /// /// The GameObject has been destroyed. /// private void OnDestroy() { EventHandler.UnregisterEvent(m_GameObject, "OnCharacterAttachLookSource", OnAttachLookSource); EventHandler.UnregisterEvent(gameObject, "OnCharacterChangePerspectives", OnChangePerspectives); EventHandler.UnregisterEvent(m_GameObject, "OnAimAbilityAim", OnAiming); EventHandler.UnregisterEvent(m_GameObject, "OnDeath", OnDeath); EventHandler.UnregisterEvent(m_GameObject, "OnRespawn", OnRespawn); EventHandler.UnregisterEvent(m_GameObject, "OnCharacterImmediateTransformChange", OnImmediateTransformChange); EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorSnapped", AnimatorSnapped); // The current movement type is no longer active when the object is destroyed. if (m_MovementType != null) { m_MovementType.ChangeMovementType(false); } // Call OnDestroy to notify all of the abilities and effects that the GameObject has been destroyed. if (m_Abilities != null) { for (int i = 0; i < m_Abilities.Length; ++i) { m_Abilities[i].OnDestroy(); } } if (m_ItemAbilities != null) { for (int i = 0; i < m_ItemAbilities.Length; ++i) { m_ItemAbilities[i].OnDestroy(); } } if (m_Effects != null) { for (int i = 0; i < m_Effects.Length; ++i) { m_Effects[i].OnDestroy(); } } } } }