Newer
Older
PixelPaintWar / apps / client / src / scenes / game / input / joystick / useJoystick.ts
/**
 * useJoystick
 * ジョイスティック入力を受け取り,座標計算と正規化ベクトルの出力を行うフック
 * UI描画に必要な中心点・ノブ位置・半径も合わせて提供する
 */
import { useCallback, useState } from "react";
import type React from "react";
import { MAX_DIST } from "./common";
import { computeJoystick } from "./JoystickModel";
import type { NormalizedInput, Point } from "./common";

/** フックに渡す設定 */
type Props = {
  maxDist?: number;
};

/** フックが返すUI向けの状態とハンドラ */
type UseJoystickReturn = {
  isMoving: boolean;
  center: Point;
  knobOffset: Point;
  radius: number;
  handleStart: (e: React.TouchEvent | React.MouseEvent) => void;
  handleMove: (e: React.TouchEvent | React.MouseEvent) => NormalizedInput | null;
  handleEnd: () => void;
};

/** タッチとマウスからクライアント座標を共通化して取得 */
const getClientPoint = (e: React.TouchEvent | React.MouseEvent): Point | null => {
  if ("touches" in e) {
    const touch = e.touches[0];
    if (!touch) return null;
    return { x: touch.clientX, y: touch.clientY };
  }

  return { x: e.clientX, y: e.clientY };
};

/** 正規化ベクトルの出力とUI用の座標を提供するフック */
export const useJoystick = ({ maxDist }: Props): UseJoystickReturn => {
  const [isMoving, setIsMoving] = useState(false);
  const [center, setCenter] = useState<Point>({ x: 0, y: 0 });
  const [knobOffset, setKnobOffset] = useState<Point>({ x: 0, y: 0 });
  const radius = maxDist ?? MAX_DIST;

  // 入力開始時の基準座標をセットする
  const handleStart = useCallback((e: React.TouchEvent | React.MouseEvent) => {
    const point = getClientPoint(e);
    if (!point) return;

    setCenter(point);
    setKnobOffset({ x: 0, y: 0 });
    setIsMoving(true);
  }, []);

  // 入力座標からベクトルを計算し,半径でクランプして正規化する
  const handleMove = useCallback(
    (e: React.TouchEvent | React.MouseEvent) => {
      if (!isMoving) return null;
      const point = getClientPoint(e);
      if (!point) return null;

      const computed = computeJoystick(center, point, radius);
      setKnobOffset(computed.knobOffset);
      return computed.normalized;
    },
    [isMoving, center.x, center.y, radius]
  );

  // 入力終了時に状態をリセットする
  const handleEnd = useCallback(() => {
    setIsMoving(false);
    setKnobOffset({ x: 0, y: 0 });
  }, []);

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