230110 항해99 65일차 키보드로 캐릭터 움직이게 만들기

요니링 컴터 공부즁·2023년 1월 22일
0
post-custom-banner
  1. 캐릭터가 움직일 canvas를 그리고, 키보드 이벤트와 캐릭터를 따로 만들어둔 클래스에 연결한다.
// src/components/Canvas.tsx
import useCanvas from "../hooks/useCanvas";
import Character from "../character/Character";
import { useEffect } from "react";

const Canvas = () => {
  let character: Character | null = null;
  const canvasRef = useCanvas((canvas) => {
    canvas.width = 1500;
    canvas.height = 700;
    canvas.style.background = "pink";
    character = new Character(canvas);
    document.addEventListener("keydown", character.handleArrowKeyDown());
  });

  useEffect(() => {
    return () => {
      character &&
        document.removeEventListener("keydown", character.handleArrowKeyDown());
    };
  }, []);

  return (
    <div>
      <canvas ref={canvasRef} style={{ position: "absolute" }} />
    </div>
  );
};

export default Canvas;
// src/hooks/useCanvas.ts
import { useRef, useEffect } from "react";

const useCanvas = (setCanvas: (canvas: HTMLCanvasElement) => void) => {
  const canvasRef = useRef<HTMLCanvasElement>(null);

  useEffect(() => {
    const canvas = canvasRef.current;
    canvas && setCanvas(canvas);
  }, []);

  return canvasRef;
};

export default useCanvas;
  1. 나는 이미지가 하나밖에 없어서 하나만 넣어뒀는데, 방향에 따라 다른 이미지를 보여주고 싶으면 이런식으로 이미지 객체를 형성하면 된다.
// src/graphics/CharacterImages.ts
import heayoun from "./images/heayoun.png";

const init: { [key: string]: HTMLImageElement } = {};

const imageSrc = {
  heayoun,
};

const CharacterImages = Object.entries(imageSrc).reduce(
  (images, [key, src]) => {
    const image = new Image();
    image.src = src;
    images[key] = image;
    return images;
  },
  init
);

export default CharacterImages;
  1. 캔버스를 지웠다가, 그렸다가 하면서 캐릭터가 이동하는 것처럼 보이게 만든다.
// src/graphics/Character.ts
import throttle from "../util/throttle";
import CharacterImages from "./CharacterImages";

interface Position {
  x: number;
  y: number;
}

enum Direction {
  DOWN = 0,
  UP = 1,
  LEFT = 2,
  RIGHT = 3,
}

const SIZE = 64;

class Character {
  private canvas: HTMLCanvasElement;
  private ctx: CanvasRenderingContext2D | null = null;
  private position: Position = { x: 0, y: 0 };
  private direction: number = Direction.DOWN;

  constructor(canvas: HTMLCanvasElement) {
    this.canvas = canvas;
    this.ctx = this.canvas.getContext("2d");
    this.runAnimationFrame();
  }

  private runAnimationFrame() {
    this.draw();
    requestAnimationFrame(this.runAnimationFrame.bind(this));
  }

  private draw() {
    const { x, y } = this.position;
    const image = this.getImageByDirection(0, false);

    if (!this.ctx || !image) {
      return;
    }

    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    this.ctx.drawImage(image, x, y, SIZE, SIZE);
  }

  private getImageByDirection(direction: number, isWalking: boolean) {
    const { heayoun } = CharacterImages;

    switch (direction) {
      default:
        return heayoun;
    }
  }

  handleArrowKeyDown() {
    const distance = SIZE;
    const ArrowKeys = [
      {
        code: "38",
        string: "ArrowUp",
        movement: { x: 0, y: -distance },
        isMoveable: () => this.position.y > 0,
      },
      {
        code: "40",
        string: "ArrowDown",
        movement: { x: 0, y: distance },
        isMoveable: () => this.position.y < this.canvas.height - SIZE,
      },
      {
        code: "39",
        string: "ArrowRight",
        movement: { x: distance, y: 0 },
        isMoveable: () => this.position.x < this.canvas.width - SIZE,
      },
      {
        code: "37",
        string: "ArrowLeft",
        movement: { x: -distance, y: 0 },
        isMoveable: () => this.position.x > 0,
      },
    ];

    const handler = throttle((e: KeyboardEvent) => {
      for (let i = 0; i < ArrowKeys.length; i++) {
        const { code, string, movement, isMoveable } = ArrowKeys[i];
        if ([code.toString(), string].includes(e.key) && isMoveable()) {
          this.position.x += movement.x;
          this.position.y += movement.y;
        }
      }
    }, 500);

    return (e: KeyboardEvent) => handler(e);
  }
}

export default Character;
  1. 키보드를 꾹 누르고 있으면 캐릭터가 너무 빠르게 이동해서 일정 속도로 이동하게 만들기 위해 throttle 함수를 설정해준다.
// src/util/throttle.ts
function throttle(func: Function, delay: number = 1000) {
  let timer: NodeJS.Timeout | null = null;

  return (...args: unknown[]) => {
    if (timer) {
      return;
    }
    func(...args);
    timer = setTimeout(() => {
      timer = null;
    }, delay);
  };
}

export default throttle;

  • 진행중인 프로젝트에서 움직이는 캐릭터를 그려야되는데, 어떤 식으로 표현하면 좋을지 고민해보기 위해 예제를 보며 연습했다.
  • 예제에서 클래스형으로 돼 있어서 그대로 따라했는데, 나는 클래스를 안배워서 익숙치 않으니 함수형으로 바꾸는게 목표!
  • 그리고 키보드 이벤트가 아닌 일정 데이터에 맞게 이동하도록 커스터마이징 해봐야겠당!

참조:
React + Canvas 캐릭터 이동 구현기

post-custom-banner

0개의 댓글