/// --------------------------------------------- /// Ultimate Character Controller /// Copyright (c) Opsive. All Rights Reserved. /// https://www.opsive.com /// --------------------------------------------- namespace Opsive.UltimateCharacterController.Utility.Builders { using Opsive.UltimateCharacterController.Character; using Opsive.UltimateCharacterController.Character.Abilities; using Opsive.UltimateCharacterController.Character.Identifiers; using Opsive.UltimateCharacterController.Character.MovementTypes; using Opsive.UltimateCharacterController.Game; using Opsive.UltimateCharacterController.Inventory; using System.Collections.Generic; using UnityEngine; /// /// Allows for the Ultimate Character Controller components to be added/removed at runtime. /// public static class CharacterBuilder { private const string c_MovingStateGUID = "527d884c54f1a4b4a82fed73411305a8"; /// /// Adds the essnetial components to the specified character and sets the MovementType. /// /// The GameObject of the character. /// Should the animator components be added? /// A reference to the animator controller. /// The first person MovementType that should be added. /// The third person MovementType that should be added. /// Should the character start in a first person perspective? /// The objects that should be hidden in first person view. /// The shadow caster material applied to the invisible first person objects. /// Is the character an AI agent? public static void BuildCharacter(GameObject character, bool addAnimator, RuntimeAnimatorController animatorController, string firstPersonMovementType, string thirdPersonMovementType, bool startFirstPersonPerspective, GameObject[] firstPersonHiddenObjects, Material invisibleShadowCasterMaterial, bool aiAgent) { // Determine if the ThirdPersonObject component should be added or the invisible object renderer should be directly set to the invisible shadow caster. if (firstPersonHiddenObjects != null) { for (int i = 0; i < firstPersonHiddenObjects.Length; ++i) { if (firstPersonHiddenObjects[i] == null) { continue; } if (string.IsNullOrEmpty(thirdPersonMovementType)) { var renderers = firstPersonHiddenObjects[i].GetComponents(); for (int j = 0; j < renderers.Length; ++j) { var materials = renderers[j].sharedMaterials; for (int k = 0; k < materials.Length; ++k) { materials[k] = invisibleShadowCasterMaterial; } renderers[j].sharedMaterials = materials; } } firstPersonHiddenObjects[i].AddComponent(); } } AddEssentials(character, addAnimator, animatorController, !string.IsNullOrEmpty(firstPersonMovementType) && !string.IsNullOrEmpty(thirdPersonMovementType), invisibleShadowCasterMaterial, aiAgent); // The last added MovementType is starting movement type. if (startFirstPersonPerspective) { if (!string.IsNullOrEmpty(thirdPersonMovementType)) { AddMovementType(character, thirdPersonMovementType); } if (!string.IsNullOrEmpty(firstPersonMovementType)) { AddMovementType(character, firstPersonMovementType); } } else { if (!string.IsNullOrEmpty(firstPersonMovementType)) { AddMovementType(character, firstPersonMovementType); } if (!string.IsNullOrEmpty(thirdPersonMovementType)) { AddMovementType(character, thirdPersonMovementType); } } } /// /// Adds the Ultimate Character Controller essential components to the specified character. /// /// The character to add the components to. /// Should the animator components be added? /// A reference to the animator controller. /// Should the perspective monitor be added? /// The shadow caster material applied to the invisible first person objects. /// Is the character an AI agent? public static void AddEssentials(GameObject character, bool addAnimator, RuntimeAnimatorController animatorController, bool addPerspectiveMonitor, Material invisibleShadowCasterMaterial, bool aiAgent) { if (!aiAgent) { character.tag = "Player"; } character.layer = LayerManager.Character; if (character.GetComponent() == null) { character.AddComponent(); } var rigidbody = character.GetComponent(); if (rigidbody == null) { rigidbody = character.AddComponent(); } rigidbody.useGravity = false; rigidbody.isKinematic = true; rigidbody.constraints = RigidbodyConstraints.FreezeAll; GameObject collider = null; var colliderIdentifier = character.GetComponent(); if (colliderIdentifier == null) { var colliders = new GameObject("Colliders"); colliders.layer = LayerManager.Character; colliders.transform.SetParentOrigin(character.transform); collider = new GameObject("CapsuleCollider"); collider.layer = LayerManager.Character; collider.transform.SetParentOrigin(colliders.transform); var capsuleCollider = collider.AddComponent(); capsuleCollider.center = new Vector3(0, 1, 0); capsuleCollider.height = 2; capsuleCollider.radius = 0.4f; } if (addAnimator) { AddAnimator(character, animatorController, aiAgent); } if (character.GetComponent() == null) { #if UNITY_EDITOR var characterLocomotion = character.AddComponent(); if (!Application.isPlaying) { // The Moving state should automatically be added. var presetPath = UnityEditor.AssetDatabase.GUIDToAssetPath(c_MovingStateGUID); if (!string.IsNullOrEmpty(presetPath)) { var preset = UnityEditor.AssetDatabase.LoadAssetAtPath(presetPath, typeof(StateSystem.PersistablePreset)) as StateSystem.PersistablePreset; if (preset != null) { var states = characterLocomotion.States; System.Array.Resize(ref states, states.Length + 1); // Default must always be at the end. states[states.Length - 1] = states[0]; states[states.Length - 2] = new StateSystem.State("Moving", preset, null); characterLocomotion.States = states; } } } #else character.AddComponent(); #endif } if (collider != null) { var positioner = collider.AddComponent(); positioner.FirstEndCapTarget = character.transform; var animator = character.GetComponent(); if (animator != null) { // The CapsuleColliderPositioner should follow the character's movements. var head = animator.GetBoneTransform(HumanBodyBones.Head); if (head != null) { positioner.SecondEndCapTarget = head; positioner.RotationBone = positioner.PositionBone = animator.GetBoneTransform(HumanBodyBones.Hips); } } } if (aiAgent) { AddAIAgent(character); } else { AddUnityInput(character); if (character.GetComponent() == null) { character.AddComponent(); } } #if THIRD_PERSON_CONTROLLER if (addPerspectiveMonitor && character.GetComponent() == null) { var perspectiveMonitor = character.AddComponent(); if (perspectiveMonitor.InvisibleMaterial == null) { perspectiveMonitor.InvisibleMaterial = invisibleShadowCasterMaterial; } } #endif // All of the child GameObjects should be set to the SubCharacter layer to prevent any added-colliders from interferring with the locomotion. SetRecursiveLayer(character, LayerManager.SubCharacter, LayerManager.Character); } /// /// Adds the animator with the specified controller to the character. /// /// The character to add the animator to. /// A reference to the animator controller. /// Is the character an AI agent? public static void AddAnimator(GameObject character, RuntimeAnimatorController animatorController, bool aiAgent) { Animator animator; if ((animator = character.GetComponent()) == null) { animator = character.AddComponent(); } animator.runtimeAnimatorController = animatorController; if (!aiAgent) { animator.cullingMode = AnimatorCullingMode.AlwaysAnimate; } if (character.GetComponent() == null) { character.AddComponent(); } } /// /// Removes the animator from the character. /// /// The character to remove the animator from. public static void RemoveAnimator(GameObject character) { var animator = character.GetComponent(); if (animator != null) { GameObject.DestroyImmediate(animator, true); } var animatorMonitor = character.GetComponent(); if (animatorMonitor != null) { GameObject.DestroyImmediate(animatorMonitor, true); } } /// /// Sets the GameObject to the specified layer. Will recursively set the children unless the child contains a component that shouldn't be set. /// /// The GameObject to set. /// The layer to set the GameObject to. /// The layer of the character. GameObjects with this layer will not be set to the specified layer. private static void SetRecursiveLayer(GameObject gameObject, int layer, int characterLayer) { var children = gameObject.transform.childCount; for (int i = 0; i < gameObject.transform.childCount; ++i) { var child = gameObject.transform.GetChild(i); // Do not set the layer if the child is already set to the Character layer or contains the item identifier components. if (child.gameObject.layer == characterLayer || child.GetComponent() != null) { continue; } #if FIRST_PERSON_CONTROLLER // First person objects do not need to be set. if (child.GetComponent() != null) { continue; } #endif // Set the layer. child.gameObject.layer = layer; SetRecursiveLayer(child.gameObject, layer, characterLayer); } } /// /// Removes the Ultimate Character Controller essential components from the specified character. /// /// The character to remove the components from. public static void RemoveEssentials(GameObject character) { var rigidbody = character.GetComponent(); if (rigidbody != null) { GameObject.DestroyImmediate(rigidbody, true); } var collider = character.GetComponent(); if (collider != null) { GameObject.DestroyImmediate(collider, true); } var ultimateCharacterLocomotion = character.GetComponent(); if (ultimateCharacterLocomotion != null) { GameObject.DestroyImmediate(ultimateCharacterLocomotion, true); } var ultimateCharacterLocomotionHandler = character.GetComponent(); if (ultimateCharacterLocomotionHandler != null) { GameObject.DestroyImmediate(ultimateCharacterLocomotionHandler, true); } var localLookSource = character.GetComponent(); if (localLookSource != null) { GameObject.DestroyImmediate(localLookSource, true); } var layerManager = character.GetComponent(); if (layerManager != null) { GameObject.DestroyImmediate(layerManager, true); } #if THIRD_PERSON_CONTROLLER var perspectiveMonitor = character.GetComponent(); if (perspectiveMonitor != null) { GameObject.DestroyImmediate(perspectiveMonitor, true); } #endif } /// /// Adds the specified MovementType to the character. /// /// The character to add the MovementType to. /// The MovementType to add. public static void AddMovementType(GameObject character, string movementType) { var ultimateCharacterLocomotion = character.GetComponent(); if (ultimateCharacterLocomotion != null) { // Don't allow duplicate MovementTypes. var type = System.Type.GetType(movementType); ultimateCharacterLocomotion.DeserializeMovementTypes(); var movementTypes = ultimateCharacterLocomotion.MovementTypes; var add = true; if (movementTypes != null) { for (int i = 0; i < movementTypes.Length; ++i) { if (movementTypes[i].GetType() == type) { add = false; } } } if (add) { var movementTypesList = new List(); if (movementTypes != null) { movementTypesList.AddRange(movementTypes); } var movementTypeObj = System.Activator.CreateInstance(type) as MovementType; movementTypesList.Add(movementTypeObj); ultimateCharacterLocomotion.MovementTypes = movementTypesList.ToArray(); ultimateCharacterLocomotion.MovementTypeData = Shared.Utility.Serialization.Serialize(movementTypesList); // If the character has already been initialized then the movement type should be initialized. if (Application.isPlaying) { movementTypeObj.Initialize(ultimateCharacterLocomotion); movementTypeObj.Awake(); } } // Set the added movement type as the default. ultimateCharacterLocomotion.SetMovementType(type); } } /// /// Adds the non-essential Ultimate Character Controller components to the character. /// /// The character to add the components to. /// Is the character an AI agent? /// Should the item components be added? /// A reference to the ItemCollection component. /// Does the character support first person items? /// Should the health components be added? /// Should the CharacterIK component be added? /// Should the CharacterFootEffects component be added? /// Should the standard abilities be added? /// Should the NavMeshAgent component be added? public static void BuildCharacterComponents(GameObject character, bool aiAgent, bool addItems, ItemCollection itemCollection, bool firstPersonItems, bool addHealth, bool addUnityIK, bool addFootEffects, bool addStandardAbilities, bool addNavMeshAgent) { if (addItems) { AddItemSupport(character, itemCollection, aiAgent, firstPersonItems); } if (addHealth) { AddHealth(character); } if (addUnityIK) { AddUnityIK(character); } if (addFootEffects) { AddFootEffects(character); } if (addStandardAbilities) { // Add the Jump, Fall, Speed Change, and Height Change abilities. var characterLocomotion = character.GetComponent(); var jump = AbilityBuilder.AddAbility(characterLocomotion, typeof(Character.Abilities.Jump)); if (characterLocomotion.GetComponent() == null) { (jump as Jump).JumpEvent = new AnimationEventTrigger(false, 0); AbilityBuilder.SerializeAbilities(characterLocomotion); } AbilityBuilder.AddAbility(characterLocomotion, typeof(Character.Abilities.Fall)); AbilityBuilder.AddAbility(characterLocomotion, typeof(Character.Abilities.MoveTowards)); AbilityBuilder.AddAbility(characterLocomotion, typeof(Character.Abilities.SpeedChange)); AbilityBuilder.AddAbility(characterLocomotion, typeof(Character.Abilities.HeightChange)); // The abilities should not use an input related start type. if (aiAgent) { var abilities = characterLocomotion.GetAbilities(); for (int i = 0; i < abilities.Length; ++i) { if (abilities[i].StartType != Character.Abilities.Ability.AbilityStartType.Automatic && abilities[i].StartType != Character.Abilities.Ability.AbilityStartType.Manual) { abilities[i].StartType = Character.Abilities.Ability.AbilityStartType.Manual; } if (abilities[i].StopType != Character.Abilities.Ability.AbilityStopType.Automatic && abilities[i].StopType != Character.Abilities.Ability.AbilityStopType.Manual) { abilities[i].StopType = Character.Abilities.Ability.AbilityStopType.Manual; } if (abilities[i] is Character.Abilities.Items.Use) { abilities[i].StopType = Character.Abilities.Ability.AbilityStopType.Manual; } } AbilityBuilder.SerializeAbilities(characterLocomotion); } } if (addNavMeshAgent) { var characterLocomotion = character.GetComponent(); var abilities = characterLocomotion.Abilities; var index = abilities != null ? abilities.Length : 0; if (abilities != null) { for (int i = 0; i < abilities.Length; ++i) { if (abilities[i] is Character.Abilities.SpeedChange) { index = i; break; } } } // The ability should be positioned before the SpeedChange ability. AbilityBuilder.AddAbility(characterLocomotion, typeof(Character.Abilities.AI.NavMeshAgentMovement), index); var navMeshAgent = character.GetComponent(); if (navMeshAgent != null) { navMeshAgent.stoppingDistance = 0.1f; } } if (addItems) { // Add the Equip, Aim, Use, and Reload item abilities. var characterLocomotion = character.GetComponent(); #if ULTIMATE_CHARACTER_CONTROLLER_SHOOTER AbilityBuilder.AddItemAbility(characterLocomotion, typeof(Character.Abilities.Items.Reload)); #endif AbilityBuilder.AddItemAbility(characterLocomotion, typeof(Character.Abilities.Items.Use)); AbilityBuilder.AddItemAbility(characterLocomotion, typeof(Character.Abilities.Items.EquipUnequip)); AbilityBuilder.AddItemAbility(characterLocomotion, typeof(Character.Abilities.Items.ToggleEquip)); AbilityBuilder.AddItemAbility(characterLocomotion, typeof(Character.Abilities.Items.EquipNext)); AbilityBuilder.AddItemAbility(characterLocomotion, typeof(Character.Abilities.Items.EquipPrevious)); AbilityBuilder.AddItemAbility(characterLocomotion, typeof(Character.Abilities.Items.EquipScroll)); AbilityBuilder.AddItemAbility(characterLocomotion, typeof(Character.Abilities.Items.Aim)); // The buttons should not use an input related start type. if (aiAgent) { var itemAbilities = characterLocomotion.GetAbilities(); for (int i = 0; i < itemAbilities.Length; ++i) { if (itemAbilities[i].StartType != Character.Abilities.Ability.AbilityStartType.Automatic && itemAbilities[i].StartType != Character.Abilities.Ability.AbilityStartType.Manual) { itemAbilities[i].StartType = Character.Abilities.Ability.AbilityStartType.Manual; } if (itemAbilities[i].StopType != Character.Abilities.Ability.AbilityStopType.Automatic && itemAbilities[i].StopType != Character.Abilities.Ability.AbilityStopType.Manual) { itemAbilities[i].StopType = Character.Abilities.Ability.AbilityStopType.Manual; } } AbilityBuilder.SerializeItemAbilities(characterLocomotion); } // The ItemEquipVerifier needs to be added after the item abilities. AbilityBuilder.AddAbility(characterLocomotion, typeof(Character.Abilities.ItemEquipVerifier)); AbilityBuilder.SerializeAbilities(characterLocomotion); } } /// /// Adds the ai agent components to the character. /// /// The character to add the ai agent components to. public static void AddAIAgent(GameObject character) { if (character.GetComponent() == null) { character.AddComponent(); } var locomotionHandler = character.GetComponent(); if (locomotionHandler != null) { GameObject.DestroyImmediate(locomotionHandler, true); } var itemHandler = character.GetComponent(); if (itemHandler != null) { GameObject.DestroyImmediate(itemHandler, true); } RemoveUnityInput(character); } /// /// Removes the ai agent components from the character. /// /// The character to remove the ai agent components to. public static void RemoveAIAgent(GameObject character) { var localLookSource = character.GetComponent(); if (localLookSource != null) { GameObject.DestroyImmediate(localLookSource, true); } if (character.GetComponent() == null) { character.AddComponent(); } if (character.GetComponent() == null) { character.AddComponent(); } AddUnityInput(character); AbilityBuilder.RemoveAbility(character.GetComponent()); var navMeshAgent = character.GetComponent(); if (navMeshAgent != null) { GameObject.DestroyImmediate(navMeshAgent, true); } } /// /// Adds the UnityInput component to the character. /// /// The character to add the UnityInput component to. public static void AddUnityInput(GameObject character) { if (character.GetComponent() == null) { character.AddComponent(); } } /// /// Removes the UnityInput component from the character. /// /// The character to remove the UnityInput component from. public static void RemoveUnityInput(GameObject character) { var unityInput = character.GetComponent(); if (unityInput != null) { GameObject.DestroyImmediate(unityInput, true); } } /// /// Adds support for items to the character. /// /// The character to add support for items to. /// A reference to the inventory's ItemCollection. /// Is the character an AI agent? /// Does the character support first person items? public static void AddItemSupport(GameObject character, ItemCollection itemCollection, bool aiAgent, bool firstPersonItems) { // Even if the character doesn't have an animator the items may make use of one. if (character.GetComponent() == null) { character.AddComponent(); } if (character.GetComponentInChildren() == null) { var items = new GameObject("Items"); items.transform.parent = character.transform; items.AddComponent(); } var animator = character.GetComponent(); if (animator != null) { var head = animator.GetBoneTransform(HumanBodyBones.Head); if (head != null) { var leftHand = animator.GetBoneTransform(HumanBodyBones.LeftHand); var rightHand = animator.GetBoneTransform(HumanBodyBones.RightHand); if (leftHand != null && rightHand != null) { if (leftHand.GetComponentInChildren() == null) { var items = new GameObject("Items"); items.transform.SetParentOrigin(leftHand.transform); var itemSlot = items.AddComponent(); itemSlot.ID = 1; } if (rightHand.GetComponentInChildren() == null) { var items = new GameObject("Items"); items.transform.SetParentOrigin(rightHand.transform); items.AddComponent(); } } } } // Items use the inventory for being equip/unequip. if (character.GetComponent() == null) { character.AddComponent(); } #if FIRST_PERSON_CONTROLLER if (firstPersonItems && character.GetComponentInChildren() == null) { var firstPersonObjects = new GameObject("First Person Objects"); firstPersonObjects.transform.parent = character.transform; firstPersonObjects.AddComponent(); } #endif ItemSetManager itemSetManager; if ((itemSetManager = character.GetComponent()) == null) { itemSetManager = character.AddComponent(); } itemSetManager.ItemCollection = itemCollection; if (!aiAgent && character.GetComponent() == null) { character.AddComponent(); } } /// /// Removes support for items from the character. /// /// The character to remove support for the items from. public static void RemoveItemSupport(GameObject character) { var animatorMonitor = character.GetComponent(); if (animatorMonitor != null && character.GetComponent() == null) { character.AddComponent(); } var itemHandler = character.GetComponent(); if (itemHandler != null) { GameObject.DestroyImmediate(itemHandler, true); } var itemPlacement = character.GetComponentInChildren(); if (itemPlacement != null) { GameObject.DestroyImmediate(itemPlacement.gameObject, true); } #if FIRST_PERSON_CONTROLLER var firstPersonObjects = character.GetComponentInChildren(); if (firstPersonObjects != null) { GameObject.DestroyImmediate(firstPersonObjects, true); } #endif var itemSlots = character.GetComponentsInChildren(); if (itemSlots != null && itemSlots.Length > 0) { for (int i = itemSlots.Length - 1; i >= 0; --i) { GameObject.DestroyImmediate(itemSlots[i].gameObject, true); } } var inventory = character.GetComponent(); if (inventory != null) { GameObject.DestroyImmediate(inventory, true); } var itemSetManager = character.GetComponent(); if (itemSetManager != null) { GameObject.DestroyImmediate(itemSetManager, true); } } /// /// Adds the health components to the character. /// /// The character to add the health components to. public static void AddHealth(GameObject character) { if (character.GetComponent() == null) { character.AddComponent(); } if (character.GetComponent() == null) { character.AddComponent(); } if (character.GetComponent() == null) { character.AddComponent(); } } /// /// Removes the health components from the character. /// /// The character to remove the health components from. public static void RemoveHealth(GameObject character) { var health = character.GetComponent(); if (health != null) { GameObject.DestroyImmediate(health, true); } var attributeManager = character.GetComponent(); if (attributeManager != null) { GameObject.DestroyImmediate(attributeManager, true); } var respawner = character.GetComponent(); if (respawner != null) { GameObject.DestroyImmediate(respawner, true); } } /// /// Adds the CharacterIK component to the character. /// /// The character to add the CharacterIK component to. public static void AddUnityIK(GameObject character) { if (character.GetComponent() == null) { character.AddComponent(); } } /// /// Removes the CharacterIK component from the character. /// /// The character to remove the CharacterIK component from. public static void RemoveUnityIK(GameObject character) { var characterIK = character.GetComponent(); if (characterIK != null) { GameObject.DestroyImmediate(characterIK, true); } } /// /// Adds the CharacterFootEffects component to the character. /// /// The character to add the CharacterFootEffects component to. public static void AddFootEffects(GameObject character) { if (character.GetComponent() == null) { var footEffects = character.AddComponent(); footEffects.InitializeHumanoidFeet(); } } /// /// Removes the CharacterFootEffects component from the character. /// /// The character to remove the CharacterFootEffects component from. public static void RemoveFootEffects(GameObject character) { var footEffects = character.GetComponent(); if (footEffects != null) { GameObject.DestroyImmediate(footEffects, true); } } } }