hytopia-multiplayer
HYTOPIA SDKを使ったゲームに、複数プレイヤー参加機能(プレイヤー管理、サーバー制御、ネットワーク最適化、状態同期など)を実装する際に役立ち、Playerクラスやプレイヤーデータなどを扱えるようにするSkill。
📜 元の英語説明(参考)
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.
🇯🇵 日本人クリエイター向け解説
HYTOPIA SDKを使ったゲームに、複数プレイヤー参加機能(プレイヤー管理、サーバー制御、ネットワーク最適化、状態同期など)を実装する際に役立ち、Playerクラスやプレイヤーデータなどを扱えるようにするSkill。
※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o hytopia-multiplayer.zip https://jpskill.com/download/9065.zip && unzip -o hytopia-multiplayer.zip && rm hytopia-multiplayer.zip
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/9065.zip -OutFile "$d\hytopia-multiplayer.zip"; Expand-Archive "$d\hytopia-multiplayer.zip" -DestinationPath $d -Force; ri "$d\hytopia-multiplayer.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
hytopia-multiplayer.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
hytopia-multiplayerフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-18
- 取得日時
- 2026-05-18
- 同梱ファイル
- 1
📖 Skill本文(日本語訳)
※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。
HYTOPIA Multiplayer
このスキルは、HYTOPIA SDK ゲームにマルチプレイヤー機能を実装するのに役立ちます。
このスキルを使用する場面
このスキルは、ユーザーが以下のようなことをしたい場合に利用します。
- ゲーム内で複数のプレイヤーを管理したい
- サーバー権限によるゲームプレイの仕組みが必要
- プレイヤーデータの永続性について知りたい
- ネットワークパフォーマンスを最適化したい
- プレイヤー認証または識別が必要
- プレイヤーのチーム、グループ、またはパーティーについて知りたい
コアとなるマルチプレイヤーの概念
プレイヤー管理
import { World, Player } from 'hytopia';
// すべてのプレイヤーにアクセスする
const players = world.players;
// 特定のプレイヤーを取得する
const player = world.getPlayer(playerId);
// プレイヤーを反復処理する
for (const player of world.players) {
player.sendMessage('Hello!');
}
// プレイヤー数を数える
const playerCount = world.players.length;
プレイヤーイベント
import { World, Player } from 'hytopia';
world.onPlayerJoin = (player: Player) => {
console.log(`${player.username} joined (${player.id})`);
// ウェルカムメッセージを送信する
player.sendMessage(`Welcome ${player.username}!`);
// 他のプレイヤーにブロードキャストする
world.broadcast(`${player.username} has joined the game!`, [player.id]);
// プレイヤーを指定の場所にスポーンさせる
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.`);
};
プレイヤーデータ
import { Player } from 'hytopia';
// プレイヤーにカスタムデータを設定する
player.setData('score', 0);
player.setData('kills', 0);
player.setData('inventory', []);
// プレイヤーデータを取得する
const score = player.getData('score');
const inventory = player.getData('inventory') || [];
// データを永続化する (セッション間で保存される)
player.setPersistedData('level', 5);
const level = player.getPersistedData('level');
サーバー権限
サーバー権限による移動
import { Player } from 'hytopia';
// サーバーがすべての移動を制御する - クライアントは入力のみを送信する
player.onInput = (input) => {
// サーバーで入力を処理する
if (input.isPressed('w')) {
// サーバー側で新しい位置を計算する
const newPosition = calculateMovement(player, input);
player.setPosition(newPosition);
}
};
// クライアントの位置を信用しない
// 常に検証する: 速度、範囲、衝突をチェックする
状態の同期
import { Entity } from 'hytopia';
class GameEntity extends Entity {
// 可視化する必要があるものだけを同期する
syncProperties = ['position', 'rotation', 'health'];
tick(deltaTime: number) {
// サーバーが状態を更新する
this.updateAI(deltaTime);
// syncProperties のプロパティに対する変更は、クライアントに自動的に同期される
}
}
アンチチートの基本
import { Player } from 'hytopia';
function validatePlayerMovement(player: Player, newPos: Vector3) {
const oldPos = player.position;
const distance = oldPos.distance(newPos);
const maxDistance = player.maxSpeed * deltaTime;
// 速すぎる移動かどうかをチェックする
if (distance > maxDistance * 1.1) { // 10% の許容範囲
console.warn(`Possible speed hack: ${player.username}`);
player.setPosition(oldPos); // 元に戻す
return false;
}
// 範囲内かどうかをチェックする
if (!world.isInBounds(newPos)) {
player.setPosition(oldPos);
return false;
}
return true;
}
ネットワークの最適化
効率的なブロードキャスト
import { World } from 'hytopia';
// すべてのプレイヤーに送信する
world.broadcast('Game starting!');
// 特定のプレイヤーに送信する
world.broadcast('Team message', [], [player1.id, player2.id]);
// 一部のプレイヤーを除くすべてに送信する
world.broadcast('Secret message', [player1.id]);
// 近くのプレイヤーのみに送信する
function broadcastToNearby(origin: Vector3, message: string, radius: number) {
for (const player of world.players) {
if (player.position.distance(origin) <= radius) {
player.sendMessage(message);
}
}
}
プロパティ同期の最適化
import { Entity } from 'hytopia';
class OptimizedEntity extends Entity {
// 変更されたときのみ同期する
private _health: number = 100;
get health() { return this._health; }
set health(value: number) {
if (this._health !== value) {
this._health = value;
this.sync('health', value); // 変更時のみ手動で同期する
}
}
// 内部状態は同期しない
private pathfindingTarget: Vector3; // サーバー専用
private lastUpdate: number; // サーバー専用
}
プレイヤーのチーム/グループ
import { Player } from 'hytopia';
// シンプルなチームシステム
const teams = new Map<string, Player[]>();
function assignTeam(player: Player, teamName: string) {
// 古いチームから削除する
const oldTeam = player.getData('team');
if (oldTeam) {
const oldPlayers = teams.get(oldTeam) || [];
teams.set(oldTeam, oldPlayers.filter(p => p.id !== player.id));
}
// 新しいチームに追加する
player.setData('team', teamName);
const teamPlayers = teams.get(teamName) || [];
teamPlayers.push(player);
teams.set(teamName, teamPlayers);
// チームに通知する
for (const teammate of teamPlayers) {
teammate.sendMessage(`${player.username} joined ${teamName}!`);
}
}
function getTeamPlayers(teamName: string): Player[] {
return teams.get(teamName) || [];
}
ベストプラクティス
- サーバーが権限を持つ - クライアントデータを決して信用しない
- すべての入力を検証する - 範囲、レート、権限をチェックする
- 最小限に同期する - クライアントが知る必要のあるものだけを送信する
- 空間分割を使用する - 遠くのプレイヤーにブロードキャストしない
- レート制限 - スパムと DoS を防止する
- グレースフルデグラデーション - 遅延とパケットロスを処理する
一般的なパターン
プレイヤースポーンシステム
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.setH 📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
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);
}