diff --git a/apps/client/src/scenes/game/input/joystick/JoystickController.ts b/apps/client/src/scenes/game/input/joystick/JoystickController.ts deleted file mode 100644 index db5935e..0000000 --- a/apps/client/src/scenes/game/input/joystick/JoystickController.ts +++ /dev/null @@ -1,70 +0,0 @@ -/** - * JoystickController - * 入力イベントとジョイスティック計算結果の仲介を担うコントローラー - * useJoystick の出力を受けて onInput 通知と終了時リセット通知を統一する - */ -import { useCallback } from 'react'; -import type React from 'react'; -import type { NormalizedInput, Point } from './common'; -import { useJoystick } from './useJoystick'; - -/** コントローラーに渡す入力設定 */ -type Props = { - onInput: (moveX: number, moveY: number) => void; - maxDist?: number; -}; - -/** コントローラーが返す描画状態と入力ハンドラ */ -type UseJoystickControllerReturn = { - isMoving: boolean; - center: Point; - knobOffset: Point; - radius: number; - handleStart: (e: React.TouchEvent | React.MouseEvent) => void; - handleMove: (e: React.TouchEvent | React.MouseEvent) => void; - handleEnd: () => void; -}; - -/** 入力イベントと通知処理を仲介するフック型コントローラー */ -export const useJoystickController = ({ onInput, maxDist }: Props): UseJoystickControllerReturn => { - const { - isMoving, - center, - knobOffset, - radius, - handleStart, - handleMove: baseHandleMove, - handleEnd: baseHandleEnd, - } = useJoystick({ maxDist }); - - const emitInput = useCallback( - (normalized: NormalizedInput) => { - onInput(normalized.x, normalized.y); - }, - [onInput] - ); - - const handleMove = useCallback( - (e: React.TouchEvent | React.MouseEvent) => { - const normalized = baseHandleMove(e); - if (!normalized) return; - emitInput(normalized); - }, - [baseHandleMove, emitInput] - ); - - const handleEnd = useCallback(() => { - baseHandleEnd(); - emitInput({ x: 0, y: 0 }); - }, [baseHandleEnd, emitInput]); - - return { - isMoving, - center, - knobOffset, - radius, - handleStart, - handleMove, - handleEnd, - }; -}; diff --git a/apps/client/src/scenes/game/input/joystick/JoystickInputPresenter.tsx b/apps/client/src/scenes/game/input/joystick/JoystickInputPresenter.tsx index 9276e4e..cc6dd72 100644 --- a/apps/client/src/scenes/game/input/joystick/JoystickInputPresenter.tsx +++ b/apps/client/src/scenes/game/input/joystick/JoystickInputPresenter.tsx @@ -3,7 +3,7 @@ * ジョイスティック入力の受け取りと表示状態の橋渡しを担うプレゼンター * 入力イベントをコントローラーへ委譲し,描画用状態をViewへ渡す */ -import { useJoystickController } from "./JoystickController"; +import { useJoystickController } from "./useJoystickController"; import { JoystickView } from "./JoystickView"; /** 入力半径の既定値を外部から参照できるように再公開 */ diff --git a/apps/client/src/scenes/game/input/joystick/useJoystick.ts b/apps/client/src/scenes/game/input/joystick/useJoystick.ts deleted file mode 100644 index 50021da..0000000 --- a/apps/client/src/scenes/game/input/joystick/useJoystick.ts +++ /dev/null @@ -1,77 +0,0 @@ -/** - * useJoystick - * ジョイスティック入力を受け取り,座標計算と正規化ベクトルの出力を行うフック - * UI描画に必要な中心点・ノブ位置・半径も合わせて提供する - */ -import { useCallback, useState } from "react"; -import type React from "react"; -import { MAX_DIST } from "./common"; -import { computeJoystick } from "./JoystickModel"; -import type { NormalizedInput, Point } from "./common"; - -/** フックに渡す設定 */ -type Props = { - maxDist?: number; -}; - -/** フックが返すUI向けの状態とハンドラ */ -type UseJoystickReturn = { - isMoving: boolean; - center: Point; - knobOffset: Point; - radius: number; - handleStart: (e: React.TouchEvent | React.MouseEvent) => void; - handleMove: (e: React.TouchEvent | React.MouseEvent) => NormalizedInput | null; - 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 }; -}; - -/** 正規化ベクトルの出力とUI用の座標を提供するフック */ -export const useJoystick = ({ maxDist }: Props): UseJoystickReturn => { - const [isMoving, setIsMoving] = useState(false); - const [center, setCenter] = useState({ x: 0, y: 0 }); - const [knobOffset, setKnobOffset] = useState({ x: 0, y: 0 }); - const radius = maxDist ?? MAX_DIST; - - // 入力開始時の基準座標をセットする - const handleStart = useCallback((e: React.TouchEvent | React.MouseEvent) => { - const point = getClientPoint(e); - if (!point) return; - - setCenter(point); - setKnobOffset({ x: 0, y: 0 }); - setIsMoving(true); - }, []); - - // 入力座標からベクトルを計算し,半径でクランプして正規化する - const handleMove = useCallback( - (e: React.TouchEvent | React.MouseEvent) => { - if (!isMoving) return null; - const point = getClientPoint(e); - if (!point) return null; - - const computed = computeJoystick(center, point, radius); - setKnobOffset(computed.knobOffset); - return computed.normalized; - }, - [isMoving, center.x, center.y, radius] - ); - - // 入力終了時に状態をリセットする - const handleEnd = useCallback(() => { - setIsMoving(false); - setKnobOffset({ x: 0, y: 0 }); - }, []); - - return { isMoving, center, knobOffset, radius, handleStart, handleMove, handleEnd }; -}; diff --git a/apps/client/src/scenes/game/input/joystick/useJoystickController.ts b/apps/client/src/scenes/game/input/joystick/useJoystickController.ts new file mode 100644 index 0000000..9d75cd8 --- /dev/null +++ b/apps/client/src/scenes/game/input/joystick/useJoystickController.ts @@ -0,0 +1,70 @@ +/** + * useJoystickController + * 入力イベントとジョイスティック計算結果の仲介を担うフック + * useJoystickState の出力を受けて onInput 通知と終了時リセット通知を統一する + */ +import { useCallback } from 'react'; +import type React from 'react'; +import type { NormalizedInput, Point } from './common'; +import { useJoystickState } from './useJoystickState'; + +/** フックに渡す入力設定 */ +type Props = { + onInput: (moveX: number, moveY: number) => void; + maxDist?: number; +}; + +/** フックが返す描画状態と入力ハンドラ */ +type UseJoystickControllerReturn = { + isMoving: boolean; + center: Point; + knobOffset: Point; + radius: number; + handleStart: (e: React.TouchEvent | React.MouseEvent) => void; + handleMove: (e: React.TouchEvent | React.MouseEvent) => void; + handleEnd: () => void; +}; + +/** 入力イベントと通知処理を仲介するフック */ +export const useJoystickController = ({ onInput, maxDist }: Props): UseJoystickControllerReturn => { + const { + isMoving, + center, + knobOffset, + radius, + handleStart, + handleMove: baseHandleMove, + handleEnd: baseHandleEnd, + } = useJoystickState({ maxDist }); + + const emitInput = useCallback( + (normalized: NormalizedInput) => { + onInput(normalized.x, normalized.y); + }, + [onInput] + ); + + const handleMove = useCallback( + (e: React.TouchEvent | React.MouseEvent) => { + const normalized = baseHandleMove(e); + if (!normalized) return; + emitInput(normalized); + }, + [baseHandleMove, emitInput] + ); + + const handleEnd = useCallback(() => { + baseHandleEnd(); + emitInput({ x: 0, y: 0 }); + }, [baseHandleEnd, emitInput]); + + return { + isMoving, + center, + knobOffset, + radius, + handleStart, + handleMove, + handleEnd, + }; +}; diff --git a/apps/client/src/scenes/game/input/joystick/useJoystickState.ts b/apps/client/src/scenes/game/input/joystick/useJoystickState.ts new file mode 100644 index 0000000..d149788 --- /dev/null +++ b/apps/client/src/scenes/game/input/joystick/useJoystickState.ts @@ -0,0 +1,77 @@ +/** + * useJoystickState + * ジョイスティック入力状態の管理と入力ハンドラの提供を担うフック + * UI描画に必要な中心点,ノブ位置,半径を保持する + */ +import { useCallback, useState } from 'react'; +import type React from 'react'; +import { MAX_DIST } from './common'; +import { computeJoystick } from './JoystickModel'; +import type { NormalizedInput, Point } from './common'; + +/** フックに渡す設定 */ +type Props = { + maxDist?: number; +}; + +/** フックが返すUI向けの状態とハンドラ */ +type UseJoystickStateReturn = { + isMoving: boolean; + center: Point; + knobOffset: Point; + radius: number; + handleStart: (e: React.TouchEvent | React.MouseEvent) => void; + handleMove: (e: React.TouchEvent | React.MouseEvent) => NormalizedInput | null; + 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 useJoystickState = ({ maxDist }: Props): UseJoystickStateReturn => { + const [isMoving, setIsMoving] = useState(false); + const [center, setCenter] = useState({ x: 0, y: 0 }); + const [knobOffset, setKnobOffset] = useState({ x: 0, y: 0 }); + const radius = maxDist ?? MAX_DIST; + + // 入力開始時の基準座標をセットする + const handleStart = useCallback((e: React.TouchEvent | React.MouseEvent) => { + const point = getClientPoint(e); + if (!point) return; + + setCenter(point); + setKnobOffset({ x: 0, y: 0 }); + setIsMoving(true); + }, []); + + // 入力座標からベクトルを計算し,半径でクランプして正規化する + const handleMove = useCallback( + (e: React.TouchEvent | React.MouseEvent) => { + if (!isMoving) return null; + const point = getClientPoint(e); + if (!point) return null; + + const computed = computeJoystick(center, point, radius); + setKnobOffset(computed.knobOffset); + return computed.normalized; + }, + [isMoving, center.x, center.y, radius] + ); + + // 入力終了時に状態をリセットする + const handleEnd = useCallback(() => { + setIsMoving(false); + setKnobOffset({ x: 0, y: 0 }); + }, []); + + return { isMoving, center, knobOffset, radius, handleStart, handleMove, handleEnd }; +};