Newer
Older
PixelPaintWar / apps / client / src / scenes / game / input / joystick / hooks / useJoystickController.ts
/**
 * useJoystickController
 * 入力イベントとジョイスティック計算結果の仲介を担うフック
 * useJoystickState の出力を受けて onInput 通知と終了時リセット通知を統一する
 */
import { useCallback, 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";

/** 入力イベントと通知処理を仲介するフック */
export const useJoystickController = ({
  onInput,
  maxDist,
}: UseJoystickControllerProps): UseJoystickControllerReturn => {
  const {
    isMoving,
    center,
    knobOffset,
    radius,
    handleStart,
    handleMove: baseHandleMove,
    handleEnd: baseHandleEnd,
    reset: baseReset,
  } = useJoystickState({ maxDist });

  const lastEmittedRef = useRef<NormalizedInput | null>(null);

  const emitInput = useCallback(
    (normalized: NormalizedInput) => {
      onInput(normalized.x, normalized.y);
    },
    [onInput],
  );

  const handleMove = useCallback(
    (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(
    (e: JoystickPointerEvent) => {
      baseHandleEnd(e);

      if (JOYSTICK_SEND_ZERO_ON_END) {
        emitInput({ x: 0, y: 0 });
        lastEmittedRef.current = { x: 0, y: 0 };
        return;
      }

      lastEmittedRef.current = null;
    },
    [baseHandleEnd, emitInput],
  );

  const reset = useCallback(() => {
    baseReset();
    emitInput({ x: 0, y: 0 });
    lastEmittedRef.current = { x: 0, y: 0 };
  }, [baseReset, emitInput]);

  return {
    isMoving,
    center,
    knobOffset,
    radius,
    handleStart,
    handleMove,
    handleEnd,
    reset,
  };
};