CREATE YOUR WORLD
Players & Characters : Basic
ZEPETO Players
20분
zepetoplayers 는 zepeto 플레이어와 zepeto 캐릭터를 제어하기 위해 설계된 매니저(싱글톤) 클래스입니다 zepetoplayers를 장면에 추가하면 zepeto 플레이어를 생성, 삭제 및 조작할 수 있습니다 zepetoplayer 는 멀티플레이어 월드 에서 직접 제어하는 플레이어와 다른 플레이어를 관리하는 데 사용되는 zepeto 캐릭터의 개별 인스턴스를 나타냅니다 멀티플레이어 월드 에서 생성된 모든 zepetoplayer는 고유한 세션 id가 할당되며 이 세션 id를 사용하여 관리됩니다 zepetoplayer는 zepetocharacter의 속성을 가지고 있기 때문에 zepetocharacter와 관련된 기능을 사용하여 제어할 수 있습니다 zepetoplayer의 유형은 세 가지입니다 zepetoplayer 설명 로컬 플레이어 로컬 사용자가 직접 제어하는 zepeto 캐릭터 인스턴스입니다 \ 캐릭터 컨트롤러/zepeto 카메라 컴포넌트가 부착되어 있습니다 네트워크 플레이어 (원격 플레이어) 멀티플레이 콘텐츠에서 로드하고 활용할 수 있는 zepeto 캐릭터 인스턴스입니다 \ 캐릭터 컨트롤러/zepeto 카메라 컴포넌트가 부착되어 있지 않습니다 봇 플레이어 멀티플레이 콘텐츠를 위한 zepeto 캐릭터 인스턴스이지만, 실제 사용자가 아닌 봇에 의해 제어됩니다 플레이어가 부족한 멀티플레이 월드를 시작할 때 또는 플레이 중 플레이어가 나갈 경우 대체로 사용됩니다 \ 캐릭터 컨트롤러/zepeto 카메라 컴포넌트가 부착되어 있지 않습니다 다음 가이드를 참조하십시오 \[ zepeto players docid\ f640tn2uw qrnv1mlagv2 ] \[ bot 플레이어 생성 가이드 docid\ ubtun8rdukqtokwdgzbyw ] zepetocharacter 는 월드 씬에서 로드되고 제어될 수 있는 zepeto 캐릭터의 기본 인스턴스 단위입니다 zepetocharacter는 zepeto 앱을 통해 생성된 아바타의 외형을 가지고 있습니다 다음 가이드를 참조하십시오 \[ zepeto character docid\ tbhz4sg67nxaodv xl5wv ] zepetoplayers 추가하기 계층 창에서 zepeto → zepetoplayers 탭을 선택하십시오 이제 장면에 추가할 수 있습니다 zepetoplayers를 추가하는 것만으로는 zepeto player가 장면에 들어오지 않음을 유의하세요 zepetoplayers의 캐릭터 생성 api를 사용하여 스크립트를 구현해야 합니다 장면에서 로컬 플레이어만 빠르게 생성하고 시도하고 싶다면, 가이드를 참조하세요 다음 가이드를 참조하세요 \[ 제페토 캐릭터 생성하기 docid\ qup q pugs7gyvyosl va ] zepeto 플레이어의 이름과 프로필 사진을 표시하는 방법에 대한 샘플은 가이드를 확인하세요 다음 가이드를 참조하세요 \[ 유저 정보 docid\ fezf1tvnnxndjui8c2ygz ] zepeto 플레이어 api zepetoplayers api에 관심이 있다면 문서를 참조하세요 다음 가이드를 참조하세요 \[ zepeto character controller api ] 이 가이드는 주로 멀티플레이어 시나리오에서 zepeto 플레이어를 사용하는 예제를 다룹니다 zepeto 플레이어를 사용한 멀티플레이 위치 동기화 구현 싱글 플레이어 월드에서는 로컬 플레이어만 생성하면 되고, 로컬 플레이어만 화면에 나타나므로 동기화가 필요하지 않습니다 그러나 멀티플레이어 월드 에서는 직접 제어하는 로컬 플레이어뿐만 아니라 네트워크 플레이어라고 불리는 다른 플레이어도 화면에 표시되어야 합니다 네트워크 플레이어의 모든 행동 이동, 점프 및 특정 제스처 수행 은 화면에 동일하게 나타나야 합니다 이 과정을 동기화라고 합니다 동기화 스크립트를 구현하지 않으면 네트워크 플레이어의 외형이나 움직임을 볼 수 없습니다 동기화가 없는 멀티플레이 월드 에서는 다른 클라이언트가 방에 들어왔는지 알 수 있는 유일한 방법은 홈 버튼을 통해서입니다 1단계 멀티플레이 환경 설정 멀티플레이 튜토리얼 비디오를 통해 멀티플레이의 기본 설정과 개념을 이해하는 것으로 시작하는 것이 좋습니다 다음 가이드를 참조하십시오 \[ 멀티플레이 제작하기 docid\ s6aoernolpac9cyqi onr ] \[ 멀티플레이 docid\ lqdlv5a9ek5hwd16tv0y ] 2단계 화면에 다른 플레이어 표시하기 당신의 로컬 플레이어는 다른 사람의 장치에서 네트워크 플레이어로 취급됩니다 이는 당신의 로컬 플레이어도 동기화를 위해 서버에 정보를 보내야 함을 의미합니다 멀티플레이 월드 에 연결된 모든 플레이어는 자신의 정보를 공유해야 합니다 멀티플레이 방에 연결된 모든 클라이언트는 멀티플레이 방 상태 데이터를 공유합니다 이 방 상태 데이터는 schemas json에 정의된 스키마를 따릅니다 (스키마를 데이터 구조로 고려해 주세요) 이 가이드에서는 플레이어의 위치를 room state 데이터를 통해 동기화합니다 따라서 각 플레이어의 위치 데이터를 나타낼 수 있는 스키마를 schemas json에 정의하세요 schema json { "state" {"players" {"map" "player"}}, "player" {"sessionid" "string","zepetouserid" "string","transform" "transform","state" "number","substate" "number"}, "transform" {"position" "vector3","rotation" "vector3"}, "vector3" {"x" "number","y" "number","z" "number"} } 다음 가이드를 참조하세요 \[ multiplay room state docid\ thsr nwc iwkzht3kiqxy ] 👍 팁 서버와 모든 클라이언트가 공유해야 할 모든 정보를 멀티플레이 룸 상태에 저장하세요 각 플레이어의 레벨, 경험치, 점수 등과 같은 개별 데이터를 데이터 저장소를 사용하여 저장하세요 서버 스크립트는 다른 플레이어가 방에 들어왔을 때 이를 인식하고 해당 정보를 클라이언트에 전송하여 그 플레이어를 장면에 로드할 수 있습니다 단계 2 1 기본 서버 스크립트 플레이어가 멀티플레이 월드 의 방에 들어오면 onjoin()이 호출됩니다 서버 스크립트에서 연결된 플레이어의 정보를 방 상태의 플레이어 목록에 추가합니다 또한, 플레이어가 방을 나가면 onleave()가 호출됩니다 서버 스크립트는 방 상태의 플레이어 목록에서 나간 플레이어의 정보를 제거합니다 import {sandbox, sandboxoptions, sandboxplayer} from "zepeto multiplay"; import {player} from "zepeto multiplay schema"; export default class extends sandbox { oncreate(options sandboxoptions) { } async onjoin(client sandboxplayer) { 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); } async onleave(client sandboxplayer, consented? boolean) { this state players delete(client sessionid); } } 단계 2 2 기본 클라이언트 스크립트 멀티플레이 월드를 생성할 때 서버와 통신하기 위해 클라이언트 스크립트가 필요합니다 아래는 멀티플레이를 위한 기본 클라이언트 스크립트의 예입니다 클라이언트 측에서는 currentplayers 맵 데이터 구조를 사용하여 클라이언트에 표시할 플레이어 데이터를 관리합니다 아래에 중요한 코드 줄이 설명되어 있습니다 zepetoplayers instance createplayerwithuserid(sessionid, player zepetouserid, spawninfo, islocal); 클라이언트가 서버의 room state에서 플레이어 정보를 수신하고 새로운 플레이어가 방에 참여하면 세션 id가 할당됩니다 생성 중인 플레이어의 세션 id가 우리의 세션 id와 일치하면 해당 플레이어는 로컬로 간주됩니다 이 경우 플레이어는 islocal = true , 로컬 플레이어임을 나타냅니다 그 후, 지역 주민이 아닌 플레이어는 islocal = false 이는 모든 플레이어의 외관이 화면에 렌더링되도록 보장합니다 지역 플레이어와 비지역 플레이어 모두 포함됩니다 import {zepetoscriptbehaviour} from 'zepeto script' import {zepetoworldmultiplay} from 'zepeto world' import {room, roomdata} from 'zepeto multiplay' import {player, state, vector3} from 'zepeto multiplay schema' import {characterstate, spawninfo, zepetoplayers, zepetoplayer, characterjumpstate} from 'zepeto character controller' import as unityengine from "unityengine"; export default class multiplayclientcode extends zepetoscriptbehaviour { public multiplay zepetoworldmultiplay; private room room; private currentplayers map\<string, player> = new map\<string, player>(); private zepetoplayer zepetoplayer; private start() { this multiplay roomcreated += (room room) => { this room = room; }; this multiplay roomjoined += (room room) => { room onstatechange += this onstatechange; }; } private onstatechange(state state, isfirst boolean) { // 첫 번째 onstatechange 이벤트가 수신되면 전체 상태 스냅샷이 기록됩니다 if (isfirst) { // \[charactercontroller] (local) 플레이어 인스턴스가 씬에 완전히 로드되었을 때 호출됩니다 zepetoplayers instance onaddedlocalplayer addlistener(() => { const myplayer = zepetoplayers instance localplayer zepetoplayer; this zepetoplayer = myplayer; }); // \[charactercontroller] (local) 플레이어 인스턴스가 씬에 완전히 로드되었을 때 호출됩니다 zepetoplayers instance onaddedplayer addlistener((sessionid string) => { const islocal = this room sessionid === sessionid; if (!islocal) { const player player = this currentplayers get(sessionid); } }); } let join = new map\<string, player>(); let leave = new map\<string, player>(this currentplayers); state players foreach((sessionid string, player player) => { if (!this currentplayers has(sessionid)) { join set(sessionid, player); } leave delete(sessionid); }); // \[roomstate] 방에 들어오는 플레이어를 위한 플레이어 인스턴스 생성 join foreach((player player, sessionid string) => this onjoinplayer(sessionid, player)); // \[roomstate] 방을 나가는 플레이어를 위한 플레이어 인스턴스 제거 leave foreach((player player, sessionid string) => this onleaveplayer(sessionid, player)); } private onjoinplayer(sessionid string, player player) { console log(`\[onjoinplayer] players sessionid ${sessionid}`); this currentplayers set(sessionid, player); // 플레이어 생성 const islocal = this room sessionid === player sessionid; zepetoplayers instance createplayerwithuserid(sessionid, player zepetouserid, new spawninfo(), islocal); } private onleaveplayer(sessionid string, player player) { console log(`\[onremove] players sessionid ${sessionid}`); this currentplayers delete(sessionid); zepetoplayers instance removeplayer(sessionid); } } 이제 플레이어가 들어오면, zepeto 캐릭터가 화면에 생성되었음을 확인할 수 있습니다 하지만 플레이어의 움직임은 아직 화면에 반영되지 않습니다 다음으로 위치 동기화로 넘어갑니다 3단계 다른 플레이어의 정보를 가져와 동기화하기 동기화를 위해, 플레이어가 움직이거나 어떤 행동을 할 때마다 그들은 자신의 상태 변화를 서버에 전송해야 합니다 상태 변화를 포함한 메시지를 서버에 전송하는 것은 룸 메시지 통신을 통해 이루어집니다 서버가 플레이어의 상태 변화에 대한 메시지를 받으면, 룸 상태를 업데이트합니다 다음 가이드를 참조하십시오 \[ multiplay room message docid\ yt8t8xpljhscnor5y4r7n ] 예를 들어, 내 로컬 플레이어의 이름이 b라고 가정해 보겠습니다 네트워크 플레이어 a가 룸에 들어오면, 그들은 좌표 x 0, y 0, z 0에서 인스턴스화됩니다 플레이어 a가 위치 x 10, y 0, z 0으로 이동하면, b는 a가 이동하고 있다는 것을 실제로 알 방법이 없다 이는 두 장치에서 각각 로컬로 운영되고 있기 때문에, 서로 다른 클라이언트에서 작동하고 있기 때문이다 따라서 a를 조종하는 사람은 방 메시지를 통해 자신의 이동을 서버에 전달해야 한다 서버가 이 정보를 수신하면, 방에 있는 모든 사람에게 a의 실시간 위치를 알린다 이렇게 하면 b는 a가 이동하고 있다는 것을 알게 된다 서버가 다른 플레이어에게 알리기 위해서는 두 가지 방법이 있다 방 메시지 방송 사용하기 방 상태 업데이트 후 클라이언트가 방 상태를 가져와 적용하기 이 가이드는 방 상태를 업데이트하는 두 번째 방법을 사용한다 zepeto 플레이어가 장면에 로드되면 zepeto 캐릭터 속성을 가지므로, zepeto 캐릭터 기능을 사용하여 특정 위치로 이동하거나 점프를 시작할 수 있습니다 b의 클라이언트가 a의 이동을 x 10, y 0, z 0으로 시각화하기 위해 가장 직관적인 방법은 movetoposition()을 사용하는 것입니다 이 함수는 서버에서 수신한 a의 가장 최근 위치로 플레이어를 이동시킵니다 위치 변경뿐만 아니라 제스처, 스킬 사용, 아이템 수집 및 모든 상태 변경은 동기화를 위해 서버 클라이언트 통신이 필요합니다 네트워크 전반에 걸쳐 모든 행동을 조화롭게 유지하기 위해 동기화를 구현해야 합니다 👍 동기화 개념 요약 로컬 플레이어가 상태 변경을 할 때, 이를 방 메시지를 사용하여 서버에 전송합니다 서버는 로컬 플레이어를 제외한 모든 다른 플레이어에게 상태 변경을 알립니다 상태 변경 메시지를 수신하면, 클라이언트 코드는 메시지를 보낸 플레이어의 상태를 업데이트합니다 3단계 1 완료된 위치 동기화가 있는 서버 스크립트 기본 서버 스크립트에서는 로컬 플레이어의 클라이언트에서 상태 변경에 대한 메시지를 받을 때마다 방 상태를 업데이트하기 위한 추가 구현이 필요합니다 import {sandbox, sandboxoptions, sandboxplayer} from "zepeto multiplay"; import {player, transform, vector3} from "zepeto multiplay schema"; export default class extends sandbox { constructor() { super(); } 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; if (player) { player transform = transform; } }); this onmessage("onchangedstate", (client, message) => { const player = this state players get(client sessionid); if (player) { player state = message state; player substate = message substate; // 캐릭터 컨트롤러 v2 } }); } async 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를 사용하여 플레이어 객체를 관리합니다 // 클라이언트는 players 객체에 add onadd 이벤트를 추가하여 추가된 플레이어 객체에 대한 정보를 확인할 수 있습니다 this state players set(client sessionid, player); } async onleave(client sandboxplayer, consented? boolean) { // allowreconnection을 설정하면 회로에 대한 연결을 유지할 수 있지만 기본 가이드에서는 즉시 정리합니다 // 클라이언트는 players 객체에 add onremove 이벤트를 추가하여 삭제된 플레이어 객체에 대한 정보를 확인할 수 있습니다 this state players delete(client sessionid); } } 3단계 2 완료된 위치 동기화가 있는 클라이언트 스크립트 기본 클라이언트 스크립트의 주요 구현 사항은 다음과 같습니다 서버의 방 상태가 변경될 때 onstatechange를 자동으로 호출합니다 다음의 sendmessageloop(0 04) 함수를 사용하여, 로컬 플레이어의 위치와 캐릭터 점프 상태 정보를 서버에 0 04초마다 전송합니다 import {zepetoscriptbehaviour} from 'zepeto script' import {zepetoworldmultiplay} from 'zepeto world' import {room, roomdata} from 'zepeto multiplay' import {player, state, vector3} from 'zepeto multiplay schema' import {characterstate, spawninfo, zepetoplayers, zepetoplayer, characterjumpstate} from 'zepeto character controller' import as unityengine from "unityengine"; export default class clientstarterv2 extends zepetoscriptbehaviour { public multiplay zepetoworldmultiplay; private room room; private currentplayers map\<string, player> = new map\<string, player>(); private zepetoplayer zepetoplayer; private start() { this multiplay roomcreated += (room room) => { this room = room; }; this multiplay roomjoined += (room room) => { room onstatechange += this onstatechange; }; this startcoroutine(this sendmessageloop(0 04)); } // 로컬 캐릭터 변환을 서버에 예정된 간격으로 전송합니다 private sendmessageloop(tick number) { while (true) { yield new unityengine waitforseconds(tick); if (this room != null && this room isconnected) { const hasplayer = zepetoplayers instance hasplayer(this room sessionid); if (hasplayer) { const character = zepetoplayers instance getplayer(this room sessionid) character; this sendtransform(character transform); this sendstate(character currentstate); } } } } private onstatechange(state state, isfirst boolean) { // 첫 번째 onstatechange 이벤트가 수신되면 전체 상태 스냅샷이 기록됩니다 if (isfirst) { // \[charactercontroller] (로컬) 플레이어 인스턴스가 씬에 완전히 로드될 때 호출됩니다 zepetoplayers instance onaddedlocalplayer addlistener(() => { const myplayer = zepetoplayers instance localplayer zepetoplayer; this zepetoplayer = myplayer; }); // \[charactercontroller] (로컬) 플레이어 인스턴스가 씬에 완전히 로드될 때 호출됩니다 zepetoplayers instance onaddedplayer addlistener((sessionid string) => { const islocal = this room sessionid === sessionid; if (!islocal) { const player player = this currentplayers get(sessionid); // \[roomstate] 플레이어 인스턴스의 상태가 업데이트될 때마다 호출됩니다 player onchange += (changevalues) => this onupdateplayer(sessionid, player); } }); } let join = new map\<string, player>(); let leave = new map\<string, player>(this currentplayers); state players foreach((sessionid string, player player) => { if (!this currentplayers has(sessionid)) { join set(sessionid, player); } leave delete(sessionid); }); // \[roomstate] 방에 들어오는 플레이어를 위한 플레이어 인스턴스를 생성합니다 join foreach((player player, sessionid string) => this onjoinplayer(sessionid, player)); // \[roomstate] 방을 나가는 플레이어를 위한 플레이어 인스턴스를 제거합니다 leave foreach((player player, sessionid string) => this onleaveplayer(sessionid, player)); } private onjoinplayer(sessionid string, player player) { console log(`\[onjoinplayer] players sessionid ${sessionid}`); this currentplayers set(sessionid, player); const spawninfo = new spawninfo(); const position = this parsevector3(player transform position); const rotation = this parsevector3(player transform rotation); spawninfo position = position; spawninfo rotation = unityengine quaternion euler(rotation); const islocal = this room sessionid === player sessionid; zepetoplayers instance createplayerwithuserid(sessionid, player zepetouserid, spawninfo, islocal); } private onleaveplayer(sessionid string, player player) { console log(`\[onremove] players sessionid ${sessionid}`); this currentplayers delete(sessionid); zepetoplayers instance removeplayer(sessionid); } private onupdateplayer(sessionid string, player player) { const position = this parsevector3(player transform position); const zepetoplayer = zepetoplayers instance getplayer(sessionid); var movedir = unityengine vector3 op subtraction(position, zepetoplayer character transform position); movedir = new unityengine vector3(movedir x, 0, movedir z); if (movedir magnitude < 0 05) { if (player state === characterstate moveturn) return; zepetoplayer character stopmoving(); } else { zepetoplayer character movecontinuously(movedir); } if (player state === characterstate jump) { if (zepetoplayer character currentstate !== characterstate jump) { zepetoplayer character jump(); } if (player substate === characterjumpstate jumpdouble) { zepetoplayer character doublejump(); } } } private sendtransform(transform unityengine transform) { const data = new roomdata(); const pos = new roomdata(); pos add("x", transform localposition x); pos add("y", transform localposition y); pos add("z", transform localposition z); data add("position", pos getobject()); const rot = new roomdata(); rot add("x", transform localeulerangles x); rot add("y", transform localeulerangles y); rot add("z", transform localeulerangles z); data add("rotation", rot getobject()); this room send("onchangedtransform", data getobject()); } private sendstate(state characterstate) { const data = new roomdata(); data add("state", state); if(state === characterstate jump) { data add("substate", this zepetoplayer character motionv2 currentjumpstate); } this room send("onchangedstate", data getobject()); } private parsevector3(vector3 vector3) unityengine vector3 { return new unityengine vector3 ( vector3 x, vector3 y, vector3 z ); } } 👍 팁 이 가이드는 위치 동기화만 구현합니다 제스처 동기화, 객체 동기화 등은 구현되지 않았습니다 원리는 모두 동일하지만, 필요한 순간마다 방 메시지를 보내고 받는 과정이 필요합니다 동기화를 더 편리하게 구현하고 싶다면, 멀티플레이 동기화 모듈을 사용하는 것을 고려하세요