TUTORIALS
멀티플레이 제작하기
15min
샘플 프로젝트 📘 멀티플레이 샘플 https //github com/naverz/zepeto multiplay example https //github com/naverz/zepeto multiplay example 요약 요약 멀티플레이 서버 생성부터 클라이언트 생성까지, 멀티플레이 월드 개발을 위한 환경 설정을 진행합니다 난이도 중급 소요 시간 1시간 part 1 멀티플레이 설정하기 멀티플레이 서버 생성부터 클라이언트 생성까지, 멀티플레이 월드 개발을 위한 환경 설정을 진행합니다 https //www youtube com/watch?v=s68b1tma a8 https //www youtube com/watch?v=s68b1tma a8 part 2 월드 로직 작성하기 서버, 클라이언트 간 통신을 위해 필요한 schema에 대해 알아보고, schema types와 room state를 정의합니다 https //www youtube com/watch?v=tfky rabov0 https //www youtube com/watch?v=tfky rabov0 영상에 사용된 서버 스크립트 중에서 hashcode 관련된 내용은 더이상 지원하지 않습니다 따라서 아래 코드는 제외하고 작성하여 주세요 if (client hashcode) { 	player zepetohash = client hashcode; } part 3 월드 로직 작성하기 2 플레이어의 위치 동기화부터 플레이어의 퇴장까지를 진행합니다 https //www youtube com/watch?v=h92gd2g9dhm https //www youtube com/watch?v=h92gd2g9dhm part 4 서버 구동 및 멀티플레이 접속하기 서버를 실행하고 멀티플레이에 연결합니다 https //www youtube com/watch?v=fmk6jnlsjja https //www youtube com/watch?v=fmk6jnlsjja part 5 scripts schemas json schemas json { "state" {"players" {"map" "player"}}, "player" {"sessionid" "문자열","zepetouserid" "문자열","transform" "변환","state" "숫자","substate" "숫자"}, "transform" {"position" "벡터3","rotation" "벡터3"}, "vector3" {"x" "숫자","y" "숫자","z" "숫자"} } 서버 코드 index ts import {sandbox, sandboxoptions, sandboxplayer} from "zepeto multiplay"; import {datastorage} from "zepeto multiplay datastorage"; import {player, transform, vector3} from "zepeto multiplay schema"; export default class extends sandbox { storagemap\ map\<string,datastorage> = new map\<string, datastorage>(); 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; } // \[datastorage] 들어온 플레이어의 datastorage 로드 const storage datastorage = client loaddatastorage(); this storagemap set(client sessionid,storage); let visit cnt = await storage get("visitcount") as number; if (visit cnt == null) visit cnt = 0; console log(`\[onjoin] ${client sessionid}의 방문 횟수 ${visit cnt}`) // \[datastorage] 플레이어의 방문 횟수를 업데이트하고 저장합니다 await storage set("visitcount", ++visit cnt); // 클라이언트 객체의 고유 키 값인 sessionid를 사용하여 플레이어 객체를 관리합니다 // 클라이언트는 players 객체에 add onadd 이벤트를 추가하여 추가된 플레이어 객체에 대한 정보를 확인할 수 있습니다 this state players set(client sessionid, player); } ontick(deltatime number) void { // 서버에서 설정된 시간마다 반복적으로 호출되며, deltatime을 사용하여 특정 간격 이벤트를 관리할 수 있습니다 } async onleave(client sandboxplayer, consented? boolean) { 	 // allowreconnection을 설정하면 회로를 위해 연결을 유지할 수 있지만, 기본 가이드에서 즉시 정리합니다 // 클라이언트는 players 객체에 add onremove 이벤트를 추가하여 삭제된 플레이어 객체에 대한 정보를 확인할 수 있습니다 this state players delete(client sessionid); } } 클라이언트 코드 clientstarter ts 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)); } // send the local character transform to the server at the scheduled interval time 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) { // when the first onstatechange event is received, a full state snapshot is recorded if (isfirst) { // \[charactercontroller] (local) called when the player instance is fully loaded in scene zepetoplayers instance onaddedlocalplayer addlistener(() => { const myplayer = zepetoplayers instance localplayer zepetoplayer; this zepetoplayer = myplayer; }); // \[charactercontroller] (local) called when the player instance is fully loaded in scene zepetoplayers instance onaddedplayer addlistener((sessionid string) => { const islocal = this room sessionid === sessionid; if (!islocal) { const player player = this currentplayers get(sessionid); // \[roomstate] called whenever the state of the player instance is updated 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] create a player instance for players that enter the room join foreach((player player, sessionid string) => this onjoinplayer(sessionid, player)); // \[roomstate] remove the player instance for players that exit the room 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 ); } }