Contents
Overview
This section describes how to port MagicaCloth to another character.
This is useful if you want to dress up your game character with different hair and outfits.
It is assumed that the reader is familiar with C# programming in Unity.
Sample scene
Sample scenes are available for the dressing process.
These can be found in the following folders. To test them, select the scene in your render pipeline.
The RuntimeDressUpDemo.cs attached to the RuntimeDressUpDemo in this sample scene is the test code.
This page will also be explained based on the contents of this test code.
The reason for the separate scenes is simply to switch the rendering material.
The sample code included inside is identical.
Sample data
This section describes the data for the sample scene.
Utc_sum_humanoid (Skeleton)
First, there is Utc_sum_humanoid (Skeleton), which is a skeleton-only character.
This character is a Transform-only skeletal character with no hair, clothing, or MagicaCloth set up.
You will be adding hair and clothing to this Utc_sum_humanoid (Skeleton).
Utc_sum_humanoid (Hair)
Prefabricated skeletal character with hair renderer and MagicaCloth set up.
This prefab contains all of the skeleton.
(Actually, only the necessary GameObjects are sufficient.)
Utc_sum_humanoid (Body)
It is prefabricated with a clothing renderer and MagicaCloth set up for the skeletal character.
This prefab contains all of the skeleton.
(Actually, only the necessary GameObjects are sufficient.)
How to change your clothes
To change clothes, follow the steps below.
- Generate prefabrication for dressing
- Call MagicaCloth initialization
- Stop MagicaCloth from building automatically
- Porting Renderer to a skeletal avatar
- Transplanting MagicaCloth to a skeletal avatar
- Transplanting colliders and other items to skeletal avatars
- Start MagicaCloth running
The porting of the Renderer in (4) has nothing to do with the MagicaCloth system.
Therefore, you may use another program or asset.
Release procedure
The following procedure is used to remove a change of clothes.
- Destroy() the Renderer
- Destroy() MagicaCloth
- Destroy() unnecessary colliders
- Destroy() unwanted GameObjects
Basically, all you have to do is Destroy() unnecessary GameObjects, including MagicaCloth.
As for (1), you may use another program or asset as in the dress-up procedure.
Example
This section describes the sample scene RuntimeDressUpDemo.cs.
If you look at the code based on the dress-up and undress-up procedures above, you should be able to get a general idea of what is going on.
// 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();
}
}
}
Create a Transform dictionary
First, create a dictionary of Transforms for the skeletal avatar.
This dictionary is keyed by name.
This dictionary is used to perform bone substitutions.
/// <summary>
/// Bones dictionary of avatars to dress up.
/// </summary>
Dictionary<string, Transform> targetAvatarBoneMap = new Dictionary<string, Transform>();
/// <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}");
}
}
}
Initializing MagicaCloth and stopping automatic construction
First, it’s important to manually call initialization on the MagicaCloth component.
Make sure you initialize it before replacing bones.
Next, pause the automatic cloth construction work.
// 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();
}
SkinnedMeshRenderer porting
Before MagicaCloth, the renderer is ported to the skeletal avatar.
This is done by replacing the bones of the SkinnedMeshRenderer with those of the skeletal avatar.
// 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];
}
}
Note that this process has nothing to do with MagicaCloth.
This means that you can do your own processing in this area or use other dress-up assets.
MagicaCloth component porting
Implant MagicaCloth into the skeletal avatar.
This is done by replacing the internal bones as in SkinnedMeshRenderer.
The replacement is done by passing a pre-created Transform dictionary.
// Here, replace the bones used by the MagicaCloth component.
foreach (var cloth in clothList)
{
// Replaces a component's transform.
cloth.ReplaceTransform(targetAvatarBoneMap);
}
Transplantation of collider
If you are using a collider for MagicaCloth, port that to the skeletal avatar as well.
// 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;
}
}
Start running MagicaCloth
Finally, begin building and running MagicaCloth.
// 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();
}
Release
When a change of clothes is no longer needed, it is released as follows.
This simply Destroy() all GameObjects that are no longer needed.
/// <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();
}
Notes
Please note that this case code does not cover all possibilities.
This case study is for reference only.
For example, GameObjects that exist in the dress-up prefab but not in the skeletal avatar will not be ported in this case.
They will need to be ported with additional processing.
end.