ZEPETO Players

- ZepetoPlayers est une classe Manager (Singleton) conçue pour contrôler à la fois le joueur ZEPETO et le personnage ZEPETO.
- En ajoutant ZepetoPlayers à une scène, vous pouvez créer, supprimer et manipuler un joueur ZEPETO.
- ZepetoPlayer représente une instance individuelle d'un personnage ZEPETO utilisée pour gérer à la fois le joueur que vous contrôlez directement dans un monde multijoueur et d'autres joueurs.
- Chaque ZepetoPlayer créé dans un monde multijoueur se voit attribuer un ID de session unique et est géré à l'aide de cet ID de session.
- Parce que ZepetoPlayer possède les attributs de ZepetoCharacter, il peut être contrôlé à l'aide de fonctions liées à ZepetoCharacter.
- Il existe trois types de ZepetoPlayer :
ZepetoPlayer | Description |
|---|---|
Joueur local | Représente une instance de personnage ZEPETO directement contrôlée par l'utilisateur local. - Équipé des composants Character Controller/Zepeto Camera. |
Joueur réseau (Joueur distant) | Une instance de personnage ZEPETO qui peut être chargée et utilisée dans du contenu multijoueur. - N'a pas les composants Character Controller/Zepeto Camera attachés. |
Joueur bot | Une instance de personnage ZEPETO pour du contenu multijoueur, mais contrôlée par un bot au lieu d'un utilisateur réel. Utilisé comme substitut lors du démarrage d'un monde multijoueur avec un nombre insuffisant de joueurs ou si un joueur quitte pendant le jeu. - N'a pas les composants Character Controller/Zepeto Camera attachés. |
- Veuillez vous référer au guide suivant.
- ZepetoCharacter est une unité d'instance de base du personnage ZEPETO qui peut être chargée et contrôlée dans la scène du monde.
- ZepetoCharacter possède l'apparence d'un avatar créé via l'application ZEPETO.
Ajouter des ZepetoPlayers
Dans la fenêtre Hiérarchie, sélectionnez ZEPETO → onglet ZepetoPlayers.

Vous pouvez maintenant l'ajouter à votre scène.

Notez que l'ajout de ZepetoPlayers ne fait pas entrer le Zepeto Player dans la scène. Vous devez implémenter un script en utilisant l'API de création de personnage de ZepetoPlayers.
Si vous souhaitez créer rapidement et essayer uniquement le Local Player dans une scène, référez-vous au guide :
Pour un exemple sur la façon d'afficher le nom et la photo de profil d'un joueur Zepeto, consultez le guide :
API des Joueurs ZEPETO
Si vous êtes intéressé par l'API ZepetoPlayers, consultez la documentation :
Ce guide couvre principalement des exemples d'utilisation des Joueurs ZEPETO dans des scénarios multijoueurs.
Implémentation de la Synchronisation de Position Multijoueur utilisant les Joueurs ZEPETO
Dans un monde un joueur, seul un Joueur Local doit être créé et puisque seul le Joueur Local apparaît à l'écran, la synchronisation n'est pas nécessaire.
Cependant, dans un monde multijoueur, non seulement le Joueur Local que vous contrôlez directement mais aussi d'autres Joueurs, appelés Joueurs Réseau, doivent être affichés à l'écran.
Chaque action des Joueurs Réseau - se déplacer, sauter et effectuer des gestes spécifiques - doit apparaître de manière identique sur votre écran.
Ce processus est appelé synchronisation.
Sans mettre en œuvre le script de synchronisation, vous ne pourrez pas voir l'apparence ou le mouvement du joueur réseau.
Dans un monde multijoueur sans synchronisation, le seul moyen de savoir si un autre client est entré dans la salle est par le bouton d'accueil.

ÉTAPE 1 : Configuration de l'environnement multijoueur
Il est recommandé de commencer par comprendre les paramètres de base et les concepts du multijoueur à travers une vidéo tutorielle multijoueur.
- Veuillez vous référer au guide suivant.
ÉTAPE 2 : Affichage des autres joueurs sur votre écran
Votre joueur local est traité comme un joueur réseau sur l'appareil de quelqu'un d'autre.
Cela signifie que même votre joueur local doit envoyer des informations au serveur pour la synchronisation.
Tous les joueurs connectés au monde multijoueur doivent partager leurs informations.
Tous les clients connectés à la salle multijoueur partagent les données d'état de la salle multijoueur.
Ces données d'état de la salle suivent le schéma défini dans Schemas.json.
(Veuillez considérer le Schéma comme une structure de données)
Dans ce guide, vous synchroniserez la position du joueur à travers les données de l'état de la salle. Ainsi, définissez un Schéma dans Schemas.json qui peut représenter les données de position pour chaque joueur.
{
"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"}
}
👍 Conseils
- Stockez toutes les informations que les serveurs et tous les clients doivent partager dans le État de la salle Multiplay.
- Enregistrez les données individuelles pour chaque joueur, telles que les niveaux, les points d'expérience, les scores, etc., en utilisant DataStorage.
Le script du serveur peut reconnaître quand un autre joueur est entré dans la salle et peut envoyer cette information au client pour charger ce joueur dans la scène.
ÉTAPE 2-1 : Script de serveur de base
Lorsque un joueur entre dans la salle du monde multijoueur, onJoin() est appelé.
Dans le script du serveur, ajoutez les informations du joueur connecté à l'état de la salle des joueurs.
De plus, lorsque un joueur quitte la salle, onLeave() est appelé.
Le script du serveur supprimera les informations du joueur qui a quitté l'état des joueurs de la salle.
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;
}
// Gérer l'objet Player en utilisant sessionId, une valeur clé unique de l'objet client.
// Le client peut vérifier les informations sur l'objet joueur ajouté en ajoutant l'événement add_OnAdd à l'objet joueurs.
this.state.players.set(client.sessionId, player);
}
async onLeave(client: SandboxPlayer, consented?: boolean) {
this.state.players.delete(client.sessionId);
}
}
ÉTAPE 2-2 : Script Client de Base
Lors de la création d'un monde multiplay, un script client est nécessaire pour communiquer avec le serveur.
Ci-dessous un exemple d'un script client fondamental pour le jeu multijoueur.
Du côté client, nous gérons les données du joueur à afficher dans notre client en utilisant la currentPlayers structure de données de carte.
La ligne de code pivot est illustrée ci-dessous :
ZepetoPlayers.instance.CreatePlayerWithUserId(sessionId, player.zepetoUserId, spawnInfo, isLocal);
Lorsque le client reçoit des informations sur le joueur de l'état de la salle du serveur et qu'un nouveau joueur rejoint la salle, un ID de session est attribué. Si l'ID de session du joueur en cours de création correspond à notre propre ID de session, le joueur est considéré comme local. Dans ce cas, le joueur sera instancié avec isLocal = true, indiquant qu'il s'agit du joueur local.
Par la suite, les joueurs qui ne sont pas locaux sont créés avec isLocal = false. Cela garantit que les apparences de tous les joueurs, locaux et non locaux, sont affichées à l'écran.
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) {
// Lorsque le premier événement OnStateChange est reçu, un instantané complet de l'état est enregistré.
if (isFirst) {
// [CharacterController] (Local) Appelé lorsque l'instance du joueur est complètement chargée dans la scène
ZepetoPlayers.instance.OnAddedLocalPlayer.AddListener(() => {
const myPlayer = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer;
this.zepetoPlayer = myPlayer;
});
// [CharacterController] (Local) Appelé lorsque l'instance du joueur est complètement chargée dans la scène
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] Créer une instance de joueur pour les joueurs qui entrent dans la salle
join.forEach((player: Player, sessionId: string) => this.OnJoinPlayer(sessionId, player));
// [RoomState] Supprimer l'instance de joueur pour les joueurs qui quittent la salle
leave.forEach((player: Player, sessionId: string) => this.OnLeavePlayer(sessionId, player));
}
private OnJoinPlayer(sessionId: string, player: Player) {
console.log(`[OnJoinPlayer] joueurs - sessionId : ${sessionId}`);
this.currentPlayers.set(sessionId, player);
// Créer le joueur
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] joueurs - sessionId : ${sessionId}`);
this.currentPlayers.delete(sessionId);
ZepetoPlayers.instance.RemovePlayer(sessionId);
}
}
Maintenant, lorsque un joueur entre, vous pouvez confirmer que le personnage ZEPETO est créé sur votre écran.
Mais, le mouvement du joueur n'est pas encore reflété à l'écran.

Passons maintenant à la synchronisation de la position.
ÉTAPE 3 : Synchronisation en récupérant les informations des autres joueurs
Pour la synchronisation, chaque fois qu'un joueur se déplace ou effectue une action, il doit envoyer son changement de statut au serveur.
Envoyer un message contenant leur changement de statut au serveur se fait par le biais de la communication par message de salle.
Lorsque le serveur reçoit un message concernant un changement de statut d'un joueur, il met à jour l'état de la salle.
Par exemple, considérons que votre joueur local s'appelle B.
Lorsqu'un joueur réseau A rejoint la salle, il est instancié aux coordonnées x:0, y:0, z:0.
Si le joueur A se déplace vers la position x : -10, y : 0, z : 0, B n'a aucun moyen réel de savoir qu'A se déplace.
C'est parce que les deux sont opérés localement sur des appareils séparés, donc sur des clients séparés.
Ainsi, la personne contrôlant A doit communiquer son mouvement au serveur via un message de salle.
Une fois que le serveur reçoit cette information, il informe tous les présents dans la salle de la position en temps réel d'A. De cette façon, B est enfin conscient qu'A se déplace.
Pour que le serveur notifie les autres joueurs, il existe deux méthodes :
- Utiliser la diffusion de message de salle.
- Mettre à jour l'état de la salle, puis faire en sorte que les clients récupèrent et appliquent l'état de la salle.
Ce guide utilise la deuxième méthode de mise à jour de l'état de la salle.

Étant donné qu'un joueur Zepeto chargé dans la scène possède des attributs de personnage Zepeto, vous pouvez utiliser les fonctions de personnage Zepeto pour commander des mouvements vers des emplacements spécifiques ou initier des sauts.
Pour que le client de B puisse visualiser le mouvement de A vers x:-10, y:0, z:0, l'approche la plus intuitive est d'utiliser MoveToPosition(). Cette fonction déplacera le joueur vers la position la plus récente de A telle que reçue du serveur.
Non seulement les changements de position, mais aussi les gestes, l'utilisation de compétences, la collecte d'objets et tous les changements d'état nécessitent une communication serveur-client pour la synchronisation.
Vous devrez mettre en œuvre la synchronisation pour garder chaque action en harmonie à travers le réseau.
👍 Résumé du concept de synchronisation
- Lorsqu'un joueur local a un changement de statut, il l'envoie au serveur en utilisant Room Message.
- Le serveur notifie tous les autres joueurs sauf le joueur local au sujet du changement de statut.
- Lors de la réception du message de changement de statut, le code client met à jour le statut du joueur qui a envoyé le message.
ÉTAPE 3-1 : Script Serveur avec Synchronisation de Position Complète
Dans le script serveur de base, une implémentation supplémentaire est nécessaire pour mettre à jour l'état de la salle chaque fois qu'un message concernant un changement de statut est reçu du client du joueur local.
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) {
// Appelé lorsque l'objet Room est créé.
// Gérer l'initialisation de l'état ou des données de l'objet 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;
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; // Contrôleur de personnage V2
}
});
}
async onJoin(client: SandboxPlayer) {
// Créer l'objet joueur défini dans schemas.json et définir la valeur initiale.
console.log(`[OnJoin] sessionId : ${client.sessionId}, userId : ${client.userId}`)
const player = new Player();
player.sessionId = client.sessionId;
if (client.userId) {
player.zepetoUserId = client.userId;
}
// Gérer l'objet Player en utilisant sessionId, une valeur clé unique de l'objet client.
// Le client peut vérifier les informations sur l'objet joueur ajouté en ajoutant l'événement add_OnAdd à l'objet players.
this.state.players.set(client.sessionId, player);
}
async onLeave(client: SandboxPlayer, consented?: boolean) {
// En définissant allowReconnection, il est possible de maintenir la connexion pour le circuit, mais de nettoyer immédiatement dans le guide de base.
// Le client peut vérifier les informations sur l'objet joueur supprimé en ajoutant l'événement add_OnRemove à l'objet players.
this.state.players.delete(client.sessionId);
}
}
ÉTAPE 3-2 : Script Client avec Synchronisation de Position Complète
Les implémentations clés dans le script client de base sont :
- Appeler automatiquement OnStateChange lorsque l'état de la salle du serveur change.
- En utilisant le SendMessageLoop(0.04) fonction, envoyez la position du joueur local et les informations sur l'état de saut du personnage au serveur toutes les 0,04 secondes.
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));
}
// Envoyer la transformation du personnage local au serveur à l'intervalle prévu.
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) {
// Lorsque le premier événement OnStateChange est reçu, un instantané complet de l'état est enregistré.
if (isFirst) {
// [CharacterController] (Local) Appelé lorsque l'instance du joueur est entièrement chargée dans la scène
ZepetoPlayers.instance.OnAddedLocalPlayer.AddListener(() => {
const myPlayer = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer;
this.zepetoPlayer = myPlayer;
});
// [CharacterController] (Local) Appelé lorsque l'instance du joueur est entièrement chargée dans la scène
ZepetoPlayers.instance.OnAddedPlayer.AddListener((sessionId: string) => {
const isLocal = this.room.SessionId === sessionId;
if (!isLocal) {
const player: Player = this.currentPlayers.get(sessionId);
// [RoomState] Appelé chaque fois que l'état de l'instance du joueur est mis à jour.
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] Créer une instance de joueur pour les joueurs qui entrent dans la salle
join.forEach((player: Player, sessionId: string) => this.OnJoinPlayer(sessionId, player));
// [RoomState] Supprimer l'instance de joueur pour les joueurs qui quittent la salle
leave.forEach((player: Player, sessionId: string) => this.OnLeavePlayer(sessionId, player));
}
private OnJoinPlayer(sessionId: string, player: Player) {
console.log(`[OnJoinPlayer] joueurs - 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] joueurs - 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
);
}
}

👍 Conseils
- Ce guide n'implémente que la synchronisation de position. La synchronisation des gestes, la synchronisation des objets, etc., n'ont pas été implémentées.
- Le principe est le même pour tous, mais le processus d'envoi et de réception des messages de salle à chaque moment requis est nécessaire.
- Si vous souhaitez implémenter la synchronisation de manière plus pratique, envisagez d'utiliser un module de synchronisation multiplay.