CREATE YOUR WORLD

Gesture

32min
zepetoworldcontent api를 사용하면 원하는 제스처/포즈 카테고리에 대한 썸네일을 설정하고 썸네일을 클릭할 때 특정 제스처/포즈를 활성화할 수 있습니다 zepetoworldcontent api를 사용하려면 다음과 같이 import 문을 작성해야 합니다 import { officialcontenttype, worldservice, zepetoworldcontent, content } from 'zepeto world'; 제스처/포즈 정보를 포함하는 content 클래스의 멤버 변수 및 함수 정보는 다음과 같습니다 api 설명 public get id() string 콘텐츠 고유 id public get title() string 제스처, 포즈 제목 텍스트 \ 언어는 장치 언어에 따라 자동으로 번역됩니다 public get thumbnail() unityengine texture2d 2d 썸네일 public get animationclip() unityengine animationclip 제스처 애니메이션 클립 public get isdownloadedthumbnail() boolean 이 썸네일을 이전에 다운로드했는지 확인하는 기능 public get isdownloadedanimation() boolean 이 애니메이션 클립을 이전에 다운로드했는지 확인하는 기능 public downloadanimation($complete system action)\ void 완료 콜백을 받는 애니메이션 클립 다운로드 기능 \ isdownloadedanimation()이 false인 경우 downloadanimation()을 호출하도록 구현합니다 public downloadthumbnail($complete system action)\ void 썸네일을 다운로드하는 기능 \ isdownloadedthumbnail()이 false인 경우 downloadthumbnail()을 호출하도록 구현합니다 officialcontenttype enum 콘텐츠 유형 (world 1 9 0 이상) \ 제스처 = 2 \ 포즈 = 4 \ 셀카 = 8 \ 제스처인사 = 16 \ 제스처포즈 = 32 \ 제스처확인 = 64 \ 제스처댄스 = 128 \ 제스처거부 = 256 \ 제스처기타 = 512 \ 모두 = 14 기존 기능을 사용할 수 있습니다, public downloadthumbnail($character zepeto character controller zepetocharacter, $complete system action)\ void , 기능에 대한 문제 없이 그러나 이제 zepeto 캐릭터를 인수로 받지 않으므로, 새로 수정된 기능을 사용하십시오 public downloadthumbnail($complete system action)\ void 대신에 1단계 ui 설정 1 1단계 제스처 버튼 만들기 1\) 계층 구조 > ui > 캔버스를 추가하고 정렬 순서를 2로 설정하여 다른 ui에 가려지지 않도록 합니다 2\) 계층 추가 > ui > 버튼 1 2단계 제스처 패널 정리 1\) 계층 추가 > 빈 객체 만들기 및 이름을 panelparent로 변경합니다 2\) 계층 추가 > ui > panel을 panelparent의 자식으로 추가합니다 3\) 닫기 버튼 ui > 버튼을 추가한 후 제스처 패널을 비활성화하는 onclick 이벤트를 추가합니다 4\) 열기 버튼 위에서 생성한 열기 버튼에 제스처 패널을 활성화하는 onclick 이벤트를 추가해 주세요 5\) 제목 영역으로 사용할 이미지를 추가하세요 6\) 제스처 썸네일을 표시하기 위해 스크롤 뷰를 구성하세요 계층 추가 > ui > 스크롤 뷰 수평을 체크하고 스크롤 바 이미지를 비활성화하세요 수직 스크롤만 사용할 것이며 수평 스크롤은 필요하지 않습니다 스크롤 뷰의 콘텐츠에 그리드 레이아웃을 추가하여 썸네일을 그리드 패턴으로 정렬하세요 객체의 크기를 콘텐츠의 크기에 맞게 조정하기 위해 콘텐츠 사이즈 피터를 추가하세요 스크립트를 구현할 때, 스크롤 뷰의 콘텐츠를 제스처 썸네일의 부모로 설정해야 합니다 (전체 영역이 인식되고 스크롤되도록) 7\) 제스처 유형별로 탭을 구성하세요 계층 추가 > 패널의 자식으로 빈 객체 생성하고 이름을 gesturetitle로 변경합니다 이것은 토글 버튼의 부모 객체입니다 탭을 수평으로 정렬하기 위해 수평 레이아웃을 추가합니다 토글 그룹 컴포넌트를 추가합니다 👍 더 많은 탭을 구성하려면 계층 추가 > ui > 스크롤 뷰를 추가하고 스크롤 뷰 옵션에서 수평을 체크합니다 8\) gesturetitle의 자식으로 토글 버튼으로 사용할 텍스트를 추가하고, 이를 all로 교체합니다 텍스트의 색상을 회색으로 설정합니다 체크할 때 표시될 강조된 텍스트를 텍스트의 자식으로 추가합니다 폰트 내용, 크기 및 두께를 동일하게 설정하고 색상을 검정으로 설정합니다 toggle 구성 요소를 추가합니다 그룹에서 부모 객체를 지정합니다 graphic에 자식으로 추가한 강조된 텍스트를 추가합니다 먼저 표시될 all 토글 구성 요소에 대해서만 ison을 확인합니다 gesture와 pose 토글 버튼을 동일한 방식으로 생성합니다 1 3단계 썸네일 프리팹 만들기 썸네일 버튼을 프리팹으로 생성한 다음, 스크립트에서 인스턴스로 생성하는 방법을 사용하세요 1\) ui > 버튼을 스크롤 뷰의 콘텐츠 자식으로 추가하고 이름을 prethumb로 변경하세요 2\) raw image를 추가한 후 이름을 thumb으로 변경하세요 이 이미지는 썸네일이 됩니다 크기를 적절하게 조정하세요 3\) 텍스트를 추가하세요 이미지의 하단을 중앙에 위치하도록 설정하세요 글자의 크기와 두께를 조정하고, content size fitter를 추가하세요 수평 맞춤 선호 크기 수직 맞춤 선호 크기 4\) 설정이 완료되면 프리팹으로 만들고 resources 폴더에 넣어주세요 1 4단계 ui 설정 가이드 비디오 https //www youtube com/watch?v=v ias8t8wq0 https //www youtube com/watch?v=v ias8t8wq0 👍 비디오에 표시된 ui 크기 및 위치 값은 권장 사항이지만, 원하는 값으로 수정할 수 있습니다! ui 설정이 완료되면 스크립팅으로 진행하세요 2단계 스크립트 작성하기 이 스크립트는 단일 플레이를 기반으로 합니다 2 1단계 썸네일 프로젝트 > 생성 > zepeto > typescript로 이름을 썸네일로 변경합니다 아래와 같은 샘플 스크립트를 작성합니다 이 스크립트는 제스처 콘텐츠 정보를 ui로 정리합니다 (제목, 이미지) 썸네일 import { zepetoscriptbehaviour } from 'zepeto script'; import { content } from 'zepeto world'; import { rawimage, text } from 'unityengine ui'; import { texture2d } from 'unityengine'; export default class thumbnail extends zepetoscriptbehaviour { @hideininspector() public content content; start() { this getcomponentinchildren\<text>() text = this content title; this getcomponentinchildren\<rawimage>() texture = this content thumbnail as texture2d; } } 스크립트를 생성한 후, prethumb 프리팹을 열고 스크립트를 추가합니다 2단계 2 gestureloader 계층 구조 만들기 > 빈 객체 생성 후 gestureloader로 이름 변경하기 프로젝트 만들기 > 생성 > zepeto > typescript로 생성 후 gestureloader로 이름 변경하기 아래와 같은 샘플 스크립트 작성하기 gestureloader import { zepetoscriptbehaviour } from 'zepeto script'; import { localplayer, spawninfo, zepetocharacter, zepetoplayers } from 'zepeto character controller'; import { officialcontenttype, worldservice, zepetoworldcontent, content } from 'zepeto world'; import { rawimage, text, button } from 'unityengine ui'; import { gameobject, texture2d, transform, waituntil } from 'unityengine'; import thumbnail from ' /thumbnail'; export default class gestureloader extends zepetoscriptbehaviour { @hideininspector() public contents content\[] = \[]; @hideininspector() public thumbnails gameobject\[] = \[]; @serializefield() private count number = 50; @serializefield() private contentsparent transform; @serializefield() private prefthumb gameobject; private mycharacter zepetocharacter; start() { // creating a character zepetoplayers instance createplayerwithuserid(worldservice userid, new spawninfo(), true); zepetoplayers instance onaddedlocalplayer addlistener(() => { this mycharacter = zepetoplayers instance localplayer zepetoplayer character; // in order to take a thumbnail with my character, you need to request the content after the character is created this contentrequest(); }); } // 1 receive content from the server private contentrequest() { // all type request zepetoworldcontent requestofficialcontentlist(officialcontenttype all, contents => { this contents = contents; for (let i = 0; i < this count; i++) { if (!this contents\[i] isdownloadedthumbnail) { // take a thumbnail photo using my character this contents\[i] downloadthumbnail(() =>{ this createthumbnailobjcet(this contents\[i]); }); } else { this createthumbnailobjcet(this contents\[i]); } } }); } // 2 creating thumbnail objects private createthumbnailobjcet(content content) { const newthumb gameobject = gameobject instantiate(this prefthumb, this contentsparent) as gameobject; newthumb getcomponent\<thumbnail>() content = content; // button listener for each thumbnail newthumb getcomponent\<button>() onclick addlistener(() => { this loadanimation(content); }); this thumbnails push(newthumb); } // 3 loading animation private loadanimation(content content) { // verify animation load if (!content isdownloadedanimation) { // if the animation has not been downloaded, download it content downloadanimation(() => { // play animation clip this mycharacter setgesture(content animationclip); }); } else { this mycharacter setgesture(content animationclip); } } } 카운트는 각 탭에서 다운로드할 최대 제스처 수입니다 100보다 큰 숫자로 설정하면 썸네일 다운로드 과정에서 오류가 발생할 수 있으므로 필요한 만큼만 설정해 주세요 스크립트는 다음과 같이 진행됩니다 1\) zepeto 캐릭터를 로드한 후 썸네일 생성을 위해 contentsrequest() 사용자 정의 함수를 호출합니다 contentsrequest() 함수는 제스처와 포즈를 각각 분리하여 콘텐츠 정보를 수신합니다 기존 썸네일이 있으면 건너뛰고, 그렇지 않으면 썸네일을 가져옵니다 가져온 썸네일 데이터는 각각의 리스트에 저장됩니다 단계 2 3 uicontroller 계층 구조 만들기 > 빈 객체 만들기 및 이름을 uicontoller로 변경합니다 프로젝트 만들기 > 만들기 > zepeto > typescript로 이름을 uicontoller로 변경합니다 아래와 같은 샘플 스크립트를 작성합니다 uicontroller import { zepetoscriptbehaviour } from 'zepeto script'; import { button, rawimage, text, toggle } from 'unityengine ui'; import { localplayer, zepetocharacter, zepetoplayers, zepetoscreentouchpad } from 'zepeto character controller'; import { officialcontenttype, content } from 'zepeto world'; import { object, gameobject, transform } from 'unityengine'; import gestureloader from ' /gestureloader'; import thumbnail from ' /thumbnail'; export default class uicontroller extends zepetoscriptbehaviour { @serializefield() private closebutton button; @serializefield() private typetogglegroup toggle\[]; private gesturelodaer gestureloader; private mycharacter zepetocharacter; start() { this gesturelodaer = object findobjectoftype\<gestureloader>(); zepetoplayers instance onaddedlocalplayer addlistener(() => { this mycharacter = zepetoplayers instance localplayer zepetoplayer character; // if click the touchpad, cancel the gesture object findobjectoftype\<zepetoscreentouchpad>() onpointerdownevent addlistener(() => { this stopgesture(); }); // if click the close button, cancel the gesture this closebutton onclick addlistener(() => { this stopgesture(); }); }); // ui listener this typetogglegroup\[0] onvaluechanged addlistener(() => { this setcategoryui(officialcontenttype all); }); this typetogglegroup\[1] onvaluechanged addlistener(() => { this setcategoryui(officialcontenttype gesture); }); this typetogglegroup\[2] onvaluechanged addlistener(() => { this setcategoryui(officialcontenttype pose); }); } // category toggle ui set private setcategoryui(category officialcontenttype) { if (category == officialcontenttype all) { this gesturelodaer thumbnails foreach((obj) => { obj setactive(true); }); } else { for (let i = 0; i < this gesturelodaer thumbnails length; i++) { const content = this gesturelodaer thumbnails\[i] getcomponent\<thumbnail>() content; if (content keywords includes(category)) { this gesturelodaer thumbnails\[i] setactive(true); } else { this gesturelodaer thumbnails\[i] setactive(false); } } } } private stopgesture() { this mycharacter cancelgesture(); } } 스크립트 흐름은 다음과 같습니다 재생을 취소하려면 터치패드나 닫기 버튼을 터치하여 cancelgesture() 함수를 사용하세요 탭(토글 버튼)을 탭하여 setcategoryui() 사용자 정의 함수를 호출하세요 setcategoryui() 함수는 각 썸네일의 제스처 콘텐츠 정보를 사용하여 해당 카테고리에 설정합니다 적용 가능한 유형이면 활성화하고, 그렇지 않으면 비활성화하세요 스크립트를 완료한 후, 인스펙터에 닫기 버튼과 타입 토글 그룹을 할당하세요 타입 토글 그룹에 대한 항목은 제스처 패널의 토글 그룹의 자식인 토글입니다 3단계 실행 ❗️ 주의 재생하기 전에 panelparent를 비활성화하여 재생 중에 열기 버튼만 보이도록 하세요 4단계 멀티 플레이 제스처 동기화 멀티 플레이의 경우 특정 플레이어가 가져온 제스처 정보 값을 수신하고 이를 방에 접근하는 모든 플레이어에게 적용하는 동기화 코드를 추가해야 합니다 핵심은 어떤 플레이어가 어떤 제스처를 만들었는지에 대한 방 메시지를 서버와 클라이언트 간에 송수신하는 것입니다 단계 4 1 클라이언트 코드 썸네일 멀티플레이 싱글 플레이 클라이언트 코드에 구현된 것과 동일한 스크립트를 작성하십시오 썸네일 멀티플레이 import { zepetoscriptbehaviour } from 'zepeto script'; import { content } from 'zepeto world'; import { rawimage, text } from 'unityengine ui'; import { texture2d } from 'unityengine'; export default class thumbnail extends zepetoscriptbehaviour { @hideininspector() public content content; start() { this getcomponentinchildren\<text>() text = this content title; this getcomponentinchildren\<rawimage>() texture = this content thumbnail as texture2d; } } 제스처 로더 멀티플레이 기본적으로 싱글 플레이 클라이언트 코드에 구현된 스크립트는 동일하게 작성됩니다 추가로, 클라이언트는 playergestureinfo를 포함하는 인터페이스를 선언합니다 서버에 정보를 보낼 때 sendmygesture() 사용자 정의 함수 참조 플레이어가 썸네일을 눌러 제스처를 만들 때, 제스처 id를 room send()를 사용하여 서버에 전송합니다 제스처를 취소할 때, 취소했다는 정보를 전송하도록 처리합니다 서버에서 다른 클라이언트로부터 제스처 정보를 받을 때 "onchangegesture" 룸 메시지가 start() 내의 this room addmessagehandler()로 전송됩니다 동기화는 "onchangegesture" 메시지에 세션 id와 제스처 id를 포함하여 적절한 플레이어가 제스처를 재생하도록 함으로써 이루어집니다 import { zepetoscriptbehaviour } from 'zepeto script'; import { localplayer, spawninfo, zepetocharacter, zepetoplayers } from 'zepeto character controller'; import { officialcontenttype, worldservice, zepetoworldcontent, content, zepetoworldmultiplay } from 'zepeto world'; import { rawimage, text, button } from 'unityengine ui'; import { gameobject, texture2d, transform, waituntil } from 'unityengine'; import thumbnail from ' /thumbnail'; import { room, roomdata } from 'zepeto multiplay'; interface playergestureinfo { sessionid string, gestureid string } const cancelmotion = "" as const; export default class gestureloadermultiplay extends zepetoscriptbehaviour { public multiplay zepetoworldmultiplay; @hideininspector() public contents content\[] = \[]; @hideininspector() public thumbnails gameobject\[] = \[]; @serializefield() private count number = 50; @serializefield() private contentsparent transform; @serializefield() private prefthumb gameobject; private mycharacter zepetocharacter; private room room; private contentsmap map\<string, content> = new map\<string, content>(); start() { // 캐릭터 생성 zepetoplayers instance onaddedlocalplayer addlistener(() => { this mycharacter = zepetoplayers instance localplayer zepetoplayer character; // 내 캐릭터로 썸네일을 찍으려면 캐릭터가 생성된 후 콘텐츠를 요청해야 합니다 this contentrequest(); }); // 멀티플레이를 위한 this multiplay roomcreated += (room room) => { this room = room; // 서버에서 사용자 제스처 정보를 수신 this room addmessagehandler("onchangegesture", (message playergestureinfo) => { let playergestureinfo playergestureinfo = { sessionid message sessionid, gestureid message gestureid }; this loadanimation(playergestureinfo); }); }; } // 1 서버에서 콘텐츠 수신 private contentrequest(){ // 모든 유형 요청 zepetoworldcontent requestofficialcontentlist(officialcontenttype all, contents => { this contents = contents; for (let i = 0; i < this count; i++) { if (!this contents\[i] isdownloadedthumbnail) { // 내 캐릭터를 사용하여 썸네일 사진 찍기 this contents\[i] downloadthumbnail(() =>{ this createthumbnailobjcet(this contents\[i]); }); } else { this createthumbnailobjcet(this contents\[i]); } } }); } // 2 썸네일 객체 생성 private createthumbnailobjcet(content content) { const newthumb gameobject = gameobject instantiate(this prefthumb, this contentsparent) as gameobject; newthumb getcomponent\<thumbnail>() content = content; // 콘텐츠 id로 콘텐츠를 찾기 위한 사전 생성 this contentsmap set(content id, content); // 각 썸네일에 대한 버튼 리스너 newthumb getcomponent\<button>() onclick addlistener(() => { this sendmygesture(content id); }); // 썸네일 목록 this thumbnails push(newthumb); } // 멀티플레이를 위한 // 클릭한 제스처 정보를 서버에 전송 public sendmygesture(gestureid) { const data = new roomdata(); data add("gestureid", gestureid); this room send("onchangegesture", data getobject()); } // 3 애니메이션 로딩 private loadanimation(playergestureinfo playergestureinfo){ if (!zepetoplayers instance hasplayer(playergestureinfo sessionid)) { console log("플레이어가 존재하지 않습니다"); return; } const zepetoplayer = zepetoplayers instance getplayer(playergestureinfo sessionid) character; if (playergestureinfo gestureid == cancelmotion) { zepetoplayer cancelgesture(); return; } else if(!this contentsmap has(playergestureinfo gestureid)) { console log("리소스가 아직 로드되지 않았습니다"); return; } const content = this contentsmap get(playergestureinfo gestureid); // 애니메이션 로드 확인 if (!content isdownloadedanimation) { // 애니메이션이 다운로드되지 않은 경우 다운로드합니다 content downloadanimation(() => { // 애니메이션 클립 재생 zepetoplayer setgesture(content animationclip); }); } else { zepetoplayer setgesture(content animationclip); } } } 스크립트를 완료한 후, 검사자는 zepeto world multiplay 구성 요소와 함께 multiplay에 추가 객체를 할당합니다 uicontroller 멀티플레이 기본적으로, 단일 플레이 클라이언트 코드에서 구현된 스크립트는 동일하게 작성됩니다 단일 플레이 클라이언트 코드와의 차이점은 stopgesture() 사용자 정의 함수입니다 gestureloadermultiplay 내에서 sendmygesture() 사용자 정의 함수를 호출합니다 제스처가 취소되었다는 정보를 전송하는 프로세스입니다 import { zepetoscriptbehaviour } from 'zepeto script'; import { button, rawimage, text, toggle } from 'unityengine ui'; import { localplayer, zepetocharacter, zepetoplayers, zepetoscreentouchpad } from 'zepeto character controller'; import { officialcontenttype, content } from 'zepeto world'; import { object, gameobject, transform } from 'unityengine'; import gestureloadermultiplay from ' /gestureloadermultiplay'; import thumbnail from ' /thumbnail'; const cancelmotion = "" as const; export default class uicontroller extends zepetoscriptbehaviour { @serializefield() private closebutton button; @serializefield() private typetogglegroup toggle\[]; private gesturelodaer gestureloadermultiplay; private mycharacter zepetocharacter; start() { this gesturelodaer = object findobjectoftype\<gestureloadermultiplay>(); zepetoplayers instance onaddedlocalplayer addlistener(() => { this mycharacter = zepetoplayers instance localplayer zepetoplayer character; // 터치패드를 클릭하면 제스처를 취소합니다 object findobjectoftype\<zepetoscreentouchpad>() onpointerdownevent addlistener(() => { this stopgesture(); }); // 닫기 버튼을 클릭하면 제스처를 취소합니다 this closebutton onclick addlistener(() => { this stopgesture(); }); }); // ui 리스너 this typetogglegroup\[0] onvaluechanged addlistener(() => { this setcategoryui(officialcontenttype all); }); this typetogglegroup\[1] onvaluechanged addlistener(() => { this setcategoryui(officialcontenttype gesture); }); this typetogglegroup\[2] onvaluechanged addlistener(() => { this setcategoryui(officialcontenttype pose); }); } // 카테고리 토글 ui 설정 private setcategoryui(category officialcontenttype) { if (category == officialcontenttype all) { this gesturelodaer thumbnails foreach((obj) => { obj setactive(true); }); } else { for (let i = 0; i < this gesturelodaer thumbnails length; i++) { const content = this gesturelodaer thumbnails\[i] getcomponent\<thumbnail>() content; if (content keywords includes(category)) { this gesturelodaer thumbnails\[i] setactive(true); } else { this gesturelodaer thumbnails\[i] setactive(false); } } } } private stopgesture() { this gesturelodaer sendmygesture(cancelmotion); } } 단계 4 2 서버 코드 서버 코드는 playergestureinfo를 포함하도록 인터페이스를 선언합니다 서버 코드는 기본적으로 multiplay sample의 서버 코드를 기반으로 합니다 oncreate() 내에서 제스처가 변경될 때 다른 클라이언트에 제스처 정보를 전송하는 onmessage() 콜백을 생성합니다 import { sandbox, sandboxoptions, sandboxplayer } from 'zepeto multiplay'; import { player, transform, vector3 } from 'zepeto multiplay schema'; // 플레이어의 제스처 정보를 나타내기 위한 인터페이스 playergestureinfo를 정의합니다 interface playergestureinfo { sessionid string, gestureid string } export default class extends sandbox { // 스크립트에서 사용되는 메시지 유형을 저장하기 위한 상수 객체 `message type`을 정의합니다 message type = { onchangegesture "onchangegesture" } oncreate(options sandboxoptions) { // 방이 생성될 때 호출됩니다 // 방의 상태 또는 데이터 초기화를 처리합니다 this onmessage("onchangedtransform", (client, message) => { const player = this state players get(client sessionid); const transform = new transform(); transform position = new vector3(); transform position x = message position x; transform position y = message position y; transform position z = message position z; transform rotation = new vector3(); transform rotation x = message rotation x; transform rotation y = message rotation y; transform rotation z = message rotation z; player transform = transform; }); this onmessage("onchangedstate", (client, message) => { const player = this state players get(client sessionid); player state = message state; player substate = message substate; // 캐릭터 컨트롤러 v2 }); // 제스처가 변경될 때, this onmessage\<playergestureinfo>(this message type onchangegesture, (client, message) => { let gestureinfo\ playergestureinfo = { sessionid client sessionid, gestureid message gestureid }; // 클라이언트를 제외한 다른 플레이어에게 제스처를 전송합니다 this broadcast(this message type onchangegesture, gestureinfo); }); } onjoin(client sandboxplayer) { // schemas json에 정의된 플레이어 객체를 생성한 후 초기 값을 설정합니다 console log(`\[onjoin] sessionid ${client sessionid}, userid ${client userid}`) const player = new player(); player sessionid = client sessionid; if (client userid) { player zepetouserid = client userid; } // 클라이언트 객체의 고유 키 값인 sessionid를 사용하여 player 객체를 관리합니다 // 클라이언트는 players 객체에 add onadd 이벤트를 추가하여 추가된 플레이어 객체에 대한 정보를 확인할 수 있습니다 this state players set(client sessionid, player); } onleave(client sandboxplayer, consented? boolean) { // allowreconnection을 설정하면 회로에 대한 연결을 유지할 수 있지만 기본 가이드에서 즉시 정리합니다 // 클라이언트는 players 객체에 add onremove 이벤트를 추가하여 삭제된 플레이어 객체에 대한 정보를 확인할 수 있습니다 this state players delete(client sessionid); } }