Learn-skills.dev hytopia-multiplayer
Helps implement multiplayer features in HYTOPIA SDK games. Use when users need player management, server-authoritative gameplay, networking, or state synchronization. Covers Player class, server authority, network optimization, and player data.
install
source · Clone the upstream repo
git clone https://github.com/NeverSight/learn-skills.dev
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/NeverSight/learn-skills.dev "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/skills-md/abstrucked/hytopia-skills/hytopia-multiplayer" ~/.claude/skills/neversight-learn-skills-dev-hytopia-multiplayer && rm -rf "$T"
manifest:
data/skills-md/abstrucked/hytopia-skills/hytopia-multiplayer/SKILL.mdsource content
HYTOPIA Multiplayer
This skill helps you implement multiplayer features in HYTOPIA SDK games.
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
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
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
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
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, collision
State Synchronization
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
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
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
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
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
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
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); }