Three.js(R3F)でWASD操作を行う

# 初めに
最近毎日ここであさかつしてます。
https://stacklive.jp/
朝に勉強するの結構はかどります。

このコードですが、あとでスマホ対応とゲームパッド対応する予定です。

# 実行結果

![layer1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/493888/f56a6f92-b092-9ba9-ea2b-5960012e9daf.gif)


# ソースコード
```react
import { useFrame, useThree } from "@react-three/fiber"
import { useEffect, useRef } from "react";
import { Mesh, Vector2, Vector3 } from "three";

/**
 * ActionキーのリストEnum
 */
export const EActionKey = {
    KeyW       : "forward",
    KeyS       : "backward",
    KeyA       : "left",
    KeyD       : "right",
    Space      : "jump",
    ShiftLeft  : "dash",
    ShiftRight : "dash",
    Shift      : "dash"
}

/**
 * 入力イベント / 入力の型
 */
interface HTMLElementEvent<T extends HTMLElement> extends Event {
    target : T;
    code   : string;
}
export interface IInputMovement {
    forward   : boolean;
    backward  : boolean;
    left      : boolean;
    right     : boolean;
    jump      : boolean;
    dash      : boolean;
    prevDrag? : Vector2; // カメラ向きに利用（あとで実装）
    currDrag? : Vector2; // カメラ向きに利用（あとで実装）
}
export const useInputControl = () => {

    const moveKeyFromCode = (key: string) => EActionKey[key];
    const movement = useRef<IInputMovement>({
        forward   : false,
        backward  : false,
        left      : false,
        right     : false,
        jump      : false,
        dash      : false,
        prevDrag  : null,
        currDrag  : null
    });

    useEffect(() => {
        /**
         * キーボード対応
         */
        const handleKeyDown = (e: HTMLElementEvent<HTMLInputElement>) => {
            movement.current[moveKeyFromCode(e.code)] = true;
        } 
        const handleKeyUp = (e: HTMLElementEvent<HTMLInputElement>) => {
            movement.current[moveKeyFromCode(e.code)] = false;
        };
        document.addEventListener("keydown", handleKeyDown);
        document.addEventListener("keyup", handleKeyUp);

        /**
         * スマホ対応 (あとで実装)
         */
        // handleTouch

        /**
         * ゲームパッド対応 (あとで実装)
         */
        // handleGamePad
        return () => {
            document.removeEventListener("keydown", handleKeyDown);
            document.removeEventListener("keyup", handleKeyUp);
        }
    }, []);

    return movement.current;
}

/**
 * キーボード操作Component
 */
export const TestInputControlComponent = () => {
    const ref = useRef<Mesh>();
    const input = useInputControl();// キーボード入力を取得
    const { camera } = useThree();

    useEffect(() => {
        // 初期カメラの位置を物体の後ろに移動
        camera.position.copy(new Vector3(0, 1, -3));
        camera.lookAt(new Vector3(0, 0, 0));
    }, [])

    useFrame((_, delta) => {
        const { forward, backward, left, right, dash, jump } = input;
        if ( forward || backward || right || left || jump ){
            const velocity = new Vector3(0, 0, 0);
            // それぞれの方向に入力がある分だけ、ベクトルを加算していく
            if (forward && !backward) velocity.add(new Vector3(0, 0, 1))
            else if (!forward && backward) velocity.add(new Vector3(0, 0, -1))
            if (right && !left) velocity.add(new Vector3(-1, 0, 0))
            else if (!right && left) velocity.add(new Vector3(1, 0, 0))
            if (jump) velocity.add(new Vector3(0, 1, 0))

            // フレーム時間でかけた値を位置ベクトルに加算する
            if (ref.current){
                const mag = 1.5; // 速度倍率を1.5倍に早くする
                ref.current.position.add(velocity.multiplyScalar(delta).multiplyScalar(mag));
            }
        }
    });
 
    return (
        <mesh ref={ref}>
            <sphereBufferGeometry/>
            <meshStandardMaterial color={0xff5500}/>
        </mesh>
    )
}
```