CREATE YOUR WORLD
Interaction with Object
오브젝트와 인터렉션하기
15분
zepeto 캐릭터가 객체에 접근할 때 나타나는 상호작용 버튼을 구현하세요 1단계 환경 설정 상호작용 샘플 및 가이드에 사용된 애니메이션과 버튼 리소스를 다음 링크에서 다운로드할 수 있습니다 📘 zepeto 상호작용 샘플 zepeto 상호작용 모듈 https //github com/naverz/zepeto multiplay example/tree/main/assets/zepeto%20interaction%20module 장면에서 zepeto 캐릭터 생성 코드를 기본값으로 구현하세요 📘 다음 가이드를 참조하세요 \[ zepeto player docid\ ddd3oqk0bvtohqy9zzfgu ] 단계 2 객체 설정 zepeto 캐릭터와 상호작용할 객체를 설정합니다 1\) zepeto 캐릭터가 상호작용할 객체를 배치합니다 2\) 계층 만들기 > 빈 객체 만들기를 선택하고 이름을 dockpoint로 변경합니다 이 지점이 zepeto 캐릭터가 상호작용할 지점입니다 객체의 위치를 조정하세요 유니티 에디터 상단의 변환 기즈모 토글 버튼이 로컬인지 확인하고 z축(파란 화살표)을 객체의 바깥쪽으로 회전시킵니다 콜라이더 컴포넌트를 추가한 후 istrigger를 확인합니다 플레이어가 객체와 상호작용할 수 있는 범위에 맞게 콜라이더의 크기를 조정합니다 3\) 계층 구조 만들기 > dockpoint의 자식으로 빈 객체를 생성하고 이름을 iconpos로 변경합니다 3단계 ui 설정 1\) 계층 구조 만들기 > ui > 캔버스를 zepeto 캐릭터가 상호작용할 객체의 자식으로 만들고 이름을 preficoncanvas로 변경합니다 렌더 모드를 월드 스페이스로 설정합니다 너비와 높이를 각각 1로 설정합니다 graphic raycaster 구성 요소에서 ignore reversed graphics 옵션의 선택을 해제합니다 2\) 계층 구조 만들기 > ui > 버튼을 preficoncanvas의 자식으로 만듭니다 3\) 설정이 완료되면, 이를 프리팹으로 만들고 계층 구조에서 나머지 preficoncanvas를 삭제합니다 단계 4 스크립트 작성 단계 4 1 interactionicon 1\) 프로젝트 생성 > 생성 > zepeto > typescript로 이름을 interactionicon으로 변경합니다 2\) 아래와 같은 샘플 스크립트를 작성합니다 import { zepetoscriptbehaviour } from 'zepeto script'; import { camera, canvas, collider, gameobject, transform, object } from 'unityengine'; import { button } from 'unityengine ui'; import { unityevent } from 'unityengine events'; import { zepetoplayers } from 'zepeto character controller'; export default class interactionicon extends zepetoscriptbehaviour { // 아이콘 @header("\[아이콘]") @serializefield() private preficoncanvas gameobject; @serializefield() private iconposition transform; // 유니티 이벤트 @header("\[유니티 이벤트]") public onclickevent unityevent; public ontriggerenterevent unityevent; public ontriggerexitevent unityevent; private button button; private canvas canvas; private cachedworldcamera camera; private isiconactive boolean = false; private isdonefirsttrig boolean = false; private update() { if (this isdonefirsttrig && this canvas? gameobject activeself) { this updateiconrotation(); } } private ontriggerenter(coll collider) { if (coll != zepetoplayers instance localplayer? zepetoplayer? character getcomponent\<collider>()) { return; } this showicon(); this ontriggerenterevent? invoke(); } private ontriggerexit(coll collider) { if (coll != zepetoplayers instance localplayer? zepetoplayer? character getcomponent\<collider>()) { return; } this hideicon(); this ontriggerexitevent? invoke(); } public showicon(){ if (!this isdonefirsttrig) { this createicon(); this isdonefirsttrig = true; } else { this canvas gameobject setactive(true); } this isiconactive = true; } public hideicon() { this canvas? gameobject setactive(false); this isiconactive = false; } private createicon() { if (this canvas === undefined) { const canvas = gameobject instantiate(this preficoncanvas, this iconposition) as gameobject; this canvas = canvas getcomponent\<canvas>(); this button = canvas getcomponentinchildren\<button>(); this canvas transform position = this iconposition position; } this cachedworldcamera = object findobjectoftype\<camera>(); this canvas worldcamera = this cachedworldcamera; this button onclick addlistener(() => { this onclickicon(); }); } private updateiconrotation() { this canvas transform lookat(this cachedworldcamera transform); } private onclickicon() { this onclickevent? invoke(); } } 스크립트의 흐름은 다음과 같습니다 update() updateiconrotation() 사용자 정의 함수를 호출하여 아이콘 캔버스를 카메라 회전과 일치하도록 회전시킵니다 ontriggerenter(), ontriggerexit() collider 영역에 들어가고 트리거를 감지하면 showicon() 사용자 정의 함수를 호출하여 아이콘을 활성화합니다 collider 영역에서 나가면 hideicon() 사용자 정의 함수를 호출하여 아이콘을 비활성화합니다 3\) 스크립트 생성을 완료한 후, 스크립트를 dockpoint 객체에 추가합니다 4\) 검사기에서 pref icon canvas와 icon position을 지정합니다 step 4 2 gestureinteraction 1\) 프로젝트 생성 > 생성 > zepeto > typescript를 선택하고 gestureinteraction으로 이름을 변경합니다 2\) 아래와 같은 샘플 스크립트를 작성하세요 import { animationclip, animator, humanbodybones, physics, transform, vector3, waitforendofframe} from 'unityengine'; import { zepetoscriptbehaviour } from 'zepeto script'; import { zepetoplayers, zepetocharacter } from "zepeto character controller"; import interactionicon from ' /interactionicon'; export default class gestureinteraction extends zepetoscriptbehaviour { @serializefield() private animationclip animationclip; @serializefield() private issnapbone boolean = true; @serializefield() private bodybone humanbodybones; @serializefield() private allowoverlap boolean = false; private interactionicon interactionicon; private isfirst boolean = true; private localcharacter zepetocharacter; private outposition vector3; private playergestureposition vector3; private start() { this interactionicon = this transform getcomponent\<interactionicon>(); zepetoplayers instance onaddedlocalplayer addlistener(() => { this localcharacter = zepetoplayers instance localplayer zepetoplayer character; }); this interactionicon onclickevent addlistener(()=> { // when onclick interaction icon this interactionicon hideicon(); this dointeraction(); }); } private dointeraction() { this outposition = this transform position; if (this issnapbone) { // is place empty if (this allowoverlap || this findotherplayernum() < 1) { this localcharacter setgesture(this animationclip); this startcoroutine(this snapbone()); this startcoroutine(this waitforexit()); } else { // the seats are full this interactionicon showicon(); } } else { this localcharacter setgesture(this animationclip); this startcoroutine(this waitforexit()); } } private snapbone() { const animator animator = this localcharacter zepetoanimator; const bone transform = animator getbonetransform(this bodybone); let idx = 0; while(true) { const distance = vector3 op subtraction(bone position, this localcharacter transform position); const newpos vector3 = vector3 op subtraction(this transform position, distance); this playergestureposition = newpos; this localcharacter transform position = this playergestureposition; this localcharacter transform rotation = this transform rotation; yield new waitforendofframe(); idx++; // calibrate position during 5 frames of animation if (idx > 5) { return; } } } // the exact method must go through the server code, // but it is calculated by the local client for server optimization private findotherplayernum() { const hitinfos = physics overlapsphere(this transform position, 0 1); let playernum = 0; if (hitinfos length > 0) { hitinfos foreach((hitinfo) => { if (hitinfo transform getcomponent\<zepetocharacter>()) { playernum ++; } }); } return playernum; } private waitforexit() { if (this localcharacter) { while (true) { if (this localcharacter tryjump || this localcharacter trymove) { this localcharacter cancelgesture(); this transform position = this outposition; this interactionicon showicon(); break; } else if(this issnapbone && this playergestureposition != this localcharacter transform position){ this interactionicon showicon(); break; } yield; } } } } 스크립트의 흐름은 다음과 같습니다 시작() 아이콘이 클릭되면 비활성화되고 dointeraction() 사용자 정의 함수를 호출합니다 dointeraction() issnapbone이 체크되어 있으면, 좌석이 비어 있으면 (allowoverlap이 체크되어 있거나 findotherplayernum() 사용자 정의 함수의 반환 값이 1보다 작음) 애니메이션 클립에 할당된 제스처를 가져옵니다 snapbone() 코루틴을 시작하고 zepeto 캐릭터의 bodybone을 targettransform에 연결합니다 waitforexit() 코루틴을 시작합니다 zepeto 캐릭터가 점프하거나 이동하거나 콜라이더 영역을 벗어날 때, 제스처를 취소하고 아이콘을 활성화합니다 좌석 수용 인원이 가득 차면 아이콘을 활성화합니다 issnapbone이 체크되어 있지 않으면, 애니메이션 클립에 할당된 제스처를 가져옵니다 waitforexit() 코루틴을 시작합니다 3\) 스크립트 생성을 완료한 후, 스크립트를 dockpoint 객체에 추가합니다 4\) 검사기에서 애니메이션 클립, 스냅 본, 본체 본 및 겹침 허용을 할당합니다 애니메이션 클립을 할당합니다 이는 상호작용 시 취할 제스처입니다 스냅 본을 확인합니다 본체 본에 할당된 부분이 도크 포인트에 위치해 있는지 확인합니다 본체 본을 엉덩이로 설정합니다 앉는 제스처가 될 것이므로 엉덩이가 도크 포인트에 위치해 있는지 확인합니다 겹침 허용은 여러 사람이 하나의 좌석에 앉을 수 있는지를 결정할 수 있게 해줍니다 5단계 플레이 zepeto 캐릭터가 객체에 접근할 때 버튼이 나타나고, 멀어질 때 사라집니다 버튼에 접근하고 상호작용할 때 설정한 제스처가 재생된다면 성공입니다 제스처 외에도 상호작용 후 발생할 수 있는 다양한 이벤트를 구현할 수 있습니다 다음은 상호작용 후 아이템을 생성하는 이벤트를 구현하는 예입니다 https //www youtube com/watch?v=ooazdb4 lgo https //www youtube com/watch?v=ooazdb4 lgo 📘 zepeto 월드 샘플 3장 상호작용 샘플 https //github com/naverz/zepeto world sample/tree/main/assets/chapter3 https //github com/naverz/zepeto world sample/tree/main/assets/chapter3