diff --git a/apps/client/src/scenes/game/input/joystick/JoystickInputPresenter.tsx b/apps/client/src/scenes/game/input/joystick/JoystickInputPresenter.tsx
index cc49572..e3fec49 100644
--- a/apps/client/src/scenes/game/input/joystick/JoystickInputPresenter.tsx
+++ b/apps/client/src/scenes/game/input/joystick/JoystickInputPresenter.tsx
@@ -6,6 +6,7 @@
import { useJoystickController } from "./useJoystickController";
import { JoystickView } from "./JoystickView";
import type { UseJoystickInputPresenterProps } from "./common";
+import { useEffect } from "react";
/** 入力と表示状態の橋渡しを行う */
export const JoystickInputPresenter = ({
@@ -21,15 +22,24 @@
handleStart,
handleMove,
handleEnd,
+ reset,
} = useJoystickController({ onInput, maxDist });
+ useEffect(() => {
+ if (isEnabled) {
+ return;
+ }
+
+ reset();
+ }, [isEnabled, reset]);
+
return (
void;
handleMove: (e: JoystickPointerEvent) => NormalizedInput | null;
handleEnd: (e: JoystickPointerEvent) => void;
+ reset: () => void;
};
/** useJoystickController に渡す入力設定型 */
@@ -46,6 +47,7 @@
handleStart: (e: JoystickPointerEvent) => void;
handleMove: (e: JoystickPointerEvent) => void;
handleEnd: (e: JoystickPointerEvent) => void;
+ reset: () => void;
};
/** JoystickInputPresenter に渡す入力設定型 */
diff --git a/apps/client/src/scenes/game/input/joystick/useJoystickController.ts b/apps/client/src/scenes/game/input/joystick/useJoystickController.ts
index e85fb02..70ce011 100644
--- a/apps/client/src/scenes/game/input/joystick/useJoystickController.ts
+++ b/apps/client/src/scenes/game/input/joystick/useJoystickController.ts
@@ -30,6 +30,7 @@
handleStart,
handleMove: baseHandleMove,
handleEnd: baseHandleEnd,
+ reset: baseReset,
} = useJoystickState({ maxDist });
const lastEmittedRef = useRef
(null);
@@ -75,6 +76,12 @@
[baseHandleEnd, emitInput],
);
+ const reset = useCallback(() => {
+ baseReset();
+ emitInput({ x: 0, y: 0 });
+ lastEmittedRef.current = { x: 0, y: 0 };
+ }, [baseReset, emitInput]);
+
return {
isMoving,
center,
@@ -83,5 +90,6 @@
handleStart,
handleMove,
handleEnd,
+ reset,
};
};
diff --git a/apps/client/src/scenes/game/input/joystick/useJoystickState.ts b/apps/client/src/scenes/game/input/joystick/useJoystickState.ts
index b5199d9..b9e8ee2 100644
--- a/apps/client/src/scenes/game/input/joystick/useJoystickState.ts
+++ b/apps/client/src/scenes/game/input/joystick/useJoystickState.ts
@@ -3,7 +3,7 @@
* ジョイスティック入力状態の管理と入力ハンドラの提供を担うフック
* UI描画に必要な中心点,ノブ位置,半径を保持する
*/
-import { useCallback, useRef, useState } from "react";
+import { useCallback, useEffect, useRef, useState } from "react";
import { JOYSTICK_DEADZONE, MAX_DIST } from "./common";
import { computeJoystick } from "./JoystickModel";
import type {
@@ -26,8 +26,26 @@
const [center, setCenter] = useState({ x: 0, y: 0 });
const [knobOffset, setKnobOffset] = useState({ x: 0, y: 0 });
const activePointerIdRef = useRef(null);
+ const activePointerTargetRef = useRef(null);
const radius = maxDist ?? MAX_DIST;
+ const reset = useCallback(() => {
+ const pointerId = activePointerIdRef.current;
+ const pointerTarget = activePointerTargetRef.current;
+ if (
+ pointerId !== null &&
+ pointerTarget &&
+ pointerTarget.hasPointerCapture(pointerId)
+ ) {
+ pointerTarget.releasePointerCapture(pointerId);
+ }
+
+ activePointerIdRef.current = null;
+ activePointerTargetRef.current = null;
+ setIsMoving(false);
+ setKnobOffset({ x: 0, y: 0 });
+ }, []);
+
// 入力開始時の基準座標をセットする
const handleStart = useCallback((e: JoystickPointerEvent) => {
if (activePointerIdRef.current !== null) return;
@@ -37,6 +55,7 @@
if (point.x > window.innerWidth / 2) return;
activePointerIdRef.current = e.pointerId;
+ activePointerTargetRef.current = e.currentTarget;
e.currentTarget.setPointerCapture(e.pointerId);
setCenter(point);
setKnobOffset({ x: 0, y: 0 });
@@ -68,17 +87,34 @@
);
// 入力終了時に状態をリセットする
- const handleEnd = useCallback((e: JoystickPointerEvent) => {
- if (activePointerIdRef.current !== e.pointerId) return;
+ const handleEnd = useCallback(
+ (e: JoystickPointerEvent) => {
+ if (activePointerIdRef.current !== e.pointerId) return;
- if (e.currentTarget.hasPointerCapture(e.pointerId)) {
- e.currentTarget.releasePointerCapture(e.pointerId);
- }
+ reset();
+ },
+ [reset],
+ );
- activePointerIdRef.current = null;
- setIsMoving(false);
- setKnobOffset({ x: 0, y: 0 });
- }, []);
+ useEffect(() => {
+ const handleWindowBlur = () => {
+ reset();
+ };
+
+ const handleVisibilityChange = () => {
+ if (document.visibilityState === "hidden") {
+ reset();
+ }
+ };
+
+ window.addEventListener("blur", handleWindowBlur);
+ document.addEventListener("visibilitychange", handleVisibilityChange);
+
+ return () => {
+ window.removeEventListener("blur", handleWindowBlur);
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
+ };
+ }, [reset]);
return {
isMoving,
@@ -88,5 +124,6 @@
handleStart,
handleMove,
handleEnd,
+ reset,
};
};