ท่าทาง
ZepetoWorldContent API ช่วยให้คุณตั้งภาพขนาดย่อสำหรับหมวดหมู่ท่าทาง/ท่าทางที่ต้องการและเปิดใช้งานท่าทาง/ท่าทางเฉพาะเมื่อคลิกที่ภาพขนาดย่อ.

ในการใช้ ZepetoWorldContent API คุณต้องเขียนคำสั่งนำเข้า ดังนี้.
import { OfficialContentType, WorldService, ZepetoWorldContent, Content } from 'ZEPETO.World';
ข้อมูลตัวแปรและฟังก์ชันของคลาส Content ที่มีข้อมูลการเคลื่อนไหว/ท่าทางมีดังนี้:
API | คำอธิบาย |
|---|---|
public get Id(): string | รหัสเฉพาะเนื้อหา |
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) เพิ่ม Hierarchy > UI > Canvas และตั้งค่า Sort Order เป็น 2 เพื่อหลีกเลี่ยงการถูกบดบังโดย UI อื่น.

2) เพิ่มลำดับชั้น > UI > ปุ่ม.

ขั้นตอน 1-2 : จัดระเบียบแผงท่าทาง
1) เพิ่มลำดับชั้น > สร้างวัตถุว่างและเปลี่ยนชื่อเป็น PanelParent.

2) เพิ่มลำดับชั้น > UI > แผงเป็นลูกของ PanelParent.

3) ปุ่มปิด: หลังจากเพิ่ม UI > ปุ่ม ให้เพิ่มเหตุการณ์ onClick เพื่อปิดแผงท่าทาง.

4) ปุ่มเปิด: กรุณาเพิ่มเหตุการณ์ onClick ที่เปิดแผงท่าทางไปยังปุ่มเปิดที่สร้างขึ้นข้างต้น.

5) เพิ่มภาพเพื่อใช้เป็นพื้นที่ชื่อเรื่อง.

6) ตั้งค่ามุมมองเลื่อนเพื่อแสดงภาพขนาดย่อของท่าทาง.
- เพิ่ม Hierarchy > UI > Scroll View.
- ตรวจสอบ Horizontal และปิดการใช้งานภาพแถบเลื่อนเนื่องจากคุณจะใช้เฉพาะการเลื่อนแนวตั้งและไม่ต้องการการเลื่อนแนวนอน.
- เพิ่มเลย์เอาต์แบบกริดไปยังเนื้อหาใน Scroll View เพื่อจัดเรียงภาพขนาดย่อในรูปแบบกริด.
- เพิ่ม Content Size Fitter เพื่อทำให้ขนาดของวัตถุเหมาะสมกับขนาดของเนื้อหา.
- เมื่อคุณดำเนินการสคริปต์ คุณต้องตั้งค่าเนื้อหาใน Scroll View เป็นผู้ปกครองของภาพขนาดย่อของท่าทาง (เพื่อให้พื้นที่ทั้งหมดถูกจดจำและเลื่อน)

7) ตั้งค่าแท็บตามประเภทท่าทาง.
- เพิ่มลำดับชั้น > สร้างวัตถุว่างเป็นลูกของแผงและเปลี่ยนชื่อเป็น GestureTitle.
- นี่คือวัตถุหลักของปุ่มสลับ.
- เพิ่มเลย์เอาต์แนวนอนเพื่อจัดเรียงแท็บในแนวนอน.
- เพิ่มส่วนประกอบกลุ่มสลับ.
👍 ในการกำหนดค่าแท็บเพิ่มเติม ให้เพิ่มลำดับชั้น > UI > Scroll View และตรวจสอบแนวนอนในตัวเลือก Scroll View.

8) เพิ่มข้อความที่จะใช้เป็นปุ่มสลับเป็นลูกของ GestureTitle และแทนที่ด้วย All.
- ตั้งค่าสีของข้อความเป็นสีเทา.
- เพิ่มข้อความที่เน้นซึ่งจะแสดงเมื่อถูกเลือกเป็นลูกของข้อความ.
- ตั้งค่าขนาดและความหนาของฟอนต์ให้เหมือนกัน และตั้งค่าสีเป็นสีดำ.
- เพิ่มส่วนประกอบ Toggle.
- ระบุวัตถุหลักในกลุ่ม.
- เพิ่มข้อความที่เน้นซึ่งคุณได้เพิ่มเป็นลูกให้กับ Graphic
- ตรวจสอบ isOn เฉพาะสำหรับส่วนประกอบ Toggle ทั้งหมดที่จะถูกแสดงก่อน.
- สร้างปุ่มสลับ Gesture และ Pose ทั้งสองในลักษณะเดียวกัน.

ขั้นตอน 1-3 : สร้างพรีแฟบภาพขนาดย่อ
ใช้วิธีการสร้างปุ่มภาพขนาดย่อเป็นพรีแฟบและจากนั้นสร้างเป็นอินสแตนซ์ในสคริปต์
1) เพิ่ม UI > ปุ่ม เป็นลูกของเนื้อหาใน Scroll View และเปลี่ยนชื่อเป็น preThumb
2) กรุณาเปลี่ยนชื่อเป็น Thumb หลังจากเพิ่ม Raw Image
- ภาพนี้จะเป็นภาพขนาดย่อ ปรับขนาดให้เหมาะสม
3) เพิ่มข้อความ
- ตั้งตำแหน่งให้กึ่งกลางด้านล่างของภาพ
- ปรับขนาดและความหนาของการเขียน และเพิ่ม Content Size Fitter
- Horizontal Fit : ขนาดที่ต้องการ
- Vertical Fit : ขนาดที่ต้องการ
4) หากการตั้งค่าเสร็จสิ้น กรุณาทำให้เป็น Prefab และใส่ในโฟลเดอร์ Resources


ขั้นตอนที่ 1-4 : วิดีโอแนะนำการตั้งค่า UI
👍 ค่าขนาดและตำแหน่งของ 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;
}
}- หลังจากสร้างสคริปต์แล้ว ให้เปิด prefab preThumb และเพิ่มสคริปต์เข้าไป.

ขั้นตอนที่ 2-2 : GestureLoader
- สร้างลำดับชั้น > สร้างวัตถุเปล่าและเปลี่ยนชื่อเป็น GestureLoader.
- สร้างโปรเจกต์ > สร้าง > ZEPETO > TypeScript และเปลี่ยนชื่อเป็น 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) เรียกใช้ฟังก์ชัน ContentsRequest() ที่กำหนดเองเพื่อสร้างภาพขนาดย่อหลังจากโหลดตัวละคร ZEPETO.
- ฟังก์ชัน ContentsRequest() จะรับข้อมูลเนื้อหาโดยการแยกท่าทางและท่าทางออกจากกันตามลำดับ.
- หากมีภาพขนาดย่อที่มีอยู่แล้ว จะถูกข้ามไป; มิฉะนั้น จะดึงภาพขนาดย่อมา.
- ข้อมูลภาพขนาดย่อที่ดึงมาได้จะถูกเก็บไว้ในรายการที่เกี่ยวข้อง.

ขั้นตอน 2-3 : UIController
- สร้าง Hierarchy > สร้างวัตถุเปล่าและเปลี่ยนชื่อเป็น UIContoller.
- สร้างโปรเจกต์ > สร้าง > ZEPETO > TypeScript และเปลี่ยนชื่อเป็น UIContoller.
- เขียนสคริปต์ตัวอย่างตามด้านล่าง.
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() ใช้ข้อมูลเนื้อหาท่าทางในแต่ละภาพขนาดย่อเพื่อตั้งค่าสำหรับแต่ละหมวดหมู่ที่เกี่ยวข้อง
- เปิดใช้งานหากเป็นประเภทที่ใช้ได้ และปิดใช้งานหากไม่ใช่
หลังจากเสร็จสิ้นการเขียนสคริปต์ ให้กำหนดปุ่มปิดและ TypeToggleGroup ใน Inspector.
- การเข้าถึง Type Toggle Group คือ Toggle ที่เป็นลูกของ Toggle Group ในแผงการเคลื่อนไหว.

ขั้นตอนที่ 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;
}
}
GestureLoader - หลายผู้เล่น
- โดยค่าเริ่มต้น สคริปต์ที่ใช้ในรหัสลูกค้าแบบเล่นคนเดียวจะเขียนเหมือนกัน
- นอกจากนี้ ลูกค้าจะประกาศอินเตอร์เฟซเพื่อให้มี PlayerGestureInfo
- เมื่อส่งข้อมูลของคุณไปยังเซิร์ฟเวอร์: ดู SendMyGesture() ฟังก์ชันที่กำหนดเอง
- เมื่อผู้เล่นของคุณกดที่ภาพขนาดย่อเพื่อทำท่าทาง ส่ง ID ของท่าทางไปยังเซิร์ฟเวอร์โดยใช้ room.Send()
- เมื่อคุณยกเลิกท่าทาง ให้ประมวลผลเพื่อส่งข้อมูลว่าคุณได้ยกเลิกแล้ว
- เมื่อได้รับข้อมูลท่าทางจากลูกค้าอื่นจากเซิร์ฟเวอร์: "OnChangeGesture" ข้อความห้องจะถูกส่งไปยัง this.room.AddMessageHandler() ภายใน Start()
- การซิงโครไนซ์จะเกิดขึ้นโดยการมี ID เซสชันและ ID ท่าทางในข้อความ "OnChangeGesture" และทำให้ผู้เล่นที่เหมาะสมเล่นท่าทางนั้น
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;
// เพื่อที่จะถ่ายภาพ thumbnail ด้วยตัวละครของฉัน คุณต้องขอเนื้อหาหลังจากที่ตัวละครถูกสร้างขึ้น
this.ContentRequest();
});
// สำหรับ MultiPlay
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) {
// ถ่ายภาพ thumbnail โดยใช้ตัวละครของฉัน
this.contents[i].DownloadThumbnail(() =>{
this.CreateThumbnailObjcet(this.contents[i]);
});
} else {
this.CreateThumbnailObjcet(this.contents[i]);
}
}
});
}
// 2. สร้างวัตถุ Thumbnail
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);
// ตัวฟังปุ่มสำหรับแต่ละ thumbnail
newThumb.GetComponent<Button>().onClick.AddListener(() => {
this.SendMyGesture(content.Id);
});
// รายการ thumbnail
this.thumbnails.push(newThumb);
}
// สำหรับ MultiPlay
// ส่งข้อมูลท่าทางที่คลิกไปยังเซิร์ฟเวอร์
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);
}
}
}
- หลังจากเสร็จสิ้นการเขียนสคริปต์ ผู้ตรวจสอบจะมอบวัตถุเพิ่มเติมให้กับ Multiplay โดยใช้ส่วนประกอบ Zepeto World Multiplay.


UIController - Multiplay
- ตามค่าเริ่มต้น สคริปต์ที่ใช้ในโค้ดไคลเอนต์แบบเล่นคนเดียวจะเขียนเหมือนกันทั้งหมด.
- ความแตกต่างจากโค้ดไคลเอนต์แบบเล่นคนเดียวคือฟังก์ชันที่กำหนดเอง StopGesture().
- เรียกใช้ฟังก์ชันที่กำหนดเอง SendMyGesture() ภายใน GestureLoaderMultiplay.
- กระบวนการส่งข้อมูลว่าท่าทางถูกยกเลิกแล้ว.
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;
// หากคลิกที่ touchpad ให้ยกเลิกท่าทาง
Object.FindObjectOfType<ZepetoScreenTouchpad>().OnPointerDownEvent.AddListener(() => {
this.StopGesture();
});
// หากคลิกที่ปุ่มปิด ให้ยกเลิกท่าทาง
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);
});
}
// ตั้งค่า UI หมวดหมู่ Toggle
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 โดยค่าเริ่มต้น.
- มันสร้าง callback onMessage() ที่ส่งข้อมูลการเคลื่อนไหวไปยังลูกค้าอื่นเมื่อมีการเปลี่ยนแปลงการเคลื่อนไหวภายใน onCreate().
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 ซึ่งเป็นค่าคีย์ที่ไม่ซ้ำกันของวัตถุลูกค้า
// ลูกค้าสามารถตรวจสอบข้อมูลเกี่ยวกับวัตถุผู้เล่นที่เพิ่มโดยการตั้งค่าโดยการเพิ่มเหตุการณ์ add_OnAdd ไปยังวัตถุผู้เล่น
this.state.players.set(client.sessionId, player);
}
onLeave(client: SandboxPlayer, consented?: boolean) {
// โดยการตั้งค่า allowReconnection จะสามารถรักษาการเชื่อมต่อสำหรับวงจรได้ แต่ทำความสะอาดทันทีในคู่มือพื้นฐาน
// ลูกค้าสามารถตรวจสอบข้อมูลเกี่ยวกับวัตถุผู้เล่นที่ถูกลบโดยการเพิ่มเหตุการณ์ add_OnRemove ไปยังวัตถุผู้เล่น
this.state.players.delete(client.sessionId);
}
}