using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEditor; using UnityEngine.Events; using UnityEngine.Serialization; using Random = System.Random; /* * Prefab Child Manager is meant to make it a bit easier to set up and manage large amounts of prefab options, whether * they are prefabs added at runtime or children of an object turned on/off. It makes it easier to create groups of * objects, and then activate and deactivate them at runtime with a single line of code. * * Use with "Wardrobe Prefab Manager" to include rigging and blend shape handling (for Infinity PBR characters) */ namespace InfinityPBR { [System.Serializable] public class PrefabAndObjectManager : MonoBehaviour { public PrefabChildEvent _event; public List prefabGroups = new List(); public bool onlyOneGroupActivePerType = true; public bool unpackPrefabs = true; public bool revertToDefaultGroupByType = true; public WardrobePrefabManager WardrobePrefabManager => GetWardrobePrefabManager(); private WardrobePrefabManager _wardrobePrefabManager; public BlendShapesManager BlendShapesManager => GetBlendShapesManager(); private BlendShapesManager _blendShapesManager; private BlendShapesManager GetBlendShapesManager() { if (_blendShapesManager != null) return _blendShapesManager; if (TryGetComponent(out BlendShapesManager foundManager)) _blendShapesManager = foundManager; return _blendShapesManager; } private WardrobePrefabManager GetWardrobePrefabManager() { if (_wardrobePrefabManager != null) return _wardrobePrefabManager; if (TryGetComponent(out WardrobePrefabManager foundManager)) _wardrobePrefabManager = foundManager; return _wardrobePrefabManager; } public Transform thisTransform => transform; [HideInInspector] public bool showHelpBoxes = true; [HideInInspector] public bool showSetup = true; [HideInInspector] public bool showFullInspector = false; [HideInInspector] public bool showPrefabGroups = true; [HideInInspector] public bool instantiatePrefabsAsAdded = true; public List GroupTypeNames => GetGroupTypeNames(); private List _groupTypeNames = new List(); public bool cacheTypes = true; private void Start() { if (_event == null) _event = new PrefabChildEvent(); cacheTypes = true; // Set true on start, so this will trigger the first time it's called. } public void CreateNewPrefabGroup(string groupType = null) { var newGroup = new PrefabGroup {name = GetNextDefaultName(), groupType = groupType}; newGroup.CreateNewUid(); prefabGroups.Add(newGroup); } private string GetNextDefaultName() { int g = prefabGroups.Count; var newName = "Prefab Group " + g; while (NameExists(newName)) { g++; newName = "Prefab Group " + g; } return newName; } private bool NameExists(string newName) { if (prefabGroups.FirstOrDefault(x => x.name == newName) != null) return true; return false; } public void ActivateRandomGroupType() { Debug.Log($"Count: {GroupTypeNames.Count}"); var groupType = GroupTypeNames[UnityEngine.Random.Range(0, GroupTypeNames.Count)]; ActivateRandomGroup(groupType); } public void ActivateRandomAllGroups() { foreach (var groupType in GroupTypeNames) ActivateRandomGroup(groupType); } /// /// Will activate a random group of a named type /// /// /// public void ActivateRandomGroup(string type) { if (string.IsNullOrWhiteSpace(type)) return; var groupsOfType = GetGroupsOfType(type); ActivateGroup(groupsOfType[UnityEngine.Random.Range(0, groupsOfType.Count)]); } /// /// Activate a group by name /// /// public void ActivateGroup(string groupName) { var prefabGroup = prefabGroups.FirstOrDefault(x => x.name == groupName); if (prefabGroup == null) { Debug.LogWarning("Warning: No prefab group found with the name " + groupName); return; } ActivateGroup(prefabGroup); } /// /// Activate a group by index /// /// public void ActivateGroup(int groupIndex) { if (prefabGroups.Count < groupIndex || prefabGroups.Count == 0) { Debug.LogWarning("Warning: Group index (" + groupIndex + ") out of range."); return; } ActivateGroup(prefabGroups[groupIndex]); } /// /// Will activate a group which is passed into this. Will also deactivate others of the same type, if any and /// onlyOneGroupActivePerType = true. /// /// public void ActivateGroup(PrefabGroup group) { // If we are only allowing one group per type, and the group has a type, then deactivate the other groups // of this type. Don't check for default, as we will be activating this group right after. if (onlyOneGroupActivePerType && !String.IsNullOrWhiteSpace(group.groupType)) DeactivateGroupsOfType(group.groupType, false); group.isActive = true; // Set this to be true -- group is now active // This is where we activate each group. Instantiate prefabs or turn on game objects. foreach (var groupObject in group.groupObjects) { if (groupObject.isPrefab && groupObject.inGameObject == groupObject.objectToHandle) groupObject.inGameObject = null; //groupObject.inGameObject = null; // This is where we instantiate the prefab if (groupObject.isPrefab && groupObject.objectToHandle && groupObject.inGameObject == null) { // Instantiate the object, set it's transform info, and make sure it's active. groupObject.inGameObject = Instantiate(groupObject.objectToHandle, groupObject.parentTransform.position, groupObject.parentTransform.rotation, groupObject.parentTransform); groupObject.inGameObject.SetActive(true); #if UNITY_EDITOR // If we are in the editor, then we will unpack this prefab if (unpackPrefabs && PrefabUtility.IsAnyPrefabInstanceRoot(groupObject.inGameObject)) PrefabUtility.UnpackPrefabInstance(groupObject.inGameObject, PrefabUnpackMode.OutermostRoot, InteractionMode.AutomatedAction); #endif // TryBlendShapesManager(); // March 13, 2022 -- We can do this once later, we don't have to do it each time. } else if (!groupObject.isPrefab) { // The object is not a prefab, so we will just turn on the object. if (groupObject.objectToHandle) { groupObject.objectToHandle.SetActive(true); groupObject.inGameObject = groupObject.objectToHandle; } } } TryWardrobePrefabManagerAutoRig(); TryWardrobePrefabManagerOnActivate(group); TryBlendShapesManager(); } private void TryWardrobePrefabManagerOnActivate(PrefabGroup group) { if (!WardrobePrefabManager) return; WardrobePrefabManager.OnActivate(group.name); } // If the Wardrobe Prefab Manager is attached, then we may want to auto rig the new objects private void TryWardrobePrefabManagerAutoRig() { if (!WardrobePrefabManager) return; // Return if there is none if (!WardrobePrefabManager.autoRigWhenActivated) return; // Return if we aren't auto rigging // IPBR_CharacterEquip.EquipCharacter(gameObject); // THIS IS THE OLD METHOD EquipObject.Equip(gameObject); } // If the Blend Shapes Manager is attached, then we will LoadBlendShapeData to make sure the new // objects get the right information set. private void TryBlendShapesManager() { if (!BlendShapesManager) return; BlendShapesManager.LoadBlendShapeData(); } /// /// Deactivate a group by PrefabGroup /// /// /// public void DeactivateGroup(PrefabGroup group, bool checkForDefault = true) { group.isActive = false; if (WardrobePrefabManager) WardrobePrefabManager.OnDeactivate(group.name); // ITEMS foreach (var groupObject in group.groupObjects) { if (groupObject.isPrefab) { DestroyObject(groupObject.inGameObject); groupObject.inGameObject = null; continue; } if (groupObject.objectToHandle) groupObject.objectToHandle.SetActive(false); } if (checkForDefault && !String.IsNullOrWhiteSpace(group.groupType)) CheckForDefaultGroup(group.groupType); } /// /// Deactivate a group by name. /// /// /// public void DeactivateGroup(string groupName, bool checkForDefault = true) { var prefabGroup = prefabGroups.FirstOrDefault(x => x.name == groupName); if (prefabGroup == null) { Debug.LogWarning("Warning: No prefab group found with the name " + groupName); return; } DeactivateGroup(prefabGroup, checkForDefault); } /// /// Deactivate a group by the index /// /// /// public void DeactivateGroup(int groupIndex, bool checkForDefault = true) { if (prefabGroups.Count < groupIndex || prefabGroups.Count == 0) { Debug.LogWarning("Warning: Group index (" + groupIndex + ") out of range."); return; } DeactivateGroup(prefabGroups[groupIndex], checkForDefault); } /// /// Deactivate all groups of a specific type /// /// /// public void DeactivateGroupsOfType(string type, bool checkForDefault = true) { for (int g = 0; g < prefabGroups.Count; g++) { if (prefabGroups[g].groupType != type) continue; DeactivateGroup(g, checkForDefault); } } /// /// This will activate the default group of the named type, if there is one. /// /// private void CheckForDefaultGroup(string type) { int defaultGroupIndex = -1; for (int g = 0; g < prefabGroups.Count; g++) { if (prefabGroups[g].groupType != type) continue; // Continue if the type is wrong if (!prefabGroups[g].isDefault) continue; // Continue if this isn't the default if (GroupIsActive(g) > 0) return; // If it is the default, and active, return ActivateGroup(prefabGroups[g]); // Activate the default group return; } } public void DestroyObject(GameObject inGameObject) { if (inGameObject) { #if UNITY_EDITOR DestroyImmediate(inGameObject); #else Destroy(inGameObject); #endif } } public int GroupIsActive(PrefabGroup group) { int prefabs = 0; int inGameObjects = 0; for (int i = 0; i < group.groupObjects.Count; i++) { if (!group.groupObjects[i].objectToHandle) continue; prefabs++; if (group.groupObjects[i].isPrefab && group.groupObjects[i].inGameObject) inGameObjects++; else if (!group.groupObjects[i].isPrefab && group.groupObjects[i].objectToHandle.activeSelf) inGameObjects++; } if (prefabs == inGameObjects && (prefabs > 0 || group.isActive)) return 2; if (prefabs > inGameObjects && inGameObjects > 0) return 1; return 0; } public int GroupIsActive(int groupIndex) => GroupIsActive(prefabGroups[groupIndex]); public int GroupIsActive(string groupName) => GroupIsActive(GetPrefabGroup(groupName)); public void AddPrefabToGroup(GameObject prefab, PrefabGroup group) { var newGroupObject = new GroupObject { objectToHandle = group.newPrefab, parentTransform = thisTransform, isPrefab = true }; group.groupObjects.Add(newGroupObject); } public void AddGameObjectToGroup(GameObject newObject, PrefabGroup group) { var newGroupObject = new GroupObject { objectToHandle = group.newPrefab, isPrefab = false }; group.groupObjects.Add(newGroupObject); } /// /// Returns a List with the group names of a type /// /// /// public List GetGroupsOfType(string type) { return prefabGroups .Where(x => x.groupType == type) .Select(x => x.name) .ToList(); /* List newList = new List(); for (int i = 0; i < prefabGroups.Count; i++) { if (prefabGroups[i].groupType != type) continue; newList.Add(prefabGroups[i].name); } return newList; */ } public void MarkPrefabs () { foreach(PrefabGroup prefabGroup in prefabGroups) { foreach (GroupObject groupObject in prefabGroup.groupObjects) { if (!groupObject.objectToHandle) continue; if (groupObject.isPrefab && groupObject.inGameObject == groupObject.objectToHandle) groupObject.inGameObject = null; groupObject.isPrefab = !groupObject.objectToHandle.transform.IsChildOf(transform); // Not a prefab if it's a child of the prefab manager object if (groupObject.isPrefab && groupObject.inGameObject == groupObject.objectToHandle) groupObject.inGameObject = null; } } } /// /// Returns a PrefabGroup of a given name. /// /// /// public PrefabGroup GetPrefabGroup(string groupName) { for (int i = 0; i < prefabGroups.Count; i++) { if (prefabGroups[i].name == groupName) return GetPrefabGroup(i); } Debug.LogWarning("Warning: Could not find prefab group named " + groupName); return null; } /// /// Returns a List with all Prefab Group types. /// /// public List GetGroupTypeNames() { if (!cacheTypes) return _groupTypeNames; _groupTypeNames = CacheGroupTypeNames(); cacheTypes = false; return _groupTypeNames; } private List CacheGroupTypeNames() => _groupTypeNames = prefabGroups.OrderBy(x => x.groupType).Select(x => x.groupType).Distinct().ToList(); public PrefabGroup GetPrefabGroup(int index) { if (prefabGroups.Count < index) return prefabGroups[index]; Debug.Log($"Warning: The index {index} is out of range of the prefab groups {prefabGroups.Count}"); return null; } public void RemovePrefabGroup(PrefabGroup prefabGroup) => prefabGroups.RemoveAll(x => x == prefabGroup); public void SetUid() { foreach (var group in prefabGroups) { if (string.IsNullOrEmpty(group.uid)) group.CreateNewUid(); } } } [System.Serializable] public class PrefabGroup { [HideInInspector] public bool showPrefabs = false; [HideInInspector] public bool showShapes = false; [HideInInspector] public bool showObjectsOnActivate = false; [HideInInspector] public bool showObjectsOnDeactivate = false; public string uid; public bool isDefault = false; public string name; public string groupType; public bool isActive; [FormerlySerializedAs("prefabObjects")] public List groupObjects = new List(); [HideInInspector] public GameObject newPrefab; [HideInInspector] public GameObject newGameObject; public void RemoveGroupObject(GroupObject groupObject) => groupObjects.Remove(groupObject); public void CreateNewUid(bool forceNew = false) { if (!string.IsNullOrEmpty(uid) && !forceNew) return; uid = GetUid(); } private string GetUid() { #if UNITY_EDITOR return GUID.Generate().ToString(); #endif return "ThisShoutNotHappen"; } } [System.Serializable] public class GroupObject { [FormerlySerializedAs("prefab")] public GameObject objectToHandle; public Transform parentTransform; public GameObject inGameObject; public MeshRenderer meshRenderer; public SkinnedMeshRenderer skinnedMeshRenderer; public bool isPrefab = false; } [System.Serializable] public class PrefabChildEvent : UnityEvent { } }