jpskill.com
🛠️ 開発・MCP コミュニティ

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本体の挙動とは独立した参考情報です。

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
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
🪟 Windows (PowerShell)
$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. 1. 下の青いボタンを押して hytopia-multiplayer.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → hytopia-multiplayer フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

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) || [];
}

ベストプラクティス

  1. サーバーが権限を持つ - クライアントデータを決して信用しない
  2. すべての入力を検証する - 範囲、レート、権限をチェックする
  3. 最小限に同期する - クライアントが知る必要のあるものだけを送信する
  4. 空間分割を使用する - 遠くのプレイヤーにブロードキャストしない
  5. レート制限 - スパムと DoS を防止する
  6. グレースフルデグラデーション - 遅延とパケットロスを処理する

一般的なパターン

プレイヤースポーンシステム

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

  1. Server is authoritative - Never trust client data
  2. Validate all inputs - Check bounds, rates, permissions
  3. Sync minimally - Only send what clients need to know
  4. Use spatial partitioning - Don't broadcast to distant players
  5. Rate limit - Prevent spam and DoS
  6. 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);
}