최근 UnityEngine의 개발환경을 그대로 차용한 VRChat 게임에 관심을 가지고 있었다. Unity Tool로 구현이 가능한 그래픽적 요소가 바로 게임 내에서 반영된다는 점 덕분에 수많은 창작자들이 UnityEngine을 탐구하며 별도의 포럼을 이루고 있는 듯 했다. 당연하게 게임 내엔 Compiler가 없기 때문에 코드적인 접근은 불가능하지만 VRChat에서 제공하는 별도의 SDK 내의 Class들은 전부 접근을 용이하게 해 놓았더라. 정말 대놓고 창작 자체를 유니티로만 하라 라는 뜻이리라. 장단은 잘 모르겠지만.

 

배경은 뒤로 하고, 어쨌건 내가 원하는 아바타를 만들기 위해선 좋든 싫든 유니티 Tool을 만져야 한다. 그래픽과 Legacy/Mechanim 애니메이션 요소의 Customize는 충분히 코드 없이도 가능하다. 하지만 지식이 없어도 되는것은 아니다. 방대한 창작의 자유도를 전부 창작자 본인에게 맡기는 방식이 누군가에겐 독이 될 수도 있다는 생각을 했다.

 

제한적인 Preset 형태의 커스터마이징조차 누군가에겐 스트레스일지도.

 

단순히 커스터마이징이 아닌 기능 구현의 경우는 더 골머리가 아프다. Animation Controller를 기반으로 작동하는 Descripter의 FX는 이 기능 자체를 세세하게 파고들지 않으면 습득이 어려운 축이다. 내가 캐릭터의 안경을 벗고 쓰는 동작을 만들기 위해 변수와 Parameter의 개념을 이해할 필요가 있나 싶은 것. 수개월 수년동안 툴을 만진 사람이 포럼 내에서는 "고인물"로 통하며, 누군가에게 돈을 받고 대신 작업을 해주기도 한다. 오 맙소사.

 

앞서 말한 VRC SDK의 경우 Humanoid 기반의 Avatar Object의 Serialization과 Upload를 담당한다. 그 과정 속에 당연히 Anim Controller 관련 Parameter 정보를 포함하며, 이는 코드로 접근이 가능하다고 했다. 안경을 벗고 쓰는 메뉴와 동작을 손쉽게 좀 바꾸어 보자. 기본적으로 Bool 형태의 Parameter를 이용해 구현하는 방법은 다음과 같다.

 

1. 해당 아바타의 FX 레이어를 찾는다.

2. VRC Parameter를 추가한다.

3. Animation Controller를 생성하고, State와 Transition을 설정한다.

4. Expression Menu에서 해당 Parameter를 변경하는 메뉴를 추가시킨다.

 

이 과정의 목표를 따라, 한번 자동화를 구현해보도록 하자. 기능 구현에 초점을 맞추기보단 VRC SDK가 유저편의를 위해 어떤 Class들을 제공하는지 알아보는 것이 목표긴 했지만 (..)

 

 

1. FX Layer / Parameter 파일 탐지

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
bool fxFind = false;
            for (int i = 0; i < avatarDescriptor.baseAnimationLayers.Length; i++)
            {
                var test = avatarDescriptor.baseAnimationLayers[i];
                switch (test.type)
                {
                    case VRCAvatarDescriptor.AnimLayerType.FX:
                        var tempFX = avatarDescriptor.baseAnimationLayers[i];
                        if (!tempFX.isDefault)
                        {
                            fxFind = true;
                        }
                        else
                        {
                            fxFind = false;
                        }
                        if (tempFX.animatorController.ToString() == "null")
                        {
                            fxFind = false;
                        }
                        else
                        {
                            fxFind = true;
                        }
                        FX = avatarDescriptor.baseAnimationLayers[i].animatorController as UnityEditor.Animations.AnimatorController;
                        break;
                }
            }
cs

Avatar Descriptor Class엔 CustomAnimLayer[] 형태의 AnimLayer가 존재한다. 그리고 Enum Type의 AnimLayerType.FX로 해당 FX 레이어를 탐색하면 손쉽게 아바타의 FX Controller를 찾을 수 있었다.

 

Parameter 탐지의 경우 좀더 쉬웠다. Descriptor Class만 찾을 수 있다면 VRCAvatarDescriptor.VRCExpressionParameter로 바로 접근이 가능하다. 이를 생성해주면 ,FX와 Parameter의 할당은 끝이 난다.

 

 

2. Animation State, Transition 생성

 

여기서부턴 VRChat의 영역이 아니다. 손쉽게 작업할 수 있는 Tool을 버리고 코드를 짠다는게 웃기긴 하겠지만 개발자는 5분 편하자고 5시간을 고생하는 똥멍청이들이다. 어쨌든, 1번에서 찾은 Parameter와 FX Layer에 해당 이름을 추가하는데 성공했다면 안경을 벗고 끄는것에 대한 Anim State를 자동으로 생성해줘야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
FX.AddLayer(targetLayer);
 
var nullState = FX.layers.Last().stateMachine.AddState("null");
 
var turnonState = FX.layers.Last().stateMachine.AddState(turnOn.name);
turnonState.motion = turnOn;
turnonState.writeDefaultValues = true;
 
var turnoffState = FX.layers.Last().stateMachine.AddState(turnOff.name);
turnoffState.motion = turnOff;
turnoffState.writeDefaultValues = true;
 
var nullToTurnOnTrans = nullState.AddTransition(turnonState);
var nullToTurnOffTrans = nullState.AddTransition(turnoffState);
 
nullToTurnOnTrans.hasExitTime = false;
nullToTurnOnTrans.exitTime = 0;
nullToTurnOnTrans.duration = 0;
 
nullToTurnOnTrans.AddCondition(UnityEditor.Animations.AnimatorConditionMode.If, 0, resultParamName);
 
nullToTurnOffTrans.hasExitTime = false;
nullToTurnOffTrans.exitTime = 0;
nullToTurnOffTrans.duration = 0;
cs

FX에 해당 Layer를 추가하고, Transition을 일일히 설정해주는 노가다를 진행한다. 단순히 옷을 입고 벗는 과정에서의 Transition의 시간은 필요없으니 hasExitTime과 duration은 전부 0으로 설정한다. 이렇게 앞뒤로, If와 Ifnot을 설정해준다면 원하는 형태의 Parameter로 조정할 수 있게 된다.

 

생성 완료! 위치는 신경쓰지 말것 :D

이제 이 자동화 기능을 가지고, Editor 단에서 UI를 입혀주면 끝이 난다. 내가 캐릭터에게 안경을 벗고 씌우는 기능을 구현하기 위해서 필요한것은 FX Layer와 Parameter Control이 아닌, 안경 하나여야만 한다는게 취지였다. 창작 자유도의 경계를 제한하려는 것이 아닌, 단순한 접근성과 편의성에 대한 고민이다. 이 포스팅에선 단순 bool 구현에서 그쳤지만 다른 많은 부분에서도 창작자들의 편의를 위해 SDK가 꾸준히 업데이트되길 바란다.

 

 

 

참고

https://forum.unity.com/threads/animatorcontroller-addlayer-doesnt-create-default-animatorstatemachine.307873/

https://answers.unity.com/questions/1023907/how-can-i-create-a-animatorstatetransition-conditi.html

+ Recent posts