diff --git a/apps/client/src/app.tsx b/apps/client/src/app.tsx index 7077170..ce01578 100644 --- a/apps/client/src/app.tsx +++ b/apps/client/src/app.tsx @@ -1,58 +1,135 @@ -import { useEffect, useRef } from 'preact/hooks'; -import { Application } from 'pixi.js'; +import { useEffect, useState, useRef } from "react"; +import { Joystick } from "react-joystick-component"; +import { io, Socket } from "socket.io-client"; -// 👇 ここを変更しました! -// 元: import { Joystick } from './Joystick'; -import { Joystick } from './input/Joystick'; +// プレイヤーの型定義(サーバーと同じ形にする) +type Player = { + id: string; + x: number; + y: number; + color: string; +}; - -import { Player } from './entities/Player'; - -import './app.css'; - -export function App() { - const containerRef = useRef(null); +function App() { + // 自分のIDを保存する場所 + const [myId, setMyId] = useState(null); + // 全員のプレイヤー情報を保存する場所 { "ID": {プレイヤー情報}, ... } + const [players, setPlayers] = useState>({}); + + // 通信の接続(二重接続防止のため useRef を使う) + const socketRef = useRef(null); useEffect(() => { - // 1. PixiJSアプリケーションの作成 - const app = new Application(); + // 1. サーバーに接続 + // Viteのプロキシ設定があるので、パスは自動で解決されます + socketRef.current = io(); + const socket = socketRef.current; - const initGame = async () => { - await app.init({ - resizeTo: window, - backgroundColor: 0x1099bb, - resolution: window.devicePixelRatio || 1, - autoDensity: true, + // 接続成功したとき + socket.on("connect", () => { + console.log("✅ Connected with ID:", socket.id); + setMyId(socket.id || null); + }); + + // --- サーバーからの命令を受け取る --- + + // 1. 今いる全員のリストを受け取る + socket.on("current_players", (serverPlayers: Player[]) => { + const playersMap: Record = {}; + serverPlayers.forEach((p) => { + playersMap[p.id] = p; }); + setPlayers(playersMap); + }); - if (containerRef.current) { - containerRef.current.appendChild(app.canvas); - } + // 2. 新しい人が来た + socket.on("new_player", (player: Player) => { + setPlayers((prev) => ({ ...prev, [player.id]: player })); + }); - // 2. プレイヤーの作成 - const player = new Player(0xff0000); - player.x = app.screen.width / 2; - player.y = app.screen.height / 2; - app.stage.addChild(player); - - // 3. ジョイスティックの作成 - const joystick = new Joystick(); - app.stage.addChild(joystick); - - // 4. ゲームループ - app.ticker.add((ticker) => { - if (joystick.input.x !== 0 || joystick.input.y !== 0) { - player.move(joystick.input.x, joystick.input.y, ticker.deltaTime); - } + // 3. 誰かが動いた + socket.on("update_player", (data: { id: string; x: number; y: number }) => { + setPlayers((prev) => { + const target = prev[data.id]; + if (!target) return prev; // 知らない人なら何もしない + // その人の位置だけ更新して新しいリストを作る + return { + ...prev, + [data.id]: { ...target, x: data.x, y: data.y }, + }; }); - }; + }); - initGame(); + // 4. 誰かがいなくなった + socket.on("remove_player", (id: string) => { + setPlayers((prev) => { + const newPlayers = { ...prev }; + delete newPlayers[id]; + return newPlayers; + }); + }); + // 片付け(画面を閉じたときに通信を切る) return () => { - app.destroy(true, { children: true }); + socket.disconnect(); }; }, []); - return
; -} \ No newline at end of file + // ジョイスティックを動かしたとき + const handleMove = (event: any) => { + if (socketRef.current) { + socketRef.current.emit("move", { x: event.x, y: event.y }); + } + }; + + const handleStop = () => { + if (socketRef.current) { + socketRef.current.emit("move", { x: 0, y: 0 }); + } + }; + + return ( +
+

Multiplayer: {Object.keys(players).length} Online

+ + {/* ゲーム画面エリア */} +
+ {/* 全員分のボールを描画するループ処理 */} + {Object.values(players).map((player) => ( +
+ ))} +
+ + +
+ ); +} + +export default App; \ No newline at end of file diff --git a/apps/client/src/main.tsx b/apps/client/src/main.tsx index bfb172e..1b7c45c 100644 --- a/apps/client/src/main.tsx +++ b/apps/client/src/main.tsx @@ -1,5 +1,10 @@ -import { render } from 'preact' +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './app.tsx' // 小文字のままでOK import './index.css' -import { App } from './app.tsx' -render(, document.getElementById('app')!) +ReactDOM.createRoot(document.getElementById('app')!).render( + + + , +) \ No newline at end of file