diff --git a/apps/client/src/scenes/game/GameManager.ts b/apps/client/src/scenes/game/GameManager.ts index 5570080..30eaa9c 100644 --- a/apps/client/src/scenes/game/GameManager.ts +++ b/apps/client/src/scenes/game/GameManager.ts @@ -4,7 +4,7 @@ import type { playerTypes } from "@repo/shared"; import { BasePlayer, LocalPlayer, RemotePlayer } from "./entities/player/Player"; import { GameMap } from "./entities/map/GameMap"; -import { MAX_DIST } from "./input/joystick/VirtualJoystick"; +import { MAX_DIST } from "./input/joystick/Joystick"; export class GameManager { private app: Application; diff --git a/apps/client/src/scenes/game/GameScene.tsx b/apps/client/src/scenes/game/GameScene.tsx index 410ad99..8bd7e4e 100644 --- a/apps/client/src/scenes/game/GameScene.tsx +++ b/apps/client/src/scenes/game/GameScene.tsx @@ -1,5 +1,5 @@ import { useEffect, useRef, useState } from "react"; -import { VirtualJoystick } from "./input/joystick/VirtualJoystick"; +import { Joystick } from "./input/joystick/Joystick"; import { GameManager } from "./GameManager"; import { config } from "@repo/shared"; @@ -74,7 +74,7 @@ {/* UI 配置領域 */}
- { // ジョイスティックの入力を毎フレーム Manager に渡す gameManagerRef.current?.setJoystickInput(x, y); diff --git a/apps/client/src/scenes/game/input/joystick/Joystick.tsx b/apps/client/src/scenes/game/input/joystick/Joystick.tsx new file mode 100644 index 0000000..b8d2ea3 --- /dev/null +++ b/apps/client/src/scenes/game/input/joystick/Joystick.tsx @@ -0,0 +1,64 @@ +import { MAX_DIST, useJoystick } from "./useJoystick"; + +export { MAX_DIST } from "./useJoystick"; + +type Props = { + onMove: (moveX: number, moveY: number) => void; +}; + +export const Joystick = ({ onMove }: Props) => { + const { isMoving, basePos, stickPos, handleStart, handleMove, handleEnd } = + useJoystick({ onMove }); + + return ( +
+ {isMoving && ( +
+
+
+ )} +
+ ); +}; \ No newline at end of file diff --git a/apps/client/src/scenes/game/input/joystick/VirtualJoystick.tsx b/apps/client/src/scenes/game/input/joystick/VirtualJoystick.tsx deleted file mode 100644 index 90928a1..0000000 --- a/apps/client/src/scenes/game/input/joystick/VirtualJoystick.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { useState } from "react"; - -// ジョイスティック最大入力距離 -export const MAX_DIST = 60; - -type Props = { - // 正規化前入力ベクトル通知コールバック - onMove: (moveX: number, moveY: number) => void; -}; - -// タッチ・マウス両対応仮想ジョイスティック -export const VirtualJoystick = ({ onMove }: Props) => { - // 入力中フラグ - const [isMoving, setIsMoving] = useState(false); - // ジョイスティック基準座標 - const [basePos, setBasePos] = useState({ x: 0, y: 0 }); - // ノブ描画オフセット座標 - const [stickPos, setStickPos] = useState({ x: 0, y: 0 }); - - const handleStart = (e: React.TouchEvent | React.MouseEvent) => { - const clientX = "touches" in e ? e.touches[0].clientX : (e as React.MouseEvent).clientX; - const clientY = "touches" in e ? e.touches[0].clientY : (e as React.MouseEvent).clientY; - setBasePos({ x: clientX, y: clientY }); - setStickPos({ x: 0, y: 0 }); - setIsMoving(true); - }; - - const handleMove = (e: React.TouchEvent | React.MouseEvent) => { - if (!isMoving) return; - const clientX = "touches" in e ? e.touches[0].clientX : (e as React.MouseEvent).clientX; - const clientY = "touches" in e ? e.touches[0].clientY : (e as React.MouseEvent).clientY; - - const dx = clientX - basePos.x; - const dy = clientY - basePos.y; - const dist = Math.sqrt(dx * dx + dy * dy); - const angle = Math.atan2(dy, dx); - - const limitedDist = Math.min(dist, MAX_DIST); - const moveX = Math.cos(angle) * limitedDist; - const moveY = Math.sin(angle) * limitedDist; - - setStickPos({ x: moveX, y: moveY }); - // 距離制限後入力ベクトル通知 - onMove(moveX, moveY); - }; - - const handleEnd = () => { - setIsMoving(false); - setStickPos({ x: 0, y: 0 }); - // 入力終了時停止ベクトル通知 - onMove(0, 0); - }; - - return ( -
- {isMoving && ( -
-
-
- )} -
- ); -}; \ No newline at end of file diff --git a/apps/client/src/scenes/game/input/joystick/useJoystick.ts b/apps/client/src/scenes/game/input/joystick/useJoystick.ts new file mode 100644 index 0000000..0ffd6bb --- /dev/null +++ b/apps/client/src/scenes/game/input/joystick/useJoystick.ts @@ -0,0 +1,75 @@ +import { useCallback, useState } from "react"; +import type React from "react"; + +export const MAX_DIST = 60; + +type Props = { + onMove: (moveX: number, moveY: number) => void; + maxDist?: number; +}; + +type Point = { x: number; y: number }; + +type UseJoystickReturn = { + isMoving: boolean; + basePos: Point; + stickPos: Point; + handleStart: (e: React.TouchEvent | React.MouseEvent) => void; + handleMove: (e: React.TouchEvent | React.MouseEvent) => void; + handleEnd: () => void; +}; + +const getClientPoint = (e: React.TouchEvent | React.MouseEvent): Point | null => { + if ("touches" in e) { + const touch = e.touches[0]; + if (!touch) return null; + return { x: touch.clientX, y: touch.clientY }; + } + + return { x: e.clientX, y: e.clientY }; +}; + +export const useJoystick = ({ onMove, maxDist }: Props): UseJoystickReturn => { + const [isMoving, setIsMoving] = useState(false); + const [basePos, setBasePos] = useState({ x: 0, y: 0 }); + const [stickPos, setStickPos] = useState({ x: 0, y: 0 }); + const limit = maxDist ?? MAX_DIST; + + const handleStart = useCallback((e: React.TouchEvent | React.MouseEvent) => { + const point = getClientPoint(e); + if (!point) return; + + setBasePos(point); + setStickPos({ x: 0, y: 0 }); + setIsMoving(true); + }, []); + + const handleMove = useCallback( + (e: React.TouchEvent | React.MouseEvent) => { + if (!isMoving) return; + const point = getClientPoint(e); + if (!point) return; + + const dx = point.x - basePos.x; + const dy = point.y - basePos.y; + const dist = Math.sqrt(dx * dx + dy * dy); + const angle = Math.atan2(dy, dx); + + const limitedDist = Math.min(dist, limit); + const moveX = Math.cos(angle) * limitedDist; + const moveY = Math.sin(angle) * limitedDist; + + setStickPos({ x: moveX, y: moveY }); + onMove(moveX, moveY); + }, + [isMoving, basePos.x, basePos.y, limit, onMove] + ); + + const handleEnd = useCallback(() => { + setIsMoving(false); + setStickPos({ x: 0, y: 0 }); + onMove(0, 0); + }, [onMove]); + + return { isMoving, basePos, stickPos, handleStart, handleMove, handleEnd }; +};