/// --------------------------------------------- /// Ultimate Character Controller /// Copyright (c) Opsive. All Rights Reserved. /// https://www.opsive.com /// --------------------------------------------- namespace Opsive.UltimateCharacterController.Editor.Managers { using Opsive.UltimateCharacterController.Character; using Opsive.UltimateCharacterController.Editor.Utility; using Opsive.UltimateCharacterController.Editor.Inspectors.Utility; using Opsive.UltimateCharacterController.StateSystem; using Opsive.UltimateCharacterController.Utility; using Opsive.UltimateCharacterController.Utility.Builders; using System; using System.Collections.Generic; using UnityEditor; using UnityEngine; /// /// Draws the Character Builder settings within the window. /// [Serializable] [OrderedEditorItem("Character", 2)] public class CharacterManager : Manager { /// /// Specifies the perspective that the character can change into. /// private enum Perspective { First, // The character can only be in first person perspective. Third, // The character can only be in third person perspective. Both, // The character can be in first or third person perspective. None // Default value. } /// /// Specifies if the rig is a humanoid or generic. Humanoids allow for animation retargetting whereas generic characters do not. /// private enum ModelType { Humanoid, Generic } private string[] m_ToolbarStrings = { "New Character", "Existing Character" }; [SerializeField] private bool m_DrawNewCharacter = true; [SerializeField] private GameObject m_AddCharacter; [SerializeField] private Perspective m_AddPerspective = Perspective.None; [SerializeField] private string m_FirstPersonMovementType; [SerializeField] private string m_ThirdPersonMovementType; [SerializeField] private bool m_StartFirstPersonPerspective = true; [SerializeField] private bool m_AddAnimator = true; [SerializeField] private ModelType m_AddModelType; [SerializeField] private RuntimeAnimatorController m_AddAnimatorController; [SerializeField] private GameObject[] m_AddFirstPersonArms; [SerializeField] private RuntimeAnimatorController[] m_AddFirstPersonArmsAnimatorController; [SerializeField] private GameObject[] m_AddFirstPersonHiddenObjects; [SerializeField] private bool m_AddStandardAbilities = true; [SerializeField] private bool m_AddAIAgent = false; [SerializeField] private bool m_AddNavMeshAgent = false; [SerializeField] private bool m_AddItems = true; [SerializeField] private Inventory.ItemCollection m_AddItemCollection; [SerializeField] private bool m_AddHealth = true; [SerializeField] private bool m_AddUnityIK = true; [SerializeField] private bool m_AddFootEffects = true; [SerializeField] private bool m_AddRagdoll = true; [SerializeField] private StateConfiguration m_AddStateConfiguration; [SerializeField] private int m_AddProfileIndex; [SerializeField] private string m_AddProfileName; [SerializeField] private GameObject m_ExistingCharacter; [SerializeField] private Perspective m_ExistingPerspective; [SerializeField] private bool m_ExistingAnimator = true; [SerializeField] private ModelType m_ExistingModelType; [SerializeField] private RuntimeAnimatorController m_ExistingAnimatorController; [SerializeField] private GameObject[] m_OriginalExistingFirstPersonArms; [SerializeField] private GameObject[] m_ExistingFirstPersonArms; [SerializeField] private RuntimeAnimatorController[] m_ExistingFirstPersonArmsAnimatorController; [SerializeField] private GameObject[] m_OriginalExistingFirstPersonHiddenObjects; [SerializeField] private GameObject[] m_ExistingFirstPersonHiddenObjects; [SerializeField] private bool m_ExistingAIAgent = false; [SerializeField] private bool m_ExistingNavMeshAgent = true; [SerializeField] private bool m_ExistingItems = true; [SerializeField] private Inventory.ItemCollection m_ExistingItemCollection; [SerializeField] private bool m_ExistingHealth = true; [SerializeField] private bool m_ExistingUnityIK = true; [SerializeField] private bool m_ExistingFootEffects = true; [SerializeField] private StateConfiguration m_ExistingStateConfiguration; [SerializeField] private int m_ExistingProfileIndex; [SerializeField] private string m_ExistingProfileName; private List m_FirstPersonMovementTypes = new List(); private string[] m_FirstPersonMovementTypeStrings; private List m_ThirdPersonMovementTypes = new List(); private string[] m_ThirdPersonMovementTypeStrings; private string[] m_PerspectiveNames = { "First", "Third", "Both" }; private Material m_InvisibleShadowCaster; /// /// Initialize the manager after deserialization. /// public override void Initialize(MainManagerWindow mainManagerWindow) { base.Initialize(mainManagerWindow); // Set the default perspective based on what asset is installed. if (m_AddPerspective == Perspective.None) { #if FIRST_PERSON_CONTROLLER m_AddPerspective = Perspective.First; #elif THIRD_PERSON_CONTROLLER m_AddPerspective = Perspective.Third; #endif } // Get a list of the available movement types. var assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < assemblies.Length; ++i) { var assemblyTypes = assemblies[i].GetTypes(); for (int j = 0; j < assemblyTypes.Length; ++j) { // Must derive from MovementType. if (!typeof(Character.MovementTypes.MovementType).IsAssignableFrom(assemblyTypes[j])) { continue; } // Ignore abstract classes. if (assemblyTypes[j].IsAbstract) { continue; } if (assemblyTypes[j].FullName.Contains("FirstPersonController")) { m_FirstPersonMovementTypes.Add(assemblyTypes[j]); } else if (assemblyTypes[j].FullName.Contains("ThirdPersonController")) { m_ThirdPersonMovementTypes.Add(assemblyTypes[j]); } } } // Create an array of display names for the popup. if (m_FirstPersonMovementTypes.Count > 0) { m_FirstPersonMovementTypeStrings = new string[m_FirstPersonMovementTypes.Count]; for (int i = 0; i < m_FirstPersonMovementTypes.Count; ++i) { m_FirstPersonMovementTypeStrings[i] = InspectorUtility.DisplayTypeName(m_FirstPersonMovementTypes[i], true); } } if (m_ThirdPersonMovementTypes.Count > 0) { m_ThirdPersonMovementTypeStrings = new string[m_ThirdPersonMovementTypes.Count]; for (int i = 0; i < m_ThirdPersonMovementTypes.Count; ++i) { m_ThirdPersonMovementTypeStrings[i] = InspectorUtility.DisplayTypeName(m_ThirdPersonMovementTypes[i], true); } } // Look for the ItemCollection within the scene if it isn't already populated. if (m_AddItemCollection == null || m_ExistingItemCollection == null) { m_AddItemCollection = m_ExistingItemCollection = ManagerUtility.FindItemCollection(m_MainManagerWindow); } // Find the state configuration. var stateConfiguration = ManagerUtility.FindStateConfiguration(m_MainManagerWindow); if (stateConfiguration != null) { if (m_AddStateConfiguration == null) { m_AddStateConfiguration = stateConfiguration; } if (m_ExistingStateConfiguration == null) { m_ExistingStateConfiguration = stateConfiguration; } } m_InvisibleShadowCaster = ManagerUtility.FindInvisibleShadowCaster(m_MainManagerWindow); } /// /// Draws the Manager. /// public override void OnGUI() { var toolbarSelection = GUILayout.Toolbar(m_DrawNewCharacter ? 0 : 1, m_ToolbarStrings, EditorStyles.toolbarButton); m_DrawNewCharacter = toolbarSelection == 0; GUILayout.Space(10); if (m_DrawNewCharacter) { DrawNewCharacter(); } else { DrawExistingCharacter(); } } /// /// Draws the controls for a new character. /// private void DrawNewCharacter() { DrawPerspective(ref m_AddPerspective); if (m_AddPerspective == Perspective.Both) { m_StartFirstPersonPerspective = EditorGUILayout.Popup("Start Perspective", m_StartFirstPersonPerspective ? 0 : 1, new string[] { "First Person", "Third Person" }) == 0; } else { m_StartFirstPersonPerspective = (m_AddPerspective == Perspective.First); } // Show the available first person MovementTypes. if (m_AddPerspective == Perspective.First || m_AddPerspective == Perspective.Both) { var selectedMovementType = -1; for (int i = 0; i < m_FirstPersonMovementTypes.Count; ++i) { if (m_FirstPersonMovementTypes[i].FullName == m_FirstPersonMovementType) { selectedMovementType = i; break; } } var movementType = selectedMovementType == -1 ? 0 : selectedMovementType; selectedMovementType = EditorGUILayout.Popup("First Person Movement", movementType, m_FirstPersonMovementTypeStrings); if (movementType != selectedMovementType || string.IsNullOrEmpty(m_FirstPersonMovementType)) { m_FirstPersonMovementType = m_FirstPersonMovementTypes[selectedMovementType].FullName; } if (m_AddPerspective != Perspective.Both) { m_ThirdPersonMovementType = string.Empty; } } // Show the available third person MovementTypes. if (m_AddPerspective == Perspective.Third || m_AddPerspective == Perspective.Both) { var selectedMovementType = -1; for (int i = 0; i < m_ThirdPersonMovementTypes.Count; ++i) { if (m_ThirdPersonMovementTypes[i].FullName == m_ThirdPersonMovementType) { selectedMovementType = i; break; } } var movementType = selectedMovementType == -1 ? 0 : selectedMovementType; selectedMovementType = EditorGUILayout.Popup("Third Person Movement", movementType, m_ThirdPersonMovementTypeStrings); if (movementType != selectedMovementType || string.IsNullOrEmpty(m_ThirdPersonMovementType)) { m_ThirdPersonMovementType = m_ThirdPersonMovementTypes[selectedMovementType].FullName; } if (m_AddPerspective != Perspective.Both) { m_FirstPersonMovementType = string.Empty; } } var character = EditorGUILayout.ObjectField("Character", m_AddCharacter, typeof(GameObject), true) as GameObject; if (character != m_AddCharacter) { m_AddCharacter = character; if (IsValidHumanoid(m_AddCharacter)) { m_AddModelType = ModelType.Humanoid; } else { m_AddModelType = ModelType.Generic; } if (m_AddModelType == ModelType.Humanoid) { m_AddAnimatorController = ManagerUtility.FindAnimatorController(m_MainManagerWindow); } } // An animator is required for third person and AI agents. var validCharacter = CheckValidCharacter(m_AddCharacter, true); GUI.enabled = validCharacter && m_AddPerspective == Perspective.First; var prevAddAnimator = m_AddAnimator; var canBuild = DrawAnimator(m_AddCharacter, m_AddPerspective != Perspective.First, validCharacter, ref m_AddAnimator, ref m_AddModelType, ref m_AddAnimatorController); if (prevAddAnimator && !m_AddAnimator) { m_AddUnityIK = m_AddFootEffects = m_AddRagdoll = false; } GUI.enabled = validCharacter && canBuild; if (m_AddPerspective == Perspective.First || m_AddPerspective == Perspective.Both) { GUILayout.Space(7); DrawFirstPersonArms(ref m_AddFirstPersonArms, ref m_AddFirstPersonArmsAnimatorController); if (m_AddCharacter != null) { GUILayout.Space(5); DrawFirstPersonHiddenObjects(ref m_AddFirstPersonHiddenObjects); } GUILayout.Space(7); } GUILayout.Space(5); // Reduce clutter by having an advanced foldout. if (InspectorUtility.Foldout(this, "Advanced")) { EditorGUI.indentLevel += 2; // Abilities are added and removed through the inspector after the character has been built. m_AddStandardAbilities = EditorGUILayout.Toggle("Standard Abilities", m_AddStandardAbilities); DrawAdvancedComponents(m_AddAnimator, ref m_AddAIAgent, ref m_AddNavMeshAgent, ref m_AddItems, ref m_AddItemCollection, ref m_AddHealth, ref m_AddUnityIK, ref m_AddFootEffects, true, canBuild, m_AddCharacter); DrawProfileFields(ref m_AddStateConfiguration, ref m_AddProfileIndex, ref m_AddProfileName, null); if (m_AddItems && m_AddItemCollection == null) { canBuild = false; } if (m_AddUnityIK && !IsValidHumanoid(character)) { canBuild = false; } if (m_AddFootEffects && !m_AddAnimator) { canBuild = false; } if (m_AddRagdoll && !IsValidHumanoid(character)) { canBuild = false; } EditorGUI.indentLevel -= 2; } // If the character hasn't been built yet then it should be created. GUI.enabled = validCharacter && canBuild; GUILayout.Space(5); if (GUILayout.Button("Build Character")) { // The first person perspective allows for null characters. if (m_AddCharacter == null) { m_AddCharacter = new GameObject("First Person Character"); } var origCharacter = m_AddCharacter; if (EditorUtility.IsPersistent(m_AddCharacter)) { var name = m_AddCharacter.name; m_AddCharacter = GameObject.Instantiate(m_AddCharacter) as GameObject; m_AddCharacter.name = name; } CharacterBuilder.BuildCharacter(m_AddCharacter, m_AddAnimator, m_AddAnimatorController, m_FirstPersonMovementType, m_ThirdPersonMovementType, m_StartFirstPersonPerspective, m_AddFirstPersonHiddenObjects, m_InvisibleShadowCaster, m_AddAIAgent); CharacterBuilder.BuildCharacterComponents(m_AddCharacter, m_AddAIAgent, m_AddItems, m_AddItemCollection, !string.IsNullOrEmpty(m_FirstPersonMovementType) || (m_AddFirstPersonArms != null && m_AddFirstPersonArms.Length > 1), m_AddHealth, m_AddUnityIK, m_AddFootEffects, m_AddStandardAbilities, m_AddNavMeshAgent); #if FIRST_PERSON_CONTROLLER if (m_AddFirstPersonArms != null) { var firstPersonObjects = m_AddCharacter.GetComponentInChildren(); if (firstPersonObjects != null) { for (int i = 0; i < m_AddFirstPersonArms.Length; ++i) { if (m_AddFirstPersonArms[i] == null) { continue; } if (EditorUtility.IsPersistent(m_AddFirstPersonArms[i])) { var name = m_AddFirstPersonArms[i].name; m_AddFirstPersonArms[i] = GameObject.Instantiate(m_AddFirstPersonArms[i]) as GameObject; m_AddFirstPersonArms[i].name = name; } ItemBuilder.AddFirstPersonArms(m_AddCharacter, m_AddFirstPersonArms[i], m_AddFirstPersonArmsAnimatorController[i]); m_AddFirstPersonArms[i].transform.SetParentOrigin(firstPersonObjects.transform); } } } #endif if (m_AddRagdoll) { // Add the ragdoll ability and open Unity's ragdoll builder. var characterLocomotion = m_AddCharacter.GetComponent(); AbilityBuilder.AddAbility(characterLocomotion, typeof(Character.Abilities.Ragdoll)); Inspectors.Character.Abilities.RagdollInspectorDrawer.AddRagdollColliders(characterLocomotion.gameObject); } if (m_AddAnimator) { // Ensure the Animator Controller has the required parameters. AnimatorBuilder.AddParameters((UnityEditor.Animations.AnimatorController)m_AddAnimatorController); } if (m_AddStateConfiguration != null) { if (m_AddProfileIndex > 0) { m_AddStateConfiguration.AddStatesToGameObject(m_AddProfileName, m_AddCharacter); InspectorUtility.SetDirty(m_AddCharacter); } } m_AddFirstPersonHiddenObjects = null; Selection.activeObject = m_AddCharacter; m_AddCharacter = origCharacter; } GUI.enabled = true; } /// /// Draws the perspective controls. /// private void DrawPerspective(ref Perspective perspective) { var selectedPerspective = (Perspective)EditorGUILayout.Popup("Perspective", (int)perspective, m_PerspectiveNames); var canSwitchPerspective = true; // Determine if the selected perspective is supported. #if !FIRST_PERSON_CONTROLLER if (selectedPerspective == Perspective.First || selectedPerspective == Perspective.Both) { Debug.LogError("Unable to select the first person perspective. If you'd like to create a first person character ensure the Firts Person Controller is imported."); canSwitchPerspective = false; } #endif #if !THIRD_PERSON_CONTROLLER if (selectedPerspective == Perspective.Third || selectedPerspective == Perspective.Both) { Debug.LogError("Unable to select the third person perspective. If you'd like to create a third person character ensure the Third Person Controller is imported."); canSwitchPerspective = false; } #endif if (canSwitchPerspective && selectedPerspective != perspective) { perspective = selectedPerspective; } } /// /// Is the character a valid character? /// /// The character to check against. /// Is the character a new character? /// True if the character is a valid character. private bool CheckValidCharacter(GameObject character, bool buildCharacter) { // A character is required in order to add or remove components from it. if (character == null) { // The first person perspective doesn't need a predefined Character GameObject. if (buildCharacter && m_AddPerspective == Perspective.First) { return true; } EditorGUILayout.HelpBox("Select the GameObject which will be used as the character. This object will have the majority of the components added to it.", MessageType.Error); return false; } var characterLocomotion = character.GetComponent(); if (buildCharacter && characterLocomotion != null) { EditorGUILayout.HelpBox("The character has already been created. Use the \"Existing Character\" tab to modify the character components.", MessageType.Error); return false; } if (!buildCharacter && characterLocomotion == null) { EditorGUILayout.HelpBox("The character hasn't been created. Use the \"New Character\" tab to build the character.", MessageType.Error); return false; } if (EditorUtility.IsPersistent(character)) { EditorGUILayout.HelpBox("Please drag your character into the scene. The Character Manager cannot add components to prefabs.", MessageType.Error); return false; } return true; } /// /// Draws the animator controls. /// private bool DrawAnimator(GameObject character, bool forceAdd, bool enableGUI, ref bool animator, ref ModelType modelType, ref RuntimeAnimatorController animatorController) { animator = EditorGUILayout.Toggle("Animator", animator || forceAdd); GUI.enabled = enableGUI; if (animator) { if (character == null) { if (enableGUI) { EditorGUILayout.HelpBox("A rigged character must be selected in order for an Animator to be added.", MessageType.Error); } return false; } EditorGUI.indentLevel++; var selectedModelType = (ModelType)EditorGUILayout.EnumPopup("Model Type", modelType); if (selectedModelType != modelType) { modelType = selectedModelType; if (modelType == ModelType.Humanoid) { // Humanoids support retargetting so can use the demo controller. animatorController = ManagerUtility.FindAnimatorController(m_MainManagerWindow); } else { // Generic characters require a custom animator controller. animatorController = null; } } if (modelType == ModelType.Humanoid && !IsValidHumanoid(character)) { animatorController = null; if (character != null) { EditorGUILayout.HelpBox("The specified character is not a humanoid.\nHumanoid characters should have the avatar is set to humanoid and have a head bone assigned.", MessageType.Error); } } // The non-default animator controller can be selected. animatorController = EditorGUILayout.ObjectField("Animator Controller", animatorController, typeof(RuntimeAnimatorController), false) as RuntimeAnimatorController; EditorGUI.indentLevel--; } return !animator || animatorController != null; } /// /// Is the character a valid humanoid? /// /// The character GameObject to check against. /// True if the character is a valid humanoid. private bool IsValidHumanoid(GameObject character) { if (character == null) { return false; } var spawnedCharacter = false; // The character has to be spawned in order to be able to detect if it is a Humanoid. if (AssetDatabase.GetAssetPath(character).Length > 0) { character = GameObject.Instantiate(character) as GameObject; spawnedCharacter = true; } var animator = character.GetComponent(); var hasAnimator = animator != null; if (!hasAnimator) { animator = character.AddComponent(); } // A human will have a head. var isHumanoid = animator.GetBoneTransform(HumanBodyBones.Head) != null; // GetBoneTransform sometimes returns a false negative. if (!isHumanoid) { isHumanoid = animator.isHuman; } // Clean up. if (!hasAnimator) { UnityEngine.Object.DestroyImmediate(animator, true); } if (spawnedCharacter) { UnityEngine.Object.DestroyImmediate(character, true); } return isHumanoid; } /// /// Draws the controls for the objects that will act as the first person arms. /// private void DrawFirstPersonArms(ref GameObject[] firstPersonArms, ref RuntimeAnimatorController[] firstPersonArmsAnimator) { if (firstPersonArms == null || firstPersonArms.Length == 0) { firstPersonArms = new GameObject[1]; firstPersonArmsAnimator = new RuntimeAnimatorController[1]; } for (int i = 0; i < firstPersonArms.Length; ++i) { GUILayout.BeginHorizontal(); var guiContent = new GUIContent(i == 0 ? "First Person Arms" : " ", "First person objects that are the base for the items. The second field contains a reference to the Animator Controller that the arms should use."); firstPersonArms[i] = EditorGUILayout.ObjectField(guiContent, firstPersonArms[i], typeof(GameObject), true) as GameObject; if (firstPersonArms[i] != null) { firstPersonArmsAnimator[i] = EditorGUILayout.ObjectField(firstPersonArmsAnimator[i], typeof(RuntimeAnimatorController), true, GUILayout.MaxWidth(180)) as RuntimeAnimatorController; } if (i == firstPersonArms.Length - 1 && firstPersonArms[i] != null) { Array.Resize(ref firstPersonArms, firstPersonArms.Length + 1); Array.Resize(ref firstPersonArmsAnimator, firstPersonArmsAnimator.Length + 1); break; } if (i != firstPersonArms.Length - 1 && GUILayout.Button(InspectorStyles.RemoveIcon, InspectorStyles.NoPaddingButtonStyle, GUILayout.Width(18))) { var arms = new List(firstPersonArms); arms.RemoveAt(i); firstPersonArms = arms.ToArray(); var animators = new List(firstPersonArmsAnimator); animators.RemoveAt(i); firstPersonArmsAnimator = animators.ToArray(); break; } GUILayout.EndHorizontal(); } } /// /// Draws the controls for the objects that will be hidden while in first person view. /// private void DrawFirstPersonHiddenObjects(ref GameObject[] firstPersonObjects) { if (firstPersonObjects == null || firstPersonObjects.Length == 0) { firstPersonObjects = new GameObject[1]; } for (int i = 0; i < firstPersonObjects.Length; ++i) { GUILayout.BeginHorizontal(); var guiContent = new GUIContent(i == 0 ? "Third Person Objects" : " ", "Third person objects that should be hidden when the first person view is active (character arms and head)."); firstPersonObjects[i] = EditorGUILayout.ObjectField(guiContent, firstPersonObjects[i], typeof(GameObject), true) as GameObject; if (i == firstPersonObjects.Length - 1 && firstPersonObjects[i] != null) { Array.Resize(ref firstPersonObjects, firstPersonObjects.Length + 1); break; } if (i != firstPersonObjects.Length - 1 && GUILayout.Button(InspectorStyles.RemoveIcon, InspectorStyles.NoPaddingButtonStyle, GUILayout.Width(18))) { var list = new List(firstPersonObjects); list.RemoveAt(i); firstPersonObjects = list.ToArray(); break; } GUILayout.EndHorizontal(); } } /// /// Draws the controls for the advanced character components. /// private void DrawAdvancedComponents(bool addAnimator, ref bool aiAgent, ref bool navMeshAgent, ref bool items, ref Inventory.ItemCollection itemCollection, ref bool health,ref bool unityIK, ref bool footEffects, bool drawRagdoll, bool showError, GameObject character) { aiAgent = EditorGUILayout.Toggle("AI Agent", aiAgent); if (aiAgent) { EditorGUI.indentLevel++; navMeshAgent = EditorGUILayout.Toggle("NavMeshAgent", navMeshAgent); EditorGUI.indentLevel--; } items = EditorGUILayout.Toggle("Items", items); if (items) { EditorGUI.indentLevel++; itemCollection = EditorGUILayout.ObjectField("Item Collection", itemCollection, typeof(Inventory.ItemCollection), false) as Inventory.ItemCollection; if (itemCollection == null) { EditorGUILayout.HelpBox("An ItemCollection needs to be specified for the character to be created.", MessageType.Error); } EditorGUI.indentLevel--; } health = EditorGUILayout.Toggle("Health", health); unityIK = EditorGUILayout.Toggle("Unity IK", unityIK); if (unityIK && !IsValidHumanoid(character) && showError) { EditorGUILayout.HelpBox("The Unity IK component requires a humanoid character with an Animator.", MessageType.Error); } footEffects = EditorGUILayout.Toggle("Foot Effects", footEffects); if (footEffects && !addAnimator && showError) { EditorGUILayout.HelpBox("The foot effects component requires an Animator.", MessageType.Error); } if (drawRagdoll) { m_AddRagdoll = EditorGUILayout.Toggle("Ragdoll", m_AddRagdoll); if (m_AddRagdoll && !IsValidHumanoid(character) && showError) { EditorGUILayout.HelpBox("Unity's ragdoll system requires a humanoid character with an Animator.", MessageType.Error); } } } /// /// Draws the fields related to the profile. /// private void DrawProfileFields(ref StateConfiguration stateConfiguration, ref int profileIndex, ref string profileName, GameObject character) { var updatedStateConfiguration = EditorGUILayout.ObjectField("State Configuration", stateConfiguration, typeof(StateConfiguration), false) as StateConfiguration; if (updatedStateConfiguration != stateConfiguration) { if (updatedStateConfiguration != null) { EditorPrefs.SetString(ManagerUtility.LastStateConfigurationGUIDString, AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(updatedStateConfiguration))); } else { EditorPrefs.SetString(ManagerUtility.LastStateConfigurationGUIDString, string.Empty); } stateConfiguration = updatedStateConfiguration; } EditorGUI.indentLevel++; if (stateConfiguration != null) { var profiles = stateConfiguration.GetProfilesForGameObject(character, StateConfiguration.Profile.ProfileType.Character); // The character can be added without any profiles. profiles.Insert(0, "(None)"); profileIndex = EditorGUILayout.Popup("Profile", profileIndex, profiles.ToArray()); profileName = profiles[profileIndex]; } EditorGUI.indentLevel--; } /// /// Draws the controls for an existing character. /// private void DrawExistingCharacter() { var character = EditorGUILayout.ObjectField("Character", m_ExistingCharacter, typeof(GameObject), true) as GameObject; if (character != m_ExistingCharacter) { m_ExistingCharacter = character; if (m_ExistingCharacter != null) { m_ExistingAIAgent = IsAIAgent(m_ExistingCharacter); if (m_ExistingAIAgent) { m_ExistingNavMeshAgent = HasNavMeshAgent(m_ExistingCharacter); } else { m_ExistingNavMeshAgent = false; } if (!HasEssentials(m_ExistingCharacter, m_ExistingPerspective == Perspective.Both)) { EditorGUILayout.HelpBox("Error: The character doesn't contain all of the essential components. Press \"Add\" to add the missing components.", MessageType.Error); if (GUILayout.Button("Add")) { CharacterBuilder.AddEssentials(m_ExistingCharacter, m_ExistingAnimator, m_ExistingAnimatorController, m_ExistingPerspective == Perspective.Both, m_InvisibleShadowCaster, m_ExistingAIAgent); } } m_ExistingPerspective = CurrentPerspective(m_ExistingCharacter); m_ExistingAnimator = HasAnimator(m_ExistingCharacter); if (m_ExistingAnimator) { var animator = m_ExistingCharacter.GetComponent(); m_ExistingAnimatorController = animator.runtimeAnimatorController; m_ExistingModelType = IsValidHumanoid(m_ExistingCharacter) ? ModelType.Humanoid : ModelType.Generic; } #if FIRST_PERSON_CONTROLLER // Find any existing first person arms. var firstPersonBaseObjects = m_ExistingCharacter.GetComponentsInChildren(); var existingFirstPersonArms = new List(); m_ExistingFirstPersonArmsAnimatorController = new RuntimeAnimatorController[firstPersonBaseObjects.Length]; for (int i = 0; i < firstPersonBaseObjects.Length; ++i) { existingFirstPersonArms.Add(firstPersonBaseObjects[i].gameObject); var animator = firstPersonBaseObjects[i].GetComponent(); if (animator != null) { m_ExistingFirstPersonArmsAnimatorController[i] = animator.runtimeAnimatorController; } } m_OriginalExistingFirstPersonArms = m_ExistingFirstPersonArms = existingFirstPersonArms.ToArray(); #endif // Search for any existing first person hidden objects. var renderers = m_ExistingCharacter.GetComponentsInChildren(); var existingFirstPersonObjects = new List(); for (int i = 0; i < renderers.Length; ++i) { if (renderers[i].GetComponent() != null) { existingFirstPersonObjects.Add(renderers[i].gameObject); continue; } var addGameObject = false; var materials = renderers[i].sharedMaterials; for (int j = 0; j < materials.Length; ++j) { if (materials[j] == m_InvisibleShadowCaster) { addGameObject = true; break; } } if (addGameObject) { existingFirstPersonObjects.Add(renderers[i].gameObject); } } m_OriginalExistingFirstPersonHiddenObjects = m_ExistingFirstPersonHiddenObjects = existingFirstPersonObjects.ToArray(); m_ExistingItems = HasItems(m_ExistingCharacter, m_ExistingAIAgent, m_ExistingPerspective != Perspective.Third); if (m_ExistingItems) { var itemSetManager = m_ExistingCharacter.GetComponent(); if (itemSetManager != null) { m_ExistingItemCollection = itemSetManager.ItemCollection; } } m_ExistingHealth = HasHealth(m_ExistingCharacter); m_ExistingUnityIK = HasUnityIK(m_ExistingCharacter); m_ExistingFootEffects = HasFootEffects(m_ExistingCharacter); } else { m_OriginalExistingFirstPersonArms = m_ExistingFirstPersonArms = m_OriginalExistingFirstPersonHiddenObjects = m_ExistingFirstPersonHiddenObjects = null; } } var validCharacter = CheckValidCharacter(m_ExistingCharacter, false); GUI.enabled = validCharacter; DrawPerspective(ref m_ExistingPerspective); GUI.enabled = validCharacter && m_ExistingPerspective == Perspective.First; DrawAnimator(m_ExistingCharacter, m_ExistingPerspective != Perspective.First, validCharacter, ref m_ExistingAnimator, ref m_ExistingModelType, ref m_ExistingAnimatorController); GUI.enabled = validCharacter; if (m_ExistingPerspective == Perspective.First || m_ExistingPerspective == Perspective.Both) { GUILayout.Space(7); DrawFirstPersonArms(ref m_ExistingFirstPersonArms, ref m_ExistingFirstPersonArmsAnimatorController); if (m_ExistingCharacter != null) { GUILayout.Space(5); DrawFirstPersonHiddenObjects(ref m_ExistingFirstPersonHiddenObjects); } GUILayout.Space(7); } DrawAdvancedComponents(m_ExistingAnimator, ref m_ExistingAIAgent, ref m_ExistingNavMeshAgent, ref m_ExistingItems, ref m_ExistingItemCollection, ref m_ExistingHealth, ref m_ExistingUnityIK, ref m_ExistingFootEffects, false, validCharacter, m_ExistingCharacter); DrawProfileFields(ref m_ExistingStateConfiguration, ref m_ExistingProfileIndex, ref m_ExistingProfileName, m_ExistingCharacter); GUILayout.Space(5); if (GUILayout.Button("Update Character")) { if (m_ExistingPerspective != CurrentPerspective(m_ExistingCharacter)) { #if THIRD_PERSON_CONTROLLER // If the perspective was switched from/to the both perspective then the perspective monitor needs to be added or removed. var perspectiveMonitor = m_ExistingCharacter.GetComponent(); if (m_ExistingPerspective == Perspective.Both) { if (perspectiveMonitor == null) { m_ExistingCharacter.AddComponent(); } } else if (perspectiveMonitor != null) { UnityEngine.Object.DestroyImmediate(perspectiveMonitor, true); } #endif #if FIRST_PERSON_CONTROLLER // The First Person Objects component should also be added/removed if the character supports items. if (HasItems(m_ExistingCharacter, m_ExistingAIAgent, false)) { var firstPersonObjects = m_ExistingCharacter.GetComponentInChildren(); if (m_ExistingPerspective != Perspective.Third && firstPersonObjects == null) { var firstPersonObjectsGameObject = new GameObject("First Person Objects"); firstPersonObjectsGameObject.transform.parent = character.transform; firstPersonObjectsGameObject.AddComponent(); } else if (m_ExistingPerspective == Perspective.Third && firstPersonObjects != null) { UnityEngine.Object.DestroyImmediate(firstPersonObjects, true); } } #endif } if (m_ExistingAnimator != HasAnimator(m_ExistingCharacter)) { if (m_ExistingAnimator) { CharacterBuilder.AddAnimator(m_ExistingCharacter, m_ExistingAnimatorController, m_ExistingAIAgent); } else { CharacterBuilder.RemoveAnimator(m_ExistingCharacter); } } else if (m_ExistingAnimator) { // The animator controller may have changed. var animator = m_ExistingCharacter.GetComponent(); if (animator != null && animator.runtimeAnimatorController != m_ExistingAnimatorController) { animator.runtimeAnimatorController = m_ExistingAnimatorController; } } #if FIRST_PERSON_CONTROLLER // The arms may have changed. var firstPersonArmsChange = (m_OriginalExistingFirstPersonArms != null && m_OriginalExistingFirstPersonArms.Length != (m_ExistingFirstPersonArms.Length - 1)) || (m_OriginalExistingFirstPersonArms == null && m_ExistingFirstPersonArms.Length > 1); if (m_OriginalExistingFirstPersonArms != null && !firstPersonArmsChange) { for (int i = 0; i < m_OriginalExistingFirstPersonArms.Length; ++i) { var containsObject = false; for (int j = 0; j < m_ExistingFirstPersonArms.Length; ++j) { if (m_OriginalExistingFirstPersonArms[i] == m_ExistingFirstPersonArms[j]) { containsObject = true; break; } } if (!containsObject) { firstPersonArmsChange = true; break; } } } if (firstPersonArmsChange) { // Remove the original arms who are no longer used. if (m_OriginalExistingFirstPersonArms != null) { for (int i = 0; i < m_OriginalExistingFirstPersonArms.Length; ++i) { if (m_OriginalExistingFirstPersonArms[i] == null) { continue; } var remove = true; for (int j = 0; j < m_ExistingFirstPersonArms.Length; ++j) { if (m_OriginalExistingFirstPersonArms[i] == m_ExistingFirstPersonArms[j]) { remove = false; break; } } if (remove) { UnityEngine.Object.DestroyImmediate(m_OriginalExistingFirstPersonArms[i], true); } } } // Add the new objects. var firstPersonObjects = m_ExistingCharacter.GetComponentInChildren(); for (int i = 0; i < m_ExistingFirstPersonArms.Length; ++i) { if (m_ExistingFirstPersonArms[i] == null) { continue; } var add = true; for (int j = 0; j < m_OriginalExistingFirstPersonArms.Length; ++j) { if (m_ExistingFirstPersonArms[i] == m_OriginalExistingFirstPersonArms[j]) { add = false; break; } } if (add) { if (EditorUtility.IsPersistent(m_ExistingFirstPersonArms[i])) { var name = m_ExistingFirstPersonArms[i].name; m_ExistingFirstPersonArms[i] = GameObject.Instantiate(m_ExistingFirstPersonArms[i]) as GameObject; m_ExistingFirstPersonArms[i].name = name; } ItemBuilder.AddFirstPersonArms(m_ExistingCharacter, m_ExistingFirstPersonArms[i], m_ExistingFirstPersonArmsAnimatorController[i]); m_ExistingFirstPersonArms[i].transform.SetParentOrigin(firstPersonObjects.transform); } } } #endif // Determine if the hidden objects have changed. var firstPersonHiddenObjectChange = (m_OriginalExistingFirstPersonHiddenObjects != null && m_ExistingFirstPersonHiddenObjects.Length != (m_ExistingFirstPersonHiddenObjects.Length - 1)) || (m_OriginalExistingFirstPersonHiddenObjects == null && m_ExistingFirstPersonHiddenObjects.Length > 1); if (m_OriginalExistingFirstPersonHiddenObjects != null && !firstPersonHiddenObjectChange) { for (int i = 0; i < m_OriginalExistingFirstPersonHiddenObjects.Length; ++i) { var containsObject = false; for (int j = 0; j < m_ExistingFirstPersonHiddenObjects.Length; ++j) { if (m_OriginalExistingFirstPersonHiddenObjects[i] == m_ExistingFirstPersonHiddenObjects[j]) { containsObject = true; break; } } if (!containsObject) { firstPersonHiddenObjectChange = true; break; } } } if (firstPersonHiddenObjectChange) { // Remove all of the original hidden objects before adding them back again. if (m_OriginalExistingFirstPersonHiddenObjects != null) { for (int i = 0; i < m_OriginalExistingFirstPersonHiddenObjects.Length; ++i) { if (m_OriginalExistingFirstPersonHiddenObjects[i] == null) { continue; } Character.Identifiers.ThirdPersonObject thirdPersonObject; if ((thirdPersonObject = m_OriginalExistingFirstPersonHiddenObjects[i].GetComponent())) { UnityEngine.Object.DestroyImmediate(thirdPersonObject, true); continue; } var renderers = m_OriginalExistingFirstPersonHiddenObjects[i].GetComponents(); for (int j = 0; j < renderers.Length; ++j) { var materials = renderers[j].sharedMaterials; for (int k = 0; k < materials.Length; ++k) { if (materials[k] == m_InvisibleShadowCaster) { materials[k] = null; } } renderers[j].sharedMaterials = materials; } } } var addThirdPersonObject = false; #if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER var networkInfo = m_ExistingCharacter.GetComponent(); if (networkInfo != null) { addThirdPersonObject = true; } #endif if (!addThirdPersonObject) { var characterLocomotion = m_ExistingCharacter.GetComponent(); characterLocomotion.DeserializeMovementTypes(); var movementTypes = characterLocomotion.MovementTypes; for (int i = 0; i < movementTypes.Length; ++i) { if (movementTypes[i].GetType().FullName.Contains("ThirdPerson")) { addThirdPersonObject = true; break; } } } // All of the original hidden objects have been removed. Add the new objects. for (int i = 0; i < m_ExistingFirstPersonHiddenObjects.Length; ++i) { if (m_ExistingFirstPersonHiddenObjects[i] == null) { continue; } if (!addThirdPersonObject) { var renderers = m_ExistingFirstPersonHiddenObjects[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] = m_InvisibleShadowCaster; } renderers[j].sharedMaterials = materials; } } else { // The PerspectiveMonitor component is responsible for switching out the material. m_ExistingFirstPersonHiddenObjects[i].AddComponent(); } } InspectorUtility.SetDirty(m_ExistingCharacter); m_OriginalExistingFirstPersonHiddenObjects = m_ExistingFirstPersonHiddenObjects; // An empty element will occupy the last existing slot. if (m_OriginalExistingFirstPersonHiddenObjects != null && m_OriginalExistingFirstPersonHiddenObjects.Length > 0) { Array.Resize(ref m_OriginalExistingFirstPersonHiddenObjects, m_OriginalExistingFirstPersonHiddenObjects.Length - 1); } } if (m_ExistingAIAgent != IsAIAgent(m_ExistingCharacter)) { if (m_ExistingAIAgent) { CharacterBuilder.AddAIAgent(m_ExistingCharacter); } else { CharacterBuilder.RemoveAIAgent(m_ExistingCharacter); } } if (m_ExistingNavMeshAgent != HasNavMeshAgent(m_ExistingCharacter)) { if (m_ExistingNavMeshAgent) { var characterLocomotion = m_ExistingCharacter.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 = m_ExistingCharacter.GetComponent(); if (navMeshAgent != null) { navMeshAgent.stoppingDistance = 0.1f; } } else { AbilityBuilder.RemoveAbility(m_ExistingCharacter.GetComponent()); var navMeshAgent = m_ExistingCharacter.GetComponent(); if (navMeshAgent != null) { GameObject.DestroyImmediate(navMeshAgent, true); } } } if (m_ExistingItems != HasItems(m_ExistingCharacter, m_ExistingAIAgent, m_ExistingPerspective != Perspective.Third)) { if (m_ExistingItems) { CharacterBuilder.AddItemSupport(m_ExistingCharacter, m_ExistingItemCollection, m_ExistingAIAgent, m_ExistingPerspective != Perspective.Third); } else { CharacterBuilder.RemoveItemSupport(m_ExistingCharacter); } } if (m_ExistingHealth != HasHealth(m_ExistingCharacter)) { if (m_ExistingHealth) { CharacterBuilder.AddHealth(m_ExistingCharacter); } else { CharacterBuilder.RemoveHealth(m_ExistingCharacter); } } if (m_ExistingUnityIK != HasUnityIK(m_ExistingCharacter)) { if (m_ExistingUnityIK) { if (IsValidHumanoid(m_ExistingCharacter)) { CharacterBuilder.AddUnityIK(m_ExistingCharacter); } } else { CharacterBuilder.RemoveUnityIK(m_ExistingCharacter); } } if (m_ExistingFootEffects != HasFootEffects(m_ExistingCharacter)) { if (m_ExistingFootEffects) { CharacterBuilder.AddFootEffects(m_ExistingCharacter); } else { CharacterBuilder.RemoveFootEffects(m_ExistingCharacter); } } if (m_ExistingStateConfiguration != null && m_ExistingProfileIndex > 0) { m_ExistingStateConfiguration.AddStatesToGameObject(m_ExistingProfileName, m_ExistingCharacter); InspectorUtility.SetDirty(m_ExistingCharacter); } } GUI.enabled = true; } /// /// Does the character have the essential components? /// /// The GameObject of the character to determine if it has the required components. /// True if the character has the essential components. private bool HasEssentials(GameObject character, bool usePerspectiveMonitor) { #if THIRD_PERSON_CONTROLLER if (usePerspectiveMonitor && character.GetComponent() == null) { return false; } #endif return character.GetComponent() != null && character.GetComponent() != null && character.GetComponent() && character.GetComponent() != null; } /// /// Retrieves the current perspective of the character. /// /// The character to determine the perspective of. /// The character's perspective. private Perspective CurrentPerspective(GameObject character) { var hasBothComponents = false; #if THIRD_PERSON_CONTROLLER hasBothComponents = m_ExistingCharacter.GetComponentInChildren() != null; #endif if (hasBothComponents) { // If the character has the perspective monitor then it can switch perspectives. return Perspective.Both; } else { if (!m_ExistingAnimator) { // If the character doesn't have an animator then it has to be in first person. return Perspective.First; } else { // Use the movement types to determine the perspective. var perspective = Perspective.Third; var characterLocomotion = m_ExistingCharacter.GetComponent(); if (characterLocomotion != null) { characterLocomotion.DeserializeMovementTypes(); var movementTypes = characterLocomotion.MovementTypes; for (int i = 0; i < movementTypes.Length; ++i) { if (movementTypes[i].GetType().Namespace.Contains("FirstPersonController")) { perspective = Perspective.First; break; } } } return perspective; } } } /// /// Does the character have an animator? /// /// The GameObject of the character to determine if it has the required components. /// True if the character has an animator. private bool HasAnimator(GameObject character) { return character.GetComponent() && character.GetComponent(); } /// /// Is the character an AI agent? /// /// The GameObject of the character to determine if it has the required components. /// True if the character is an AI agent. private bool IsAIAgent(GameObject character) { return character.GetComponent() && !character.GetComponent() && !character.GetComponent(); } /// /// Does the character have the NavMeshAgent components? /// /// The GameObject of the character to determine if it has the required components. /// True if the character has the NavMeshAgent components. private bool HasNavMeshAgent(GameObject character) { return character.GetComponent() != null && character.GetComponent().GetAbility() != null; } /// /// Does the character have the components required for items? /// /// The GameObject of the character to determine if it has the required components. /// Is the character an AI agent? /// Should the FirstPersonObjects component be checked? /// True if the character has the components required for items. private bool HasItems(GameObject character, bool aiAgent, bool checkFirstPersonObjects) { if ((!aiAgent && character.GetComponent() == null) || character.GetComponentInChildren() == null || character.GetComponent() == null) { return false; } #if FIRST_PERSON_CONTROLLER if (checkFirstPersonObjects && character.GetComponentInChildren() == null) { return false; } #endif 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 || rightHand.GetComponentInChildren() == null) { return false; } } } } return character.GetComponent() && character.GetComponent(); } /// /// Does the character have the health components? /// /// The GameObject of the character to determine if it has the required components. /// True if the character has the health components. private bool HasHealth(GameObject character) { return character.GetComponent() && character.GetComponent() && character.GetComponent(); } /// /// Does the character have the CharacterIK component? /// /// The GameObject of the character to determine if it has the required components. /// True if the character has the CharacterIK component. private bool HasUnityIK(GameObject character) { return character.GetComponent(); } /// /// Does the character have the CharacterFootEffects component? /// /// The GameObject of the character to determine if it has the required components. /// True if the character has the CharacterFootEffects component. private bool HasFootEffects(GameObject character) { return character.GetComponent(); } } }