ผู้เล่น ZEPETO

- ZepetoPlayers เป็นคลาสผู้จัดการ (Singleton) ที่ออกแบบมาเพื่อควบคุมทั้ง ZEPETO Player และ ZEPETO Character.
- โดยการเพิ่ม ZepetoPlayers ลงในฉาก คุณสามารถสร้าง ลบ และจัดการ ZEPETO Player ได้.
- ZepetoPlayer แทนตัวอย่างเฉพาะของตัวละคร ZEPETO ที่ใช้ในการจัดการทั้งผู้เล่นที่คุณควบคุมโดยตรงในโลกหลายผู้เล่นและผู้เล่นคนอื่น ๆ.
- ทุก ZepetoPlayer ที่สร้างขึ้นในโลกหลายผู้เล่นจะถูกกำหนดให้มี ID เซสชันที่ไม่ซ้ำกันและจะถูกจัดการโดยใช้ ID เซสชันนี้.
- เนื่องจาก ZepetoPlayer มีคุณสมบัติของ ZepetoCharacter มันจึงสามารถควบคุมได้โดยใช้ฟังก์ชันที่เกี่ยวข้องกับ ZepetoCharacter.
- มี ZepetoPlayer สามประเภทคือ:
ZepetoPlayer | คำอธิบาย |
|---|---|
ผู้เล่นท้องถิ่น | แสดงถึงอินสแตนซ์ตัวละคร ZEPETO ที่ถูกควบคุมโดยผู้ใช้ท้องถิ่นโดยตรง。 - ติดตั้งด้วย Character Controller/Zepeto Camera components. |
ผู้เล่นเครือข่าย (ผู้เล่นระยะไกล) | อินสแตนซ์ตัวละคร ZEPETO ที่สามารถโหลดและใช้งานในเนื้อหาหลายผู้เล่น。 - ไม่มีการติดตั้ง Character Controller/Zepeto Camera components. |
ผู้เล่นบอท | อินสแตนซ์ตัวละคร ZEPETO สำหรับเนื้อหาหลายผู้เล่น แต่ถูกควบคุมโดยบอทแทนที่จะเป็นผู้ใช้จริง。 ใช้เป็นตัวแทนเมื่อเริ่มโลกหลายผู้เล่นที่มีผู้เล่นไม่เพียงพอหรือหากผู้เล่นออกจากเกมระหว่างเล่น。 - ไม่มีการติดตั้ง Character Controller/Zepeto Camera components. |
- โปรดดูคู่มือต่อไปนี้.
- ZepetoCharacter เป็นหน่วยตัวอย่างพื้นฐานของตัวละคร ZEPETO ที่สามารถโหลดและควบคุมใน World Scene ได้.
- ZepetoCharacter มีลักษณะเป็นอวตารที่สร้างขึ้นผ่านแอป ZEPETO.
การเพิ่ม ZepetoPlayers
ในหน้าต่าง Hierarchy ให้เลือก ZEPETO → แท็บ ZepetoPlayers.

คุณสามารถเพิ่มมันลงในฉากของคุณได้แล้ว

โปรดทราบว่าการเพิ่ม ZepetoPlayers เพียงอย่างเดียวจะไม่ทำให้ Zepeto Player เข้ามาในฉาก คุณต้องดำเนินการเขียนสคริปต์โดยใช้ API การสร้างตัวละครของ ZepetoPlayers
หากคุณต้องการสร้างและลองใช้ Local Player ในฉากอย่างรวดเร็ว โปรดดูคู่มือ:
สำหรับตัวอย่างวิธีการแสดงชื่อและรูปโปรไฟล์ของผู้เล่น Zepeto โปรดดูที่คู่มือ:
API ผู้เล่น ZEPETO
หากคุณสนใจใน API ของ ZepetoPlayers โปรดดูที่เอกสาร:
คู่มือนี้ครอบคลุมตัวอย่างการใช้ผู้เล่น ZEPETO ในสถานการณ์หลายผู้เล่นเป็นหลัก.
การซิงโครไนซ์ตำแหน่งหลายผู้เล่นโดยใช้ผู้เล่น ZEPETO
ในโลกผู้เล่นคนเดียว จะต้องสร้างผู้เล่นท้องถิ่นเพียงคนเดียว และเนื่องจากมีเพียงผู้เล่นท้องถิ่นที่ปรากฏบนหน้าจอ การซิงโครไนซ์จึงไม่จำเป็น.
อย่างไรก็ตาม ในโลกหลายผู้เล่น ไม่เพียงแต่ผู้เล่นท้องถิ่นที่คุณควบคุมโดยตรง แต่ยังมีผู้เล่นอื่น ๆ ที่เรียกว่าผู้เล่นเครือข่าย ต้องแสดงบนหน้าจอด้วย.
ทุกการกระทำของผู้เล่นเครือข่าย - การเคลื่อนที่, การกระโดด, และการทำท่าทางเฉพาะ - ควรปรากฏเหมือนกันบนหน้าจอของคุณ.
กระบวนการนี้เรียกว่าการซิงโครไนซ์.
หากไม่ดำเนินการตามสคริปต์การซิงโครไนซ์ คุณจะไม่สามารถเห็นการปรากฏตัวหรือการเคลื่อนไหวของผู้เล่นในเครือข่ายได้
ในโลกที่มีการเล่นหลายคนโดยไม่มีการซิงโครไนซ์ วิธีเดียวที่จะรู้ว่าลูกค้าอื่นได้เข้ามาในห้องคือผ่านปุ่มโฮม

ขั้นตอนที่ 1 : การตั้งค่าสภาพแวดล้อมการเล่นหลายคน
แนะนำให้เริ่มต้นด้วยการเข้าใจการตั้งค่าและแนวคิดพื้นฐานของการเล่นหลายคนผ่านวิดีโอสอนการเล่นหลายคน
- กรุณาอ้างอิงไปยังคู่มือต่อไปนี้
ขั้นตอนที่ 2 : แสดงผู้เล่นคนอื่นบนหน้าจอของคุณ
ผู้เล่นท้องถิ่นของคุณจะถูกมองว่าเป็นผู้เล่นในเครือข่ายบนอุปกรณ์ของคนอื่น
หมายความว่าผู้เล่นท้องถิ่นของคุณต้องส่งข้อมูลไปยังเซิร์ฟเวอร์เพื่อการซิงโครไนซ์
ผู้เล่นทั้งหมดที่เชื่อมต่อกับโลกการเล่นหลายคนควรแชร์ข้อมูลของพวกเขา
ลูกค้าทั้งหมดที่เชื่อมต่อกับห้องเล่นหลายคนจะแชร์ข้อมูลสถานะของห้องเล่นหลายคน
ข้อมูลสถานะของห้องนี้จะปฏิบัติตามสคีมาที่กำหนดใน Schemas.json
(โปรดพิจารณา Schema เป็นโครงสร้างข้อมูล)
ในคู่มือนี้ คุณจะซิงโครไนซ์ตำแหน่งของผู้เล่นผ่านข้อมูลสถานะห้อง ดังนั้น ให้กำหนด Schema ใน Schemas.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"}
}
👍 เคล็ดลับ
- เก็บข้อมูลทั้งหมดที่เซิร์ฟเวอร์และลูกค้าทั้งหมดควรแชร์ใน สถานะห้องหลายผู้เล่น.
- บันทึกข้อมูลเฉพาะสำหรับผู้เล่นแต่ละคน เช่น ระดับ, คะแนนประสบการณ์, คะแนน, ฯลฯ โดยใช้ DataStorage.
สคริปต์เซิร์ฟเวอร์สามารถรับรู้เมื่อผู้เล่นคนอื่นเข้ามาในห้องและสามารถส่งข้อมูลนั้นไปยังไคลเอนต์เพื่อโหลดผู้เล่นคนนั้นเข้าสู่ฉากได้
ขั้นตอน 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 ซึ่งเป็นค่าคีย์ที่ไม่ซ้ำกันของวัตถุไคลเอนต์
// ไคลเอนต์สามารถตรวจสอบข้อมูลเกี่ยวกับวัตถุผู้เล่นที่เพิ่มโดยการตั้งค่าโดยการเพิ่มเหตุการณ์ 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);
เมื่อไคลเอนต์ได้รับข้อมูลผู้เล่นจากสถานะห้องของเซิร์ฟเวอร์และผู้เล่นใหม่เข้าร่วมห้อง จะมีการกำหนด 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] (ท้องถิ่น) ถูกเรียกเมื่ออินสแตนซ์ของผู้เล่นถูกโหลดอย่างสมบูรณ์ในฉาก
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);
}
});
}
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] ผู้เล่น - 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] ผู้เล่น - sessionId : ${sessionId}`);
this.currentPlayers.delete(sessionId);
ZepetoPlayers.instance.RemovePlayer(sessionId);
}
}
ตอนนี้ เมื่อผู้เล่นเข้ามา คุณสามารถยืนยันได้ว่าตัวละคร ZEPETO ถูกสร้างขึ้นบนหน้าจอของคุณแล้ว.
แต่การเคลื่อนไหวของผู้เล่นยังไม่สะท้อนบนหน้าจอ.

เรามาดำเนินการซิงโครไนซ์ตำแหน่งกันต่อไป.
ขั้นตอนที่ 3 : การซิงโครไนซ์โดยการดึงข้อมูลของผู้เล่นคนอื่น
สำหรับการซิงโครไนซ์ ทุกครั้งที่ผู้เล่นเคลื่อนไหวหรือทำการใดๆ พวกเขาต้องส่งการเปลี่ยนแปลงสถานะของพวกเขาไปยังเซิร์ฟเวอร์.
การส่งข้อความที่มีการเปลี่ยนแปลงสถานะของพวกเขาไปยังเซิร์ฟเวอร์จะทำผ่านการสื่อสารข้อความในห้อง.
เมื่อเซิร์ฟเวอร์ได้รับข้อความเกี่ยวกับการเปลี่ยนแปลงสถานะของผู้เล่น มันจะอัปเดตสถานะห้อง.
ตัวอย่างเช่น สมมติว่าผู้เล่นท้องถิ่นของคุณชื่อ 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 ตามที่ได้รับจากเซิร์ฟเวอร์
ไม่เพียงแต่การเปลี่ยนตำแหน่ง แต่ยังรวมถึงท่าทาง การใช้ทักษะ การเก็บไอเทม และการเปลี่ยนแปลงสถานะทั้งหมดต้องการการสื่อสารระหว่างเซิร์ฟเวอร์และลูกค้าเพื่อการซิงโครไนซ์
คุณจะต้องดำเนินการซิงโครไนซ์เพื่อให้ทุกการกระทำอยู่ในความกลมกลืนกันทั่วทั้งเครือข่าย
👍 สรุปแนวคิดการซิงโครไนซ์
- เมื่อผู้เล่นท้องถิ่นมีการเปลี่ยนแปลงสถานะ พวกเขาจะส่งไปยังเซิร์ฟเวอร์โดยใช้ Room Message
- เซิร์ฟเวอร์จะแจ้งให้ผู้เล่นคนอื่น ๆ ทั้งหมดทราบเกี่ยวกับการเปลี่ยนแปลงสถานะ ยกเว้นผู้เล่นท้องถิ่น
- เมื่อได้รับข้อความการเปลี่ยนแปลงสถานะ โค้ดของลูกค้าจะอัปเดตสถานะของผู้เล่นที่ส่งข้อความ
ขั้นตอนที่ 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 ซึ่งเป็นค่าคีย์ที่ไม่ซ้ำกันของวัตถุไคลเอนต์
// ไคลเอนต์สามารถตรวจสอบข้อมูลเกี่ยวกับวัตถุผู้เล่นที่เพิ่มโดยการตั้งค่า add_OnAdd event ไปยังวัตถุผู้เล่น
this.state.players.set(client.sessionId, player);
}
async onLeave(client: SandboxPlayer, consented?: boolean) {
// โดยการตั้งค่า allowReconnection จะสามารถรักษาการเชื่อมต่อสำหรับวงจรได้ แต่จะทำความสะอาดทันทีในคู่มือพื้นฐาน
// ไคลเอนต์สามารถตรวจสอบข้อมูลเกี่ยวกับวัตถุผู้เล่นที่ถูกลบโดยการตั้งค่า add_OnRemove event ไปยังวัตถุผู้เล่น
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
);
}
}

👍 เคล็ดลับ
- คู่มือนี้ใช้การซิงโครไนซ์ตำแหน่งเท่านั้น การซิงโครไนซ์ท่าทาง การซิงโครไนซ์วัตถุ ฯลฯ ยังไม่ได้ดำเนินการ
- หลักการเหมือนกันสำหรับทุกอย่าง แต่กระบวนการส่งและรับข้อความในห้องในทุกช่วงเวลาที่จำเป็นนั้นเป็นสิ่งจำเป็น
- หากคุณต้องการดำเนินการซิงโครไนซ์ได้สะดวกยิ่งขึ้น ให้พิจารณาใช้โมดูลซิงโครไนซ์แบบหลายผู้เล่น