221 lines
7.6 KiB
C#
221 lines
7.6 KiB
C#
|
// Magica Cloth 2.
|
|||
|
// Copyright (c) 2023 MagicaSoft.
|
|||
|
// https://magicasoft.jp
|
|||
|
using System.Collections.Generic;
|
|||
|
using UnityEngine;
|
|||
|
|
|||
|
namespace MagicaCloth2
|
|||
|
{
|
|||
|
/// <summary>
|
|||
|
/// Dress-up sample.
|
|||
|
/// </summary>
|
|||
|
public class RuntimeDressUpDemo : MonoBehaviour
|
|||
|
{
|
|||
|
/// <summary>
|
|||
|
/// Avatar to change clothes.
|
|||
|
/// </summary>
|
|||
|
public GameObject targetAvatar;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Hair prefab with MagicaCloth set in advance.
|
|||
|
/// </summary>
|
|||
|
public GameObject hariEqupPrefab;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Clothes prefab with MagicaCloth set in advance.
|
|||
|
/// </summary>
|
|||
|
public GameObject bodyEquipPrefab;
|
|||
|
|
|||
|
//=========================================================================================
|
|||
|
/// <summary>
|
|||
|
/// Bones dictionary of avatars to dress up.
|
|||
|
/// </summary>
|
|||
|
Dictionary<string, Transform> targetAvatarBoneMap = new Dictionary<string, Transform>();
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Information class for canceling dress-up.
|
|||
|
/// </summary>
|
|||
|
class EquipInfo
|
|||
|
{
|
|||
|
public GameObject equipObject;
|
|||
|
public List<ColliderComponent> colliderList;
|
|||
|
|
|||
|
public bool IsValid() => equipObject != null;
|
|||
|
}
|
|||
|
EquipInfo hairEquipInfo = new EquipInfo();
|
|||
|
EquipInfo bodyEquipInfo = new EquipInfo();
|
|||
|
|
|||
|
//=========================================================================================
|
|||
|
private void Awake()
|
|||
|
{
|
|||
|
Init();
|
|||
|
}
|
|||
|
|
|||
|
void Start()
|
|||
|
{
|
|||
|
}
|
|||
|
|
|||
|
void Update()
|
|||
|
{
|
|||
|
}
|
|||
|
|
|||
|
//=========================================================================================
|
|||
|
public void OnHairEquipButton()
|
|||
|
{
|
|||
|
if (hairEquipInfo.IsValid())
|
|||
|
Remove(hairEquipInfo);
|
|||
|
else
|
|||
|
Equip(hariEqupPrefab, hairEquipInfo);
|
|||
|
}
|
|||
|
|
|||
|
public void OnBodyEquipButton()
|
|||
|
{
|
|||
|
if (bodyEquipInfo.IsValid())
|
|||
|
Remove(bodyEquipInfo);
|
|||
|
else
|
|||
|
Equip(bodyEquipPrefab, bodyEquipInfo);
|
|||
|
}
|
|||
|
|
|||
|
//=========================================================================================
|
|||
|
/// <summary>
|
|||
|
/// Create an avatar bone dictionary in advance.
|
|||
|
/// </summary>
|
|||
|
void Init()
|
|||
|
{
|
|||
|
Debug.Assert(targetAvatar);
|
|||
|
|
|||
|
// Create all bone maps for the target avatar
|
|||
|
foreach (Transform bone in targetAvatar.GetComponentsInChildren<Transform>())
|
|||
|
{
|
|||
|
if (targetAvatarBoneMap.ContainsKey(bone.name) == false)
|
|||
|
{
|
|||
|
targetAvatarBoneMap.Add(bone.name, bone);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
Debug.Log($"Duplicate bone name :{bone.name}");
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Equip clothes.
|
|||
|
/// </summary>
|
|||
|
/// <param name="equipPrefab"></param>
|
|||
|
/// <param name="einfo"></param>
|
|||
|
void Equip(GameObject equipPrefab, EquipInfo einfo)
|
|||
|
{
|
|||
|
Debug.Assert(equipPrefab);
|
|||
|
|
|||
|
// Generate a prefab with cloth set up.
|
|||
|
var gobj = Instantiate(equipPrefab, targetAvatar.transform);
|
|||
|
|
|||
|
// All cloth components included in the prefab.
|
|||
|
var clothList = new List<MagicaCloth>(gobj.GetComponentsInChildren<MagicaCloth>());
|
|||
|
|
|||
|
// All collider components included in the prefab.
|
|||
|
var colliderList = new List<ColliderComponent>(gobj.GetComponentsInChildren<ColliderComponent>());
|
|||
|
|
|||
|
// All renderers included in the prefab.
|
|||
|
var skinList = new List<SkinnedMeshRenderer>(gobj.GetComponentsInChildren<SkinnedMeshRenderer>());
|
|||
|
|
|||
|
// First stop the automatic build that is executed with Start().
|
|||
|
// And just in case, it does some initialization called Awake().
|
|||
|
foreach (var cloth in clothList)
|
|||
|
{
|
|||
|
// Normally it is called with Awake(), but if the component is disabled, it will not be executed, so call it manually.
|
|||
|
// Ignored if already run with Awake().
|
|||
|
cloth.Initialize();
|
|||
|
|
|||
|
// Turn off auto-build on Start().
|
|||
|
cloth.DisableAutoBuild();
|
|||
|
}
|
|||
|
|
|||
|
// Swap the bones of the SkinnedMeshRenderer.
|
|||
|
// This process is a general dress-up process for SkinnedMeshRenderer.
|
|||
|
// Comment out this series of processes when performing this process with functions such as other assets.
|
|||
|
foreach (var sren in skinList)
|
|||
|
{
|
|||
|
var bones = sren.bones;
|
|||
|
Transform[] newBones = new Transform[bones.Length];
|
|||
|
|
|||
|
for (int i = 0; i < bones.Length; ++i)
|
|||
|
{
|
|||
|
Transform bone = bones[i];
|
|||
|
if (!targetAvatarBoneMap.TryGetValue(bone.name, out newBones[i]))
|
|||
|
{
|
|||
|
// Is the bone the renderer itself?
|
|||
|
if (bone.name == sren.name)
|
|||
|
{
|
|||
|
newBones[i] = sren.transform;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// bone not found
|
|||
|
Debug.Log($"[SkinnedMeshRenderer({sren.name})] Unable to map bone [{bone.name}] to target skeleton.");
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
sren.bones = newBones;
|
|||
|
|
|||
|
// root bone
|
|||
|
if (targetAvatarBoneMap.ContainsKey(sren.rootBone?.name))
|
|||
|
{
|
|||
|
sren.rootBone = targetAvatarBoneMap[sren.rootBone.name];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Here, replace the bones used by the MagicaCloth component.
|
|||
|
foreach (var cloth in clothList)
|
|||
|
{
|
|||
|
// Replaces a component's transform.
|
|||
|
cloth.ReplaceTransform(targetAvatarBoneMap);
|
|||
|
}
|
|||
|
|
|||
|
// Move all colliders to the new avatar.
|
|||
|
foreach (var collider in colliderList)
|
|||
|
{
|
|||
|
Transform parent = collider.transform.parent;
|
|||
|
if (parent && targetAvatarBoneMap.ContainsKey(parent.name))
|
|||
|
{
|
|||
|
Transform newParent = targetAvatarBoneMap[parent.name];
|
|||
|
|
|||
|
// After changing the parent, you need to write back the local posture and align it.
|
|||
|
var localPosition = collider.transform.localPosition;
|
|||
|
var localRotation = collider.transform.localRotation;
|
|||
|
collider.transform.SetParent(newParent);
|
|||
|
collider.transform.localPosition = localPosition;
|
|||
|
collider.transform.localRotation = localRotation;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Finally let's start building the cloth component.
|
|||
|
foreach (var cloth in clothList)
|
|||
|
{
|
|||
|
// I disabled the automatic build, so I build it manually.
|
|||
|
cloth.BuildAndRun();
|
|||
|
}
|
|||
|
|
|||
|
// Record information for release.
|
|||
|
einfo.equipObject = gobj;
|
|||
|
einfo.colliderList = colliderList;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Removes equipped clothing.
|
|||
|
/// </summary>
|
|||
|
/// <param name="einfo"></param>
|
|||
|
void Remove(EquipInfo einfo)
|
|||
|
{
|
|||
|
Destroy(einfo.equipObject);
|
|||
|
foreach (var c in einfo.colliderList)
|
|||
|
{
|
|||
|
Destroy(c.gameObject);
|
|||
|
}
|
|||
|
|
|||
|
einfo.equipObject = null;
|
|||
|
einfo.colliderList.Clear();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|