Newer
Older
PixelPaintWar / apps / client / src / scenes / game / input / joystick / useJoystickController.ts
/**
 * useJoystickController
 * 入力イベントとジョイスティック計算結果の仲介を担うフック
 * 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';

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

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

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

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