机器人玩家创建指南
机器人玩家用于在没有足够的人开始多人世界时填补空缺,或者当玩家在游戏中离开时。
机器人玩家的行为需要为每个内容实现。
本指南描述了创建机器人玩家的一般方法。
📘 机器人玩家创建指南基于多人游戏指南。 [创建多人游戏]
步骤 1:创建一个机器人玩家
1-1. 添加一个名为 IsBot 的布尔值到多人游戏架构中。

1-2. 在服务器脚本 index.ts 中定义以下函数以创建一个机器人玩家,并在所需位置调用它。
// `CreateBot()` 方法用于创建一个具有给定 `userId` 的机器人玩家。
CreateBot(userId: string) {
// 使用提供的 `userId` 为机器人玩家生成一个会话 ID。
const sessionId = "Bot_" + userId;
// 检查是否已经存在具有相同会话 ID 的机器人玩家。如果是,则返回而不创建重复项。
if (this.state.players.has(sessionId)) {
return;
}
// 为机器人玩家创建一个新的 `Player` 对象。
const player: Player = new Player();
player.sessionId = sessionId;
if (userId) {
player.zepetoUserId = userId;
}
player.isBot = true;
// 使用会话 ID 作为键将机器人玩家添加到状态的玩家映射中。
this.state.players.set(player.sessionId, player);
this._botMap.set(sessionId, player);
}
👍 提示
- 特定用户的 userId 被提前存储,以便创建机器人角色。
- 您可以通过检查连接到服务器的 OnJoin 的客户端的 userId 来检查特定用户的 UserId。在服务器脚本中编写以下脚本后,从相关世界连接。
onJoin(client: SandboxPlayer) {
console.log(client.userId);
}
步骤 2:在客户端创建一个机器人玩家
2-1. 如果服务器在某个时刻创建了一个机器人玩家,客户端将在 OnJoinPlayer() 中将其识别为新玩家。
- 创建项目 > 创建 > ZEPETO > TypeScript,并将其重命名为 BotPlayerManager。
- 在 OnAddedPlayer() 中添加逻辑以创建每个玩家,并添加逻辑以区分机器人玩家并创建他们的 ZEPETO 角色。
import { ZepetoCharacter, ZepetoPlayers } from 'ZEPETO.Character.Controller';
import { Room } from 'ZEPETO.Multiplay';
import { ZepetoScriptBehaviour } from 'ZEPETO.Script'
import { ZepetoWorldMultiplay } from 'ZEPETO.World';
export default class BotPlayerManager extends ZepetoScriptBehaviour {
public zepetoWorldMultiplay: ZepetoWorldMultiplay;
// 私有变量用于存储当前房间和机器人玩家数据。
private _room: Room;
private _botMapData: Map<string, ZepetoCharacter> = new Map<string, ZepetoCharacter>();
Start() {
// 监听来自 `ZepetoWorldMultiplay` 组件的 `RoomJoined` 事件。
this.zepetoWorldMultiplay.RoomJoined += (room: Room) => {
this._room = room;
}
// 监听来自 `ZepetoPlayers.instance` 的 `OnAddedPlayer` 事件以处理新添加的玩家。
ZepetoPlayers.instance.OnAddedPlayer.AddListener((userId: string) => {
// 使用 `userId` 从房间状态获取当前玩家数据。
const currentPlayer = this._room.State.players.get_Item(userId);
// 检查玩家是否为机器人,如果是,则将其设置为机器人玩家。
if (currentPlayer.isBot) {
this.SetBotPlayer(currentPlayer.sessionId);
}
});
}
}
2-2. 编写 SetBotPlayer 函数,以将标签和同步组件添加到机器人玩家,并创建控制它们的脚本。
- 将 _botMapData 设置为以 Map 格式保存机器人玩家数据,以管理机器人玩家。
// `SetBotPlayer()` 方法用于将玩家设置为机器人。
SetBotPlayer(userId: string) {
// 使用他们的 `userId` 获取与机器人玩家关联的 ZEPETO 角色。
const bot = ZepetoPlayers.instance.GetPlayer(userId).character;
// 将角色的名称设置为 `userId` 以便识别。
bot.gameObject.name = userId;
// 使用他们的 `userId` 作为键,将机器人玩家的数据存储在 `_botMapData` 映射中。
this._botMapData.set(userId, bot);
}
👍 提示 您可以向 SetBotPlayer() 添加额外的脚本或设置,以控制机器人玩家的行为。
步骤 3:在客户端创建一个机器人玩家按钮
对于需要一定数量玩家才能开始的世界,有时玩家数量不足,您必须等待很长时间才能开始游戏。
在这种情况下,您可以通过添加一个机器人玩家来启动世界。
3-1. 在 index.ts 中注册一个函数,当服务器接收到来自客户端的消息时执行 CreateBot()。
async OnCreate() {
// 处理 "CreateBot" 消息,该消息使用给定的 userId 创建一个机器人玩家。
this.onMessage("CreateBot", (client, message) => {
this.CreateBot(message);
});
}
3-2. 在客户端脚本 BotPlayerManager.ts 中,编写一个函数将 "CreateBot" 消息发送到服务器。
- 执行函数的方法是通过按下按钮发送消息。
- 通过消息将要创建的机器人玩家的用户 ID 作为字符串发送。
public buttonCreateBot: Button;
public botPlayerId: string;
Start() {
// 为 "创建机器人" 按钮添加点击监听器,以发送消息以创建一个机器人玩家。
this.buttonCreateBot.onClick.AddListener(() => {
this._room.Send("CreateBot", this.botPlayerId);
});
}
3-3. 现在,当你运行服务器和运行时,你可以看到当你按下按钮时会创建机器人玩家。

步骤 4:通过添加机器人玩家来启动世界
当没有足够的玩家来启动世界时,你可以添加机器人玩家来启动世界。
4-1. 在服务器脚本中,在 OnJoin 期间添加以下代码,以检查玩家数量,并在至少有四名玩家时启动世界。
- 在 CreateBot() 中添加一个函数以检查玩家数量。
- 在 StartWorld() 函数中添加一个计数器以记录游戏次数。
async onJoin(client: SandboxPlayer) {
// 在玩家加入后检查房间中的玩家数量。
this.CheckPlayerNumber();
}
// `CheckPlayerNumber()` 方法检查房间中的玩家数量,并在至少有四名玩家时启动世界。
CheckPlayerNumber() {
// 将当前房间中的玩家数量打印到控制台。
console.log(`玩家数量, ${this.state.players.size}`);
// 如果房间中至少有四名玩家,则启动世界。
if (this.state.players.size >= 4) {
this.StartWorld();
}
}
// `CreateBot()` 方法用于创建具有给定 `userId` 的机器人玩家。
CreateBot(userId: string) {
// 使用提供的 `userId` 为机器人玩家生成会话 ID。
const sessionId = "Bot_" + userId;
// 检查是否已经存在具有相同会话 ID 的机器人玩家。如果存在,则返回而不创建重复项。
if (this.state.players.has(sessionId)) {
return;
}
// 为机器人玩家创建一个新的 `Player` 对象。
const player: Player = new Player();
player.sessionId = sessionId;
if (userId) {
player.zepetoUserId = userId;
}
player.isBot = true;
// 使用会话 ID 作为键将机器人玩家添加到状态的玩家映射中。
this.state.players.set(player.sessionId, player);
this._botMap.set(sessionId, player);
// 在添加机器人玩家后检查房间中的玩家数量。
this.CheckPlayerNumber();
}
private playTime: number = 0;
// `StartWorld()` 方法增加游戏时间并向所有客户端广播 "StartWorld" 消息。
StartWorld() {
this.playTime += 1;
// 打印一条消息,指示世界的开始和当前的游戏时间。
console.log("开始世界!");
this.broadcast("StartWorld", this.playTime);
}
- 在服务器上,当真实玩家加入房间时,会执行 OnJoin。因此,当通过 CreateBot 创建一个 Bot 玩家时,以及当玩家通过 OnJoin 进入时,checkPlayerNumber() 会增加人数。
4-2. 在客户端脚本 BotPlayerManager.ts 中,编写 StartWorld(),当从服务器接收到 StartWorld 消息时执行。
Start() {
// 监听来自 `ZepetoWorldMultiplay` 组件的 `RoomJoined` 事件。
this.zepetoWorldMultiplay.RoomJoined += (room: Room) => {
// 为 "StartWorld" 消息类型添加消息处理程序。
this._room.AddMessageHandler("StartWorld", (playTime: number) => {
this.StartWorld(playTime);
});
}
// 当世界以提供的 `playTime` 启动时调用 `StartWorld()` 方法。
StartWorld(playTime: number) {
// 打印世界启动消息以及 `playTime`。
console.log(`开始世界 : ${playTime}`);
}
4-3. 在运行时,当玩家数量超过 4 个(包括 Bot 玩家)时,您可以在服务器控制台和客户端控制台上看到名为 World Start 的日志。

步骤 5:同步 Bot 玩家位置
以下是示例代码,它将添加的机器人玩家移动到本地玩家的位置并同步移动位置。
5-1. 首先,编写代码以在服务器的 index.ts 中接收到来自客户端的消息时移动机器人玩家,MoveBot。
async OnCreate() {
// 处理 "MoveBot" 消息,该消息将机器人玩家移动到指定位置。
this.onMessage("MoveBot", (client, message) => {
this.MoveBot(client, message);
});
}
// `MoveBot()` 方法根据接收到的消息将机器人玩家移动到指定位置。
MoveBot(client: SandboxPlayer, message: string) {
// 解析从客户端接收到的 JSON 消息以提取位置信息。
const position = JSON.parse(message);
// 创建一个新的消息对象,包含用户的会话 ID 和解析后的位置信息。
const newMessage = {
user: client.sessionId,
positionX: position.x,
positionY: position.y,
positionZ: position.z
}
// 将 "MoveBotToPosition" 消息广播给所有客户端,消息数据作为 JSON 字符串。
this.broadcast("MoveBotToPosition", JSON.stringify(newMessage));
}
5-2. 在客户端脚本 BotPlayerManager.ts 中,编写 SendBotPosition(),当按下 buttonCallBot 时将本地玩家位置发送到服务器。
- 然后编写代码,当从服务器接收到消息 MoveBotToPosition 时,将所有 Bot 玩家移动到消息中包含的位置。
public buttonCallBot: Button;
Start(){
// 监听来自 `ZepetoWorldMultiplay` 组件的 `RoomJoined` 事件。
this.zepetoWorldMultiplay.RoomJoined += (room: Room) => {
this._room = room;
// 为 "StartWorld" 消息类型添加消息处理程序。
this._room.AddMessageHandler("StartWorld", (playTime: number) => {
this.StartWorld(playTime);
});
// 为 "Call Bot" 按钮添加点击监听器,以发送消息以发送 Bot 玩家位置。
this.buttonCallBot.onClick.AddListener(() => {
this.SendBotPosition();
});
}
// 此方法将本地玩家角色的位置发送到服务器以进行 Bot 移动同步。
SendBotPosition() {
// 获取本地玩家角色的位置。
const localPlayerPosition = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer.character.transform.position;
// 创建一个包含本地玩家角色的 x、y 和 z 坐标的位置对象。
const position = {
x: localPlayerPosition.x,
y: localPlayerPosition.y,
z: localPlayerPosition.z
}
// 将位置对象转换为 JSON 字符串,并使用 "MoveBot" 消息类型将其发送到服务器。
this._room.Send("MoveBot", JSON.stringify(position));
}
MoveBotToPosition(message) {
// 解析从客户端接收到的 JSON 消息以提取位置信息。
const jsonMessage = JSON.parse(message);
const position = new Vector3(jsonMessage.positionX, jsonMessage.positionY, jsonMessage.positionZ);
// 将 `_botMapData` 映射中的每个 Bot 角色移动到指定位置,并在 x 和 z 轴上添加一个小的随机偏移。
this._botMapData.forEach((character: ZepetoCharacter) => {
// 使用 `MoveToPosition()` 方法将角色移动到指定位置。
// 在这里,向目标位置添加一个小的随机偏移,以为 Bot 创建自然的移动效果。
character.MoveToPosition(position + new Vector3(Random.Range(0.5, 1), 0, Random.Range(0.5, 1)));
});
}
5-3. 现在,如果您在运行时创建一个 Bot 玩家并按下 buttonCallBot 按钮,您应该会看到创建的 Bot 玩家移动到本地玩家角色的位置。

BotPlayerManager.ts 完整代码
import { Random, Vector3 } from 'UnityEngine';
import { Button } from 'UnityEngine.UI';
import { ZepetoCharacter, ZepetoPlayers } from 'ZEPETO.Character.Controller';
import { Room } from 'ZEPETO.Multiplay';
import { ZepetoScriptBehaviour } from 'ZEPETO.Script'
import { ZepetoWorldMultiplay } from 'ZEPETO.World';
export default class BotPlayerManager extends ZepetoScriptBehaviour {
// 公共属性,用于引用 Inspector 中所需的组件和设置。
public zepetoWorldMultiplay: ZepetoWorldMultiplay;
public buttonCreateBot: Button;
public buttonCallBot: Button;
public botPlayerId: string;
// 私有变量,用于存储当前房间和机器人玩家数据。
private _room: Room;
private _botMapData: Map<string, ZepetoCharacter> = new Map<string, ZepetoCharacter>();
Start() {
// 监听来自 `ZepetoWorldMultiplay` 组件的 `RoomJoined` 事件。
this.zepetoWorldMultiplay.RoomJoined += (room: Room) => {
this._room = room;
// 为 "StartWorld" 消息类型添加消息处理程序。
this._room.AddMessageHandler("StartWorld", (playTime: number) => {
this.StartWorld(playTime);
});
}
// 监听来自 `ZepetoPlayers.instance` 的 `OnAddedPlayer` 事件,以处理新添加的玩家。
ZepetoPlayers.instance.OnAddedPlayer.AddListener((userId: string) => {
// 使用 `userId` 从房间状态中获取当前玩家数据。
const currentPlayer = this._room.State.players.get_Item(userId);
// 检查玩家是否为机器人,如果是,则将其设置为机器人玩家。
if (currentPlayer.isBot) {
this.SetBotPlayer(currentPlayer.sessionId);
}
});
// 为 "Create Bot" 按钮添加点击监听器,以发送消息创建机器人玩家。
this.buttonCreateBot.onClick.AddListener(() => {
this._room.Send("CreateBot", this.botPlayerId);
});
this.zepetoWorldMultiplay.RoomJoined += (room: Room) => {
this._room = room;
this._room.AddMessageHandler("MoveBotToPosition", (message: string) => {
this.MoveBotToPosition(message);
});
}
// 为 "Call Bot" 按钮添加点击监听器,以发送消息发送机器人玩家位置。
this.buttonCallBot.onClick.AddListener(() => {
this.SendBotPosition();
});
}
// `SetBotPlayer()` 方法用于将玩家设置为机器人。
SetBotPlayer(userId: string) {
// 使用其 `userId` 获取与机器人玩家相关联的 ZEPETO 角色。
const bot = ZepetoPlayers.instance.GetPlayer(userId).character;
// 将角色的名称设置为 `userId` 以便识别。
bot.gameObject.name = userId;
// 使用其 `userId` 作为键将机器人玩家的数据存储在 `_botMapData` 映射中。
this._botMapData.set(userId, bot);
}
// `StartWorld()` 方法在世界开始时调用,提供的 `playTime`。
StartWorld(playTime: number) {
// 打印世界开始消息以及 `playTime`。
console.log(`Start World : ${playTime}`);
}
// 此方法将本地玩家角色的位置发送到服务器以进行机器人移动同步。
SendBotPosition() {
// 获取本地玩家角色的位置。
const localPlayerPosition = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer.character.transform.position;
// 创建一个位置对象,包含本地玩家角色的 x、y 和 z 坐标。
const position = {
x: localPlayerPosition.x,
y: localPlayerPosition.y,
z: localPlayerPosition.z
}
// 将位置对象转换为 JSON 字符串,并使用 "MoveBot" 消息类型将其发送到服务器。
this._room.Send("MoveBot", JSON.stringify(position));
}
// 当服务器从客户端接收到 "MoveBot" 消息时调用此方法,并将机器人角色移动到指定位置。
MoveBotToPosition(message) {
// 解析从客户端接收到的 JSON 消息以提取位置信息。
const jsonMessage = JSON.parse(message);
const position = new Vector3(jsonMessage.positionX, jsonMessage.positionY, jsonMessage.positionZ);
// 将 `_botMapData` 映射中的每个机器人角色移动到指定位置,并在 x 和 z 轴上添加一个小的随机偏移。
this._botMapData.forEach((character: ZepetoCharacter) => {
// `MoveToPosition()` 方法用于将角色移动到指定位置。
// 在这里,向目标位置添加一个小的随机偏移,以创建自然的机器人移动效果。
character.MoveToPosition(position + new Vector3(Random.Range(0.5, 1), 0, Random.Range(0.5, 1)));
});
}
}
index.ts 服务器完整代码
import { Sandbox, SandboxOptions, SandboxPlayer } from "ZEPETO.Multiplay";
import { DataStorage } from "ZEPETO.Multiplay.DataStorage";
import { Player, Transform, Vector3 } from "ZEPETO.Multiplay.Schema";
export default class extends Sandbox {
storageMap: Map<string, DataStorage> = new Map<string, DataStorage>();
// 保存机器人玩家的地图数据为 _botMap
private _botMap: Map<string, Player> = new Map<string, Player>();
private playTime: number = 0;
constructor() {
super();
}
onCreate(options: SandboxOptions) {
// 当房间对象被创建时调用。
// 处理房间对象的状态或数据初始化。
this.onMessage("onChangedTransform", (client, message) => {
this.state.players.get(client.sessionId);
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;
}
});
// 处理 "CreateBot" 消息,该消息使用给定的 userId 创建一个机器人玩家。
this.onMessage("CreateBot", (client, message) => {
this.CreateBot(message);
});
// 处理 "MoveBot" 消息,该消息将机器人玩家移动到指定位置。
this.onMessage("MoveBot", (client, message) => {
this.MoveBot(client, message);
});
}
// `CreateBot()` 方法用于使用给定的 `userId` 创建一个机器人玩家。
CreateBot(userId: string) {
// 使用提供的 `userId` 为机器人玩家生成一个会话 ID。
const sessionId = "Bot_" + userId;
// 检查是否已经存在具有相同会话 ID 的机器人玩家。如果存在,则返回而不创建重复。
if (this.state.players.has(sessionId)) {
return;
}
// 为机器人玩家创建一个新的 `Player` 对象。
const player: Player = new Player();
player.sessionId = sessionId;
if (userId) {
player.zepetoUserId = userId;
}
player.isBot = true;
// 使用会话 ID 作为键将机器人玩家添加到状态的玩家映射中。
this.state.players.set(player.sessionId, player);
this._botMap.set(sessionId, player);
// 在添加机器人玩家后检查房间中的玩家数量。
this.CheckPlayerNumber();
}
// `CheckPlayerNumber()` 方法检查房间中的玩家数量,并在至少有四名玩家时启动世界。
CheckPlayerNumber() {
// 将房间中当前的玩家数量打印到控制台。
console.log(`玩家数量, ${this.state.players.size}`);
// 如果房间中至少有四名玩家,则启动世界。
if (this.state.players.size >= 4) {
this.StartWorld();
}
}
// `StartWorld()` 方法增加游戏时间并向所有客户端广播 "StartWorld" 消息。
StartWorld() {
this.playTime += 1;
// 打印一条消息,指示世界的开始和当前的游戏时间。
console.log("开始世界!");
this.broadcast("StartWorld", this.playTime);
}
// `MoveBot()` 方法根据接收到的消息将机器人玩家移动到指定位置。
MoveBot(client: SandboxPlayer, message: string) {
// 解析从客户端接收到的 JSON 消息以提取位置信息。
const position = JSON.parse(message);
// 创建一个新的消息对象,包含用户的会话 ID 和解析后的位置信息。
const newMessage = {
user: client.sessionId,
positionX: position.x,
positionY: position.y,
positionZ: position.z
}
// 将 "MoveBotToPosition" 消息广播给所有客户端,消息数据作为 JSON 字符串。
this.broadcast("MoveBotToPosition", JSON.stringify(newMessage));
}
async onJoin(client: SandboxPlayer) {
// 创建在 schemas.json 中定义的玩家对象并设置初始值。
console.log(`[OnJoin] 会话 ID : ${client.sessionId}, 哈希码 : ${client.hashCode}, 用户 ID : ${client.userId}`)
const player = new Player();
player.sessionId = client.sessionId;
if (client.hashCode) {
player.zepetoHash = client.hashCode;
}
if (client.userId) {
player.zepetoUserId = client.userId;
}
// [DataStorage] 加载进入玩家的数据存储
const storage: DataStorage = client.loadDataStorage();
this.storageMap.set(client.sessionId, storage);
let visit_cnt = await storage.get("VisitCount") as number;
if (visit_cnt == null) visit_cnt = 0;
console.log(`[OnJoin] ${client.sessionId} 的访问计数 : ${visit_cnt}`)
// [DataStorage] 更新玩家的访问计数,然后保存存储
await storage.set("VisitCount", ++visit_cnt);
// 使用会话 ID 管理玩家对象,这是客户端对象的唯一键值。
// 客户端可以通过将 add_OnAdd 事件添加到玩家对象来检查通过 set 添加的玩家对象的信息。
this.state.players.set(client.sessionId, player);
// 在玩家加入后检查房间中的玩家数量。
this.CheckPlayerNumber();
}
onTick(deltaTime: number): void {
// 它在服务器中每次设置时间时重复调用,并且可以使用 deltaTime 管理某个间隔事件。
}
async onLeave(client: SandboxPlayer, consented?: boolean) {
// 通过设置 allowReconnection,可以保持电路的连接,但在基本指南中立即清理。
// 客户端可以通过将 add_OnRemove 事件添加到玩家对象来检查已删除的玩家对象的信息。
this.state.players.delete(client.sessionId);
}
}