ReactとWebSocketを使ったリアルタイムゲーム開発入門

リアルタイムゲームの開発は、プレイヤー同士の位置情報やアクションを瞬時に同期するスリリングな体験を提供します。その中でも、Reactはコンポーネントベースの開発を可能にし、動的で洗練されたUIを作るのに最適なツールです。一方、WebSocketは、双方向のリアルタイム通信を実現するプロトコルで、ゲームの高速な反応性を支えます。本記事では、ReactとWebSocketを組み合わせてリアルタイムゲームを開発するための基本的な手順とベストプラクティスを解説します。初心者でも理解しやすいように、理論と実装を交えながら進めていきます。

目次

リアルタイムゲーム開発に必要な基礎知識


リアルタイムゲームを開発するには、データの同期や通信の効率性を確保するための基本的な概念を理解することが重要です。ここでは、リアルタイム通信とWebSocketの仕組みについて説明します。

リアルタイム通信とは


リアルタイム通信は、ユーザー間でのデータ交換を遅延なく行う技術を指します。これにより、ゲーム内でのプレイヤーアクションや状態が即座に反映されます。HTTP通信とは異なり、クライアントとサーバーが継続的に接続を維持することで、双方向の通信が可能です。

WebSocketの仕組み


WebSocketは、クライアントとサーバー間での双方向通信を効率的に行うためのプロトコルです。通常のHTTPリクエストとは異なり、接続が確立すると、その後のデータ送受信がヘッダー情報なしで行えるため、低遅延で通信が可能になります。これにより、リアルタイム性が求められるゲームやチャットアプリケーションに最適です。

WebSocketの利点

  • 低遅延:通信のオーバーヘッドが少ないため、高速なデータの送受信が可能です。
  • 双方向通信:クライアントとサーバーが双方向にデータを送れるため、リアルタイム性を実現します。
  • 軽量な接続管理:長時間の接続を維持しつつ、効率的なリソース使用を可能にします。

これらの基礎を理解することで、リアルタイム通信の仕組みを正しく活用できるようになります。次のセクションでは、Reactを使ったゲームのUI設計と状態管理について解説します。

Reactでの基本的な構成と状態管理


Reactは、コンポーネントベースのアーキテクチャを採用しており、リアルタイムゲームのような動的で複雑なUIの構築に適しています。このセクションでは、Reactを用いたゲームUIの設計と状態管理について説明します。

Reactを使ったゲームUIの構築


Reactでは、UIを再利用可能な小さなコンポーネントに分割できます。例えば、ゲーム画面を次のように構成できます:

  • GameBoardコンポーネント:ゲーム全体のキャンバスを描画。
  • Playerコンポーネント:プレイヤーのアバターや位置を管理。
  • ScoreBoardコンポーネント:スコアや進行状況を表示。

これにより、コードが整理され、変更や追加が容易になります。

例:簡単なコンポーネント構成

function GameBoard() {
  return (
    <div className="game-board">
      <Player />
      <ScoreBoard />
    </div>
  );
}

function Player() {
  return <div className="player">👾</div>;
}

function ScoreBoard() {
  return <div className="score-board">Score: 0</div>;
}

状態管理の考え方


リアルタイムゲームでは、プレイヤーの位置やスコアといった状態を頻繁に更新する必要があります。Reactでは、これを管理するためにuseStateuseReducer、場合によってはContext APIReduxを使用します。

例:プレイヤーの位置を状態管理

import React, { useState } from "react";

function Player() {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  const movePlayer = (x, y) => {
    setPosition({ x, y });
  };

  return (
    <div
      className="player"
      style={{ left: position.x, top: position.y, position: "absolute" }}
      onClick={() => movePlayer(position.x + 10, position.y + 10)}
    >
      👾
    </div>
  );
}

リアルタイム通信と状態管理の統合


Reactの状態は、WebSocketを通じて他のプレイヤーと同期されます。たとえば、サーバーから送られてきたデータを受信し、状態を更新することで、他プレイヤーの動きをリアルタイムで表示できます。

例:WebSocketデータを状態に反映

import React, { useState, useEffect } from "react";

function GameBoard() {
  const [players, setPlayers] = useState([]);

  useEffect(() => {
    const socket = new WebSocket("ws://localhost:8080");

    socket.onmessage = (event) => {
      const data = JSON.parse(event.data);
      setPlayers(data.players);
    };

    return () => socket.close();
  }, []);

  return (
    <div className="game-board">
      {players.map((player, index) => (
        <div
          key={index}
          className="player"
          style={{ left: player.x, top: player.y, position: "absolute" }}
        >
          👾
        </div>
      ))}
    </div>
  );
}

Reactを利用したコンポーネント設計と状態管理を理解すれば、リアルタイム通信を取り入れた動的なゲームの基盤を作ることができます。次はWebSocketの初期設定とサーバー構築について解説します。

WebSocketの初期設定とサーバー構築


リアルタイム通信を可能にするWebSocketは、クライアントとサーバー間の双方向通信を支える重要な技術です。このセクションでは、WebSocketの基本的なセットアップと、Node.jsを使用したサーバー構築の方法について説明します。

WebSocketの基礎


WebSocketは、従来のHTTP通信とは異なり、コネクションが確立された後に継続的な通信が可能です。これにより、ゲームのようなリアルタイム性が求められるアプリケーションで高いパフォーマンスを発揮します。

Node.jsでのWebSocketサーバー構築


WebSocketサーバーを構築するには、Node.jsとWebSocketライブラリ(ws)を使用します。このライブラリはシンプルで効率的な実装を提供します。

ステップ1: 必要なパッケージのインストール

まず、Node.jsプロジェクトをセットアップし、wsパッケージをインストールします。

npm init -y
npm install ws

ステップ2: WebSocketサーバーのセットアップ

以下のコードは、WebSocketサーバーを作成し、クライアントからの接続を受け入れる例です。

const WebSocket = require("ws");

const server = new WebSocket.Server({ port: 8080 });

server.on("connection", (socket) => {
  console.log("New client connected!");

  // メッセージを受信
  socket.on("message", (message) => {
    console.log(`Received: ${message}`);

    // 全クライアントにメッセージをブロードキャスト
    server.clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(message);
      }
    });
  });

  // クライアントが切断
  socket.on("close", () => {
    console.log("Client disconnected.");
  });
});

console.log("WebSocket server is running on ws://localhost:8080");

このコードでは、接続されたクライアント間でメッセージをブロードキャストするシンプルなサーバーを構築しています。

クライアント側のWebSocket初期化


ReactアプリケーションでWebSocketを利用するには、WebSocketオブジェクトを初期化します。

例: WebSocket接続の作成

const socket = new WebSocket("ws://localhost:8080");

socket.onopen = () => {
  console.log("Connected to WebSocket server");
};

socket.onmessage = (event) => {
  console.log(`Message received: ${event.data}`);
};

socket.onclose = () => {
  console.log("Disconnected from WebSocket server");
};

サーバーとのデータ送受信


リアルタイム通信を活用するためには、クライアントとサーバーがデータをスムーズにやり取りすることが重要です。プレイヤーの位置情報やアクションなどを送信する際には、JSON形式を使用するのが一般的です。

例: クライアントからサーバーへのメッセージ送信

socket.send(JSON.stringify({ action: "move", x: 100, y: 200 }));

まとめ


これで、WebSocketを使ったリアルタイム通信のための基礎的なサーバーを構築できました。次のセクションでは、このWebSocketサーバーをReactアプリケーションと統合する方法を学びます。

ReactとWebSocketの連携方法


ReactアプリケーションにWebSocketを統合することで、リアルタイム通信を可能にし、動的でインタラクティブなゲームを構築できます。このセクションでは、ReactとWebSocketを連携する具体的な方法について説明します。

WebSocketとReactの基本連携


WebSocketはReactコンポーネントのライフサイクルに合わせて管理するのが重要です。例えば、接続はコンポーネントのマウント時に開始し、アンマウント時にクリーンアップします。

例: WebSocket接続の基本コード

import React, { useEffect, useState } from "react";

function Game() {
  const [messages, setMessages] = useState([]);
  let socket;

  useEffect(() => {
    // WebSocket接続を初期化
    socket = new WebSocket("ws://localhost:8080");

    socket.onopen = () => {
      console.log("WebSocket connected");
    };

    socket.onmessage = (event) => {
      const data = JSON.parse(event.data);
      setMessages((prev) => [...prev, data]);
    };

    socket.onclose = () => {
      console.log("WebSocket disconnected");
    };

    // クリーンアップ
    return () => socket.close();
  }, []);

  return (
    <div>
      <h1>リアルタイムメッセージ</h1>
      <ul>
        {messages.map((msg, index) => (
          <li key={index}>{msg.content}</li>
        ))}
      </ul>
    </div>
  );
}

export default Game;

この例では、WebSocketをReactのuseEffectで管理し、サーバーから受信したデータを状態に格納しています。

リアルタイム通信のイベントハンドリング


ゲームでは、プレイヤーのアクション(移動やスコア更新など)をリアルタイムで同期する必要があります。そのために、WebSocketを通じてアクションデータをサーバーに送信します。

例: プレイヤーの移動データを送信

function sendPlayerAction(action) {
  const data = JSON.stringify(action);
  if (socket.readyState === WebSocket.OPEN) {
    socket.send(data);
  }
}

// プレイヤーが移動する際のイベント
const movePlayer = (x, y) => {
  sendPlayerAction({ type: "move", x, y });
};

サーバーからのデータ同期


サーバーからのメッセージを受信し、ゲーム状態を更新することで、他のプレイヤーのアクションを画面に反映させます。

例: 他のプレイヤーの位置を同期

socket.onmessage = (event) => {
  const data = JSON.parse(event.data);

  if (data.type === "updatePosition") {
    updateOtherPlayers(data.players);
  }
};

const updateOtherPlayers = (players) => {
  setGameState((prevState) => ({
    ...prevState,
    otherPlayers: players,
  }));
};

WebSocketの再接続とエラー処理


ゲームでは、WebSocket接続が切断されても再接続を試みる仕組みが重要です。また、エラー発生時の適切な処理も必要です。

例: 再接続の仕組み

function initializeSocket() {
  socket = new WebSocket("ws://localhost:8080");

  socket.onclose = () => {
    console.log("Connection lost. Reconnecting...");
    setTimeout(() => initializeSocket(), 1000);
  };

  socket.onerror = (error) => {
    console.error("WebSocket error:", error);
  };
}

まとめ


ReactとWebSocketの連携により、リアルタイム性を持ったダイナミックなゲームを構築できます。この基盤があれば、プレイヤー間の同期やアクションの管理を実現できます。次は、プレイヤーの位置やアクションの同期について詳しく説明します。

プレイヤーの位置やアクションの同期


リアルタイムゲームでは、プレイヤーの位置やアクションを正確に同期することがゲーム体験の品質を大きく左右します。このセクションでは、WebSocketを使用してプレイヤー間で位置情報やアクションを同期する方法について解説します。

位置やアクションデータの構造設計


プレイヤーの位置やアクションを効率的に同期するためには、サーバーとクライアント間で共有されるデータの構造を設計する必要があります。以下は基本的なデータ形式の例です。

例: データ構造

{
  "type": "playerAction",
  "playerId": "player123",
  "action": {
    "type": "move",
    "x": 150,
    "y": 300
  }
}

この構造では、typeでメッセージの種類を指定し、playerIdでアクションの送信者を特定します。

プレイヤーの位置送信


プレイヤーが移動した際に、その位置をサーバーに送信します。以下はReactコンポーネントでの実装例です。

例: プレイヤー位置の送信

function sendPlayerPosition(x, y) {
  if (socket.readyState === WebSocket.OPEN) {
    const message = JSON.stringify({
      type: "playerAction",
      playerId: "player123",
      action: { type: "move", x, y },
    });
    socket.send(message);
  }
}

function handleMove(x, y) {
  // プレイヤー位置を更新
  setPlayerPosition({ x, y });
  // サーバーに送信
  sendPlayerPosition(x, y);
}

サーバーでの位置データ管理


サーバーは受信したデータを解析し、他のクライアントにブロードキャストします。以下はNode.jsでの実装例です。

例: サーバー側での位置データ処理

server.on("connection", (socket) => {
  socket.on("message", (message) => {
    const data = JSON.parse(message);

    if (data.type === "playerAction" && data.action.type === "move") {
      // 他のクライアントにブロードキャスト
      server.clients.forEach((client) => {
        if (client !== socket && client.readyState === WebSocket.OPEN) {
          client.send(message);
        }
      });
    }
  });
});

他プレイヤーの位置を表示


クライアント側では、受信したデータを元に他プレイヤーの位置を更新します。

例: 他プレイヤー位置の更新

socket.onmessage = (event) => {
  const data = JSON.parse(event.data);

  if (data.type === "playerAction" && data.action.type === "move") {
    setOtherPlayers((prevPlayers) => {
      const updatedPlayers = { ...prevPlayers };
      updatedPlayers[data.playerId] = {
        x: data.action.x,
        y: data.action.y,
      };
      return updatedPlayers;
    });
  }
};

同期のタイミングとパフォーマンス考慮


プレイヤー間の同期では、通信頻度が多すぎるとネットワーク負荷が増大します。そのため、以下のような工夫が必要です:

  • データ送信の間隔を調整(例: 50msごとに送信)
  • 差分データのみを送信
  • クライアント側での補完処理(位置のスムーズな移動を再現)

例: データ送信の間隔調整

let lastSentTime = 0;

function sendThrottledPosition(x, y) {
  const now = Date.now();
  if (now - lastSentTime > 50) {
    sendPlayerPosition(x, y);
    lastSentTime = now;
  }
}

まとめ


プレイヤーの位置やアクションの同期は、リアルタイムゲームの根幹を成す機能です。適切なデータ構造と通信の最適化を行うことで、滑らかで一貫性のあるゲーム体験を提供できます。次は、ゲームロジックとリアルタイム処理の実装について説明します。

ゲームロジックとリアルタイム処理の実装


リアルタイムゲームでは、プレイヤーのアクションを瞬時に処理し、結果を全プレイヤーに反映することが求められます。このセクションでは、ゲームロジックの設計とリアルタイム性を活用した処理の実装について解説します。

リアルタイムゲームロジックの設計


ゲームロジックとは、プレイヤーの入力やゲーム内のルールに基づいて、ゲームの状態を更新する処理を指します。リアルタイムゲームでは以下の要素を考慮します:

  • 状態の一貫性:全プレイヤー間で同じ状態を共有する必要があります。
  • 遅延処理:通信遅延が発生してもゲームプレイがスムーズに見える工夫が必要です。
  • リアルタイム性の優先:重要なイベント(例: 衝突、スコア更新)は即時反映します。

基本的なゲーム状態の管理


状態管理にはサーバーとクライアントの両方が関与します。サーバーは信頼できる状態を管理し、クライアントはユーザーに即時のフィードバックを提供します。

サーバーでのゲームロジックの実装


Node.jsを使って、プレイヤーの位置やスコアを管理し、ゲームロジックを実装します。

例: サーバーでの衝突判定

const players = {};

server.on("connection", (socket) => {
  socket.on("message", (message) => {
    const data = JSON.parse(message);

    if (data.type === "playerAction" && data.action.type === "move") {
      players[data.playerId] = { x: data.action.x, y: data.action.y };

      // 簡易的な衝突判定
      for (let id in players) {
        if (id !== data.playerId) {
          const otherPlayer = players[id];
          const dx = otherPlayer.x - data.action.x;
          const dy = otherPlayer.y - data.action.y;

          if (Math.sqrt(dx * dx + dy * dy) < 20) {
            // 衝突イベントを送信
            socket.send(JSON.stringify({ type: "collision", with: id }));
          }
        }
      }
    }
  });
});

このコードでは、サーバー上で全プレイヤーの位置を管理し、近接するプレイヤー同士の衝突を判定しています。

クライアント側でのリアルタイム処理


クライアントでは、サーバーから受信したデータに基づいてゲーム画面を更新します。

例: 衝突イベントの処理

socket.onmessage = (event) => {
  const data = JSON.parse(event.data);

  if (data.type === "collision") {
    alert(`You collided with player ${data.with}!`);
  }
};

リアルタイム補完処理の実装


通信遅延を補うために、クライアント側で位置の補完処理を行います。これにより、プレイヤーの動きが滑らかに見えます。

例: 線形補間による位置補完

function interpolatePosition(current, target, factor) {
  return {
    x: current.x + (target.x - current.x) * factor,
    y: current.y + (target.y - current.y) * factor,
  };
}

// 毎フレーム補完処理
useEffect(() => {
  const interval = setInterval(() => {
    setOtherPlayers((prevPlayers) =>
      Object.fromEntries(
        Object.entries(prevPlayers).map(([id, player]) => [
          id,
          interpolatePosition(player, player.target, 0.1),
        ])
      )
    );
  }, 16); // 60 FPS
  return () => clearInterval(interval);
}, []);

この処理は、サーバーから受信した位置(target)に向けて、プレイヤーの現在位置(current)を少しずつ移動させます。

ゲームループの実装


ゲームロジックの更新や描画は一定の間隔で実行する必要があります。サーバー側では、タイマーを使ったゲームループを構築します。

例: サーバー側のゲームループ

setInterval(() => {
  // 全プレイヤーの状態を更新
  Object.keys(players).forEach((id) => {
    players[id].score += 1; // スコアを加算
  });

  // 全プレイヤーに状態を送信
  server.clients.forEach((client) => {
    if (client.readyState === WebSocket.OPEN) {
      client.send(JSON.stringify({ type: "update", players }));
    }
  });
}, 1000); // 毎秒実行

まとめ


ゲームロジックとリアルタイム処理は、プレイヤーの体験を支える重要な部分です。これを効率的に設計・実装することで、スムーズで楽しいゲームプレイを提供できます。次はエラーハンドリングと接続の維持について説明します。

エラーハンドリングと接続の維持


リアルタイムゲームにおいて、WebSocketの接続が切れる問題や通信エラーは避けられません。これらの問題に対処することで、ゲームの安定性を向上させ、プレイヤーの体験を損なわないようにする必要があります。このセクションでは、エラーハンドリングと接続の維持方法について解説します。

WebSocketのエラーハンドリング


エラーが発生した場合、WebSocketのイベントハンドラーを使って適切に処理します。

例: WebSocketエラーの処理

socket.onerror = (error) => {
  console.error("WebSocket error:", error);
  // エラー通知
  alert("通信エラーが発生しました。再接続を試みます。");
};

接続切断への対処


接続が切断された場合、再接続を試みるロジックを実装します。これにより、プレイヤーがゲームを続行できるようになります。

例: 再接続の実装

function initializeSocket() {
  const socket = new WebSocket("ws://localhost:8080");

  socket.onopen = () => {
    console.log("WebSocket connected");
  };

  socket.onclose = () => {
    console.log("WebSocket disconnected. Attempting to reconnect...");
    setTimeout(() => initializeSocket(), 1000); // 1秒後に再接続
  };

  socket.onerror = (error) => {
    console.error("WebSocket error:", error);
  };

  return socket;
}

// 初期化
let socket = initializeSocket();

データ損失への対処


接続が切れた際に重要なデータが失われないよう、再接続後にサーバーと状態を同期する仕組みを設けます。

例: 再接続時の状態同期

socket.onopen = () => {
  console.log("Reconnected to WebSocket server");
  // 再接続時に現在の状態をサーバーに送信
  socket.send(JSON.stringify({ type: "resync", playerId: "player123" }));
};

サーバーはresyncメッセージを受信した際、クライアントの状態を最新に更新します。

タイムアウトとリトライ戦略


サーバーが応答しない場合、タイムアウトを設定し、一定回数のリトライを試みます。

例: タイムアウトの実装

function connectWithTimeout(url, timeout = 5000) {
  return new Promise((resolve, reject) => {
    const socket = new WebSocket(url);

    const timer = setTimeout(() => {
      socket.close();
      reject(new Error("Connection timeout"));
    }, timeout);

    socket.onopen = () => {
      clearTimeout(timer);
      resolve(socket);
    };

    socket.onerror = (error) => {
      clearTimeout(timer);
      reject(error);
    };
  });
}

// 接続
connectWithTimeout("ws://localhost:8080")
  .then((socket) => {
    console.log("Connected to WebSocket server");
  })
  .catch((error) => {
    console.error("Failed to connect:", error);
  });

プレイヤーに対するフィードバック


接続が切れたりエラーが発生した場合、プレイヤーに通知して安心感を与えるUIを実装します。

例: 接続状態の表示

function ConnectionStatus({ status }) {
  return (
    <div className={`status ${status}`}>
      {status === "connected" && "接続中"}
      {status === "disconnected" && "接続切断"}
      {status === "reconnecting" && "再接続中..."}
    </div>
  );
}

// 状態管理
const [status, setStatus] = useState("connected");

useEffect(() => {
  socket.onopen = () => setStatus("connected");
  socket.onclose = () => setStatus("reconnecting");
  socket.onerror = () => setStatus("disconnected");
}, []);

まとめ


エラーハンドリングと接続の維持は、リアルタイムゲームの安定性を高める重要な要素です。適切な再接続ロジックやエラー処理を実装することで、プレイヤーのゲーム体験を大きく向上させることができます。次はパフォーマンス最適化とデバッグについて説明します。

パフォーマンス最適化とデバッグ


リアルタイムゲームの成功には、高速なレスポンスとスムーズな動作を確保するパフォーマンス最適化が欠かせません。また、トラブルシューティングのためのデバッグ技術も重要です。このセクションでは、リアルタイムゲームのパフォーマンス最適化とデバッグ手法について解説します。

パフォーマンス最適化


リアルタイムゲームでは、多数のプレイヤーや頻繁なデータ更新に対応する必要があります。以下の手法を活用して、ゲームのパフォーマンスを向上させます。

データの最適化


ネットワーク負荷を軽減するために、送受信データを必要最小限に抑えます。JSONではなく、バイナリ形式を使用することで通信量を削減できます。

例: JSONをバイナリに置き換え
const encoder = new TextEncoder();
const message = encoder.encode(JSON.stringify({ type: "move", x: 100, y: 200 }));
socket.send(message);

頻度制限(スロットリング)


データ送信頻度を制限することで、不要なトラフィックを削減します。

例: スロットリングの実装
let lastSentTime = 0;

function sendThrottledMessage(data) {
  const now = Date.now();
  if (now - lastSentTime > 50) { // 50ms間隔で送信
    socket.send(JSON.stringify(data));
    lastSentTime = now;
  }
}

レンダリング最適化


ゲームのUIが頻繁に再描画されるのを防ぐため、React.memouseMemoを活用します。

例: `React.memo`の使用
const Player = React.memo(({ x, y }) => {
  return <div className="player" style={{ left: x, top: y }} />;
});

デバッグ手法


リアルタイム通信では、トラブルシューティングのために効率的なデバッグが必要です。

サーバーログの活用


サーバー側で通信内容を記録し、不正なデータやエラーを検出します。

例: サーバーログ
server.on("connection", (socket) => {
  socket.on("message", (message) => {
    console.log("Received message:", message);
  });
});

クライアントログの活用


クライアントでも、受信したデータやエラーをコンソールに記録します。

例: クライアントログ
socket.onmessage = (event) => {
  console.log("Data received:", event.data);
};
socket.onerror = (error) => {
  console.error("WebSocket error:", error);
};

ネットワークモニタリングツールの使用


ブラウザのデベロッパーツールやツール(例: Wireshark)を使って、WebSocketの通信状況を確認します。

負荷テスト


複数の仮想プレイヤーを用意して、サーバーやクライアントの負荷耐性を評価します。

例: 負荷テストツール
  • Artillery: シナリオベースでWebSocketの負荷テストが可能。
  • Locust: 大量のクライアントシミュレーションに対応。

パフォーマンスモニタリング


リソース消費を可視化し、ボトルネックを特定します。

例: パフォーマンスプロファイラ
  • Chrome DevTools: フレームごとの描画時間やメモリ消費を解析可能。
  • Node.jsプロファイラ: サーバーサイドのCPU・メモリ消費を分析。

まとめ


パフォーマンス最適化とデバッグを実施することで、リアルタイムゲームの動作をスムーズにし、ユーザー体験を向上させることができます。次は応用例として、簡易オンラインゲームの完成例を紹介します。

応用例:簡易オンラインゲームの完成例


ここでは、ReactとWebSocketを使って構築する簡易オンラインゲームの完成例を紹介します。この例を通じて、これまで学んだ内容を実際に適用する方法を理解できます。

ゲームの概要

  • ゲームの目的: プレイヤーがリアルタイムで動き回り、他のプレイヤーとエリア内を共有します。
  • 機能: プレイヤーの移動、位置同期、スコア表示、衝突判定。

バックエンドコード


以下はNode.jsを使ったWebSocketサーバーのコードです。

例: サーバーコード

const WebSocket = require("ws");

const server = new WebSocket.Server({ port: 8080 });
const players = {};

server.on("connection", (socket) => {
  const playerId = `player${Math.random().toString(36).substring(7)}`;
  players[playerId] = { x: 0, y: 0 };

  // 新規接続通知
  socket.send(JSON.stringify({ type: "init", playerId, players }));

  socket.on("message", (message) => {
    const data = JSON.parse(message);
    if (data.type === "move") {
      players[data.playerId] = { x: data.x, y: data.y };

      // ブロードキャスト
      server.clients.forEach((client) => {
        if (client.readyState === WebSocket.OPEN) {
          client.send(JSON.stringify({ type: "update", players }));
        }
      });
    }
  });

  socket.on("close", () => {
    delete players[playerId];
    server.clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(JSON.stringify({ type: "update", players }));
      }
    });
  });
});

console.log("WebSocket server running on ws://localhost:8080");

フロントエンドコード


Reactを使って、リアルタイムで動作するゲームのUIを構築します。

例: Reactコード

import React, { useState, useEffect } from "react";

function Game() {
  const [playerId, setPlayerId] = useState(null);
  const [players, setPlayers] = useState({});
  const [position, setPosition] = useState({ x: 0, y: 0 });

  useEffect(() => {
    const socket = new WebSocket("ws://localhost:8080");

    socket.onmessage = (event) => {
      const data = JSON.parse(event.data);
      if (data.type === "init") {
        setPlayerId(data.playerId);
        setPlayers(data.players);
      } else if (data.type === "update") {
        setPlayers(data.players);
      }
    };

    const handleKeyPress = (event) => {
      let newX = position.x;
      let newY = position.y;

      if (event.key === "ArrowUp") newY -= 10;
      if (event.key === "ArrowDown") newY += 10;
      if (event.key === "ArrowLeft") newX -= 10;
      if (event.key === "ArrowRight") newX += 10;

      setPosition({ x: newX, y: newY });
      socket.send(JSON.stringify({ type: "move", playerId, x: newX, y: newY }));
    };

    window.addEventListener("keydown", handleKeyPress);
    return () => window.removeEventListener("keydown", handleKeyPress);
  }, [playerId, position]);

  return (
    <div className="game-board" style={{ position: "relative", width: "500px", height: "500px", background: "#ddd" }}>
      {Object.entries(players).map(([id, { x, y }]) => (
        <div key={id} style={{ position: "absolute", left: x, top: y, width: "20px", height: "20px", background: id === playerId ? "blue" : "red" }}></div>
      ))}
    </div>
  );
}

export default Game;

ゲームの動作確認

  1. Node.jsサーバーを起動: node server.js
  2. Reactアプリを起動: npm start
  3. ブラウザでゲームを開き、複数のウィンドウでプレイヤー間の同期を確認します。

追加機能の提案


このゲームを基に、以下の機能を追加してみましょう:

  • スコア管理: 衝突や特定の条件でスコアを更新する。
  • タイマー: 制限時間を設けてゲームを進行。
  • エフェクト: 衝突時のアニメーションやサウンドを実装。

まとめ


この簡易オンラインゲームの例を通じて、ReactとWebSocketを活用したリアルタイムゲームの開発手法を実践できました。この経験を基に、さらに複雑で魅力的なゲームを構築することが可能です。

まとめ


本記事では、ReactとWebSocketを使ったリアルタイムゲームの開発手法を解説しました。リアルタイム通信の基礎から、ReactでのUI構築、WebSocketとの連携、プレイヤー間のデータ同期、ゲームロジック、エラーハンドリング、パフォーマンス最適化、そして応用例の実装まで、幅広く学ぶことができました。

リアルタイムゲームは、双方向の即時通信を活用してプレイヤーに没入感を提供します。今回の内容を基に、独自のゲームを開発し、さらに高度な機能や洗練されたデザインを取り入れることで、魅力的な作品を作り上げることが可能です。リアルタイムゲームの開発に挑戦して、新たなスキルを習得してください!

コメント

コメントする

目次