AnyPortrait > 메뉴얼 > 메시 부착시의 Sorting Order 설정
게임을 만들다보면 서로 다른 캐릭터를 합쳐서 렌더링하거나, 외부의 메시를 의상이나 무기처럼 장착하는 것을 만들곤 합니다.
이때, 부착된 메시들의 렌더링 순서를 적절하게 수정하는 것은 매우 중요합니다.
다른 메뉴얼에서 Sorting Order를 이용하여 렌더링 순서를 정하는 방법에 대해서 소개한 적이 있습니다.
여기에 더해서, 이 페이지는 "Extra 설정"에 의해 실시간으로 메시의 렌더링 순서가 바뀌는 경우에 대해서도 다룹니다.
이 페이지와 관련된 아래의 메뉴얼들을 함께 보시면 도움이 될 것입니다.
- 스크립트 : 초기화, 기본 설정
- 스크립트 : 메시, 메시 그룹 트랜스폼
- Sorting Layer/Order 설정
- 렌더링 순서와 이미지 전환하기
- 캐릭터에 아이템 장착하기
- 다른 캐릭터와 동기화된 재생
- 본을 동기화하여 의상 교체하기
특히 이 예제에서는 "본 동기화" 기능을 이용합니다.
본 동기화에 대한 자세한 설명은 관련 페이지에서 확인해주세요.
설명을 위해 만들어진 로봇 캐릭터입니다.
이 로봇이 가진 한개의 팔이 몸 주위를 돌면서 움직이도록 만들 것입니다.
팔이 몸의 앞, 뒤로 움직이도록 만들기 위해서는 "Extra 설정" (관련 페이지)을 이용해야합니다.
(1) "Color Only (Controller)" 모디파이어를 추가합니다.
(2) "Extra Option"을 활성화합니다.
(3) 팔의 렌더링 위치를 제어할 컨트롤 파라미터를 생성한 상태에서 키들을 추가합니다.
(4) 팔 메시들을 선택합니다.
(5) "Set" 버튼을 눌러서 "Extra 설정" 다이얼로그를 엽니다.
(6) 메시들의 Detph가 몸의 앞, 뒤로 이동하도록 만듭니다.
팔이 컨트롤 파라미터에 따라서 몸의 앞 또는 뒤에서 렌더링되도록 만들었습니다.
본 애니메이션까지 적용하여 완성된 애니메이션 입니다.
팔이 몸 주위를 빙글빙글 돌면서 움직이는 것을 볼 수 있습니다.
새로운 캐릭터를 만듭니다.
이 캐릭터는 "로봇 팔에 장착되는 장비"입니다.
"Equip1 ~ 4"라는 4개의 메시들을 가지고 있습니다.
이 캐릭터는 "본 동기화"를 이용하기 위해 제작되었습니다.
제작 방법 및 아래의 스크립트에 대한 설명은 관련 페이지에서 자세히 알아볼 수 있습니다.
이제 스크립트를 작성하여 장비가 로봇 팔에 부착되도록 만들어 봅시다.
using UnityEngine;
using AnyPortrait;
public class RobotDepthSyncScript : MonoBehaviour
{
public Transform robotGroup; // 로봇의 부모 GameObject입니다.
public apPortrait mainCharacter; // 로봇 캐릭터입니다.
public apPortrait equipment; // 로봇에 부착될 장비 캐릭터입니다.
void Start()
{
}
void Update()
{
// A키를 누르면 장비가 부착됩니다.
if(Input.GetKeyDown(KeyCode.A))
{
Attach();
}
// S키를 누르면 부착이 해제됩니다.
if(Input.GetKeyDown(KeyCode.S))
{
Detach();
}
}
private void Attach()
{
// 장비를 로봇의 부모 게임 오브젝트의 자식으로 등록합니다.
equipment.transform.parent = robotGroup;
equipment.transform.localPosition = Vector3.zero;
// 본을 동기화합니다.
equipment.Synchronize(mainCharacter, false, false, false, true, SYNC_BONE_OPTION.MatchFromRoot);
}
private void Detach()
{
// 장비를 로봇의 부모 게임 오브젝트로부터 분리합니다.
equipment.transform.parent = null;
equipment.transform.position = new Vector3(-5, 0, 0);
// 동기화를 해제합니다.
equipment.Unsynchronize();
}
}
위와 같은 간단한 스크립트를 통해서 같은 본을 가진 두개의 캐릭터가 동기화되도록 만들 수 있습니다.
작성한 스크립트를 유니티에서 적용해봅시다.
(1) "RobotGroup"이라는 새로운 GameObject를 생성하고, 로봇 캐릭터를 자식으로서 등록시킵니다. 스크립트를 추가할 GameObject도 생성합니다.
(2) 스크립트의 멤버들까지 할당하여 구성된 유니티 씬입니다.
게임을 실행시켜서 A키를 누르면 장비가 로봇에 달라붙어서 움직이는 것을 볼 수 있습니다.
하지만 장비의 메시들이 렌더링되는 순서가 매우 이상해 보입니다.
스크립트를 수정해서 렌더링 순서를 지정해봅시다.
using UnityEngine;
using AnyPortrait;
public class RobotDepthSyncScript : MonoBehaviour
{
public Transform robotGroup; // 로봇의 부모 GameObject입니다.
public apPortrait mainCharacter; // 로봇 캐릭터입니다.
public apPortrait equipment; // 로봇에 부착될 장비 캐릭터입니다.
// Sorting Order값을 참조할 로봇 팔의 메시 중 하나를 변수로 저장합니다.
private apOptTransform targetMesh;
void Start()
{
// 장비가 부착되는 로봇의 팔 메시들 중 "가장 앞에서 렌더링 되는 메시"를 변수에 저장합니다.
targetMesh = mainCharacter.GetOptTransform("HandMidGear");
}
void Update()
{
// A키를 누르면 장비가 부착됩니다.
if(Input.GetKeyDown(KeyCode.A))
{
Attach();
}
// S키를 누르면 부착이 해제됩니다.
if(Input.GetKeyDown(KeyCode.S))
{
Detach();
}
}
private void Attach()
{
// 장비를 로봇의 부모 게임 오브젝트의 자식으로 등록합니다.
equipment.transform.parent = robotGroup;
equipment.transform.localPosition = Vector3.zero;
// 본을 동기화합니다.
equipment.Synchronize(mainCharacter, false, false, false, true, SYNC_BONE_OPTION.MatchFromRoot);
// 장비가 로봇 팔에 부착될 때, 장비 메시들의 렌더링 순서를 갱신합니다.
RefreshMeshDepth();
}
private void Detach()
{
// 장비를 로봇의 부모 게임 오브젝트로부터 분리합니다.
equipment.transform.parent = null;
equipment.transform.position = new Vector3(-5, 0, 0);
// 동기화를 해제합니다.
equipment.Unsynchronize();
}
// 장비 메시들의 Sorting Order를 갱신합니다.
private void RefreshMeshDepth()
{
// 로봇 캐릭터의 팔 메시의 Sorting Order를 가져옵니다.
int baseSortingOrder = targetMesh.GetSortingOrder();
// 로봇 캐릭터의 팔 메시보다 앞에서 렌더링 되도록 Sorting Order를 하나씩 설정합니다.
equipment.SetSortingOrder("Equip4", baseSortingOrder + 1);
equipment.SetSortingOrder("Equip1", baseSortingOrder + 2);
equipment.SetSortingOrder("Equip2", baseSortingOrder + 3);
equipment.SetSortingOrder("Equip3", baseSortingOrder + 4);
}
}
수정한 스크립트에 따라 렌더링 순서가 바뀌도록 설정을 바꿔봅시다.
(1) 메인 캐릭터인 로봇 캐릭터를 선택합니다. (장비 캐릭터가 아닙니다.)
(2) Sorting Order Option을 Detph To Order로 변경하고, Order Per Depth를 적당히 큰 10으로 설정합니다.
(1) 부모 GameObject를 선택합니다.
(2) Sorting Group 컴포넌트를 추가합니다.
게임을 실행하고 장비를 로봇에 장착하면 메시들이 정상적으로 렌더링되는 것을 볼 수 있습니다.
하지만 완벽하게 문제가 해결된 것은 아닙니다.
이 로봇에는 "Extra 설정"에 의해서 팔 메시들이 몸의 뒤쪽에서 렌더링되기도 하기 때문입니다.
로봇 팔이 뒤에서 렌더링 될때도 장비 메시 들은 몸 앞에서 렌더링이 되는 것을 볼 수 있습니다.
(장비를 부착하는 시점에 따라서는 그 반대일 수 있습니다.)
이전에는 "부착할 때 메시의 Sorting Order를 지정" 했지만, 이 문제를 해결하려면 "실시간으로 메시의 Sorting Order를 갱신"해야합니다.
스크립트를 더 수정해봅시다.
using UnityEngine;
using AnyPortrait;
public class RobotDepthSyncScript : MonoBehaviour
{
public Transform robotGroup; // 로봇의 부모 GameObject입니다.
public apPortrait mainCharacter; // 로봇 캐릭터입니다.
public apPortrait equipment; // 로봇에 부착될 장비 캐릭터입니다.
// Sorting Order값을 참조할 로봇 팔의 메시 중 하나를 변수로 저장합니다.
private apOptTransform targetMesh;
// 팔 메시의 렌더링 순서가 갱신되는 것을 검사하기 위한 변수들입니다.
private bool isAttached = false; // 장비가 부착된 상태인지 여부입니다.
private int prevSortingOrder = -1; // 이전 업데이트에서의 targetMesh의 SortingOrder의 값입니다.
void Start()
{
// 장비가 부착되는 로봇의 팔 메시들 중 "가장 앞에서 렌더링 되는 메시"를 변수에 저장합니다.
targetMesh = mainCharacter.GetOptTransform("HandMidGear");
}
void Update()
{
// A키를 누르면 장비가 부착됩니다.
if(Input.GetKeyDown(KeyCode.A))
{
Attach();
}
// S키를 누르면 부착이 해제됩니다.
if(Input.GetKeyDown(KeyCode.S))
{
Detach();
}
}
// AnyPortrait 캐릭터의 업데이트가 처리된 이후에 Sorting Order의 값을 참조하기 위해서 LateUpdate()를 이용합니다.
private void LateUpdate()
{
// 장비가 부착된 상태일 때, 로봇 팔 메시의 렌더링 순서가 바뀌었는지 검사합니다.
if (isAttached)
{
if (targetMesh.GetSortingOrder() != prevSortingOrder)
{
// 팔 메시의 Sorting Order가 이전 프레임과 다르다면, 부착된 장비 메시들의 Sorting Order를 갱신합니다.
RefreshMeshDepth();
// 처리가 완료된 후 현재 프레임의 Sorting Order값으로 갱신합니다.
prevSortingOrder = targetMesh.GetSortingOrder();
}
}
}
private void Attach()
{
// 장비를 로봇의 부모 게임 오브젝트의 자식으로 등록합니다.
equipment.transform.parent = robotGroup;
equipment.transform.localPosition = Vector3.zero;
// 본을 동기화합니다.
equipment.Synchronize(mainCharacter, false, false, false, true, SYNC_BONE_OPTION.MatchFromRoot);
// 장비가 로봇 팔에 부착될 때, 장비 메시들의 렌더링 순서를 갱신합니다.
RefreshMeshDepth();
// 부착 직후에 팔 메시의 Sorting Order를 저장하고, 부착되었음을 isAttached 변수에 저장합니다.
prevSortingOrder = targetMesh.GetSortingOrder();
isAttached = true;
}
private void Detach()
{
// 장비를 로봇의 부모 게임 오브젝트로부터 분리합니다.
equipment.transform.parent = null;
equipment.transform.position = new Vector3(-5, 0, 0);
// 동기화를 해제합니다.
equipment.Unsynchronize();
// 부착이 해제되었으므로, LateUpdate()에서 팔 메시의 렌더링 순서를 계속 체크하지 않도록 false를 입력합니다.
isAttached = false;
}
// 장비 메시들의 Sorting Order를 갱신합니다.
private void RefreshMeshDepth()
{
// 로봇 캐릭터의 팔 메시의 Sorting Order를 가져옵니다.
int baseSortingOrder = targetMesh.GetSortingOrder();
// 로봇 캐릭터의 팔 메시보다 앞에서 렌더링 되도록 Sorting Order를 하나씩 설정합니다.
equipment.SetSortingOrder("Equip4", baseSortingOrder + 1);
equipment.SetSortingOrder("Equip1", baseSortingOrder + 2);
equipment.SetSortingOrder("Equip2", baseSortingOrder + 3);
equipment.SetSortingOrder("Equip3", baseSortingOrder + 4);
}
}
수정된 스크립트의 핵심은 "기준이 되는 팔 메시의 Sorting Order 값이 바뀌었는지"를 계속 검사하는 것입니다.
AnyPortrait 캐릭터는 "LateUpdate()"에서 업데이트 되므로, 여기서도 "LateUpdate()"에서 Sorting Order를 검사해야 합니다.
특히, 이전 프레임의 Sorting Order를 별도의 변수(prevSortingOrder)에 저장하여 비교하는 구문을 주의깊게 봐주세요!
"LateUpdate()"에서 Sorting Order 값을 검사하도록 작성했지만, 그래도 해당 코드가 AnyPortrait 캐릭터의 업데이트보다 먼저 호출될 수 있습니다.
이 경우엔 정상적으로 "현재의 Sorting Order"를 확인하기가 어렵습니다.
이 스크립트가 AnyPortrait보다 나중에 동작하도록 만들어야 합니다.
(비슷한 이슈에 대해서 관련 페이지에서도 다루고 있습니다.)
(1) 유니티의 "Project Settings"를 열고 "Script Execution Order"를 선택합니다.
(2) 작성한 스크립트를 드래그하여 스크립트 목록에 추가한 후, "Default Time"보다 아래에 위치시킵니다.
게임을 실행하면 로봇의 팔의 움직임, 렌더링 순서에 맞게 장비들이 움직이고 렌더링되는 것을 볼 수 있습니다.
이 페이지에서는 특정 메시를 변수로 저장한 후, Sorting Order의 변화를 체크하였습니다.
그 외에도 "Extra 설정을 결정하는 컨트롤 파라미터의 값"을 이용하거나 "애니메이션 이벤트"를 이용하는 것도 가능합니다.
제작하는 일러스트에 따라서 메시들의 순서가 정해질 것입니다.
따라서 부착되는 메시가 어느 메시의 앞, 뒤에 배치될 지는 여러분이 정하시면 됩니다.
이 페이지에서의 경우처럼 메시들의 이름과 순서를 스크립트에서 직접 지정하는 것이 빠르고 간편합니다.
하지만 다양한 객체들을 부착하고자 한다면 별도의 데이터나 규칙을 정해서 스크립트를 작성하는 것이 좋을 것입니다.
서로 다른 이미지나 재질을 가진 메시들이 엇갈려서 렌더링되는 경우 드로우콜이 크게 증가합니다.
이것을 해결하기 위해서는 이미지를 공유하거나 특별한 재질을 만들어야합니다.
저희 팀은 이 경우에 드로우콜의 증가를 최소화할 수 있는 기능을 고려하고 있습니다.
이에 대한 의견을 주시면 개발에 적극 반영하겠습니다.