개요
MagicaCloth2는 런타임 구성을 완전히 지원합니다.
여기서는 스크립트를 통해 런타임 중 MagicaCloth 구성 요소를 생성하는 방법을 설명합니다.
이 문서는 Unity에서 C# 프로그래밍에 익숙한 독자를 대상으로 합니다.
샘플 씬
MagicaCloth2의 런타임 구성을 위한 샘플 씬이 제공됩니다.
아래 폴더에서 사용할 렌더 파이프라인에 맞는 씬을 선택하여 테스트할 수 있습니다.
이 샘플 씬에는 RuntimeBuildDemo라는 오브젝트에 RuntimeBuildDemo.cs라는 테스트 코드가 포함되어 있습니다.
이 페이지에서는 해당 테스트 코드의 내용을 추출하여 설명합니다.
씬이 분리된 이유는 단순히 렌더링 머티리얼을 전환하기 위함이며, 내부에 포함된 샘플 코드는 동일합니다.
구성 절차
다음 단계에 따라 MagicaCloth를 빌드할 수 있습니다.
- MagicaCloth 구성 요소 생성
- 파라미터 설정
- 옷감(Cloth) 데이터 생성 및 실행 시작
예제와 함께 각 단계를 설명하겠습니다.
또한 구성 시 주의해야 할 몇 가지 사항이 있습니다.
정점 속성 지정
MagicaCloth에서는 어떤 정점(vertex)이 움직이고 어떤 정점이 움직이지 않는지를 명확하게 지정해야 합니다.
이를 정점 속성(Vertex Attributes)이라고 합니다.
에디터에서는 정점 페인트 기능을 사용하여 수동으로 속성을 설정합니다.
그러나 이 방법은 런타임 구성에서는 사용할 수 없습니다.
따라서 런타임에서 빌드할 때는 옷감 유형에 따라 여러 가지 방법으로 정점 속성을 지정해야 합니다.
이러한 방법들을 예제를 통해 설명하겠습니다.
런타임 지연
MagicaCloth2는 런타임 중 옷감 데이터를 생성합니다.
이 과정은 별도의 스레드에서 수행되므로 메인 스레드에 미치는 영향은 적습니다.
그러나 데이터 구축에는 몇 프레임의 시간이 소요됩니다.
즉, 구성 요소를 빌드한 후 실제로 옷감 시뮬레이션이 시작되기까지 몇 프레임의 지연이 발생합니다.
예제
여기서는 런타임 구성 예제를 소개합니다.
이 예제는 샘플 씬 RuntimeBuildDemo.cs에서 발췌된 것이므로, 데모 씬도 함께 참고하는 것이 좋습니다.
사용된 API에 대한 자세한 내용은 ScriptingAPI 페이지를 확인하세요.
코드에서 "character"는 캐릭터의 GameObject를 의미합니다.
또한, "gameObjectContainer"는 이름을 통해 특정 GameObject를 가져올 수 있도록 하는 간단한 클래스입니다.
BoneCloth 구성 예제 (1)
/// <summary>
/// BoneCloth construction example (1).
/// Set all parameters from a script.
/// </summary>
void SetupHairTail_BoneCloth()
{
if (character == null)
return;
var obj = new GameObject("HairTail_BoneCloth");
obj.transform.SetParent(character.transform, false);
// add Magica Cloth
var cloth = obj.AddComponent<MagicaCloth>();
var sdata = cloth.SerializeData;
// bone cloth
sdata.clothType = ClothProcess.ClothType.BoneCloth;
sdata.rootBones.Add(gameObjectContainer.GetGameObject("J_L_HairTail_00_B").transform);
sdata.rootBones.Add(gameObjectContainer.GetGameObject("J_R_HairTail_00_B").transform);
// setup parameters
sdata.gravity = 3.0f;
sdata.damping.SetValue(0.05f);
sdata.angleRestorationConstraint.stiffness.SetValue(0.15f, 1.0f, 0.15f, true);
sdata.angleRestorationConstraint.velocityAttenuation = 0.6f;
sdata.tetherConstraint.distanceCompression = 0.5f;
sdata.inertiaConstraint.particleSpeedLimit.SetValue(true, 3.0f);
sdata.colliderCollisionConstraint.mode = ColliderCollisionConstraint.Mode.None;
// start build
cloth.BuildAndRun();
}
이 예제에서는 BoneCloth를 생성하고 모든 파라미터를 스크립트에서 직접 설정합니다.
가장 중요한 부분은 SerializeData 클래스로, 이 클래스에 포함된 속성을 조정하여 Cloth의 동작을 정의합니다.
이 예제에서는 별도의 정점 속성(Vertex Attributes)을 지정하지 않았습니다.
하지만 BoneCloth 및 BoneSpring에서는 지정된 루트 본(root bone)의 Transform이 자동으로 고정(Fixed) 속성으로 설정되며,
나머지는 변형(Translation) 속성으로 설정되므로 생략할 수 있습니다.
마지막으로 BuildAndRun()을 호출하여 Cloth 데이터를 별도의 스레드에서 생성하고 시뮬레이션을 자동 시작합니다.
BoneCloth 구성 예제 (2)
/// <summary>
/// BoneCloth construction example (2).
/// Copy parameters from an existing component.
/// </summary>
void SetupFrontHair_BoneCloth()
{
if (character == null || frontHairSource == null)
return;
var obj = new GameObject("HairFront_BoneCloth");
obj.transform.SetParent(character.transform, false);
// add Magica Cloth
var cloth = obj.AddComponent<MagicaCloth>();
var sdata = cloth.SerializeData;
// bone cloth
sdata.clothType = ClothProcess.ClothType.BoneCloth;
sdata.rootBones.Add(gameObjectContainer.GetGameObject("J_L_HairFront_00_B").transform);
sdata.rootBones.Add(gameObjectContainer.GetGameObject("J_L_HairSide2_00_B").transform);
sdata.rootBones.Add(gameObjectContainer.GetGameObject("J_L_HairSide_00_B").transform);
sdata.rootBones.Add(gameObjectContainer.GetGameObject("J_R_HairFront_00_B").transform);
sdata.rootBones.Add(gameObjectContainer.GetGameObject("J_R_HairSide2_00_B").transform);
sdata.rootBones.Add(gameObjectContainer.GetGameObject("J_R_HairSide_00_B").transform);
// Normal direction setting for backstop
sdata.normalAlignmentSetting.alignmentMode = NormalAlignmentSettings.AlignmentMode.Transform;
sdata.normalAlignmentSetting.adjustmentTransform = gameObjectContainer.GetGameObject("HeadCenter").transform;
// setup parameters
// Copy from source settings
sdata.Import(frontHairSource, false);
// start build
cloth.BuildAndRun();
}
이 예제에서는 외부의 다른 Cloth 구성 요소인 "frontHairSource"에서 파라미터를 가져와 적용합니다.
이를 통해 파라미터 설정을 보다 간편하게 할 수 있습니다.
그러나 일부 파라미터는 가져올 수 있고, 일부는 가져올 수 없습니다.
이와 관련된 사항은 소스 코드의 주석으로 문서화되어 있습니다.
/// [OK] Runtime changes.
/// [NG] Export/Import with Presets
이 예제에서는 normalAlignmentSetting이 가져올 수 없는 항목이기 때문에 수동으로 설정하고 있습니다.
BoneCloth 구성 예제 (3)
/// <summary>
/// BoneCloth construction example (3).
/// Load parameters from saved presets.
/// </summary>
void SetupRibbon_BoneCloth()
{
if (character == null || string.IsNullOrEmpty(ribbonPresetName))
return;
var obj = new GameObject("Ribbon_BoneCloth");
obj.transform.SetParent(character.transform, false);
// add Magica Cloth
var cloth = obj.AddComponent<MagicaCloth>();
var sdata = cloth.SerializeData;
// bone cloth
sdata.clothType = ClothProcess.ClothType.BoneCloth;
sdata.rootBones.Add(gameObjectContainer.GetGameObject("J_L_HeadRibbon_00_B").transform);
sdata.rootBones.Add(gameObjectContainer.GetGameObject("J_R_HeadRibbon_00_B").transform);
// setup parameters
// Load presets from the Resource folder.
// Since presets are in TextAssets format, they can also be used as asset bundles.
var presetText = Resources.Load<TextAsset>(ribbonPresetName);
sdata.ImportJson(presetText.text);
// start build
cloth.BuildAndRun();
}
이 예제에서는 **프리셋 파일(Json)**로부터 파라미터를 가져옵니다.
파라미터는 Json 형식으로 외부로 내보낼 수 있으며,
이는 단순한 텍스트 파일이므로 Resources 폴더 또는 AssetBundle에 배치하여 런타임 중 로드할 수 있습니다.
BoneCloth의 정점 속성(Vertex Attribute) 설정
BoneCloth를 구성할 때, 루트 본(root bone)은 자동으로 고정(Fixed) 속성으로 설정되며,
그 외의 본들은 자동으로 이동(Move) 속성으로 설정됩니다.
따라서 기본적으로 별도의 설정이 필요하지 않습니다.
그러나 ClothSerializeData2의 boneAttributeDict를 사용하여 수동으로 설정할 수도 있습니다.
boneAttributeDict는 Transform과 속성(Vertex Attribute) 쌍으로 이루어진 딕셔너리(Dictionary)이며, 다음과 같이 지정할 수 있습니다.
// Transform Attribute
var sdata2 = cloth.GetSerializeData2();
sdata2.boneAttributeDict.Add(bone1, VertexAttribute.Fixed);
sdata2.boneAttributeDict.Add(bone2, VertexAttribute.Invalid);
sdata2.boneAttributeDict.Add(bone3, VertexAttribute.Move);
MeshCloth 구성 예제 (1)
/// <summary>
/// MeshCloth construction example (1).
/// Reads vertex attributes from a paintmap.
/// </summary>
void SetupSkirt_MeshCloth()
{
if (character == null || skirtPaintMap == null)
return;
// skirt renderer
var sobj = gameObjectContainer.GetGameObject(skirtName);
if (sobj == null)
return;
Renderer skirtRenderer = sobj.GetComponent<Renderer>();
if (skirtRenderer == null)
return;
// add Magica Cloth
var obj = new GameObject("Skirt_MeshCloth");
obj.transform.SetParent(character.transform, false);
var cloth = obj.AddComponent<MagicaCloth>();
var sdata = cloth.SerializeData;
// mesh cloth
sdata.clothType = ClothProcess.ClothType.MeshCloth;
sdata.sourceRenderers.Add(skirtRenderer);
// reduction settings
sdata.reductionSetting.simpleDistance = 0.0212f;
sdata.reductionSetting.shapeDistance = 0.0244f;
// paint map settings
// *** Paintmaps must have Read/Write attributes enabled! ***
sdata.paintMode = ClothSerializeData.PaintMode.Texture_Fixed_Move;
sdata.paintMaps.Add(skirtPaintMap);
// setup parameters
sdata.gravity = 1.0f;
sdata.damping.SetValue(0.03f);
sdata.angleRestorationConstraint.stiffness.SetValue(0.05f, 1.0f, 0.5f, true);
sdata.angleRestorationConstraint.velocityAttenuation = 0.5f;
sdata.angleLimitConstraint.useAngleLimit = true;
sdata.angleLimitConstraint.limitAngle.SetValue(45.0f, 0.0f, 1.0f, true);
sdata.distanceConstraint.stiffness.SetValue(0.5f, 1.0f, 0.5f, true);
sdata.tetherConstraint.distanceCompression = 0.9f;
sdata.inertiaConstraint.depthInertia = 0.7f;
sdata.inertiaConstraint.movementSpeedLimit.SetValue(true, 3.0f);
sdata.inertiaConstraint.particleSpeedLimit.SetValue(true, 3.0f);
sdata.colliderCollisionConstraint.mode = ColliderCollisionConstraint.Mode.Point;
// setup collider
var lobj = new GameObject("CapsuleCollider_L");
lobj.transform.SetParent(gameObjectContainer.GetGameObject("Character1_LeftUpLeg").transform);
lobj.transform.localPosition = new Vector3(0.0049f, 0.0f, -0.0832f);
lobj.transform.localEulerAngles = new Vector3(0.23f, 16.376f, -0.028f);
var colliderL = lobj.AddComponent<MagicaCapsuleCollider>();
colliderL.direction = MagicaCapsuleCollider.Direction.Z;
colliderL.SetSize(0.082f, 0.094f, 0.3f);
var robj = new GameObject("CapsuleCollider_R");
robj.transform.SetParent(gameObjectContainer.GetGameObject("Character1_RightUpLeg").transform);
robj.transform.localPosition = new Vector3(-0.0049f, 0.0f, -0.0832f);
robj.transform.localEulerAngles = new Vector3(0.23f, -16.376f, -0.028f);
var colliderR = robj.AddComponent<MagicaCapsuleCollider>();
colliderR.direction = MagicaCapsuleCollider.Direction.Z;
colliderR.SetSize(0.082f, 0.094f, 0.3f);
sdata.colliderCollisionConstraint.colliderList.Add(colliderL);
sdata.colliderCollisionConstraint.colliderList.Add(colliderR);
// start build
cloth.BuildAndRun();
}
이 예제에서는 MeshCloth를 구성하고, 정점 속성을 페인트 맵(Paint Map)으로 지정합니다. 페인트 맵은 설정한 렌더러와 동기화되어야 한다는 점에 유의하세요. 즉, 렌더러의 개수와 페인트 맵의 개수는 동일해야 합니다.
또한 두 개의 캡슐 콜라이더(Capsule Collider)를 생성하여 콜라이더 리스트에 추가했습니다. 콜라이더 생성은 Unity에서 일반적인 컴포넌트를 생성하는 방식과 동일합니다.
그 외의 과정은 BoneCloth 예제와 동일합니다.
MeshCloth의 정점 속성 직접 지정
BoneCloth와 달리 MeshCloth에서는 정점 속성을 명확하게 지정해야 합니다. 이를 생략할 수는 없습니다.
MeshCloth의 정점 속성을 지정하는 방법은 두 가지가 있습니다. 첫 번째는 MeshCloth 구성 예제 (1)에서 설명한 페인트 맵(Paint Map)을 사용하는 방법, 두 번째는 정점 배열(Vertex Array)과 동일한 개수의 속성 배열을 준비하여 지정하는 방법입니다.
페인트 맵 대신 정점 속성 배열을 사용하려면 ClothSerializeData2의 vertexAttributeList를 사용하세요.
먼저, vertexAttributeList의 요소 개수는 MeshCloth에 등록된 렌더러(Renderer) 개수와 일치해야 합니다.
그리고 각 요소는 해당 렌더러의 메쉬 정점(Vertex) 개수와 동일해야 합니다.
다음은 페인트 맵 대신 vertexAttributeList를 사용하는 예제입니다.
이 예제는 하나의 렌더러가 있는 경우를 가정합니다.
// add vertex attribute
var sdata2 = cloth.GetSerializeData2();
var attributes = new VertexAttribute[VertexCount];
// Initialize with movement attributes
for(int i = 0; i < VertexCount; i++)
attributes[i] = VertexAttribute.Move;
// Making a specific vertex a fixed attribute
attributes[0] = VertexAttribute.Fixed;
attributes[7] = VertexAttribute.Fixed;
attributes[21] = VertexAttribute.Fixed;
// Registering vertex attributes
sdata2.vertexAttributeList.Add(attributes);
'유니티 에셋 > Magica Cloth 2' 카테고리의 다른 글
스케일 변경 (0) | 2025.03.04 |
---|---|
런타임 변경사항 (0) | 2025.03.04 |
캐릭터 인스턴스화 (Character Instantiation) (0) | 2025.03.02 |
바람 셋팅(Wind Setting) (0) | 2025.03.02 |
BoneCloth 고급 설정 (BoneCloth Advanced Settings) (0) | 2025.03.02 |