ZUOYA GMK26 QMK/VIA 기계식 숫자 패드를 구매해 주셔서 감사합니다.
제품과 받은 서비스에 만족하시기를 바랍니다.
다음은 이 제품의 기능 및 작동 방법에 대한 자세한 소개입니다.

기본 핫키

FN 키를 누른 상태에서 아래에 나열된 키를 눌러 활성화합니다.

LED 키

  • FN + + : LED 백라이트 밝기 증가
  • FN + - : LED 백라이트 밝기 감소
  • FN + / : LED 백라이트 색조 감소
  • FN + * : LED 백라이트 색조 증가
  • FN + 7 : LED 백라이트 속도 감소
  • FN + 8 : LED 백라이트 속도 증가
  • FN + BACKSPACE : 백라이트 켜기/끄기
  • FN + ENTER : 백라이트 모드 전환
  • FN + 5 : LED 백라이트 채도 증가
  • FN + 6 : LED 백라이트 채도 감소
  • FN + 0 : FN + 0을 3초간 길게 눌러 키보드를 공장 설정으로 초기화

매크로 키

총 5개의 키를 VIA 펌웨어를 통해 매크로 기능으로 설정할 수 있습니다. (M1 ~ M5)

  • M1 : CTRL + Z → 실행 취소
  • M2 : CTRL + X → 잘라내기
  • M3 : CTRL + C → 복사
  • M4 : CTRL + V → 붙여넣기
  • M5 : WIN + D → 바탕 화면 표시

펌웨어 업그레이드 모드

노브(다이얼)를 누른 상태에서 케이블을 연결하십시오.

 

 

USB 유선/BT5.0/2.4GHz 트라이 모드

유선 모드:

모드 스위치를 중간(유선 모드) 위치로 전환한 후, USB 케이블을 컴퓨터에 연결하십시오. 키보드의 백라이트가 켜지고 기본 RGB 백라이트 효과가 활성화되면 연결이 성공한 것입니다.

블루투스 페어링 방법:

다음 단계를 따라 이 키보드를 장치와 페어링하세요.

  1. ZUOYA GMK26 기계식 숫자 패드는 최대 3개의 장치에 동시에 연결할 수 있습니다.
    • 모드 스위치를 블루투스 모드로 전환하십시오.
    • FN + 1, FN + 2, 또는 FN + 3을 3~5초간 길게 눌러 페어링 모드로 진입하십시오.
    • 이때 1/2/3 키가 빠르게 파란색으로 깜박이며 연결 준비 상태를 나타냅니다.
  2. 장치에서 블루투스 설정을 열고 사용 가능한 장치를 검색합니다.
    • 목록에서 "ZUOYA GMK26-1", "ZUOYA GMK26-2", "ZUOYA GMK26-3" 중 하나를 선택하십시오.
  3. 키보드가 장치와 연결되면 1/2/3 키의 깜박임이 멈추며 블루투스 페어링이 성공했음을 나타냅니다.
    • 이후 FN + 1, FN + 2, FN + 3을 짧게 눌러 페어링된 블루투스 장치 간에 전환할 수 있습니다.

블루투스 연결은 저장됩니다. 키보드의 전원이 꺼지면 연결이 끊기지만, 전원을 다시 켜면 마지막으로 연결된 장치와 자동으로 재연결됩니다.

 

 

2.4GHz 무선 연결:

  1. 모드 스위치를 2.4GHz 모드로 전환하십시오.
  2. 무선 수신기를 장치에 꽂으십시오.
  3. "4" 키의 LED가 3초 동안 녹색으로 켜지며, 키보드의 전체 백라이트가 켜지면 2.4GHz 연결이 성공적으로 완료된 것입니다.

 

2.4GHz 페어링 안내

키보드는 기본적으로 2.4GHz 모드에 페어링되어 있습니다.
만약 2.4GHz 모드를 다시 페어링해야 하는 경우, 다음 절차를 수행하십시오:

  1. FN + 4를 길게 눌러 2.4GHz 페어링 모드로 진입하십시오.
    • 이때 "4" 키가 빠르게 녹색으로 깜박입니다.
  2. 2.4GHz 동글을 USB 포트에 삽입하십시오.
  3. 연결이 성공하면 "4" 키의 깜박임이 멈춥니다.

 

 

자동 절전 모드

  • 5분간 입력이 없을 경우
    • 백라이트가 꺼지며 배터리를 절약합니다.
    • 아무 키나 누르면 백라이트가 다시 켜집니다.
  • 30분간 입력이 없을 경우
    • 키보드는 깊은 절전 모드(Deep Sleep)로 진입합니다.
    • 블루투스 연결이 해제됩니다.
    • 아무 키나 누르면 절전 모드가 해제되며 RGB 조명이 켜지고 블루투스가 다시 연결됩니다.

 

 

배터리 충전 표시

  • 배터리 전력이 낮을 때(배터리 전압이 3.5V 이하)
    • FN 키가 빨간색으로 깜박이며 배터리가 부족함을 알립니다.

 

 

배터리 잔량 확인

  • FN + . (FN + PAUSE)를 길게 누르면 배터리 및 충전 상태를 확인할 수 있습니다.
    • 배터리 잔량 표시:
      • 1 ~ 0 숫자는 10% ~ 100%(추정치)를 의미합니다.
    • 키에서 손을 떼면 표시가 종료됩니다.
  • FN + , (FN + ,)를 눌러 충전 상태를 확인할 수 있습니다.
    • FN 키가 빨간색으로 켜져 있으면 충전 중입니다.
    • 완전히 충전되면 FN 키가 녹색으로 바뀝니다.

 

 

사용자 지정 설정 (Custom Configuration)

추가적인 사용자 지정 설정에 대한 내용은 별도로 제공되지 않았습니다.

 

 

 

ZUOYA GMK26는 QMK/VIA 펌웨어를 통해 키 리매핑을 지원합니다

주의:
키보드가 컴퓨터에 올바르게 연결되어 있는지 확인하십시오.

VIA 설치 방법:

  1. VIA 공식 GitHub 페이지에 방문하여
    • 사용 중인 운영 체제(OS)에 맞는 최신 VIA 애플리케이션을 다운로드하십시오.
    • 아래 사진을 참고하세요.
  2. 배포업체 웹사이트에서 ZUOYA GMK26.JSON 파일을 다운로드하십시오.
    • VIA 소프트웨어를 실행하고,
    • File 메뉴를 클릭한 후,
    • Import Keymap을 선택하십시오.
    • 아래 사진을 참고하세요.

 

 

3. 설치가 완료되고 VIA 소프트웨어가 성공적으로 연결되었는지 확인하세요.

아래 사진과 같이 표시되면 정상적으로 연결된 것입니다.

만약 VIA 소프트웨어가 키보드를 인식하지 못할 경우, 고객 지원팀에 문의하십시오.

  • VIA는 키보드가 연결되었을 때 자동으로 호환성을 감지합니다.
  • 키보드의 메모리는 지속적으로 유지되므로, 어디에서든 키보드를 연결하면 설정이 저장된 상태로 유지됩니다.

VIA 소프트웨어 상단에서 변경하고 싶은 키를 마우스로 선택한 후,
하단에서 원하는 키를 선택하면 즉시 적용됩니다.

 

 

 

설정 및 매크로 구성 안내

  • ZUOYA GMK26에는 물리적인 멀티미디어 키가 없지만, VIA 소프트웨어를 통해 키를 리매핑하여 해당 기능을 구현할 수 있습니다.
  • 기본적으로 0~3까지 총 4개의 레이어(Layer)가 설정되어 있으며, 개별 키에 여러 기능을 지정할 수 있습니다.
  • 작은 키보드에서는 키 수가 부족할 수 있기 때문에, 이러한 기능이 중요합니다.

 

 

모든 기능을 수행할 수 있도록 물리적 키를 설정할 수 있습니다.

특수 키 설정 (Special keys setting)

특수 키를 원하는 키로 다시 매핑하여 특정 기능을 빠르게 실행할 수 있습니다.

 

 

 

기타 사용자 지정 (Others custom)

  • ZUOYA GMK26의 레이어 기능을 사용하면 물리적인 키 개수보다 더 많은 동작을 설정할 수 있습니다.
  • 기본적으로 0~3까지 총 4개의 레이어(Layer)가 설정되어 있습니다.

Layer 0

  • 키보드가 장치에 연결될 때 기본적으로 활성화되는 레이어입니다.

Layer 1

  • 키보드가 장치에 연결되면, 이 레이어를 활성화할 수 있습니다.
  • **MO(1)**을 길게 누르면 사용할 수 있습니다.

Layer 2

  • 키보드가 장치에 연결되면, VIA 펌웨어를 통해 MO(2) 키로 변경 및 적용할 수 있습니다.
  • **MO(2)**을 길게 누르면 이 레이어를 사용할 수 있습니다.

'기억하기' 카테고리의 다른 글

All in 1 Sprite Shader  (0) 2022.04.16

Introduction


이 섹션에서는 다양한 성능 관련 문제에 대해 설명합니다.
이 내용은 주로 프로그래머를 대상으로 합니다.

 

 

 

 

CPU 의존성


MagicaCloth는 Unity DOTS(Data-Oriented Technology Stack)를 사용합니다.
따라서 시뮬레이션 성능은 전적으로 CPU에 의존합니다.
반대로, GPU는 전혀 사용되지 않습니다.

또한, DOTS는 멀티스레딩을 지원하므로 CPU에 코어(스레드)가 많을수록 병렬로 실행되어 성능이 향상됩니다.

 

 

 

 

모바일 기기의 성능


그러나 Android/iPhone에서 사용할 때는 약간의 주의가 필요합니다.
모바일 기기의 CPU는 일반적으로 고성능 코어(big core)와 저전력 코어(small core)로 구성됩니다.
이를 Big-Little 구성이라고 합니다.
예를 들어, 8코어 CPU를 가진 단말기의 경우 대부분 Big4/Little4 등의 방식으로 나뉘어 있습니다.
이 경우, (4-4 코어)로 표현됩니다.

Unity는 DOTS를 Big 코어에서만 실행합니다.
따라서 위와 같은 기기의 경우, 8코어 중 오직 4개의 코어만 DOTS에 사용할 수 있습니다.
이 점을 유의하시기 바랍니다.

이 문제는 데스크톱 PC의 CPU에서는 발생하지 않습니다.

 

 

 

 

Check with Profiler


Unity의 프로파일러 기능을 사용하여 시뮬레이션 부하를 쉽게 확인할 수 있습니다.
프로파일러에서는 타임라인에 MagicaManager 블록으로 표시됩니다.
또한, Job 항목에서 멀티스레딩 상태를 확인할 수 있습니다.

 

 

 

 

Cloth Data의 생성 및 실행


MagicaCloth는 시뮬레이션을 수행하기 위해 다양한 데이터를 필요로 합니다.
이것을 Cloth Data라고 합니다.
Cloth Data는 요청에 따라 실행 시간에 동적으로 생성됩니다.

 

Cloth Data의 생성은 상당한 계산 작업을 필요로 하며, 보통 10ms에서 50ms 정도의 시간이 걸립니다.
이 생성 과정은 백그라운드 스레드에서 실행되므로 메인 스레드에는 영향을 거의 미치지 않습니다.
또한, 여러 개의 Cloth가 여러 스레드에서 생성되며 병렬로 실행됩니다.

 

하지만, 시뮬레이션은 이 Cloth Data가 완성될 때까지 기다려야 합니다.
이로 인해 캐릭터가 실제로 생성된 후 시뮬레이션이 시작되기까지 여러 프레임의 지연이 발생합니다.

 

 

 

 

Editor 실행 시 주의사항


MagicaCloth에서 사용하는 BurstJobSystem은 빌드할 때보다 에디터에서 실행할 때 더 많은 리소스를 소모합니다.
따라서 에디터에서 실행할 때의 프로파일러 내용은 빌드에서 실행할 때와 다를 수 있습니다.
이는 다음과 같은 요소들로 인해 발생합니다.

 

 

Burst JIT Compiler

Burst는 에디터에서만 런타임(Just-In-Time Compiler)에서 컴파일됩니다.
이 컴파일은 플레이가 시작된 후에 이루어지므로, MagicaCloth가 처음 사용될 때 컴파일 시간이 수백 ms 이상 걸릴 수 있습니다.
따라서 에디터 환경에서는 플레이 후 첫 번째 시뮬레이션이 시작되기 전에 상당한 지연이 발생합니다.


이 문제는 에디터 환경에서만 발생하며 빌드 시에는 발생하지 않습니다.

이 문제를 해결하려면 Enter Play Mode Options를 사용하십시오.
이 옵션은 PlayerSettingsEditor 탭에 있습니다.

 

Enter Play Mode를 사용하면, 반복적인 플레이 작업 후에도 Burst가 다시 JIT 컴파일되지 않습니다.

 

 

JobsDebugger 처리 부하

에디터에서는 JobsDebugger가 작업의 실행을 계속 모니터링합니다.
이로 인해 작업 실행 시간이 평소보다 길어지고, 작업 간에 부자연스러운 간격이 발생할 수 있습니다.
부하가 걱정되면 JobsDebugger를 끄는 것이 좋습니다.

 

 

 

 

SafeCheck 처리 부하

마찬가지로, 에디터 환경에서는 Burst의 안전성도 모니터링됩니다.
이 부하는 어느 정도 발생하므로, 걱정된다면 두 가지 체크를 끄는 것이 좋습니다..

 

 

 

 

이 두 체크를 끄면 오류는 더 이상 보고되지 않습니다.

하지만 위에서 설명한 대로 JobDebugger SafeCheck를 끄면 Burst/Jobs 오류가 표시되지 않습니다.
따라서 MagicaCloth가 제대로 작동하지 않는다고 생각되면 모든 체크를 다시 켜고 오류를 확인해보세요

 

 

빌드에서 테스트 권장

위에서 언급한 바와 같이, 에디터에서 실행할 때는 여러 모니터링으로 인해 시뮬레이션 성능이 떨어집니다.
그러나 릴리스 빌드는 모든 모니터링을 제거합니다.
따라서 실제 기기에서 실제 성능을 확인하려면 빌드를 통해 테스트하는 것이 가장 좋습니다.

 

 

 

 

빌드 시 주의사항


Burst AOT 설정

빌드할 때 Burst를 활성화하는 것을 잊지 마세요.
이는 PlayerSettings에서 Burst AOT Settings에서 설정할 수 있습니다.
이 옵션을 체크 해제하면 빌드가 Burst가 비활성화된 상태로 빌드됩니다.
기본적으로는 활성화되어 있습니다.

 

 

IL2CPP 권장

빌드할 때 IL2CPP를 사용하는 것을 강력히 추천합니다.
이유는 C#의 처리 속도가 Mono에 비해 크게 향상되기 때문입니다.

 

 

 

 

 

처리 부하 목록


이 섹션에서는 MagicaCloth의 기능 중 가장 처리 집약적인 기능에 대해 설명합니다.
이 많을수록 부하가 높아집니다.

 

천 데이터 생성 방법

Runtime build (default) ★★★ 런타임 생성은 사용될 때 천 데이터를 그 자리에서 생성합니다. 이로 인해 초기화 시 부하가 증가합니다. 천 데이터는 백그라운드에서 생성되지만, 이 과정은 CPU를 소모합니다.
Pre-build 프리 빌드는 천 데이터를 생성하여 에셋으로 만들어 편집 중에 미리 준비합니다. 이 방법은 초기화 부하를 크게 줄여줍니다. 또한, 백그라운드 처리도 없습니다.

 

 

천 타입

MeshCloth ★★★★ MeshCloth는 시뮬레이션 외에도 프록시 메쉬 스키닝과 렌더 메쉬에 다시 쓰는 작업이 포함되기 때문에 BoneCloth보다 훨씬 더 많은 부하를 요구합니다. 따라서 모바일 기기에서 사용할 때 성능에 주의해야 합니다.
BoneCloth BoneCloth는 매우 가벼운 타입입니다. 대부분의 경우, 대량으로 사용해도 문제가 발생하지 않습니다.

 

 

충돌 처리

Self Collision ★★★★★★★★★★ Self-collision은 모든 기능 중에서 가장 눈에 띄고 처리 부하가 큰 과정입니다. 따라서 기본적으로 CPU 코어가 많은 데스크톱 PC에서 사용을 권장합니다. 모바일 기기에서 사용할 경우, 프록시 메쉬의 꼭지점 수를 가능한 한 줄이고 성능에 주의해야 합니다.
Mutual Collision ★★★★★★ Mutual collision은 다른 대상과의 충돌만 결정하기 때문에 self-collision보다는 부하가 약간 적습니다. 하지만 프로세스는 self-collision과 다를 바 없으므로 성능에 주의해야 합니다.
Edge Collision ★★★★ Edge collisions는 점 충돌보다 몇 배 더 많은 부하를 요구합니다. 점 충돌에서 문제가 발생할 때만 사용하도록 하세요.
Point Collision ★★ Point collisions는 다른 충돌 판별 방식에 비해 처리 부하가 훨씬 적습니다.
Backstop Backstop은 계산이 적게 필요하기 때문에 가장 낮은 처리 부하를 가집니다. 따라서 걱정 없이 사용할 수 있습니다.

 

 

 

 

 

시뮬레이션 주파수 및 최대 업데이트 수


MagicaCloth의 시뮬레이션은 자체적인 시간 관리 방식으로 인해 Unity의 프레임 업데이트와 다른 타이밍에 실행됩니다.
이것은 프레임 속도와 관계없이 일정한 간격으로 실행됩니다.

 

이 일정한 간격을 시뮬레이션 주파수라고 합니다.
예를 들어, 주파수가 90이면 시뮬레이션은 1초에 1/90초마다 업데이트됩니다.
이는 Unity의 물리 엔진 업데이트(FixedUpdate)와 프레임 업데이트 간의 관계와 같습니다.
MagicaCloth는 초기 주파수로 90을 설정해 두었습니다.
즉, 시뮬레이션은 1초에 90번 업데이트됩니다.

 

또한, 한 프레임에서 실행할 수 있는 최대 시뮬레이션 횟수가 설정되어 있습니다.
이것은 과도한 부하로 인해 시뮬레이션이 무한히 반복되지 않도록 방지하는 안전 기능입니다.
MagicaCloth는 초기값으로 3번이 설정되어 있습니다.
최대 횟수 때문에 시뮬레이션이 생략되면, 위치는 보간(interpolation) 함수로 보충됩니다.
이 보간 함수는 간단하고 정확도가 떨어집니다.
따라서 시뮬레이션이 생략되면 아티팩트가 발생할 수 있다는 점을 유의해야 합니다.

 

 

주파수와 성능

시뮬레이션 주파수는 성능과 밀접하게 관련이 있습니다.
주파수를 낮추면 시뮬레이션을 적게 실행하게 되어 성능이 향상됩니다.
하지만 주파수는 시뮬레이션 정확도에 큰 영향을 미칩니다.
따라서 주파수를 낮추면 시뮬레이션의 정확도도 낮아지게 됩니다.
주파수와 시뮬레이션 정확도 사이에는 트레이드오프가 있다는 점을 염두에 두어야 합니다.

 

 

주파수 및 최대 업데이트 수 변경

주파수와 최대 업데이트 횟수는 두 가지 방법으로 변경할 수 있습니다.
변경은 언제든지 가능합니다.

 

 

API

스크립트를 통해 API로 변경할 수 있습니다.

 

 

MagicaSettings

MagicaSettings라는 전용 컴포넌트를 제공하여 시스템의 상태를 변경할 수 있습니다.
이 컴포넌트를 사용하면 코딩 없이 주파수와 최대 업데이트 수를 변경할 수 있습니다.
설정 방법에 대해서는 MagicaSettings 문서를 참조하십시오.

 

 

 

주파수의 운영 효과

주파수를 변경하면 시뮬레이션 동작에 약간의 변화가 생깁니다.
예를 들어, 주파수를 90으로 설정한 상태에서 이동을 조정한 후, 주파수를 30이나 150으로 설정하면 이동이 달라지고 완전히 동일하지 않게 됩니다.
이는 주파수를 변경하면 파라미터의 효과에 약간의 차이가 발생하기 때문입니다.
따라서 주파수 변경은 파라미터 재조정이 필요할 수 있습니다.

 

 

설정예시

다음은 몇 가지 설정 예시입니다.

 

성능 우선 설정

성능이 중요하다면 주파수를 60으로 설정하고 최대 업데이트를 2로 설정해 보세요.
정확도는 조금 떨어지지만 성능이 향상됩니다.

 

고정 프레임 속도 설정

게임이 60fps와 같은 고정된 프레임 속도로 실행될 경우, 주파수를 이에 맞게 설정하는 것도 효과적입니다.
예를 들어, 주파수를 60으로 설정하고 최대 업데이트 횟수를 1로 제한하면 한 프레임의 부하가 안정화됩니다.

 

또한, 게임이 30fps로 실행된다면 주파수를 60으로 설정하고 최대 업데이트 횟수를 2로 설정하면 도움이 됩니다.
이렇게 하면 한 프레임에서 시뮬레이션이 두 번 업데이트되어 30fps에서도 주파수 60의 정확도를 확보할 수 있습니다.

 

성능 최우선 설정

성능을 최우선으로 한다면 주파수를 30으로 설정하고 최대 업데이트 횟수를 1로 설정해 보세요.
이 설정은 최대 성능을 발휘할 수 있습니다.
하지만 정확도가 크게 떨어지므로 매우 신중해야 합니다.
이 설정은 아티팩트보다 성능을 우선시하는 설정입니다.

 

 

 

 

 

컬링 시스템


컬링은 카메라에 표시되지 않거나 카메라에서 일정 거리 이상 떨어진 캐릭터들의 시뮬레이션을 중지시켜 성능을 향상시키는 기능입니다.

 

이 기능은 1인칭 FPS 게임이나 VR에서 성능을 크게 향상시킵니다.
컬링은 카메라 컬링거리 컬링의 두 가지 기능으로 구성됩니다.

https://www.youtube.com/watch?v=9uiXUocomVQ

 

 

https://www.youtube.com/watch?v=aP5ljTotmK4

 

자세한 내용은 컬링 시스템 문서를 참조하십시오.

 

 

 

 

캐릭터 배치


DOTS는 Transforms의 읽기 및 쓰기 작업에 멀티스레딩을 사용합니다.
하지만 이 혜택을 활용하려면 캐릭터 배치 방식에 주의해야 합니다.
DOTS에서는 Transform 처리가 계층 구조의 루트에 배치된 각 GameObject 그룹에 대해 멀티스레딩 방식으로 처리됩니다.

다음 예시에서, 모든 10개의 캐릭터가 루트에 배치되었으므로 각 캐릭터의 Transform 처리 과정은 여러 스레드에서 실행됩니다.
이것은 이상적인 배치입니다.

 

그러나 다음 예시에서는 모든 캐릭터가 "CharacterGroup" 객체의 자식으로 배치되어 있습니다.
이것은 매우 나쁜 예시로, Transform 처리가 전혀 멀티스레딩 방식으로 이루어지지 않습니다.
특히 많은 수의 캐릭터가 있을 경우 성능 저하가 두드러지게 나타날 수 있습니다.

 

 

 

 

 

 

시뮬레이션 작업


이 섹션에서는 천 시뮬레이션이 어떻게 수행되는지 설명합니다.
이 섹션은 시뮬레이션 프로세스가 최적화된 v2.14.0 이상 버전을 사용하고 있다고 가정합니다.

 

 

분할 작업

시뮬레이션 처리는 작업(job)이라는 처리 단위로 나누어져 실행됩니다.
이 작업들은 프로파일러에서 확인할 수 있습니다.

 

이미지에서 볼 수 있듯이 작업은 매우 작은 부분으로 나누어집니다.
이것은 각 처리 단계에서 데이터 동기화가 필요하기 때문입니다.
이 방법은 CPU 코어를 최대한 활용할 수 있다는 장점이 있지만, 작업 스케줄링 시간과 동기화 대기 시간 증가와 같은 단점도 있습니다.

 

 

배치 작업

따라서 v2.14.0부터는 작업을 나누지 않고 한 번에 처리하는 배치 작업을 추가하였습니다.
배치 작업에서는 하나의 MagicaCloth 컴포넌트 처리 작업이 하나의 작업으로 할당됩니다.
이렇게 하면 작업을 나누는 것에 비해 불필요한 동기화 시간이 제거되어 속도가 크게 향상됩니다.

 

하지만 단점도 존재합니다.
배치 작업은 작업을 나누지 않기 때문에 스레드별로 처리를 분배할 수 없습니다.따라서 처리 시간이 무겁게 드는 MagicaCloth 컴포넌트가 하나 있을 경우, 전체 처리 시간이 연장될 수 있습니다.

 

다음과 같이, 무거운 작업이 처리되는 동안 다른 CPU 코어는 완전히 유휴 상태가 됩니다.
이로 인해 CPU는 매우 비효율적이며, 작업이 분리된 작업으로 처리될 때보다 더 많은 시간이 소요됩니다.

 

 

분할 작업과 배치 작업 함께 사용하기

이 문제를 해결하기 위해, 우리는 경량 컴포넌트는 배치 작업으로 처리하고, 무거운 컴포넌트는 분할 작업으로 처리하는 하이브리드 시스템을 구현했습니다.
이렇게 하면 CPU 낭비를 없애고 코어를 최대한 활용할 수 있습니다.
배치 작업과 분할 작업은 병렬로 실행됩니다.
분할 작업은 다음 두 가지 조건에서 적용됩니다:

  • 프록시 메쉬의 꼭지점 수가 300개 이상인 경우
  • Self-collision 또는 Mutual collision을 사용하는 경우

이 조건 중 하나라도 만족하면 분할 작업이 사용됩니다.
프록시 메쉬의 꼭지점 수는 인스펙터에서 확인할 수 있습니다.

 

 

분할 작업 임계값 변경

분할 작업의 조건은 프록시 메쉬의 꼭지점 수가 300개 이상이어야 하지만, 이 기준은 변경할 수 있습니다.
이를 변경하는 두 가지 방법은 다음과 같습니다:

  • MagicaSettings
    코딩 없이 MagicaSettings 컴포넌트를 설치하여 변경할 수 있습니다.

이 변경은 런타임 중 언제든지 할 수 있으므로, 최대 효율성을 원한다면 플랫폼에 따라 임계값을 조정하는 것을 고려하세요.

 

 

 

 

 

기타


에디터 환경에서의 극단적인 성능 저하

지금까지 최종 사용자들로부터 에디터 환경에서 실행 시 성능이 매우 낮다는 보고를 받았습니다.
로드가 빌드할 때보다 수십 배 더 높은 것으로 나타났습니다.
하지만 이 문제는 재현되지 않으며, 일부 PC 환경에서만 발생하는 것으로 알려져 있습니다.
이 상황을 겪으셨다면, 프로젝트의 Library 폴더를 아래와 같이 삭제해 보세요:

 

일부 사용자들은 이 방법으로 문제를 해결했다고 보고했습니다.
Library 폴더는 프로젝트의 작업 데이터를 저장하며, 삭제하면 자동으로 다시 빌드됩니다.
하지만 Library 폴더를 삭제하기 전에 다음 단계를 따르세요:

  1. Unity 에디터 종료
  2. 프로젝트 백업
  3. Library 폴더 삭제
  4. Unity 에디터 재시작

'유니티 에셋 > Magica Cloth 2' 카테고리의 다른 글

의상 변경(Dress-up) 프로세스  (0) 2025.03.04
스케일 변경  (0) 2025.03.04
런타임 변경사항  (0) 2025.03.04
런타임 구성  (0) 2025.03.04
캐릭터 인스턴스화 (Character Instantiation)  (0) 2025.03.02

개요


이 섹션에서는 MagicaCloth를 다른 캐릭터에 적용하는 방법을 설명합니다.
이 방법을 사용하면 게임 캐릭터에 다양한 헤어스타일과 의상을 변경할 수 있습니다.
이 문서는 Unity에서 C# 프로그래밍에 익숙한 사용자를 대상으로 합니다.

 

 

 

 

 

샘플 씬


의상 변경(Dress-up) 프로세스를 위한 샘플 씬이 제공됩니다.
다음 폴더에서 씬을 찾을 수 있으며, 사용하는 렌더 파이프라인에 맞는 씬을 선택하여 테스트하세요.

 

샘플 씬 내 RuntimeDressUpDemo 오브젝트에 RuntimeDressUpDemo.cs가 포함되어 있습니다.
이 페이지에서는 해당 테스트 코드를 기반으로 설명합니다.

씬이 분리된 이유

  • 씬을 분리한 이유는 단순히 렌더링 머티리얼(Rendering Material)을 전환하기 위해서입니다.
  • 내부에 포함된 샘플 코드 자체는 모든 씬에서 동일합니다.

 

 

 

샘플 데이터


샘플 씬에서 사용되는 데이터에 대해 설명합니다.

Utc_sum_humanoid (Skeleton)

먼저 Utc_sum_humanoid (Skeleton)뼈대(Skeleton)만 있는 캐릭터입니다.

 

이 캐릭터는 Transform만 포함된 스켈레톤 캐릭터이며,
헤어(Hair), 의상(Clothing), MagicaCloth가 설정되지 않은 상태입니다.

 

이제 이 Utc_sum_humanoid (Skeleton)에 헤어와 의상을 추가할 것입니다.

 

 

Utc_sum_humanoid (Hair)

헤어 렌더러(Renderer)와 MagicaCloth가 설정된 프리팹(Prefab) 형태의 스켈레톤 캐릭터입니다.

 

이 프리팹은 전체 스켈레톤(Skeleton)을 포함하고 있습니다.
(사실, 필요한 GameObject만 포함하면 충분하지만, 샘플에서는 전체 뼈대를 포함하고 있습니다.)

 

 

Utc_sum_humanoid (Body)

의상 렌더러(Renderer)와 MagicaCloth가 설정된 프리팹(Prefab) 형태의 스켈레톤 캐릭터입니다.

 

이 프리팹은 전체 스켈레톤(Skeleton)을 포함하고 있습니다.
(사실, 필요한 GameObject만 포함하면 충분하지만, 샘플에서는 전체 뼈대를 포함하고 있습니다.)

 

 

 

 

의상 변경 방법

의상을 변경하려면 아래 단계를 따르세요.

  1. 의상 프리팹을 생성하고,
  2. MagicaCloth 초기화를 호출한 후
  3. MagicaCloth가 자동으로 빌드되지 않도록 중지합니다.
  4. 그다음 Renderer를 스켈레톤 아바타에 이식하고,
  5. MagicaCloth를 스켈레톤 아바타에 이식하며,
  6. 콜라이더 및 기타 항목을 스켈레톤 아바타에 이식한 후
  7. MagicaCloth 실행을 시작합니다.

(4)에서 Renderer를 이식하는 과정은 MagicaCloth 시스템과 무관하므로 다른 프로그램이나 에셋을 사용할 수도 있습니다.

 

 

 

 

의상 제거 방법


의상을 제거하려면 아래 절차를 따릅니다.

  1. Destroy()를 사용하여 Renderer를 제거하고,
  2. Destroy()를 사용하여 MagicaCloth를 제거하며,
  3. Destroy()를 사용하여 불필요한 콜라이더를 제거하고,
  4. Destroy()를 사용하여 원하지 않는 GameObject를 제거합니다.

기본적으로 MagicaCloth를 포함한 불필요한 GameObject를 Destroy()하면 되며, (1)의 Renderer 제거는 의상 변경 과정과 마찬가지로 다른 프로그램이나 에셋을 사용할 수도 있습니다.

 

 

 

 

예제


이 섹션에서는 샘플 씬 RuntimeDressUpDemo.cs에 대해 설명합니다.
위에서 설명한 의상 변경(Dress-up) 및 제거(Undress-up) 절차를 기반으로 코드를 살펴보면,
전체적인 동작을 쉽게 이해할 수 있을 것입니다.

// 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();
    }
  }
}

 

 

Transform 딕셔너리 생성

먼저, 스켈레톤 아바타(Skeletal Avatar)의 Transform 딕셔너리를 생성합니다.
이 딕셔너리는 이름(Name)을 키(Key)로 사용합니다.
이 딕셔너리는 본(Bone) 교체 작업을 수행하는 데 사용됩니다.

/// <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}");
    }
  }
}

 

 

MagicaCloth 초기화 및 자동 빌드 중지

먼저, MagicaCloth 컴포넌트의 초기화를 수동으로 호출하는 것이 중요합니다.
반드시 본(Bone) 교체 작업을 수행하기 전에 초기화해야 합니다.

다음으로, 자동 Cloth 빌드 작업을 중지(Pause)해야 합니다.

// 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];
  }
}

 

이 과정은 MagicaCloth와 직접적인 관련이 없습니다.
즉, 이 단계에서는 원하는 방식으로 처리할 수 있으며, 다른 의상 변경(Dress-up) 에셋을 사용할 수도 있습니다.

 

 

MagicaCloth 컴포넌트 이식(Porting)

MagicaCloth를 스켈레톤 아바타(Skeletal Avatar)에 이식(Implant) 합니다.
이 과정은 SkinnedMeshRenderer의 본 교체 방식과 동일하게 내부 본(Bone)을 교체하는 방식으로 수행됩니다.

본 교체는 미리 생성한 Transform 딕셔너리(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)

MagicaCloth에서 콜라이더(Collider)를 사용하고 있다면,
이를 스켈레톤 아바타(Skeletal Avatar)로 함께 이식해야 합니다.

// 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;
  }
}

 

 

 

MagicaCloth 실행 시작

마지막으로, MagicaCloth를 빌드(Build)하고 실행(Run)합니다.

// 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)

의상 변경이 더 이상 필요하지 않을 경우, 다음과 같이 해제합니다.
불필요한 모든 GameObject를 **Destroy()**하여 제거하면 됩니다.

/// <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)


이 예제 코드가 모든 가능성을 포함하는 것은 아닙니다.
따라서, 이 사례는 **참고용(Reference Only)**으로만 사용해야 합니다.

예를 들어, 의상 프리팹(Dress-up Prefab)에는 존재하지만, 스켈레톤 아바타(Skeletal Avatar)에는 존재하지 않는 GameObject
이 방식으로는 자동으로 이식되지 않습니다.
이러한 경우에는 추가적인 처리가 필요합니다.

끝.

'유니티 에셋 > Magica Cloth 2' 카테고리의 다른 글

성능(Performance)  (0) 2025.03.04
스케일 변경  (0) 2025.03.04
런타임 변경사항  (0) 2025.03.04
런타임 구성  (0) 2025.03.04
캐릭터 인스턴스화 (Character Instantiation)  (0) 2025.03.02

+ Recent posts