hytopia-multiplayer
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseHYTOPIA Multiplayer
HYTOPIA 多人游戏功能
This skill helps you implement multiplayer features in HYTOPIA SDK games.
Documentation: https://dev.hytopia.com/sdk-guides/multiplayer
本技能可帮助你在HYTOPIA SDK游戏中实现多人游戏功能。
When to Use This Skill
何时使用本技能
Use this skill when the user:
- Wants to manage multiple players in a game
- Needs server-authoritative gameplay mechanics
- Asks about player data persistence
- Wants to optimize network performance
- Needs player authentication or identification
- Asks about player teams, groups, or parties
当用户有以下需求时,可使用本技能:
- 希望在游戏中管理多名玩家
- 需要服务器权威的游戏机制
- 询问玩家数据持久化相关问题
- 希望优化网络性能
- 需要玩家身份验证或识别功能
- 询问玩家队伍、群组或小队相关内容
Core Multiplayer Concepts
核心多人游戏概念
Player Management
玩家管理
typescript
import { World, Player } from 'hytopia';
// Access all players
const players = world.players;
// Get specific player
const player = world.getPlayer(playerId);
// Iterate over players
for (const player of world.players) {
player.sendMessage('Hello!');
}
// Count players
const playerCount = world.players.length;typescript
import { World, Player } from 'hytopia';
// Access all players
const players = world.players;
// Get specific player
const player = world.getPlayer(playerId);
// Iterate over players
for (const player of world.players) {
player.sendMessage('Hello!');
}
// Count players
const playerCount = world.players.length;Player Events
玩家事件
typescript
import { World, Player } from 'hytopia';
world.onPlayerJoin = (player: Player) => {
console.log(`${player.username} joined (${player.id})`);
// Send welcome message
player.sendMessage(`Welcome ${player.username}!`);
// Broadcast to others
world.broadcast(`${player.username} has joined the game!`, [player.id]);
// Spawn player at location
player.setPosition({ x: 0, y: 100, z: 0 });
};
world.onPlayerLeave = (player: Player) => {
console.log(`${player.username} left`);
world.broadcast(`${player.username} has left the game.`);
};typescript
import { World, Player } from 'hytopia';
world.onPlayerJoin = (player: Player) => {
console.log(`${player.username} joined (${player.id})`);
// Send welcome message
player.sendMessage(`Welcome ${player.username}!`);
// Broadcast to others
world.broadcast(`${player.username} has joined the game!`, [player.id]);
// Spawn player at location
player.setPosition({ x: 0, y: 100, z: 0 });
};
world.onPlayerLeave = (player: Player) => {
console.log(`${player.username} left`);
world.broadcast(`${player.username} has left the game.`);
};Player Data
玩家数据
typescript
import { Player } from 'hytopia';
// Set custom data on player
player.setData('score', 0);
player.setData('kills', 0);
player.setData('inventory', []);
// Get player data
const score = player.getData('score');
const inventory = player.getData('inventory') || [];
// Persist data (saved across sessions)
player.setPersistedData('level', 5);
const level = player.getPersistedData('level');typescript
import { Player } from 'hytopia';
// Set custom data on player
player.setData('score', 0);
player.setData('kills', 0);
player.setData('inventory', []);
// Get player data
const score = player.getData('score');
const inventory = player.getData('inventory') || [];
// Persist data (saved across sessions)
player.setPersistedData('level', 5);
const level = player.getPersistedData('level');Server Authority
服务器权威
Server-Authoritative Movement
服务器权威的移动控制
typescript
import { Player } from 'hytopia';
// Server controls all movement - client sends inputs only
player.onInput = (input) => {
// Process input on server
if (input.isPressed('w')) {
// Calculate new position server-side
const newPosition = calculateMovement(player, input);
player.setPosition(newPosition);
}
};
// Never trust client position
// Always validate: check speed, bounds, collisiontypescript
import { Player } from 'hytopia';
// Server controls all movement - client sends inputs only
player.onInput = (input) => {
// Process input on server
if (input.isPressed('w')) {
// Calculate new position server-side
const newPosition = calculateMovement(player, input);
player.setPosition(newPosition);
}
};
// Never trust client position
// Always validate: check speed, bounds, collisionState Synchronization
状态同步
typescript
import { Entity } from 'hytopia';
class GameEntity extends Entity {
// Only sync what needs to be visible
syncProperties = ['position', 'rotation', 'health'];
tick(deltaTime: number) {
// Server updates state
this.updateAI(deltaTime);
// Changes automatically sync to clients
// for properties in syncProperties
}
}typescript
import { Entity } from 'hytopia';
class GameEntity extends Entity {
// Only sync what needs to be visible
syncProperties = ['position', 'rotation', 'health'];
tick(deltaTime: number) {
// Server updates state
this.updateAI(deltaTime);
// Changes automatically sync to clients
// for properties in syncProperties
}
}Anti-Cheat Basics
反作弊基础
typescript
import { Player } from 'hytopia';
function validatePlayerMovement(player: Player, newPos: Vector3) {
const oldPos = player.position;
const distance = oldPos.distance(newPos);
const maxDistance = player.maxSpeed * deltaTime;
// Check if moved too fast
if (distance > maxDistance * 1.1) { // 10% tolerance
console.warn(`Possible speed hack: ${player.username}`);
player.setPosition(oldPos); // Revert
return false;
}
// Check if in bounds
if (!world.isInBounds(newPos)) {
player.setPosition(oldPos);
return false;
}
return true;
}typescript
import { Player } from 'hytopia';
function validatePlayerMovement(player: Player, newPos: Vector3) {
const oldPos = player.position;
const distance = oldPos.distance(newPos);
const maxDistance = player.maxSpeed * deltaTime;
// Check if moved too fast
if (distance > maxDistance * 1.1) { // 10% tolerance
console.warn(`Possible speed hack: ${player.username}`);
player.setPosition(oldPos); // Revert
return false;
}
// Check if in bounds
if (!world.isInBounds(newPos)) {
player.setPosition(oldPos);
return false;
}
return true;
}Network Optimization
网络优化
Efficient Broadcasting
高效广播
typescript
import { World } from 'hytopia';
// Send to all players
world.broadcast('Game starting!');
// Send to specific players
world.broadcast('Team message', [], [player1.id, player2.id]);
// Send to all except some
world.broadcast('Secret message', [player1.id]);
// Send to nearby players only
function broadcastToNearby(origin: Vector3, message: string, radius: number) {
for (const player of world.players) {
if (player.position.distance(origin) <= radius) {
player.sendMessage(message);
}
}
}typescript
import { World } from 'hytopia';
// Send to all players
world.broadcast('Game starting!');
// Send to specific players
world.broadcast('Team message', [], [player1.id, player2.id]);
// Send to all except some
world.broadcast('Secret message', [player1.id]);
// Send to nearby players only
function broadcastToNearby(origin: Vector3, message: string, radius: number) {
for (const player of world.players) {
if (player.position.distance(origin) <= radius) {
player.sendMessage(message);
}
}
}Property Sync Optimization
属性同步优化
typescript
import { Entity } from 'hytopia';
class OptimizedEntity extends Entity {
// Only sync when changed
private _health: number = 100;
get health() { return this._health; }
set health(value: number) {
if (this._health !== value) {
this._health = value;
this.sync('health', value); // Manual sync only on change
}
}
// Don't sync internal state
private pathfindingTarget: Vector3; // Server-only
private lastUpdate: number; // Server-only
}typescript
import { Entity } from 'hytopia';
class OptimizedEntity extends Entity {
// Only sync when changed
private _health: number = 100;
get health() { return this._health; }
set health(value: number) {
if (this._health !== value) {
this._health = value;
this.sync('health', value); // Manual sync only on change
}
}
// Don't sync internal state
private pathfindingTarget: Vector3; // Server-only
private lastUpdate: number; // Server-only
}Player Teams/Groups
玩家队伍/群组
typescript
import { Player } from 'hytopia';
// Simple team system
const teams = new Map<string, Player[]>();
function assignTeam(player: Player, teamName: string) {
// Remove from old team
const oldTeam = player.getData('team');
if (oldTeam) {
const oldPlayers = teams.get(oldTeam) || [];
teams.set(oldTeam, oldPlayers.filter(p => p.id !== player.id));
}
// Add to new team
player.setData('team', teamName);
const teamPlayers = teams.get(teamName) || [];
teamPlayers.push(player);
teams.set(teamName, teamPlayers);
// Notify team
for (const teammate of teamPlayers) {
teammate.sendMessage(`${player.username} joined ${teamName}!`);
}
}
function getTeamPlayers(teamName: string): Player[] {
return teams.get(teamName) || [];
}typescript
import { Player } from 'hytopia';
// Simple team system
const teams = new Map<string, Player[]>();
function assignTeam(player: Player, teamName: string) {
// Remove from old team
const oldTeam = player.getData('team');
if (oldTeam) {
const oldPlayers = teams.get(oldTeam) || [];
teams.set(oldTeam, oldPlayers.filter(p => p.id !== player.id));
}
// Add to new team
player.setData('team', teamName);
const teamPlayers = teams.get(teamName) || [];
teamPlayers.push(player);
teams.set(teamName, teamPlayers);
// Notify team
for (const teammate of teamPlayers) {
teammate.sendMessage(`${player.username} joined ${teamName}!`);
}
}
function getTeamPlayers(teamName: string): Player[] {
return teams.get(teamName) || [];
}Best Practices
最佳实践
- Server is authoritative - Never trust client data
- Validate all inputs - Check bounds, rates, permissions
- Sync minimally - Only send what clients need to know
- Use spatial partitioning - Don't broadcast to distant players
- Rate limit - Prevent spam and DoS
- Graceful degradation - Handle lag and packet loss
- 服务器拥有权威 - 永远不要信任客户端数据
- 验证所有输入 - 检查边界、频率、权限
- 最小化同步 - 只发送客户端需要知道的内容
- 使用空间分区 - 不要向远距离玩家广播
- 速率限制 - 防止垃圾信息和拒绝服务攻击
- 优雅降级 - 处理延迟和数据包丢失
Common Patterns
常见模式
Player Spawn System
玩家重生系统
typescript
const spawnPoints = [
{ x: 10, y: 100, z: 10 },
{ x: -10, y: 100, z: 10 },
{ x: 10, y: 100, z: -10 },
{ x: -10, y: 100, z: -10 }
];
function spawnPlayer(player: Player) {
const spawnIndex = world.players.length % spawnPoints.length;
player.setPosition(spawnPoints[spawnIndex]);
player.setHealth(100);
player.clearInventory();
}typescript
const spawnPoints = [
{ x: 10, y: 100, z: 10 },
{ x: -10, y: 100, z: 10 },
{ x: 10, y: 100, z: -10 },
{ x: -10, y: 100, z: -10 }
];
function spawnPlayer(player: Player) {
const spawnIndex = world.players.length % spawnPoints.length;
player.setPosition(spawnPoints[spawnIndex]);
player.setHealth(100);
player.clearInventory();
}Leaderboard
排行榜
typescript
function updateLeaderboard() {
const sorted = [...world.players].sort((a, b) =>
(b.getData('score') || 0) - (a.getData('score') || 0)
);
const leaderboard = sorted.slice(0, 10).map((p, i) =>
`${i + 1}. ${p.username}: ${p.getData('score') || 0}`
).join('\n');
world.broadcast('=== Leaderboard ===\n' + leaderboard);
}typescript
function updateLeaderboard() {
const sorted = [...world.players].sort((a, b) =>
(b.getData('score') || 0) - (a.getData('score') || 0)
);
const leaderboard = sorted.slice(0, 10).map((p, i) =>
`${i + 1}. ${p.username}: ${p.getData('score') || 0}`
).join('\n');
world.broadcast('=== Leaderboard ===\n' + leaderboard);
}