
장비 외형 교체(Paperdoll): 머리·갑옷·무기 메시 동적 바인딩과 애니메이션 연동
캐릭터의 골격을 유지한 채 머리, 갑옷, 무기 외형을 교체하는 Paperdoll 구조를 Unity 예제로 설명합니다. 스킨드 메시 바인딩, 무기 소켓, 애니메이션 동기화, 교체 시점의 주의점을 함께 다룹니다.
Paperdoll 시스템이 해결하는 문제
Paperdoll은 캐릭터 본체를 통째로 교체하지 않고 장비 부위만 조합하는 외형 시스템이다. 플레이어의 장비 데이터는 그대로 유지하면서 머리, 갑옷, 장갑, 신발, 무기 같은 시각 요소를 바꿀 수 있다.
가장 중요한 원칙은 하나다. 장비 메시가 캐릭터의 애니메이션 골격을 따르거나 정해진 부착 지점을 따라야 한다. 갑옷처럼 변형되는 장비는 캐릭터 골격에 스킨 바인딩하고 검이나 방패처럼 단단한 장비는 손 소켓에 붙인다.
flowchart TD
A[장비 데이터 변경] --> B{장비 유형}
B -->|갑옷·머리·장갑| C[SkinnedMeshRenderer 생성]
C --> D[본 이름으로 캐릭터 골격 재바인딩]
D --> E[캐릭터 Animator가 본을 구동]
B -->|검·방패·활| F[무기 프리팹 생성]
F --> G[손 또는 등 소켓에 Parent 설정]
G --> H[소켓 Transform을 따라 이동]
장비를 두 종류로 나누기
스킨드 장비
머리, 갑옷, 바지처럼 관절의 움직임에 따라 형태가 변하는 장비는 보통 SkinnedMeshRenderer를 사용한다. 이 메시에는 정점별 본 가중치와 본 배열이 들어 있다. 따라서 장비 프리팹이 원래 제작된 캐릭터의 본을 계속 참조하면 런타임 캐릭터의 애니메이션을 따라가지 못할 수 있다.
안정적인 방식은 장비 메시의 본 이름을 기준으로 현재 캐릭터 골격의 Transform을 다시 연결하는 것이다. 장비와 캐릭터는 같은 리그 규칙과 본 이름을 공유해야 한다. 예를 들어 Hips, Spine, Head, LeftHand 같은 이름이 달라지면 바인딩에 실패한다.

소켓 장비
검, 총, 방패, 망토 장식처럼 자체적으로 변형될 필요가 없는 장비는 본에 직접 스킨하지 않아도 된다. 캐릭터 리그에 Weapon_R, Weapon_L, Back 같은 빈 Transform을 두고 장비를 자식으로 배치한다.
소켓은 무기의 기준점과 방향을 통일하는 역할도 한다. 프리팹마다 손잡이 위치가 제각각이면 장착할 때마다 보정값을 작성해야 한다. 아트 파이프라인에서 무기 피벗을 손잡이 기준으로 맞추고 소켓의 로컬 축 규칙도 문서화해 두는 편이 좋다.
데이터와 프리팹의 역할 분리
장비 데이터에는 게임 규칙과 외형 참조를 넣고 실제 메시와 머티리얼은 프리팹에 둔다. 능력치와 외형을 분리하면 같은 검 모델에 서로 다른 공격력이나 등급을 적용하기 쉽다.
using UnityEngine;
public enum EquipmentSlot
{
Head,
Body,
RightHand,
LeftHand
}
[CreateAssetMenu(menuName = "Game/Equipment Definition")]
public class EquipmentDefinition : ScriptableObject
{
public EquipmentSlot slot;
public GameObject visualPrefab;
public int attack;
public int defense;
}
visualPrefab은 슬롯별로 다음 규칙을 지키는 것이 좋다.
- 머리와 갑옷 프리팹에는
SkinnedMeshRenderer가 있다. - 무기 프리팹은 루트의 피벗이 손잡이 또는 부착 기준점에 있다.
- 스킨드 장비는 캐릭터 리그와 호환되는 본 이름을 사용한다.
- 장비마다 별도
Animator를 넣지 않는다.
장비 프리팹에 별도 Animator를 넣으면 같은 본을 여러 애니메이터가 제어하거나 불필요한 업데이트가 발생할 수 있다. 기본적으로 애니메이터는 캐릭터 루트에 하나만 둔다.
본 이름으로 스킨드 메시 다시 바인딩하기
다음 예제는 캐릭터 아래의 모든 본을 이름으로 찾은 뒤 장비 프리팹 안의 SkinnedMeshRenderer가 그 본을 사용하도록 바꾼다.
using System.Collections.Generic;
using UnityEngine;
public class PaperdollBinder : MonoBehaviour
{
[SerializeField] private Transform skeletonRoot;
private readonly Dictionary<string, Transform> bones = new();
private void Awake()
{
CacheBones();
}
private void CacheBones()
{
bones.Clear();
foreach (Transform bone in skeletonRoot.GetComponentsInChildren<Transform>(true))
{
if (!bones.TryAdd(bone.name, bone))
{
Debug.LogWarning($"중복된 본 이름: {bone.name}", bone);
}
}
}
public void BindSkinnedMeshes(GameObject equipmentInstance)
{
foreach (SkinnedMeshRenderer renderer in
equipmentInstance.GetComponentsInChildren<SkinnedMeshRenderer>(true))
{
Transform[] reboundBones = new Transform[renderer.bones.Length];
for (int i = 0; i < renderer.bones.Length; i++)
{
string boneName = renderer.bones[i].name;
if (!bones.TryGetValue(boneName, out Transform targetBone))
{
Debug.LogError($"본을 찾을 수 없습니다: {boneName}", renderer);
return;
}
reboundBones[i] = targetBone;
}
renderer.bones = reboundBones;
if (renderer.rootBone != null && bones.TryGetValue(renderer.rootBone.name, out Transform rootBone))
{
renderer.rootBone = rootBone;
}
}
}
}
동일한 본 이름이 여러 개라면 이름만으로는 어느 본인지 구별할 수 없다. 이 경우에는 리그 구조를 정리하거나 Hips/Spine/Chest처럼 루트부터의 경로를 키로 사용해야 한다. 대규모 프로젝트에서는 경로 기반 키가 더 안전하지만 리그 변경에 취약하므로 아트 파이프라인의 명명 규칙을 먼저 고정하는 편이 중요하다.
슬롯 교체 관리자 만들기
슬롯마다 현재 생성된 외형 인스턴스를 보관하고 새 장비를 장착하기 전에 같은 슬롯의 기존 인스턴스만 제거한다. 아래 예제는 스킨드 장비와 소켓 장비를 모두 처리한다.
using System.Collections.Generic;
using UnityEngine;
public class EquipmentVisualController : MonoBehaviour
{
[SerializeField] private PaperdollBinder binder;
[SerializeField] private Transform rightHandSocket;
[SerializeField] private Transform leftHandSocket;
private readonly Dictionary<EquipmentSlot, GameObject> equippedVisuals = new();
public void Equip(EquipmentDefinition definition)
{
Unequip(definition.slot);
if (definition.visualPrefab == null)
{
return;
}
Transform parent = GetParent(definition.slot);
GameObject instance = Instantiate(definition.visualPrefab, parent);
instance.transform.localPosition = Vector3.zero;
instance.transform.localRotation = Quaternion.identity;
instance.transform.localScale = Vector3.one;
if (definition.slot is EquipmentSlot.Head or EquipmentSlot.Body)
{
binder.BindSkinnedMeshes(instance);
}
equippedVisuals.Add(definition.slot, instance);
}
public void Unequip(EquipmentSlot slot)
{
if (!equippedVisuals.Remove(slot, out GameObject oldVisual))
{
return;
}
Destroy(oldVisual);
}
private Transform GetParent(EquipmentSlot slot)
{
return slot switch
{
EquipmentSlot.RightHand => rightHandSocket,
EquipmentSlot.LeftHand => leftHandSocket,
_ => transform
};
}
}
스킨드 장비를 캐릭터 루트의 자식으로 두는 것은 계층 구조를 단순하게 유지하기 위한 선택이다. 렌더러의 bones 참조가 올바르면 실제 변형은 골격을 따라간다. 반면 무기는 반드시 해당 소켓의 자식으로 두어야 손 애니메이션을 자연스럽게 따른다.
애니메이션 이벤트와 장비 교체 시점
인벤토리에서 장비를 바꾸는 경우라면 즉시 교체해도 된다. 그러나 전투 중 무기를 교체하거나 활시위를 당기는 동작처럼 애니메이션과 결합된 상황에서는 정확한 프레임에 외형을 바꾸는 것이 중요하다.
Unity에서는 공격 애니메이션 클립에 Animation Event를 넣거나 StateMachineBehaviour에서 특정 상태 진입과 종료를 감지할 수 있다. 예를 들어 칼집에서 검을 꺼내는 애니메이션은 시작 시 무기를 등 소켓에 두고 손이 손잡이에 닿는 프레임에서 오른손 소켓으로 옮긴다.
using UnityEngine;
public class WeaponAttachment : MonoBehaviour
{
[SerializeField] private Transform handSocket;
[SerializeField] private Transform backSocket;
[SerializeField] private GameObject weapon;
public void AttachToHand()
{
weapon.transform.SetParent(handSocket, false);
}
public void AttachToBack()
{
weapon.transform.SetParent(backSocket, false);
}
}
Animation Event가 호출하는 함수는 짧고 예측 가능해야 한다. 장비 데이터를 불러오거나 네트워크 요청을 보내는 무거운 작업을 이벤트 함수 안에서 처리하지 말고 이미 준비된 오브젝트의 부모만 바꾸는 정도로 제한하는 편이 좋다.
자주 발생하는 문제
장비가 애니메이션을 따라가지 않는다
대부분 SkinnedMeshRenderer.bones가 장비 프리팹 내부의 본을 가리키고 있기 때문이다. 캐릭터 골격의 본으로 다시 바인딩했는지 확인한다. rootBone도 함께 점검해야 컬링이나 바운드 계산이 안정적이다.
장비가 크게 떨리거나 위치가 어긋난다
모델링 도구의 단위, 프리팹 루트의 스케일, 소켓의 회전 축을 차례대로 확인한다. 런타임 보정값을 무기마다 누적하기보다 원본 프리팹의 피벗과 스케일을 정리하는 편이 유지보수에 유리하다.
장비가 몸을 뚫는다
스킨 웨이트가 잘못되었거나 장비 메시가 특정 체형만 기준으로 만들어졌을 수 있다. 체형 변화가 없는 게임이라면 장비별 메시를 준비하는 방식이 단순하다. 체형 슬라이더가 있다면 블렌드 셰이프, 체형별 메시 또는 의상 변형 규칙을 추가로 설계해야 한다.
머티리얼 수가 너무 많아 성능이 떨어진다
장비 조합이 늘어나면 드로우 콜과 머티리얼 인스턴스가 빠르게 증가한다. 공통 셰이더와 아틀라스를 사용하고 같은 머티리얼을 공유할 수 있는지 먼저 검토한다. 캐릭터 수가 많은 게임에서는 가까운 캐릭터만 완전한 Paperdoll을 유지하고 먼 거리에서는 단순화한 LOD 외형을 사용하는 방법도 고려할 수 있다.
구현 전 체크리스트
- 캐릭터와 모든 스킨드 장비가 같은 리그 규칙을 사용하는가?
- 본 이름이 중복되지 않고 일관적인가?
- 무기 피벗과 소켓 축 규칙이 문서화되어 있는가?
- 장비 프리팹에 불필요한 Animator가 들어 있지 않은가?
- 장착과 해제 시 기존 슬롯 인스턴스가 확실히 정리되는가?
- 전투 중 교체가 필요한 장비는 애니메이션 이벤트와 연결되어 있는가?
Paperdoll의 핵심은 장비를 단순히 생성하는 데 있지 않다. 스킨드 장비는 하나의 골격을 공유하고 소켓 장비는 하나의 애니메이션 흐름을 공유해야 한다. 이 두 경계를 분명히 정하면 장비 종류가 늘어나도 외형 교체 시스템을 예측 가능하게 확장할 수 있다.


