Game Hub 0 코드 튜토리얼 - 한국어

코드 조각의 전체 흐름 (프롬프트 코드 생성에 사용 가능):

1. 플레이어 이동 로직 및 UI 생성 부분

  1. GameLogic 컴포넌트는 게임의 핵심 로직 관리 컴포넌트 역할을 하며, 모든 이벤트는 GameLogic으로 전달되어 해당 컴포넌트로 분배되어 코드 결합도를 줄입니다.
  2. 플레이어가 BoxCollider가 있는 "소환기"에 접근하면 Transponder 컴포넌트가 GameLogic 컴포넌트에 플레이어 접근 이벤트를 전달하고, UIFactory 컴포넌트가 프롬프트를 입력할 UI 패널을 인스턴스화합니다.
  3. UI 패널에는 InputDialog 컴포넌트가 장착되어 플레이어의 키보드 입력을 감지합니다. 플레이어가 Enter 키를 누르면 입력 상자가 활성화되고 키보드 입력을 받습니다. 플레이어가 입력을 완료한 후 다시 Enter 키를 누르면 플레이어의 입력 데이터가 이벤트로 GameLogic에 전달됩니다.
  4. 플레이어가 "소환기"에서 멀어지면 UI 패널을 파괴합니다.

2. 모델 생성 및 리깅 부분 (TripoSDK 제공)

  1. GameLogic은 플레이어의 프롬프트를 수신한 후, 해당 프롬프트를 TripoClient 컴포넌트(TripoAPIKey 입력 필요)로 전달합니다.
  2. TripoClient는 TripoAPI 컴포넌트를 호출하여 프롬프트를 Tripo 서버에 전달합니다. 이때 TripoAPI는 요청 조립, 요청 전송, 서버 응답 폴링을 완료하며, 성공적으로 응답하면 서버는 모델의 TaskID를 반환합니다. 모델 생성이 완료되면 TripoAPI는 모델의 TaskID를 다시 Tripo 서버에 전달하여 리깅을 생성합니다.
  3. 리깅이 완료되면 TripoAPI는 모델의 URL을 TripoClient 컴포넌트로 전달하고, TripoClient는 GLTFfast 플러그인을 통해 GLB 형식의 모델을 로드하고 파싱합니다.
  4. 이어서 모델에 RuntimeHumanoidAvatarBuilder 컴포넌트를 장착합니다. 이 컴포넌트는 GLB 형식의 모델 리깅을 Unity Avator 시스템에 매핑하여 애니메이션 리타겟팅을 구현합니다.
  5. 최종적으로 TripoClient는 모델을 씬에 인스턴스화합니다. 이제 개발자는 기존의 휴머노이드 애니메이션 리소스로 모델을 구동할 수 있습니다.

3. 제어권 전환 부분

GameLogic은 플레이어의 키보드 입력을 감지하여 모델과 플레이어의 카메라 우선순위 및 ThirdPersonController의 응답 대상을 교환합니다. 플레이어가 F 키를 누르면 모델을 조작할 수 있습니다. 다시 F 키를 누르면 플레이어는 모델에서 벗어납니다. 게임의 경우, 모델은 매우 크거나 작을 수 있어 플레이어가 다른 장애물을 통과하는 데 도움이 되며, 외형 대체도 가능하여 저비용으로 유사한 캐릭터 커스터마이징, 스킨 시스템을 구현할 수 있습니다.

전체 코드 로직:
주요 GameObject

구체적인 구현 단계:

  • 버전: Unity 2022.3.55

  • 소스 코드 및 씬은 원본 파일의 Assets-LowPoly Environment Pack-Demo-Demo1에 있습니다.

프로젝트 설정 및 리소스 가져오기:

1. 새 프로젝트 생성
Unity를 열고 New Project(새 프로젝트)를 클릭합니다.
URP 파이프라인(Universal Render Pipeline)을 선택하고, TripoRuntimeTutorial로 이름을 지정한 후 Create(생성)를 클릭합니다.
2. 리소스 패키지 가져오기
브라우저를 열고 Unity Asset Store를 검색합니다. Unity Asset Store에서 다음 리소스(모두 무료)를 검색하여 다운로드하거나, 제공된 프로젝트에서 해당 파일을 찾을 수 있습니다:
Starter Assets - ThirdPerson Unity 공식 제공, 기본적인 3인칭 캐릭터 제어 제공 (처음 가져올 때 재시작 메시지가 표시되면 메시지에 따라 재시작 후 다시 가져오세요)
Military FREE - Low Poly 3D Models Pack 씬 소품 리소스 제공
LowPoly Environment Pack 사막 씬 기본 구성
내 자산에 추가하고, 이어서 팝업되는 창에서 Unity에서 열기를 클릭하여 다운로드합니다.
다운로드 완료 후, Unity 상단 메뉴 Assets > Import Package > Custom Package를 클릭하여 위 리소스 패키지를 순서대로 가져오고, 모두 Import(가져오기)를 클릭합니다.
3. URP 재질로 변환
모든 Material 파일을 필터링하거나 씬의 분홍색 이상 모델을 모두 선택한 후, Editor - Rendering - Materials - Convert to URP Materials를 통해 재질을 URP 재질로 변환합니다.
4. glTFast (GLB 모델 파싱용) 라이브러리 다운로드
방법 1: Git을 통해 다운로드 (로컬에 git 환경 필요)
상단 메뉴 Window > Package Manager를 클릭하고, 팝업 창에서 + > Add package from git URL을 클릭합니다. URL: https://github.com/atteneder/glTFast.git을 입력하고 Add(추가)를 클릭하여 glTFast 플러그인 가져오기를 기다립니다.
방법 2: Tripo 공식 웹사이트에서 Unity 플러그인 다운로드 (glTFast 플러그인 포함)
공식 웹사이트 홈 페이지의 리소스에서 Unity 플러그인을 찾아 다운로드한 후, 패키지 내의 튜토리얼 비디오에 따라 Unity로 가져옵니다.
5. 기본 씬 열기
Unity 왼쪽 파일 목록에서 Assets > LowPoly Environment Pack > Demo > Demo1 경로를 찾아 해당 씬을 더블클릭하여 엽니다. Assets > ithappy 폴더에서 일부 모델(예: 탱크, 돌)을 씬으로 드래그하여 간단한 환경을 구성합니다.

플레이어 이동 제어 구현: 플레이어가 씬에서 이동할 수 있도록

1. 플레이어 관련 프리팹 추가
Unity 왼쪽 파일 목록에서 Assets > StarterAsset > ThirdPersonController > Prefabs 경로를 찾아 다음 세 가지 프리팹을 씬으로 드래그합니다:
MainCamera (메인 카메라)
PlayerFollowCamera (플레이어 추적 카메라)
PlayerArmature (플레이어 캐릭터 모델)
2. 카메라 매개변수 설정
씬에서 PlayerFollowCamera를 선택하고, 오른쪽 속성 패널(Inspector)에서 CinemachineVirtualCamera 컴포넌트를 찾습니다. Follow와 Look At 필드를 모두 PlayerArmature 하위의 PlayerCameraRoot로 설정합니다 (PlayerArmature의 자식 오브젝트에서 찾을 수 있습니다).
위는 Unity 공식에서 제공하는 3인칭 제어 스크립트입니다. 이제 Unity 툴바의 Play(재생) 버튼을 클릭하고 WASD 키를 사용하여 캐릭터를 이동하면 캐릭터와 카메라가 정상적으로 따라 이동해야 합니다.

프롬프트 패널 생성: 플레이어가 특정 오브젝트에 접근할 때 입력 패널 표시, 멀어질 때 숨김

1. 트리거 영역 생성
Assets-ithappy-Military FREE-perfab에서 모델(예: 탱크)을 선택하고, 해당 모델을 마우스 오른쪽 버튼으로 클릭 > Create Empty Child(빈 자식 생성)를 선택하고 Trigger로 이름을 지정합니다.
Trigger를 선택하고, 오른쪽 속성 패널에서 Add Component > Box Collider를 클릭하여 콜라이더를 추가합니다.
Is Trigger(트리거) 옵션을 체크하고, Edit Collider를 클릭하여 씬에서 콜라이더의 핸들을 드래그하여 모델 크기의 2배로 만듭니다 (모델 주변 영역을 덮도록).
2. 트리거 이벤트 스크립트 작성
Unity 왼쪽 파일 목록에서 Assets > Create > C# Script를 마우스 오른쪽 버튼으로 클릭하고, Transponder로 이름을 지정한 후 Cursor로 스크립트를 엽니다. 우리는 플레이어가 콜라이더에 들어가거나 나갈 때 메시지를 GameLogic 컴포넌트에 전달하는 효과를 구현하고자 합니다. Cursor에서 모델을 선택하고, 다음 참고 프롬프트를 입력합니다:

플레이어가 콜라이더에 들어가거나 나갈 때 이벤트를 트리거하는 Unity C# 스크립트를 생성해 주세요.

요구사항: 컴포넌트 이름은 Transponder이며, Tag가 Player인 GameObject가 콜라이더에 진입하면 OnPlayerEnter 이벤트를 트리거하고, 플레이어가 떠나면 OnPlayerExit 이벤트를 트리거합니다.

스크립트를 저장한 후, Transponder 스크립트를 Trigger 오브젝트로 드래그합니다.
3. 게임 로직 관리자 생성
씬에서 마우스 오른쪽 버튼 클릭 > Create Empty를 선택하고 GameLogicManager로 이름을 지정하여 로직 컴포넌트의 컨테이너로 사용합니다.
GameLogicManager 아래에 빈 자식 오브젝트를 생성하고 GameManager로 이름을 지정합니다.
두 번째 스크립트를 생성하고 GameManager로 이름을 지정하여 게임의 주요 로직을 처리합니다.
Transponder 컴포넌트를 참조하고 플레이어의 진입/퇴장 이벤트를 감지합니다. 참고 프롬프트는 다음과 같습니다:

Unity C# 스크립트를 생성해 주세요. 컴포넌트 이름은 GameManager입니다.

요구사항: Transponder의 OnPlayerEnter 및 OnPlayerExit 이벤트를 감지합니다. OnPlayerEnter 시 UIFactory 스크립트를 호출하여 프롬프트 UI 프리팹을 인스턴스화합니다. OnPlayerExit 시 이 UI 프리팹을 파괴합니다.

스크립트를 저장한 후, GameManager 스크립트를 GameManager 오브젝트로 드래그하고 Transponder 컴포넌트를 Transponder 필드로 드래그합니다. 이제 플레이어 진입/퇴장 이벤트 감지가 완료되었습니다.
4. UI 패널 프리팹 생성
Hierarchy 패널(씬 오브젝트 목록)에서 마우스 오른쪽 버튼 클릭 > UI > Panel을 선택하여 UI 패널을 생성합니다.
패널 아래에 두 개의 자식 오브젝트를 생성합니다: Text(안내 텍스트)와 InputField(입력 상자). 이들의 위치와 크기를 조정합니다 (Game 창에서 미리보기 가능).
전체 패널을 선택하고 Unity 왼쪽 파일 목록의 Assets 디렉토리로 드래그하여 프리팹을 생성합니다 (InputDialogPrefab으로 이름 지정).
참고: Unity TextMeshPro를 사용하는 경우, 한글을 지원하는 폰트 애셋을 생성해야 합니다. 그렇지 않으면 한글 폰트가 사각형으로 표시될 수 있습니다. 또는 제공된 NotoSansSC SDF 애셋을 직접 사용하여 Text 컴포넌트에서 폰트를 NotoSansSC SDF로 변경할 수 있습니다.
5. InputDialog 스크립트 작성: UI 패널 초기화 및 플레이어 키보드 입력 감지
InputDialog 스크립트를 생성하고, Cursor에 다음 참고 프롬프트를 입력합니다:

Unity C# 스크립트를 생성해 주세요. 컴포넌트 이름은 InputDialog입니다.

요구사항: TMP_Text로 안내 메시지를 표시하고 TMP_InputField로 입력을 받는 TMP 입력 상자가 있는 대화 상자 시스템을 생성합니다.

Enter 키를 통해 두 가지 모드를 전환합니다:

첫 번째 Enter: 입력 상자를 활성화하고 플레이어 이동 컨트롤러(ThirdPersonController 등)를 비활성화합니다.

두 번째 Enter: 입력 내용을 제출하고 대화 상자를 파괴합니다.

입력이 비어 있을 때는 포커스를 유지합니다.

InputDialog 컴포넌트를 UI 패널 프리팹 아래에 장착하고, Text와 InputField를 패널 종속성으로 드래그합니다.
6. UIFactory 스크립트 작성: UI 패널 생성
새로운 C# 스크립트를 생성하고 UIFactory로 이름을 지정합니다. 목표는 UI 프리팹을 생성하는 인터페이스를 제공하여 코드 간의 결합을 피하는 중간 계층 역할을 하는 것입니다. 참고 프롬프트는 다음과 같습니다:

UIFactory라는 C# 스크립트를 생성하여 UI 대화 상자의 팩토리 패턴 생성 기능을 구현해 주세요. 구체적인 요구사항은 다음과 같습니다: UI 프리팹이 이미 존재하며, 함수는 UI를 생성할 위치와 회전을 입력받아 UI를 지정된 위치에 인스턴스화하고, 인스턴스화된 UI의 InputDialog 컴포넌트를 반환합니다.

7. 컴포넌트 종속성 연결
컴포넌트 종속성이 올바르게 연결되었는지 다시 확인합니다. PlayerArmature의 Inspector 패널 Tag가 Player로 설정되었는지 확인합니다.
GameManager 오브젝트를 선택하고, 오른쪽 속성 패널에서 Transponder 필드를 씬의 Trigger 오브젝트에 있는 Transponder 컴포넌트로 드래그하여 할당합니다.
UIFactory 필드를 GameLogicManager 오브젝트에 있는 UIFactory 컴포넌트로 드래그하여 할당합니다.
게임을 시작한 후, Trigger를 설정한 오브젝트에 가까이 가면 UI 패널이 보이고, 멀어지면 UI 패널이 자동으로 파괴됩니다. 참고로, 소스 파일은 패널 생성 위치를 고정해 두었으니, 선택한 Trigger의 Transform 속성에 따라 이 값을 수정해야 합니다. 또는 UI 프리팹의 Canvas 컴포넌트 아래 RenderMode를 ScreenSpace-Overlay로 설정하면 UI가 월드 공간으로 들어가지 않고 화면 공간 데이터로 표시됩니다.

Tripo API에 프롬프트 제출

이 단계에서는 아직 프롬프트를 Tripo 서버에 제출할 수 없습니다. 하지만 개발자가 작성해야 할 코드는 거의 끝났으며, 이후의 네트워크 요청, 모델 생성, 리깅, 매핑 및 적응, 애니메이션 리타겟팅은 이미 스크립트로 작성되어 있으며, 앞으로 Tripo For Unity 플러그인에 통합될 예정입니다.
씬에 UIEvent를 먼저 생성해야 합니다. 그렇지 않으면 UI가 응답하지 않습니다!

1. Tripo 플러그인 스크립트 가져오기
Unity 왼쪽 파일 목록에서 다운로드한 원본 파일의 TripoClient.cs, TripoAPI.cs, RuntimeHumanoidAvatarBuilder.cs 스크립트를 Assets/Scripts 폴더로 드래그합니다.
위 코드는 TripoClient, TripoAPI가 네트워크 요청 송수신을 처리하고, RuntimeHumanoidAvatarBuilder가 모델의 리깅을 Unity의 리깅 시스템에 매핑합니다. TripoClient와 TripoAPI는 Tripo For Unity 플러그인의 간소화된 버전이며, 리깅 기능이 보완되었습니다. 이 버전도 곧 업데이트될 예정이며, 그때는 더 편리한 절차를 제공할 것입니다.
2. Tripo 클라이언트 오브젝트 생성
GameLogicManager 아래에 빈 자식 오브젝트를 생성하고 TripoClient로 이름을 지정합니다.
TripoClient.cs 및 TripoAPI.cs 스크립트를 해당 오브젝트로 드래그합니다.
오른쪽 속성 패널에서 TripoClient 컴포넌트의 API Key 필드를 찾아 Tripo API 키를 입력합니다 (미리 Tripo 공식 웹사이트에서 계정을 등록해야 하며, 사용자는 매월 600점의 무료 포인트를 받습니다).
3. 프롬프트 전달 로직 연결
플레이어 입력 이벤트가 추가되었으므로 GameManager에 새로운 감지 로직을 추가해야 합니다. 이때 GameManager 코드는 이미 OnPlayerEnter 및 OnPlayerExit 이벤트를 감지하고 있어야 합니다.
제공된 코드 예시에서는 GameManager가 UIFactory에 인스턴스화 호출을 보낼 때 이미 콜백 함수가 정의되어 있습니다. 물론 다른 해결책도 있지만, 이 예시에서는 이 부분의 로직을 보완하는 프롬프트 예시는 다음과 같습니다. 참고로, 컴포넌트 간에 의존 관계가 있는 경우 이전에 생성된 코드를 AI에 컨텍스트로 제공하는 것이 좋습니다:
GameManager 스크립트에서, OnPlayerEnter가 트리거될 때 UIFactory를 호출하여 이전 InputDialog 프리팹을 인스턴스화하는 로직을 추가합니다. OnPlayerExit가 트리거될 때 생성된 프리팹을 파괴합니다. 플레이어가 입력을 확인한 후, 플레이어의 프롬프트를 기존 TripoClient 스크립트에 전달하고, 호출 예시는 tripoClient.TextToModel(prompt, pos);입니다. 관련 스크립트를 수정합니다.
GameManager.cs 스크립트를 열고 OnPromptConfirmed 메서드(입력 확인을 처리하는 함수)를 찾아 내부 코드를 보완합니다. 참고: tripoClient 필드가 GameManager 컴포넌트에 TripoClient 오브젝트로 할당되었는지 확인합니다.
4. 모델 컨테이너 생성
씬에 빈 자식 오브젝트를 생성하고 ModelContainer로 이름을 지정하여 생성된 모델의 부모 노드로 사용합니다.
이제 게임을 실행하고 패널에 프롬프트를 입력하면 프롬프트가 Tripo 서버에 성공적으로 업로드되는 것을 확인할 수 있습니다. Unity 하단의 Debug 바와 TripoAPI의 "입력/진행률"에서 생성 진행률을 확인할 수 있습니다. 모델 생성을 시도해 보세요!

애니메이션 리타겟팅

모델 생성 후 다음 단계는 기존 애니메이션 리소스를 모델의 리깅에 매핑하는 것입니다. RuntimeHumanoidAvatarBuilder 스크립트가 프로젝트에 복사되었는지 확인하십시오. 이 스크립트는 휴머노이드 GLB 리깅을 UnityAvator에 매핑하는 작업을 수행하며, TripoClient 스크립트는 이를 생성된 모델에 자동으로 장착합니다.
이제 프로젝트의 구조와 종속성을 정리해 봅시다. 이 시점에서 환경 오브젝트를 제외하고 우리가 직접 생성한 GameObject는 게임 시작 전 다음과 같아야 합니다. 각 스크립트를 펼쳐 모든 값이 할당되었는지 확인하고, 예기치 않은 상황이 발생하면 소스 파일을 참조하여 컴포넌트 할당 오류가 있는지 확인하십시오. 또한, 예시에서는 모델이 전투 로봇이므로 Scale 속성이 모두 10배로 확대되었고, Controller 관련 속성도 마찬가지입니다.

1. 모델 제어 컴포넌트 추가
ModelContainer 오브젝트를 선택하고 다음 컴포넌트들을 순서대로 추가합니다 (모두 Starter Assets 리소스 팩에서 제공):
Character Controller (캐릭터 컨트롤러)
Player Input (플레이어 입력)
Third Person Controller (3인칭 컨트롤러)
Third Person Controller 컴포넌트에서 Camera 필드를 씬의 MainCamera로 설정합니다.
2. 모델 카메라 설정
빈 자식 오브젝트를 생성하고 ModelCamera로 이름을 지정한 후 CinemachineFreeLook 컴포넌트를 추가합니다 (Cinemachine 플러그인 가져오기 필요, Starter Assets 리소스 팩에 이미 포함됨).
ModelContainer 아래에 자식 오브젝트 CameraRoot를 생성하고 Y 좌표를 5로 조정합니다 (모델 허리 높이).
ModelCamera의 Follow 및 LookAt 필드를 모두 CameraRoot로 설정합니다. 이제 카메라가 모델 움직임을 따라갑니다.

움직임 능력 보충:

현재 생성된 모델은 idle 애니메이션을 재생하지만, 아직 제어할 수 없습니다.
위 컴포넌트들, CharacterController, PlayerInput, ThirdPersonController, BasicRigidBody, StarterAssetsInputs는 모두 Unity StarterAssets에서 제공되며 플레이어 입력을 처리하는 데 사용됩니다. Animator 컴포넌트는 애니메이션 제어에 사용되며, RuntimeHumanoidAvatarBuilder 컴포넌트가 생성한 Avator도 자동으로 avator 필드에 할당됩니다.
테스트를 위해 PlayerArmature 컴포넌트와 그 카메라를 비활성화한 후, Tripo가 생성하고 리깅된 GLB 모델을 ModelContainer의 자식 오브젝트로 드래그하고 RuntimeHumanoidAvatarBuilder 컴포넌트를 추가합니다. 게임이 실행되는 동안 생성된 모델을 제어하게 됩니다. 마지막 단계는 제어권 전환 로직을 처리하는 것입니다.
GameLogic의 Update 함수에서 아래 그림의 로직을 업데이트합니다. 여기서 isLoad는 모델이 생성되었는지 여부를 나타냅니다. 생성되지 않았다면 처리할 필요가 없습니다. isInModel은 플레이어가 현재 모델을 제어하는지, 아니면 본체를 제어하는지를 나타냅니다.
플레이어가 본체에서 모델로 진입하면 플레이어와 플레이어의 카메라를 비활성화하고, 이때 Cinemachine 플러그인은 자동으로 카메라를 새로 생성된 ModelCamera로 전환합니다. 그리고 ModelContainer를 재설정하여 이동 스크립트가 모델에 적용되도록 합니다.
플레이어가 모델에서 본체로 돌아올 때, 모델은 제자리에 고정되고 플레이어 본체는 현재 위치에 표시되기를 원합니다. 동시에 카메라와 컨트롤러를 플레이어 본체로 전환합니다.
위 코드를 보완한 후, 모델이 존재할 때 F 키를 누르면 플레이어가 모델에 진입하여 제어하고, 다시 F 키를 누르면 플레이어 본체가 모델 위치에 표시되고 모델은 제자리에 멈춥니다.

이제 데모의 주요 부분이 완료되었습니다. 더 정교한 표현을 원한다면 몇 가지 세부 로직을 추가할 수 있습니다. 예를 들어, 플레이어가 모델에 가까이 다가갈 때 "F를 눌러 모델을 제어하세요"라는 UI 패널을 팝업하거나, 여러 모델을 생성하고 플레이어가 그중 하나를 선택하여 제어하는 등의 기능을 추가할 수 있습니다.
자, 이 단계까지 오셨다면 데모의 모든 내용이 구현되었을 것입니다. 이제 원하는 대로 휴머노이드 모델을 자유롭게 생성하고 조작할 수 있습니다.
또한 TripoV3.0의 대규모 업데이트에서는 사족 보행 동물, 조류 등 새로운 생물의 리깅을 제공하고 기존 리깅 알고리즘을 최적화할 예정입니다.
문제나 제안이 있으시면 언제든지 저희에게 피드백을 보내주십시오!

팁:

  1. 현재 데모는 실시간 게임에서 TripoAPI의 응용 시나리오를 보여주고 개발자들이 직면할 수 있는 문제를 미리 해결하는 것을 목표로 하며, 게임 세부 디자인은 참고용일 뿐입니다.
  2. 리깅이 필요하므로 현재 데모는 휴머노이드 모델 생성만 지원합니다. 또한 AI 생성 모델의 리깅 가중치가 불안정하여 애니메이션에 이상이 발생하면 Avator Mask를 사용하여 일부 이상 부위의 애니메이션을 차단할 수 있습니다. 원본 파일에서는 손 애니메이션이 차단되어 있으며, NoArmPerson Controller에서 수정할 수 있습니다.
  3. 빠른 생성을 원하면 TripoV2/TripoV1Turbo 모델을 사용할 수 있습니다. TripoAPI 스크립트에서 model_version = "v2.5-20250123" 필드의 버전 번호를 v2.0-20240919/Turbo-v1.0-20250506으로 바꾸기만 하면 됩니다.
  4. FBX 형식의 모델과 GLB 모델은 정방향이 다르며, Unity 실행 시 가져오는 종속성도 다릅니다. FBX를 반드시 사용해야 한다면 TriLib와 같은 플러그인을 사용하여 모델을 로드하고 해당 적응 로직을 처리할 수 있습니다.

Advancing 3D generation to new heights

moving at the speed of creativity, achieving the depths of imagination.