บทเรียน
บทเรียนการเล่นหลายคน
15นาที
ตัวอย่างโครงการ 📘 ตัวอย่าง multiplay https //github com/naverz/zepeto multiplay example https //github com/naverz/zepeto multiplay example สรุป สรุป จากการสร้างเซิร์ฟเวอร์ multiplay ไปยังไคลเอนต์ ตั้งค่าสภาพแวดล้อมที่จำเป็นในการพัฒนาโลก multiplay ความยาก ระดับกลาง เวลาที่ต้องใช้ 1 ชั่วโมง ส่วนที่ 1 การติดตั้ง multiplay จากการสร้างเซิร์ฟเวอร์ multiplay ไปยังไคลเอนต์ ตั้งค่าสภาพแวดล้อมที่จำเป็นในการพัฒนาโลก multiplay https //www youtube com/watch?v=s68b1tma a8 https //www youtube com/watch?v=s68b1tma a8 ส่วนที่ 2 การเขียนตรรกะของโลก เรียนรู้เกี่ยวกับ schema ที่จำเป็นสำหรับการสื่อสารระหว่างเซิร์ฟเวอร์และไคลเอนต์ และกำหนดประเภท schema และสถานะห้อง https //www youtube com/watch?v=tfky rabov0 https //www youtube com/watch?v=tfky rabov0 โปรดทราบว่า สคริปต์เซิร์ฟเวอร์ที่ใช้ในวิดีโอ ซึ่งรวมถึงเนื้อหาที่เกี่ยวข้องกับ hashcode ไม่ได้รับการสนับสนุนอีกต่อไป ดังนั้น ให้ละเว้นโค้ดต่อไปนี้เมื่อเขียน if (client hashcode) { 	player zepetohash = client hashcode; } ส่วนที่ 3 การเขียนตรรกะของโลก 2 เขียนตรรกะของโลกที่จำเป็นในการซิงค์จากตำแหน่งของผู้เล่นไปยังทางออกของผู้เล่น https //www youtube com/watch?v=h92gd2g9dhm https //www youtube com/watch?v=h92gd2g9dhm ส่วนที่ 4 การรันเซิร์ฟเวอร์และเชื่อมต่อกับ multiplay รันเซิร์ฟเวอร์และเชื่อมต่อกับ multiplay https //www youtube com/watch?v=fmk6jnlsjja https //www youtube com/watch?v=fmk6jnlsjja ส่วนที่ 5 สคริปต์ schemas json schemas json { "state" {"players" {"map" "ผู้เล่น"}}, "player" {"sessionid" "string","zepetouserid" "string","transform" "การเปลี่ยนแปลง","state" "number","substate" "number"}, "transform" {"position" "vector3","rotation" "vector3"}, "vector3" {"x" "number","y" "number","z" "number"} } รหัสเซิร์ฟเวอร์ 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] อัปเดตจำนวนการเยี่ยมชมของผู้เล่นและบันทึก storage await storage set("visitcount", ++visit cnt); // จัดการวัตถุผู้เล่นโดยใช้ sessionid ซึ่งเป็นค่าคีย์ที่ไม่ซ้ำกันของวัตถุคลาย // คลายสามารถตรวจสอบข้อมูลเกี่ยวกับวัตถุผู้เล่นที่เพิ่มโดยการตั้งค่าโดยการเพิ่มเหตุการณ์ add onadd ไปยังวัตถุผู้เล่น this state players set(client sessionid, player); } ontick(deltatime number) void { // ถูกเรียกซ้ำที่เวลาที่ตั้งไว้ในเซิร์ฟเวอร์ และสามารถจัดการเหตุการณ์ในช่วงเวลาที่แน่นอนได้โดยใช้ deltatime } async onleave(client sandboxplayer, consented? boolean) { 	 // โดยการตั้งค่า allowreconnection จะสามารถรักษาการเชื่อมต่อสำหรับวงจรได้ แต่จะทำความสะอาดทันทีในคู่มือพื้นฐาน // คลายสามารถตรวจสอบข้อมูลเกี่ยวกับวัตถุผู้เล่นที่ถูกลบโดยการเพิ่มเหตุการณ์ 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)); } // ส่งการแปลงตัวละครท้องถิ่นไปยังเซิร์ฟเวอร์ที่เวลาที่กำหนด 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 ); } }