ZepetoWorldContent API allows you to set thumbnails for desired gesture/pose categories and enable specific gestures/poses when a thumbnail is clicked.
To use the ZepetoWorldContent API, you must write an import statement as follows.
import { OfficialContentType, WorldService, ZepetoWorldContent, Content } from 'ZEPETO.World';
The member variable and function information of the Content class containing gesture/pose information are as follows:
API | Description |
---|---|
public get Id(): string | Content Unique Id |
public get Title(): string | Gesture, pose title text - The language will automatically translate depending on device language |
public get Thumbnail(): UnityEngine.Texture2D | 2D thumbnail |
public get AnimationClip(): UnityEngine.AnimationClip | Gesture Animation Clip |
public get IsDownloadedThumbnail(): boolean | Function to determine if you have previously downloaded this thumbnail |
public get IsDownloadedAnimation(): boolean | Function to determine if you have previously downloaded this animation clip |
public DownloadAnimation($complete: System.Action):void | An animation clip download function that receives a completed callback with a factor - If IsDownloadedAnimation() is false, implement DownloadAnimation() to be called. |
public DownloadThumbnail($complete: System.Action):void | Function to download thumbnails - If IsDownloadedThumbnail() is false, implement DownloadThumbnail() to be called. |
OfficialContentType : enum | Type of content (World 1.9.0 and higher) - Gesture = 2 - Pose = 4 - Selfie = 8 - GestureGreeting = 16 - GesturePose = 32 - GestureAffirmation = 64 - GestureDancing = 128 - GestureDenial = 256 - GestureEtc = 512 - All = 14 |
Caution
- You can use the existing function,
public DownloadThumbnail($character: ZEPETO_Character_Controller.ZepetoCharacter, $complete: System.Action):void
, without any issues regarding functionality. However, since it no longer accepts Zepeto character as an argument, please use the newly modified functionpublic DownloadThumbnail($complete: System.Action):void
instead.
STEP 1 : Set up UI
STEP 1-1 : Create a gesture button
- Add Hierarchy > UI > Canvas and set Sort Order to 2 to avoid being obscured by other UI.
- Add Hierachy > UI > Button.
STEP 1-2 : Organize the gesture panel
- Add Hierarchy > Create Empty Object and rename it PanelParent.
- Add Hierarchy > UI > Panel as a child of PanelParent.
- Close button: After adding UI > Button, add onClick event to disable gesture panel.
- Open button : Please add an onClick event that activates the gesture panel to the open button created above.
- Add an image to serve as the title area.
- Configure a scroll view to display the gesture thumbnail.
- Add Hierarchy > UI > Scroll View.
- Check off Horizontal and disable the scroll bar image since you will only use vertical scrolls and horizontal scrolls will not be required.
- Add Grid layout to Content in Scroll View to align thumbnails in grid pattern.
- Add Content Size Fitter to make the size of the object appropriate for the size of the content.
- When you implement a script, you must set the content in Scroll View as the parent of the gesture thumbnail (so that the entire area is recognized and scrolled)
- Configure tabs by gesture type.
- Add Hierarchy > Create Empty Object as a child oft the Panel and rename it to GestureTitle.
- This is the parent object of the toggle button.
- Add Horizontal Layout to align tabs horizontally.
- Add the Toggle Group component.
To configure more tabs, add Hierarchy > UI > Scroll View and check Horizontal in the Scroll View option.
- Add the text to be used as the toggle button as the child of the GestureTitle, and replace it with All.
- Sets the color of the text to gray.
- Add the highlighted text that will be displayed when checked as a child of the text.
- Set the font content, size, and thickness the same, and set the color to black.
- Add the Toggle component.
- Specify the parent object in the group.
- Add highlighted text that you added as a child to Graphic
- Check isOn only for the All toggle components that will be shown first.
- Create both the Gesture and Pose toggle buttons in the same way.
STEP 1-3 : Make thumbnail prefabs
Use the method of creating a thumbnail button as a pre-fab and then generating it as an instance in a script.
-
Add UI > Button as a child of Content in Scroll View and rename it preThumb.
-
Please change the name to Thumb after adding Raw Image.
- This image will be a thumbnail. Adjust the size appropriately.
- Add Text.
- Set the position to center the bottom of the image.
- Adjust the size and thickness of the writing, and add the Content Size Fitter.
- Horizontal Fit : Preferred Size
- Vertical Fit : Preferred Size
- If the setting is done, please make it a Prefab and put it in the Resources folder.
STEP 1-4 : UI Setting Guide Video
UI size and position values shown in the video are recommended, but you can modify them to the values you want!
Once the UI setup is complete, proceed to Scripting.
STEP 2 : Write a script
This script is based on single play.
STEP 2-1 : Thumbnail
- Project > Create > ZEPETO > TypeScript and rename it Thumbnail.
- Write a sample script like below.
- This is a script that organizes gesture content information (title, image) into the 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;
}
}
- After creating the script, open the preThumb prefab and add the script.
STEP 2-2 : GestureLoader
- Create a Hierarchy > Create Empty Object and rename it to GestureLoader.
- Create a Project > Create > ZEPETO > TypeScript and rename it to GestureLoader.
- Write a sample script like below.
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);
}
}
}
Caution
- Count is the maximum number of gestures to download on each tab. If you set it to a number larger than 100, there may be errors during the thumbnail download process, so please set it only as much as you need.
The script flows as follows:
- Invoke the ContentsRequest() custom function for generating thumbnails after loading the ZEPETO character.
- The ContentsRequest() function receives content information by separating gestures and poses, respectively.
- If there is an existing thumbnail, it is skipped; otherwise, the thumbnail is retrieved.
- The retrieved thumbnail data is stored in respective lists.
- The CreateThumbnailObject() custom function is then called.
- The CreateThumbnailObject() function takes information from the thumbnail list to create a thumbnail button prefab instance.
- In the Thumbnail script within the prefab, set the Thumbnail information of the content to the thumbnail button image, and set the Title information of the content to the thumbnail button text. Apply the generated thumbnail button to the UI panel.
- Click the thumbnail button to call the LoadAnimation custom function and play the corresponding gesture using the SetGesture() function.
- Assign Content Parent, Thumbnail Prefab, and Count to the Inspector after completing the script.
- The entry in Contents Parent is the Content in Scroll View.
STEP 2-3 : UIController
- Create a Hierarchy > Create Empty Object and rename it to UIContoller.
- Create Project > Create > ZEPETO > TypeScript and rename it to UIContoller.
- Write a sample script like below.
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();
}
}
The script flows as follows:
- Touch the touchpad or close button to cancel the play using the CancelGesture() function.
- Tap the tab (toggle button) to invoke the SetCategoryUI() custom function.
- The SetCategoryUI() function uses the gesture content information in each thumbnail to set it for each corresponding category.
- Enable if it's an applicable type, and disable if not.
After completing the scripting, assign Close Button, TypeToggleGroup to the Inspector.
- The entry to the Type Toggle Group is the Toggle that is a child of the Toggle Group in the gesture panel.
STEP 3 : Run
Caution
Before playing, disable PanelParent so that only the open button can be seen when playing.
STEP 4 : Synchronize multi-play gestures
- In the case of multi-play, a synchronization code must be added that receives the gesture information value taken by a particular player and applies it to all players accessing the Room.
- The key is to send and receive a Room Message between the server and the client about which player made which gesture.
STEP 4-1 : Client Code
Thumbnail - Multiplay
- Write the same script as the one implemented in the single-play client code.
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 - Multiplay
- By default, scripts implemented in single-play client code are written the same.
- Additionally, the client declares the interface to contain the PlayerGestureInfo.
- When sending your information to the server: see SendMyGesture() Custom Function
- When your player presses the thumbnail to make a gesture, send the gesture ID to the server using room.Send().
- When you cancel a gesture, process it to send the information that you canceled.
- When receiving gesture information from another client from the server: "OnChangeGesture" Room Message is sent to this.room.AddMessageHandler() within Start()
- Synchronization is achieved by having the session ID and gesture ID in the "OnChangeGesture" message and making the appropriate player play the gesture.
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() {
// Creating a Character
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();
});
// For MultiPlay
this.multiplay.RoomCreated += (room: Room) => {
this._room = room;
// Receive user's gesture information from the server
this._room.AddMessageHandler("OnChangeGesture", (message: PlayerGestureInfo) => {
let playerGestureInfo: PlayerGestureInfo = {
sessionId: message.sessionId,
gestureId: message.gestureId
};
this.LoadAnimation(playerGestureInfo);
});
};
}
// 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;
// Create a dictionary to find content with a content Id
this._contentsMap.set(content.Id, content);
// Button Listener for each thumbnail
newThumb.GetComponent<Button>().onClick.AddListener(() => {
this.SendMyGesture(content.Id);
});
// thimnail list
this.thumbnails.push(newThumb);
}
// For MultiPlay
// Send clicked gesture information to the server
public SendMyGesture(gestureId) {
const data = new RoomData();
data.Add("gestureId", gestureId);
this._room.Send("OnChangeGesture", data.GetObject());
}
// 3. Loading Animation
private LoadAnimation(playerGestureInfo: PlayerGestureInfo){
if (!ZepetoPlayers.instance.HasPlayer(playerGestureInfo.sessionId)) {
console.log("Player does not exist");
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("Resource not yet loaded");
return;
}
const content = this._contentsMap.get(playerGestureInfo.gestureId);
// Verify animation load
if (!content.IsDownloadedAnimation) {
// If the animation has not been downloaded, download it.
content.DownloadAnimation(() => {
// play animation clip
zepetoPlayer.SetGesture(content.AnimationClip);
});
} else {
zepetoPlayer.SetGesture(content.AnimationClip);
}
}
}
- After completing the scripting, the Inspector will assign an additional object to Multiplay with the Zepeto World Multiplay component.
UIController - Multiplay
- By default, scripts implemented in single-play client code are written the same.
- The difference from single-play client code is the StopGesture() custom function.
- Invoke a SendMyGesture() custom function within the GestureLoaderMultiplay.
- Process to send information that the gesture has been canceled.
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;
// 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._gestureLodaer.SendMyGesture(CancelMotion);
}
}
STEP 4-2 : Server Code
- The server code then declares the interface to contain the PlayerGestureInfo in the same way.
- The server code is based on the server code in the Multiplay Sample by default.
- It creates an onMessage() callback that sends gesture information to other clients when a gesture changes within onCreate().
import { Sandbox, SandboxOptions, SandboxPlayer } from 'ZEPETO.Multiplay';
import { Player, Transform, Vector3 } from 'ZEPETO.Multiplay.Schema';
// Define an interface PlayerGestureInfo to represent the information of a player's gesture.
interface PlayerGestureInfo {
sessionId: string,
gestureId: string
}
export default class extends Sandbox {
// Define a constant object `MESSAGE_TYPE` to store the message types used in the script.
MESSAGE_TYPE = {
OnChangeGesture: "OnChangeGesture"
}
onCreate(options: SandboxOptions) {
// Called when the Room is created.
// Handle the state or data initialization of the Room.
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; // Character Controller V2
});
// When the gesture is changed,
this.onMessage<PlayerGestureInfo>(this.MESSAGE_TYPE.OnChangeGesture, (client, message) => {
let gestureInfo:PlayerGestureInfo = {
sessionId: client.sessionId,
gestureId: message.gestureId
};
// Send gestures to other players except the client
this.broadcast(this.MESSAGE_TYPE.OnChangeGesture, gestureInfo);
});
}
onJoin(client: SandboxPlayer) {
// Set the initial value after creating the player object defined in 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;
}
// Manage the Player object using sessionId, a unique key value of the client object.
// The client can check the information about the player object added by set by adding the add_OnAdd event to the players object.
this.state.players.set(client.sessionId, player);
}
onLeave(client: SandboxPlayer, consented?: boolean) {
// By setting allowReconnection, it is possible to maintain connection for the circuit, but clean up immediately in the basic guide.
// The client can check the information about the deleted player object by adding the add_OnRemove event to the players object.
this.state.players.delete(client.sessionId);
}
}