- 캐릭터가 움직일 canvas를 그리고, 키보드 이벤트와 캐릭터를 따로 만들어둔 클래스에 연결한다.
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;
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;
- 나는 이미지가 하나밖에 없어서 하나만 넣어뒀는데, 방향에 따라 다른 이미지를 보여주고 싶으면 이런식으로 이미지 객체를 형성하면 된다.
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;
- 캔버스를 지웠다가, 그렸다가 하면서 캐릭터가 이동하는 것처럼 보이게 만든다.
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;
- 키보드를 꾹 누르고 있으면 캐릭터가 너무 빠르게 이동해서 일정 속도로 이동하게 만들기 위해 throttle 함수를 설정해준다.
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 캐릭터 이동 구현기