Studio GuideWorld SDK Guide
Log In

Gesture

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:

APIDescription
public get Id(): stringContent Unique Id
public get Title(): stringGesture, pose title text

- The language will automatically translate depending on device language
public get Thumbnail(): UnityEngine.Texture2D2D thumbnail
public get AnimationClip(): UnityEngine.AnimationClipGesture Animation Clip
public get IsDownloadedThumbnail(): booleanFunction to determine if you have previously downloaded this thumbnail
public get IsDownloadedAnimation(): booleanFunction to determine if you have previously downloaded this animation clip
public DownloadAnimation($complete: System.Action):voidAn 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):voidFunction to download thumbnails

- If IsDownloadedThumbnail() is false, implement DownloadThumbnail() to be called.
OfficialContentType : enumType 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 function public DownloadThumbnail($complete: System.Action):void instead.

STEP 1 : Set up UI


STEP 1-1 : Create a gesture button

  1. Add Hierarchy > UI > Canvas and set Sort Order to 2 to avoid being obscured by other UI.
607
  1. Add Hierachy > UI > Button.
955

Example gesture button


STEP 1-2 : Organize the gesture panel

  1. Add Hierarchy > Create Empty Object and rename it PanelParent.


  1. Add Hierarchy > UI > Panel as a child of PanelParent.
2734

Example gesture panel


  1. Close button: After adding UI > Button, add onClick event to disable gesture panel.
2880

Close button configuration example


  1. Open button : Please add an onClick event that activates the gesture panel to the open button created above.
2880

Open button configuration example


  1. Add an image to serve as the title area.
3584

Title image example


  1. 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)
3584

Scroll View Configuration Example


  1. 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.

3584

GestureTitle Configuration Example


  1. 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.
3584

Example of toggle button configuration


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.

  1. Add UI > Button as a child of Content in Scroll View and rename it preThumb.

  2. Please change the name to Thumb after adding Raw Image.

  • This image will be a thumbnail. Adjust the size appropriately.
  1. 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
  1. If the setting is done, please make it a Prefab and put it in the Resources folder.
3582

Example of Thumbnail Prefab Configuration

Example where thumbnail buttons are set on the gesture panel after execution

Example where thumbnail buttons are set on the gesture panel after execution


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.
470

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:

  1. 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.
  1. 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.
1000

Example of the Gesture Script Setting Screen


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:

  1. Touch the touchpad or close button to cancel the play using the CancelGesture() function.
  2. Tap the tab (toggle button) to invoke the SetCategoryUI() custom function.
  3. 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.
996

Example script setting screen


STEP 3 : Run

❗️

Caution

Before playing, disable PanelParent so that only the open button can be seen when playing.

425

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.

996

Example script setting screen

1000

Example of objects with a 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);
    }
}