diff --git a/apps/client/src/scenes/game/input/joystick/JoystickCoordinator.tsx b/apps/client/src/scenes/game/input/joystick/JoystickCoordinator.tsx index 253cc64..da830ac 100644 --- a/apps/client/src/scenes/game/input/joystick/JoystickCoordinator.tsx +++ b/apps/client/src/scenes/game/input/joystick/JoystickCoordinator.tsx @@ -6,7 +6,7 @@ import { useCallback } from "react"; import type React from "react"; import { JoystickView } from "./JoystickView"; -import type { NormalizedInput } from "./joystick.types"; +import type { NormalizedInput } from "./common"; import { useJoystick } from "./useJoystick"; /** JoystickCoordinator が提供する描画用データと入力ハンドラ */ diff --git a/apps/client/src/scenes/game/input/joystick/JoystickInputLayer.tsx b/apps/client/src/scenes/game/input/joystick/JoystickInputLayer.tsx index 37bce33..b634672 100644 --- a/apps/client/src/scenes/game/input/joystick/JoystickInputLayer.tsx +++ b/apps/client/src/scenes/game/input/joystick/JoystickInputLayer.tsx @@ -6,7 +6,7 @@ import { JoystickCoordinator } from "./JoystickCoordinator"; /** 入力半径の既定値を外部から参照できるように再公開 */ -export { MAX_DIST } from "./joystick.constants"; +export { MAX_DIST } from "./common"; /** JoystickInputLayer の入力コールバック */ type Props = { diff --git a/apps/client/src/scenes/game/input/joystick/JoystickModel.ts b/apps/client/src/scenes/game/input/joystick/JoystickModel.ts new file mode 100644 index 0000000..06193f5 --- /dev/null +++ b/apps/client/src/scenes/game/input/joystick/JoystickModel.ts @@ -0,0 +1,38 @@ +/** + * JoystickModel + * ジョイスティック入力の座標計算を担うモデル + * ノブ表示オフセットと正規化入力ベクトルを算出する + */ +import type { NormalizedInput, Point } from './common'; + +/** ジョイスティック計算結果を表す型 */ +export type JoystickComputed = { + knobOffset: Point; + normalized: NormalizedInput; +}; + +/** 座標差分からノブオフセットと正規化入力を計算する */ +export const computeJoystick = ( + center: Point, + current: Point, + radius: number +): JoystickComputed => { + const safeRadius = radius > 0 ? radius : 1; + + const dx = current.x - center.x; + const dy = current.y - center.y; + const dist = Math.hypot(dx, dy); + const angle = Math.atan2(dy, dx); + + const limitedDist = Math.min(dist, safeRadius); + const offsetX = Math.cos(angle) * limitedDist; + const offsetY = Math.sin(angle) * limitedDist; + + return { + knobOffset: { x: offsetX, y: offsetY }, + normalized: { + x: offsetX / safeRadius, + y: offsetY / safeRadius, + }, + }; +}; diff --git a/apps/client/src/scenes/game/input/joystick/JoystickView.tsx b/apps/client/src/scenes/game/input/joystick/JoystickView.tsx index ef316d9..cf6db61 100644 --- a/apps/client/src/scenes/game/input/joystick/JoystickView.tsx +++ b/apps/client/src/scenes/game/input/joystick/JoystickView.tsx @@ -3,7 +3,7 @@ * ジョイスティックの見た目だけを描画するコンポーネント * 入力処理は持たず,受け取った座標情報をもとにUIを描く */ -import type { Point } from "./joystick.types"; +import type { Point } from "./common"; /** 表示に必要な座標と状態 */ type Props = { diff --git a/apps/client/src/scenes/game/input/joystick/common/index.ts b/apps/client/src/scenes/game/input/joystick/common/index.ts new file mode 100644 index 0000000..2c0838b --- /dev/null +++ b/apps/client/src/scenes/game/input/joystick/common/index.ts @@ -0,0 +1,5 @@ +/** 共有型を再公開する */ +export type { NormalizedInput, Point } from './joystick.types'; + +/** 共有定数を再公開する */ +export { MAX_DIST } from './joystick.constants'; diff --git a/apps/client/src/scenes/game/input/joystick/common/joystick.constants.ts b/apps/client/src/scenes/game/input/joystick/common/joystick.constants.ts new file mode 100644 index 0000000..88b2d7b --- /dev/null +++ b/apps/client/src/scenes/game/input/joystick/common/joystick.constants.ts @@ -0,0 +1,8 @@ +/** + * joystick.constants + * ジョイスティック入力に関する定数をまとめる + * 共有する半径の既定値などを定義する + */ + +/** UI側と共有する最大半径の既定値 */ +export const MAX_DIST = 60; diff --git a/apps/client/src/scenes/game/input/joystick/common/joystick.types.ts b/apps/client/src/scenes/game/input/joystick/common/joystick.types.ts new file mode 100644 index 0000000..d55577e --- /dev/null +++ b/apps/client/src/scenes/game/input/joystick/common/joystick.types.ts @@ -0,0 +1,11 @@ +/** + * joystick.types + * ジョイスティック入力で使う型をまとめる + * 座標や入力の共通表現を定義する + */ + +/** 2D座標の簡易型 */ +export type Point = { x: number; y: number }; + +/** 正規化された入力ベクトル */ +export type NormalizedInput = { x: number; y: number }; diff --git a/apps/client/src/scenes/game/input/joystick/joystick.constants.ts b/apps/client/src/scenes/game/input/joystick/joystick.constants.ts deleted file mode 100644 index 88b2d7b..0000000 --- a/apps/client/src/scenes/game/input/joystick/joystick.constants.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * joystick.constants - * ジョイスティック入力に関する定数をまとめる - * 共有する半径の既定値などを定義する - */ - -/** UI側と共有する最大半径の既定値 */ -export const MAX_DIST = 60; diff --git a/apps/client/src/scenes/game/input/joystick/joystick.types.ts b/apps/client/src/scenes/game/input/joystick/joystick.types.ts deleted file mode 100644 index d55577e..0000000 --- a/apps/client/src/scenes/game/input/joystick/joystick.types.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * joystick.types - * ジョイスティック入力で使う型をまとめる - * 座標や入力の共通表現を定義する - */ - -/** 2D座標の簡易型 */ -export type Point = { x: number; y: number }; - -/** 正規化された入力ベクトル */ -export type NormalizedInput = { x: number; y: number }; diff --git a/apps/client/src/scenes/game/input/joystick/useJoystick.ts b/apps/client/src/scenes/game/input/joystick/useJoystick.ts index c514fbc..50021da 100644 --- a/apps/client/src/scenes/game/input/joystick/useJoystick.ts +++ b/apps/client/src/scenes/game/input/joystick/useJoystick.ts @@ -5,8 +5,9 @@ */ import { useCallback, useState } from "react"; import type React from "react"; -import { MAX_DIST } from "./joystick.constants"; -import type { NormalizedInput, Point } from "./joystick.types"; +import { MAX_DIST } from "./common"; +import { computeJoystick } from "./JoystickModel"; +import type { NormalizedInput, Point } from "./common"; /** フックに渡す設定 */ type Props = { @@ -59,19 +60,9 @@ const point = getClientPoint(e); if (!point) return null; - const dx = point.x - center.x; - const dy = point.y - center.y; - const dist = Math.sqrt(dx * dx + dy * dy); - const angle = Math.atan2(dy, dx); - - const limitedDist = Math.min(dist, radius); - const offsetX = Math.cos(angle) * limitedDist; - const offsetY = Math.sin(angle) * limitedDist; - const normalizedX = offsetX / radius; - const normalizedY = offsetY / radius; - - setKnobOffset({ x: offsetX, y: offsetY }); - return { x: normalizedX, y: normalizedY }; + const computed = computeJoystick(center, point, radius); + setKnobOffset(computed.knobOffset); + return computed.normalized; }, [isMoving, center.x, center.y, radius] );