1188 lines
36 KiB
C#
1188 lines
36 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEngine;
|
|
using UnityEngine.Events;
|
|
|
|
namespace Bozo.AnimeCharacters
|
|
{
|
|
public class OutfitSystem : MonoBehaviour
|
|
{
|
|
//[Header("Save Data")]
|
|
|
|
public CharacterObject characterData;
|
|
private CharacterObject _characterData;
|
|
public string SaveID;
|
|
|
|
|
|
//[Header("Dependencies")]
|
|
[SerializeField] SkinnedMeshRenderer CharacterBody;
|
|
|
|
//Height
|
|
public bool muteHeightChange { get; private set; }
|
|
public float height { get; private set; }
|
|
public float heeledHeight { get; private set; }
|
|
|
|
//Animation
|
|
public Animator animator
|
|
{
|
|
get
|
|
{
|
|
if (_animator == null)
|
|
{
|
|
_animator = GetComponentInParent<Animator>();
|
|
if (_animator == null) { _animator = GetComponentInChildren<Animator>(); }
|
|
}
|
|
|
|
return _animator;
|
|
}
|
|
private set
|
|
{ _animator = value; }
|
|
}
|
|
private Animator _animator;
|
|
public float stance { get; private set; }
|
|
|
|
//Dimensions
|
|
private Bounds CharacterRenderBounds;
|
|
public Dictionary<string, Transform> boneMap = new Dictionary<string, Transform>();
|
|
|
|
//Outfits
|
|
public Dictionary<OutfitType, Outfit> Outfits = new Dictionary<OutfitType, Outfit>();
|
|
public Dictionary<string, OutfitType> KnownOutfitTypes = new Dictionary<string, OutfitType>();
|
|
public Dictionary<OutfitType, List<Outfit>> hiddenTypes = new Dictionary<OutfitType, List<Outfit>>();
|
|
|
|
//Shapes
|
|
private Dictionary<string, int> bodyShapes = new Dictionary<string, int>();
|
|
private Dictionary<string, int> faceShapes = new Dictionary<string, int>();
|
|
private Dictionary<string, int> tagShapes = new Dictionary<string, int>();
|
|
public Dictionary<string, BodyShapeModifier> bodyModifiers = new Dictionary<string, BodyShapeModifier>();
|
|
private List<string> tags = new List<string>();
|
|
|
|
|
|
//Events
|
|
public UnityAction<Outfit> OnOutfitChanged;
|
|
public UnityAction<Outfit> OnOutfitRemoved;
|
|
public UnityAction<SkinnedMeshRenderer> OnRigChanged;
|
|
public UnityAction<string, float> OnShapeChanged;
|
|
public UnityAction<List<string>> OnTagsChanged;
|
|
public UnityAction OnCharacterLoaded;
|
|
|
|
public bool initalized { get; private set; }
|
|
public bool isStarted { get; private set; }
|
|
|
|
|
|
// Merged Properties
|
|
public string prefabName;
|
|
public Material mergeMaterial;
|
|
public bool mergedMode;
|
|
//public bool mergeOnAwake;
|
|
[Tooltip("Automatically Rebuilds the character if it detects a change so you don't need to call MergeCharacter()")]
|
|
public bool autoUpdate;
|
|
public bool mergeBase;
|
|
public CharacterData data;
|
|
private Dictionary<string, OutfitData> outfitData;
|
|
public Dictionary<string, Texture> customMaps = new Dictionary<string, Texture>();
|
|
public bool isDirty { get; private set; }
|
|
public MergedMaterialData[] materialData;
|
|
public List<RenderTexture> renderTextures = new List<RenderTexture>();
|
|
|
|
public enum LoadMode { OnStartAndOnValidate, OnStart, Manual }
|
|
public LoadMode loadMode;
|
|
|
|
public bool async;
|
|
|
|
[Tooltip ("Allows for this character to have the fully customized. Great for Main Characters that will change often or for the Character Creator. NOT RECOMMENDED for characters that will never change")]
|
|
public bool liveEditMode;
|
|
public bool isloading;
|
|
|
|
|
|
#if MAGICACLOTH2
|
|
//MagicaCloth
|
|
private MagicaCloth2.ColliderComponent[] ClothColliders;
|
|
#endif
|
|
|
|
|
|
private void OnValidate()
|
|
{
|
|
if (Application.isPlaying && gameObject.scene.isLoaded && loadMode == LoadMode.OnStartAndOnValidate && isStarted)
|
|
{
|
|
Invoke("LoadFromObject", 0f);
|
|
}
|
|
|
|
}
|
|
|
|
public void EditMode()
|
|
{
|
|
if (liveEditMode) BoZo_TextureBuilder.AddOutfit(this);
|
|
if (!liveEditMode) BoZo_TextureBuilder.RemoveBuilder(this);
|
|
}
|
|
|
|
private void Awake()
|
|
{
|
|
Init();
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
if (Application.isPlaying && gameObject.scene.isLoaded && !mergeBase)
|
|
{
|
|
Invoke("EditMode", 0f);
|
|
}
|
|
InitClothColliders();
|
|
if (!liveEditMode) mergedMode = true;
|
|
if (mergeBase) mergedMode = false;
|
|
if (loadMode == LoadMode.OnStart || loadMode == LoadMode.OnStartAndOnValidate)
|
|
{
|
|
LoadFromObject();
|
|
}
|
|
isStarted = true;
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
foreach (var item in renderTextures)
|
|
{
|
|
item.Release();
|
|
}
|
|
|
|
liveEditMode = false;
|
|
EditMode();
|
|
}
|
|
|
|
#region Initalizers
|
|
|
|
public void Init()
|
|
{
|
|
|
|
if (initalized) return;
|
|
|
|
if (CharacterBody == null)
|
|
{
|
|
Debug.LogWarning("Outfit System does not have a Rig assigned please assign one to prevent this warning", gameObject);
|
|
Debug.LogWarning("Attempting auto rig assignment...");
|
|
var skinnedMeshes = GetComponentsInChildren<SkinnedMeshRenderer>(true);
|
|
foreach (var item in skinnedMeshes)
|
|
{
|
|
if (item.name == "BMAC_Body")
|
|
{
|
|
CharacterBody = item;
|
|
Debug.Log("Rig Found Successfully!");
|
|
break;
|
|
}
|
|
}
|
|
Debug.LogError("Search Failed. Please Assign Mannually", gameObject);
|
|
return;
|
|
}
|
|
|
|
CharacterRenderBounds = CharacterBody.localBounds;
|
|
|
|
|
|
InitBoneMap();
|
|
InitBodyShapes();
|
|
InitBodyMods();
|
|
|
|
|
|
initalized = true;
|
|
}
|
|
|
|
private void InitBoneMap()
|
|
{
|
|
boneMap.Clear();
|
|
foreach (Transform bone in CharacterBody.bones)
|
|
{
|
|
if (boneMap.ContainsKey(bone.name) == false)
|
|
{
|
|
boneMap.Add(bone.name, bone);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private void InitBodyShapes()
|
|
{
|
|
var body = GetOutfit("Body");
|
|
|
|
bodyShapes.Clear();
|
|
tagShapes.Clear();
|
|
|
|
Mesh mesh;
|
|
int blendShapeCount = 0;
|
|
|
|
if (body != null)
|
|
{
|
|
|
|
mesh = body.skinnedRenderer.sharedMesh;
|
|
blendShapeCount = body.skinnedRenderer.sharedMesh.blendShapeCount;
|
|
}
|
|
else
|
|
{
|
|
mesh = CharacterBody.sharedMesh;
|
|
blendShapeCount = CharacterBody.sharedMesh.blendShapeCount;
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < blendShapeCount; i++)
|
|
{
|
|
var blendFullName = mesh.GetBlendShapeName(i);
|
|
var blendName = mesh.GetBlendShapeName(i);
|
|
|
|
//removing nameshape that maya gives
|
|
var sort = blendName.Split(".");
|
|
if (sort.Length > 1) { blendName = sort[1]; }
|
|
|
|
|
|
sort = blendName.Split("_");
|
|
if (sort.Length > 1)
|
|
{
|
|
if (sort[0] == "Shape") { bodyShapes.Add(sort[1], i); }
|
|
if (sort[0] == "Tag") { tagShapes.Add(sort[1], i); }
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private void InitBodyMods()
|
|
{
|
|
var bodyMods = new List<BodyShapeModifier>(GetComponentsInChildren<BodyShapeModifier>());
|
|
bodyModifiers.Clear();
|
|
for (int i = 0; i < bodyMods.Count; i++)
|
|
{
|
|
bodyModifiers.Add(bodyMods[i].name, bodyMods[i]);
|
|
}
|
|
}
|
|
|
|
private void InitFaceShapes()
|
|
{
|
|
var head = GetOutfit("Head");
|
|
|
|
faceShapes.Clear();
|
|
|
|
Mesh mesh;
|
|
if (head != null)
|
|
{
|
|
mesh = head.skinnedRenderer.sharedMesh;
|
|
}
|
|
else
|
|
{
|
|
mesh = CharacterBody.sharedMesh;
|
|
}
|
|
|
|
var blendShapeCount = mesh.blendShapeCount;
|
|
|
|
for (int i = 0; i < blendShapeCount; i++)
|
|
{
|
|
var blendFullName = mesh.GetBlendShapeName(i);
|
|
var blendName = mesh.GetBlendShapeName(i);
|
|
|
|
//removing nameshape that maya gives
|
|
var sort = blendName.Split(".");
|
|
if (sort.Length > 1) { blendName = sort[1]; }
|
|
|
|
sort = blendName.Split("_");
|
|
if (sort.Length > 1)
|
|
{
|
|
if (sort[0] == "Shape") { faceShapes.Add(sort[1], i); }
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
private void InitClothColliders()
|
|
{
|
|
#if MAGICACLOTH2
|
|
ClothColliders = GetComponentsInChildren<MagicaCloth2.ColliderComponent>();
|
|
#endif
|
|
}
|
|
|
|
#region Saving and Loading
|
|
|
|
|
|
|
|
public void LoadFromObject(CharacterObject saveData)
|
|
{
|
|
if (isloading) return;
|
|
characterData = saveData;
|
|
LoadFromObject();
|
|
}
|
|
|
|
[ContextMenu("Load")]
|
|
public void LoadFromObject()
|
|
{
|
|
if (isloading) return;
|
|
if (characterData)
|
|
{
|
|
if (_characterData != characterData)
|
|
{
|
|
SaveID = characterData.name;
|
|
|
|
if (mergedMode)
|
|
{
|
|
data = new CharacterData(characterData.GetCharacterData());
|
|
_characterData = characterData;
|
|
isDirty = true;
|
|
MergeCharacter();
|
|
}
|
|
else
|
|
{
|
|
_characterData = characterData;
|
|
LoadCharacter(characterData.GetCharacterData());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
[ContextMenu("LoadByID")]
|
|
public void LoadFromID()
|
|
{
|
|
LoadFromID(SaveID);
|
|
}
|
|
|
|
public void LoadFromID(string saveName)
|
|
{
|
|
if (string.IsNullOrEmpty(saveName)) return;
|
|
SaveID = saveName;
|
|
|
|
var data = BMAC_SaveSystem.GetDataFromID(SaveID);
|
|
if (data == null) return;
|
|
|
|
LoadCharacter(data);
|
|
}
|
|
|
|
public async void LoadCharacter(CharacterData data)
|
|
{
|
|
if (mergedMode)
|
|
{
|
|
this.data = data;
|
|
isDirty = true;
|
|
MergeCharacter();
|
|
}
|
|
else
|
|
{
|
|
await BMAC_SaveSystem.LoadCharacter(this, data, false, async);
|
|
}
|
|
}
|
|
|
|
[ContextMenu("SaveToObject")]
|
|
public void SaveToObject()
|
|
{
|
|
if (!characterData)
|
|
{
|
|
Debug.LogWarning("Character Data Field is empty. Please provide a BSMC_CharacterObject to " + transform.name);
|
|
return;
|
|
}
|
|
BMAC_SaveSystem.SaveCharacter(this, characterData.GetCharacterData().characterName, characterData.GetCharacterIcon());
|
|
}
|
|
|
|
[ContextMenu("SaveByID")]
|
|
|
|
public void SaveByID()
|
|
{
|
|
SaveByID(SaveID);
|
|
}
|
|
|
|
public void SaveByID(string characterName)
|
|
{
|
|
if (string.IsNullOrEmpty(characterName))
|
|
{
|
|
Debug.LogWarning("No ID provided saving aborted");
|
|
return;
|
|
}
|
|
|
|
//Creating EmptyIcon
|
|
if (!System.IO.File.Exists(BMAC_SaveSystem.iconFilePath + "/" + characterName + ".png"))
|
|
{
|
|
Texture2D tex = new Texture2D(2, 2, TextureFormat.RGBA32, false);
|
|
byte[] bytes = tex.EncodeToPNG();
|
|
System.IO.File.WriteAllBytes(BMAC_SaveSystem.iconFilePath + "/" + characterName + ".png", bytes);
|
|
}
|
|
|
|
BMAC_SaveSystem.SaveCharacter(this, characterName);
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
#region Outfit Removeal
|
|
public void RemoveOutfit(Outfit outfit, bool destory)
|
|
{
|
|
if (Outfits.TryGetValue(outfit.Type, out Outfit currentOutfitInSlot))
|
|
{
|
|
if (destory == true && currentOutfitInSlot != null)
|
|
{
|
|
currentOutfitInSlot.ReturnBones();
|
|
Destroy(currentOutfitInSlot.gameObject);
|
|
Outfits[outfit.Type] = null;
|
|
}
|
|
}
|
|
RemoveHide(outfit);
|
|
RemoveTags(outfit.tags);
|
|
OnOutfitChanged?.Invoke(null);
|
|
OnOutfitRemoved?.Invoke(outfit);
|
|
}
|
|
|
|
|
|
public void RemoveOutfit(OutfitType type, bool destory)
|
|
{
|
|
|
|
if (mergedMode && !isloading)
|
|
{
|
|
outfitData.Remove(type.name);
|
|
data.outfitDatas = outfitData.Values.ToList();
|
|
isDirty = true;
|
|
if (autoUpdate) MergeCharacter();
|
|
return;
|
|
}
|
|
|
|
if (Outfits.TryGetValue(type, out Outfit currentOutfitInSlot))
|
|
{
|
|
if (destory == true && currentOutfitInSlot != null)
|
|
{
|
|
currentOutfitInSlot.ReturnBones();
|
|
Destroy(currentOutfitInSlot.gameObject);
|
|
Outfits[type] = null;
|
|
}
|
|
}
|
|
|
|
if (currentOutfitInSlot)
|
|
{
|
|
RemoveHide(currentOutfitInSlot);
|
|
RemoveTags(currentOutfitInSlot.tags);
|
|
}
|
|
OnOutfitChanged?.Invoke(null);
|
|
OnOutfitRemoved?.Invoke(currentOutfitInSlot);
|
|
}
|
|
|
|
public void RemoveTags(string[] outfitTags)
|
|
{
|
|
foreach (var item in outfitTags)
|
|
{
|
|
tags.Remove(item);
|
|
}
|
|
OnTagsChanged?.Invoke(tags);
|
|
//tags.RemoveAll(item => removedOutfit.tags.Contains(item));
|
|
}
|
|
|
|
public void RemoveAllOutfits()
|
|
{
|
|
List<Outfit> list = new List<Outfit>(Outfits.Values);
|
|
foreach (var item in list)
|
|
{
|
|
if (item == null) continue;
|
|
|
|
Destroy(item.gameObject);
|
|
}
|
|
Outfits.Clear();
|
|
tags.Clear();
|
|
hiddenTypes.Clear();
|
|
OnOutfitChanged?.Invoke(null);
|
|
}
|
|
#endregion
|
|
|
|
public Outfit InstantiateOutfit(Outfit outfit)
|
|
{
|
|
var inst = Instantiate(outfit, transform);
|
|
inst.name = inst.name.Replace("(Clone)", "");
|
|
return inst;
|
|
}
|
|
|
|
//Legacy Method
|
|
public void AttachSkinnedOutfit(Outfit outfit)
|
|
{
|
|
AttachOutfit(outfit);
|
|
}
|
|
|
|
public void ReattachOutfit(Outfit outfit)
|
|
{
|
|
AddTags(outfit.tags);
|
|
SetHide(outfit);
|
|
ApplyTags();
|
|
OnOutfitChanged?.Invoke(outfit);
|
|
}
|
|
|
|
public void AttachOutfit(Outfit outfit)
|
|
{
|
|
if (!initalized) return;
|
|
|
|
|
|
if (mergedMode)
|
|
{
|
|
if(outfitData == null)
|
|
{
|
|
Destroy(outfit.gameObject);
|
|
return;
|
|
}
|
|
|
|
outfitData[outfit.Type.name] = outfit.GetOutfitData();
|
|
data.outfitDatas = outfitData.Values.ToList();
|
|
isDirty = true;
|
|
Destroy(outfit.gameObject);
|
|
if (autoUpdate) MergeCharacter();
|
|
return;
|
|
}
|
|
|
|
if (!KnownOutfitTypes.ContainsKey(outfit.Type.name))
|
|
{
|
|
KnownOutfitTypes.Add(outfit.Type.name, outfit.Type);
|
|
}
|
|
|
|
|
|
//check if an outfit is already in that slot and replace it
|
|
ReplaceOutfit(outfit);
|
|
|
|
|
|
//Merging outfit bones or attaching outfit to specified bone
|
|
MergeBones(outfit);
|
|
|
|
|
|
//Adjusting Mesh bounds so the meshes don't unexpectingly disappear.
|
|
if (outfit.skinnedRenderer)
|
|
{
|
|
UpdateCharacterBounds(outfit);
|
|
}
|
|
|
|
|
|
//Apply the Current Body Morphs to the Outfit
|
|
ApplyShapesToOufit(outfit);
|
|
|
|
//If Head get its Morphs
|
|
if (outfit.Type.name == "Head") { InitFaceShapes(); }
|
|
|
|
//If Body get its Morphs
|
|
if (outfit.Type.name == "Body") { InitBodyShapes(); }
|
|
|
|
SetHide(outfit);
|
|
AddTags(outfit.tags);
|
|
ApplyTags();
|
|
|
|
if (hiddenTypes.ContainsKey(outfit.Type)) outfit.gameObject.SetActive(false);
|
|
|
|
OnOutfitChanged?.Invoke(outfit);
|
|
|
|
|
|
}
|
|
|
|
private void ApplyShapesToOufit(Outfit outfit)
|
|
{
|
|
var keys = new List<string>(bodyShapes.Keys);
|
|
for (int i = 0; i < keys.Count; i++)
|
|
{
|
|
GetBodyShapeValues();
|
|
outfit.SetShape(keys[i], GetShape(keys[i]));
|
|
}
|
|
}
|
|
|
|
public void SetShape(string key, float value)
|
|
{
|
|
SkinnedMeshRenderer renderer = null;
|
|
var index = -1;
|
|
|
|
var body = GetOutfit("Body");
|
|
var head = GetOutfit("Head");
|
|
|
|
if (bodyShapes.TryGetValue(key, out int bodyValue) && body)
|
|
{
|
|
index = bodyValue;
|
|
if (body != null) { renderer = body.skinnedRenderer; }
|
|
}
|
|
else if (faceShapes.TryGetValue(key, out int faceValue) && head)
|
|
{
|
|
index = faceValue;
|
|
if (head != null) { renderer = head.skinnedRenderer; }
|
|
}
|
|
else
|
|
{
|
|
if (bodyShapes.TryGetValue(key, out int blendValue) && mergedMode)
|
|
{
|
|
index = blendValue;
|
|
renderer = CharacterBody;
|
|
}
|
|
|
|
}
|
|
|
|
if (renderer != null) renderer.SetBlendShapeWeight(index, value);
|
|
|
|
OnShapeChanged?.Invoke(key, value);
|
|
}
|
|
|
|
public void AddTags(string[] tags)
|
|
{
|
|
this.tags.AddRange(tags);
|
|
OnTagsChanged?.Invoke(this.tags);
|
|
}
|
|
|
|
public void SetHide(Outfit outfit)
|
|
{
|
|
if (outfit == null) return;
|
|
foreach (var item in outfit.HideTypes)
|
|
{
|
|
if (hiddenTypes.ContainsKey(item))
|
|
{
|
|
hiddenTypes[item].Add(outfit);
|
|
}
|
|
else
|
|
{
|
|
hiddenTypes.Add(item, new List<Outfit>());
|
|
hiddenTypes[item].Add(outfit);
|
|
}
|
|
var hidden = GetOutfit(item);
|
|
if (hidden) hidden.gameObject.SetActive(false);
|
|
}
|
|
|
|
}
|
|
|
|
public void RemoveHide(Outfit outfit)
|
|
{
|
|
if (outfit == null) return;
|
|
foreach (var item in outfit.HideTypes)
|
|
{
|
|
if (hiddenTypes.ContainsKey(item))
|
|
{
|
|
hiddenTypes[item].Remove(outfit);
|
|
if (hiddenTypes[item].Count == 0)
|
|
{
|
|
hiddenTypes.Remove(item);
|
|
var hidden = GetOutfit(item);
|
|
if (hidden) hidden.gameObject.SetActive(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ApplyTags()
|
|
{
|
|
// This method is intented for when you merged the body but still want to attach outfits dynamically
|
|
if (GetOutfit("Body") != null) { return; }
|
|
|
|
var shapes = new List<string>(tagShapes.Keys);
|
|
if (!CharacterBody) return;
|
|
if (CharacterBody.sharedMesh.blendShapeCount == 0) return;
|
|
for (int i = 0; i < shapes.Count; i++)
|
|
{
|
|
var yes = ContainsTag(shapes[i]);
|
|
if (yes) { CharacterBody.SetBlendShapeWeight(tagShapes[shapes[i]], 100); }
|
|
else { CharacterBody.SetBlendShapeWeight(tagShapes[shapes[i]], 0); }
|
|
}
|
|
}
|
|
|
|
public void SetStance(float value)
|
|
{
|
|
foreach (AnimatorControllerParameter param in animator.parameters)
|
|
{
|
|
if (param.name == "Stance") animator.SetFloat("Stance", value);
|
|
}
|
|
stance = value;
|
|
}
|
|
|
|
public void SetHeight(float value)
|
|
{
|
|
//Check if has heels animaton property
|
|
bool HasHeeledParamter = false;
|
|
foreach (AnimatorControllerParameter param in animator.parameters)
|
|
{
|
|
if (param.name == "HeelHeight") HasHeeledParamter = true;
|
|
}
|
|
|
|
//remove Previous Height
|
|
transform.localPosition = new Vector3(transform.localPosition.x, transform.localPosition.y - height, transform.localPosition.z);
|
|
//Apply New Height
|
|
height = value;
|
|
if (HasHeeledParamter) heeledHeight = animator.GetFloat("HeelHeight");
|
|
if (!muteHeightChange)
|
|
{
|
|
transform.localPosition = new Vector3(transform.localPosition.x, transform.localPosition.y + value, transform.localPosition.z);
|
|
if (HasHeeledParamter)
|
|
{
|
|
|
|
animator.SetFloat("HeelHeight", 0);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
public void MuteHeightChange(bool value)
|
|
{
|
|
if (value == muteHeightChange) return;
|
|
|
|
foreach (AnimatorControllerParameter param in animator.parameters)
|
|
{
|
|
if(param.name == "HeelHeight") animator.SetFloat("HeelHeight", heeledHeight);
|
|
}
|
|
|
|
muteHeightChange = value;
|
|
|
|
var height = this.height;
|
|
if (muteHeightChange)
|
|
{
|
|
height = -height;
|
|
}
|
|
|
|
transform.localPosition = new Vector3(transform.localPosition.x, transform.localPosition.y + height, transform.localPosition.z);
|
|
}
|
|
|
|
private void ReplaceOutfit(Outfit outfit)
|
|
{
|
|
if (Outfits.TryGetValue(outfit.Type, out Outfit currentOutfitInSlot))
|
|
{
|
|
if (Outfits[outfit.Type])
|
|
{
|
|
if (outfit.transform != Outfits[outfit.Type].transform)
|
|
{
|
|
currentOutfitInSlot.ReturnBones();
|
|
Destroy(currentOutfitInSlot.gameObject);
|
|
}
|
|
else
|
|
{
|
|
OnOutfitChanged?.Invoke(outfit);
|
|
|
|
}
|
|
}
|
|
Outfits[outfit.Type] = outfit;
|
|
}
|
|
else
|
|
{
|
|
Outfits.Add(outfit.Type, outfit);
|
|
}
|
|
}
|
|
|
|
private void MergeBones(Outfit outfit)
|
|
{
|
|
if (outfit.additionalBones.Length != 0)
|
|
{
|
|
for (int i = 0; i < outfit.additionalBones.Length; i++)
|
|
{
|
|
var bone = outfit.additionalBones[i];
|
|
var newParent = GetBones()[bone.parent.name];
|
|
|
|
bone.parent.SetPositionAndRotation(newParent.position, newParent.rotation);
|
|
|
|
bone.parent = newParent;
|
|
|
|
foreach (Transform item in bone.GetComponentsInChildren<Transform>(true))
|
|
{
|
|
if (boneMap.ContainsKey(bone.name))
|
|
{
|
|
boneMap[item.name] = item;
|
|
}
|
|
else
|
|
{
|
|
boneMap.Add(item.name, item);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (var smr in outfit.skinnedRenderers)
|
|
{
|
|
var renderer = smr;
|
|
|
|
if (outfit.AttachPoint == "" && renderer)
|
|
{
|
|
var oldBones = renderer.bones.ToArray();
|
|
var newBones = new Transform[renderer.bones.Length];
|
|
for (int i = 0; i < oldBones.Length; i++)
|
|
{
|
|
var bone = oldBones[i];
|
|
boneMap.TryGetValue(bone.name, out Transform baseBone);
|
|
newBones[i] = baseBone;
|
|
}
|
|
renderer.bones = newBones;
|
|
renderer.rootBone = CharacterBody.rootBone;
|
|
}
|
|
else
|
|
{
|
|
Transform bone = null;
|
|
try
|
|
{
|
|
bone = boneMap[outfit.AttachPoint];
|
|
}
|
|
catch
|
|
{
|
|
Debug.LogError(name + " is missing " + outfit.AttachPoint + " that " + outfit.name + " requires");
|
|
return;
|
|
}
|
|
|
|
|
|
outfit.transform.parent = bone.transform;
|
|
outfit.transform.position = bone.position;
|
|
outfit.transform.rotation = bone.rotation;
|
|
outfit.transform.localScale = Vector3.one;
|
|
}
|
|
}
|
|
|
|
outfit.Initalized = true;
|
|
|
|
if (outfit.outfitRenderer && outfit.AttachPoint != "")
|
|
{
|
|
Transform bone = null;
|
|
try
|
|
{
|
|
bone = boneMap[outfit.AttachPoint];
|
|
}
|
|
catch
|
|
{
|
|
Debug.LogError(name + " is missing " + outfit.AttachPoint + " that " + outfit.name + " requires");
|
|
return;
|
|
}
|
|
|
|
|
|
outfit.transform.parent = bone.transform;
|
|
outfit.transform.position = bone.position;
|
|
outfit.transform.rotation = bone.rotation;
|
|
outfit.transform.localScale = Vector3.one;
|
|
}
|
|
}
|
|
|
|
public void UpdateCharacterBounds(Outfit outfit)
|
|
{
|
|
if (outfit.AttachPoint != "") return;
|
|
foreach (var smr in outfit.skinnedRenderers)
|
|
{
|
|
smr.localBounds = CharacterRenderBounds;
|
|
}
|
|
}
|
|
|
|
public bool ContainsTag(string tag)
|
|
{
|
|
return tags.Contains(tag);
|
|
}
|
|
|
|
#region Getters
|
|
|
|
public Outfit GetOutfit(OutfitType outfitType)
|
|
{
|
|
if (Outfits.TryGetValue(outfitType, out Outfit item))
|
|
{
|
|
return item;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public Outfit GetOutfit(string outfitType)
|
|
{
|
|
if (KnownOutfitTypes.TryGetValue(outfitType, out OutfitType type))
|
|
{
|
|
if (Outfits.TryGetValue(type, out Outfit item))
|
|
{
|
|
return item;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public List<Outfit> GetOutfits()
|
|
{
|
|
return new List<Outfit>(Outfits.Values);
|
|
}
|
|
|
|
public List<string> GetShapes()
|
|
{
|
|
return bodyShapes.Keys.ToList();
|
|
}
|
|
|
|
public List<string> GetFaceShapes()
|
|
{
|
|
return faceShapes.Keys.ToList();
|
|
}
|
|
|
|
#if MAGICACLOTH2
|
|
public MagicaCloth2.ColliderComponent[] GetClothColliders()
|
|
{
|
|
return ClothColliders;
|
|
}
|
|
#endif
|
|
|
|
public float GetShape(string key)
|
|
{
|
|
if (bodyShapes.TryGetValue(key, out int value))
|
|
{
|
|
var body = GetOutfit("Body");
|
|
if (body != null)
|
|
{
|
|
var weightValue = body.skinnedRenderer.GetBlendShapeWeight(value);
|
|
return weightValue;
|
|
}
|
|
else return -10000;
|
|
}
|
|
else return -10000;
|
|
}
|
|
|
|
public Dictionary<string, BodyShapeModifier> GetMods()
|
|
{
|
|
return bodyModifiers;
|
|
}
|
|
public Dictionary<string, Transform> GetBones()
|
|
{
|
|
return boneMap;
|
|
}
|
|
|
|
public float GetShapeValue(string key)
|
|
{
|
|
var weight = -1f;
|
|
|
|
var body = GetOutfit("Body");
|
|
|
|
if (body == null) return -1;
|
|
if (bodyShapes.TryGetValue(key, out int bodyValue))
|
|
{
|
|
weight = body.skinnedRenderer.GetBlendShapeWeight(bodyValue);
|
|
}
|
|
else if (faceShapes.TryGetValue(key, out int faceValue))
|
|
{
|
|
var face = GetOutfit("Head");
|
|
if (face == null) return -1;
|
|
weight = face.skinnedRenderer.GetBlendShapeWeight(faceValue);
|
|
}
|
|
|
|
return weight;
|
|
}
|
|
|
|
public float GetShapeValue(int key)
|
|
{
|
|
SkinnedMeshRenderer renderer;
|
|
var body = GetOutfit("Body");
|
|
if (body == null) renderer = CharacterBody;
|
|
else renderer = body.skinnedRenderer;
|
|
|
|
var weightValue = renderer.GetBlendShapeWeight(key);
|
|
return weightValue;
|
|
}
|
|
|
|
public Dictionary<string, float> GetBodyShapeValues()
|
|
{
|
|
var bodyShapeValues = new Dictionary<string, float>();
|
|
var shapes = bodyShapes.Values.ToArray();
|
|
var keys = bodyShapes.Keys.ToArray();
|
|
|
|
SkinnedMeshRenderer renderer;
|
|
var body = GetOutfit("Body");
|
|
if (body == null) renderer = CharacterBody;
|
|
else renderer = body.skinnedRenderer;
|
|
|
|
for (int i = 0; i < shapes.Length; i++)
|
|
{
|
|
var weightValue = renderer.GetBlendShapeWeight(shapes[i]);
|
|
bodyShapeValues.Add(keys[i], weightValue);
|
|
}
|
|
|
|
return bodyShapeValues;
|
|
}
|
|
|
|
public Dictionary<string, float> GetFaceShapeValues()
|
|
{
|
|
var faceShapeValues = new Dictionary<string, float>();
|
|
var shapes = faceShapes.Values.ToArray();
|
|
var keys = faceShapes.Keys.ToArray();
|
|
|
|
SkinnedMeshRenderer renderer;
|
|
var head = GetOutfit("Head");
|
|
if (head == null) renderer = CharacterBody;
|
|
else renderer = head.skinnedRenderer;
|
|
|
|
|
|
for (int i = 0; i < shapes.Length; i++)
|
|
{
|
|
var weightValue = renderer.GetBlendShapeWeight(shapes[i]);
|
|
faceShapeValues.Add(keys[i], weightValue);
|
|
}
|
|
|
|
return faceShapeValues;
|
|
}
|
|
#endregion
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
public void SoftAttach(Outfit outfit)
|
|
{
|
|
//For Attaching outfits during in the Editor
|
|
if (CharacterBody == null)
|
|
{
|
|
Debug.LogWarning("Soft Attach attempted but OuftitSystem did not have a CharacterBody please assign in the inspector", gameObject);
|
|
return;
|
|
}
|
|
|
|
Dictionary<string, Transform> boneMap = new Dictionary<string, Transform>();
|
|
foreach (Transform bone in CharacterBody.bones)
|
|
{
|
|
if (boneMap.ContainsKey(bone.name) == false)
|
|
{
|
|
boneMap.Add(bone.name, bone);
|
|
}
|
|
}
|
|
|
|
var renderers = outfit.GetComponentsInChildren<SkinnedMeshRenderer>();
|
|
|
|
|
|
//Already Attached
|
|
if (outfit.originalBones.Length > 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < renderers.Length; i++)
|
|
{
|
|
renderers[i].localBounds = CharacterBody.localBounds;
|
|
|
|
if (outfit.AttachPoint == "" && renderers[i])
|
|
{
|
|
if (outfit.Initalized == false)
|
|
{
|
|
outfit.originalBones = renderers[i].bones;
|
|
outfit.originalRootBone = renderers[i].rootBone;
|
|
|
|
var oldBones = renderers[i].bones.ToArray();
|
|
var newBones = new Transform[renderers[i].bones.Length];
|
|
for (int b = 0; b < oldBones.Length; b++)
|
|
{
|
|
var bone = oldBones[b];
|
|
boneMap.TryGetValue(bone.name, out newBones[b]);
|
|
}
|
|
renderers[i].bones = newBones;
|
|
renderers[i].rootBone = CharacterBody.rootBone;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
#endif
|
|
|
|
public SkinnedMeshRenderer GetCharacterBody() { return CharacterBody; }
|
|
public void SetCharacterBody(GameObject newBody)
|
|
{
|
|
if (newBody == null) return;
|
|
var smr = newBody.GetComponentInChildren<SkinnedMeshRenderer>();
|
|
if (smr == null) return;
|
|
|
|
RemoveAllOutfits();
|
|
DestroyImmediate(CharacterBody.transform.parent.gameObject);
|
|
|
|
newBody.transform.parent = transform;
|
|
newBody.transform.localPosition = Vector3.zero;
|
|
newBody.transform.localRotation = Quaternion.identity;
|
|
newBody.transform.localScale = Vector3.one;
|
|
|
|
CharacterBody = smr;
|
|
|
|
|
|
InitBoneMap();
|
|
InitBodyShapes();
|
|
InitBodyMods();
|
|
InitClothColliders();
|
|
|
|
BMAC_SaveSystem.LoadBodyMods(this, data);
|
|
OnRigChanged?.Invoke(CharacterBody);
|
|
|
|
Invoke("RebindBody", 0);
|
|
|
|
SetStance(data.stance);
|
|
}
|
|
|
|
public void SetRenderTextures(List<RenderTexture> rt)
|
|
{
|
|
foreach (var item in renderTextures)
|
|
{
|
|
item.Release();
|
|
}
|
|
renderTextures = new List<RenderTexture>(rt);
|
|
}
|
|
|
|
public void RebindBody()
|
|
{
|
|
animator.Rebind();
|
|
|
|
foreach (AnimatorControllerParameter param in animator.parameters)
|
|
{
|
|
if (param.name == "HeelHeight") animator.SetFloat("HeelHeight", heeledHeight);
|
|
if (param.name == "Stance") animator.SetFloat("Stance", stance);
|
|
}
|
|
|
|
animator.enabled = true;
|
|
}
|
|
|
|
[ContextMenu("Merge")]
|
|
public void MergeCharacter()
|
|
{
|
|
if (Application.isPlaying && gameObject.scene.isLoaded)
|
|
{
|
|
var optimizer = new BoZo_CharacterOptimizer();
|
|
|
|
outfitData = new Dictionary<string, OutfitData>();
|
|
data = BMAC_SaveSystem.GetCharacterData(this);
|
|
foreach (var item in data.outfitDatas)
|
|
{
|
|
var split = item.outfit.Split("/");
|
|
outfitData[split[0]] = item;
|
|
}
|
|
|
|
if (mergedMode && isDirty)
|
|
{
|
|
BoZo_TextureBuilder.QuickBuild(this);
|
|
}
|
|
else
|
|
{
|
|
var preMergedData = BMAC_SaveSystem.GetCharacterData(this);
|
|
data = preMergedData;
|
|
|
|
data = BMAC_SaveSystem.GetCharacterData(this);
|
|
BoZo_TextureBuilder.QuickBuild(this);
|
|
mergedMode = true;
|
|
|
|
|
|
//optimizer.OptimizeCharacter(this, preMergedData);
|
|
//mergedMode = true;
|
|
}
|
|
liveEditMode = false;
|
|
EditMode();
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning("For stability reason Character Merging is only available in Play Mode");
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
[ContextMenu("SaveToPrefab")]
|
|
public void SaveCharacterToPrefab()
|
|
{
|
|
#if UNITY_EDITOR
|
|
if (Application.isPlaying && gameObject.scene.isLoaded)
|
|
{
|
|
var optimizer = new BoZo_CharacterOptimizer();
|
|
|
|
if (mergedMode && isDirty)
|
|
{
|
|
BoZo_TextureBuilder.QuickBuild(this, true);
|
|
}
|
|
else
|
|
{
|
|
data = BMAC_SaveSystem.GetCharacterData(this);
|
|
|
|
BoZo_TextureBuilder.QuickBuild(this, true);
|
|
mergedMode = true;
|
|
}
|
|
|
|
liveEditMode = false;
|
|
EditMode();
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning("For stability reason Character Merging is only available in Play Mode");
|
|
}
|
|
|
|
#endif
|
|
}
|
|
|
|
}
|
|
}
|