diff --git a/apps/client/src/scenes/game/input/joystick/JoystickInputPresenter.tsx b/apps/client/src/scenes/game/input/joystick/JoystickInputPresenter.tsx index 6ce42aa..e1db3b9 100644 --- a/apps/client/src/scenes/game/input/joystick/JoystickInputPresenter.tsx +++ b/apps/client/src/scenes/game/input/joystick/JoystickInputPresenter.tsx @@ -7,9 +7,6 @@ import { JoystickView } from "./JoystickView"; import type { UseJoystickInputPresenterProps } from "./common"; -/** 入力半径の既定値を外部から参照できるように再公開 */ -export { MAX_DIST } from "./common"; - /** 入力と表示状態の橋渡しを行う */ export const JoystickInputPresenter = ({ onInput, maxDist }: UseJoystickInputPresenterProps) => { const { isMoving, center, knobOffset, radius, handleStart, handleMove, handleEnd } = diff --git a/apps/client/src/scenes/game/input/joystick/common/index.ts b/apps/client/src/scenes/game/input/joystick/common/index.ts index ce854ff..2333f85 100644 --- a/apps/client/src/scenes/game/input/joystick/common/index.ts +++ b/apps/client/src/scenes/game/input/joystick/common/index.ts @@ -21,8 +21,11 @@ JOYSTICK_BASE_BG_COLOR, JOYSTICK_BASE_BORDER_COLOR, JOYSTICK_BASE_BORDER_WIDTH, + JOYSTICK_DEADZONE, JOYSTICK_KNOB_BG_COLOR, JOYSTICK_KNOB_SHADOW, JOYSTICK_KNOB_SIZE, + JOYSTICK_MIN_MOVEMENT_DELTA, + JOYSTICK_SEND_ZERO_ON_END, 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 index 3645788..5d87d53 100644 --- a/apps/client/src/scenes/game/input/joystick/common/joystick.constants.ts +++ b/apps/client/src/scenes/game/input/joystick/common/joystick.constants.ts @@ -7,6 +7,15 @@ /** UI側と共有する最大半径の既定値 */ export const MAX_DIST = 60; +/** 微小入力をゼロ扱いにするデッドゾーン閾値 */ +export const JOYSTICK_DEADZONE = 0.08; + +/** 連続送信を間引く最小移動量閾値 */ +export const JOYSTICK_MIN_MOVEMENT_DELTA = 0.02; + +/** 入力終了時にゼロ入力を送信するかどうかの方針 */ +export const JOYSTICK_SEND_ZERO_ON_END = true; + /** ジョイスティックベースの背景色 */ export const JOYSTICK_BASE_BG_COLOR = 'rgba(255, 255, 255, 0.1)'; diff --git a/apps/client/src/scenes/game/input/joystick/useJoystickController.ts b/apps/client/src/scenes/game/input/joystick/useJoystickController.ts index c371f7e..be2f033 100644 --- a/apps/client/src/scenes/game/input/joystick/useJoystickController.ts +++ b/apps/client/src/scenes/game/input/joystick/useJoystickController.ts @@ -4,12 +4,14 @@ * useJoystickState の出力を受けて onInput 通知と終了時リセット通知を統一する */ import { useCallback } from 'react'; +import { useRef } from 'react'; import type { JoystickPointerEvent, NormalizedInput, UseJoystickControllerProps, UseJoystickControllerReturn, } from './common'; +import { JOYSTICK_MIN_MOVEMENT_DELTA, JOYSTICK_SEND_ZERO_ON_END } from './common'; import { useJoystickState } from './useJoystickState'; /** 入力イベントと通知処理を仲介するフック */ @@ -27,6 +29,8 @@ handleEnd: baseHandleEnd, } = useJoystickState({ maxDist }); + const lastEmittedRef = useRef(null); + const emitInput = useCallback( (normalized: NormalizedInput) => { onInput(normalized.x, normalized.y); @@ -38,14 +42,31 @@ (e: JoystickPointerEvent) => { const normalized = baseHandleMove(e); if (!normalized) return; + + const last = lastEmittedRef.current; + if (last) { + const delta = Math.hypot(normalized.x - last.x, normalized.y - last.y); + if (delta < JOYSTICK_MIN_MOVEMENT_DELTA) { + return; + } + } + emitInput(normalized); + lastEmittedRef.current = normalized; }, [baseHandleMove, emitInput] ); const handleEnd = useCallback(() => { baseHandleEnd(); - emitInput({ x: 0, y: 0 }); + + if (JOYSTICK_SEND_ZERO_ON_END) { + emitInput({ x: 0, y: 0 }); + lastEmittedRef.current = { x: 0, y: 0 }; + return; + } + + lastEmittedRef.current = null; }, [baseHandleEnd, emitInput]); return { diff --git a/apps/client/src/scenes/game/input/joystick/useJoystickState.ts b/apps/client/src/scenes/game/input/joystick/useJoystickState.ts index 261a127..7f38616 100644 --- a/apps/client/src/scenes/game/input/joystick/useJoystickState.ts +++ b/apps/client/src/scenes/game/input/joystick/useJoystickState.ts @@ -4,7 +4,7 @@ * UI描画に必要な中心点,ノブ位置,半径を保持する */ import { useCallback, useState } from 'react'; -import { MAX_DIST } from './common'; +import { JOYSTICK_DEADZONE, MAX_DIST } from './common'; import { computeJoystick } from './JoystickModel'; import type { JoystickPointerEvent, @@ -49,6 +49,13 @@ if (!point) return null; const computed = computeJoystick(center, point, radius); + + const magnitude = Math.hypot(computed.normalized.x, computed.normalized.y); + if (magnitude < JOYSTICK_DEADZONE) { + setKnobOffset({ x: 0, y: 0 }); + return { x: 0, y: 0 }; + } + setKnobOffset(computed.knobOffset); return computed.normalized; },