지난번에 canvas를 사용해서 캐릭터 이동을 구현해봤었다.
이번에는 라이브러리를 사용해서 캐릭터 이동을 구현해보자~!~
vertex shader
: 정점의 모양과 위치를 계산fragment shader
: fragment의 각 픽셀에 대한 색상을 계산Application
: PixiJS를 사용해 간편하게 그릴 수 있도록 renderer, stage 등을 만드는 역할stage
: 화면에 그려질 요소들을 가지고 있는 도화지renderer
: stage를 받아서 그리는 역할Sprite
: 화면에 렌더링되는 object로 texture에 기반을 두고 만들어진다Texture
: 이미지를 나타내는 정보를 저장한다yarn add pixi.js
canvas만 사용할때는 canvas 태그를 선언해줬는데, pixiJS를 사용하기 위해서는 Application을 선언해줘야했다.
import { Application, Sprite } from "pixi.js";
import { createRef, useEffect, KeyboardEvent } from "react";
const app = new Application({
backgroundColor: 0x000000,
width: window.innerWidth,
height: window.innerHeight,
});
const backgroundImage = Sprite.from("/map.png");
const characterImage = Sprite.from("/character_ArrowDown.png");
app.stage.addChild(backgroundImage);
app.stage.addChild(characterImage);
function Map() {
const canvasRef = createRef<HTMLDivElement>();
useEffect(() => {
canvasRef.current?.appendChild(app.view);
app.start();
return () => {
app.stop();
};
}, []);
useEffect(() => {
backgroundImage.position.set(-900, -900);
}, []);
const handleKeyDown = (e: KeyboardEvent) => {
// 캐릭터 이동!
};
return <div ref={canvasRef} onKeyDown={handleKeyDown} tabIndex={0}></div>;
}
export default Map;
위 코드는 PixiJS를 사용해서 map.png
, character_ArrowDown.png
라는 이미지를 띄우는 코드이다.
const app = new Application({
backgroundColor: 0x000000,
width: window.innerWidth,
height: window.innerHeight,
});
const backgroundImage = Sprite.from("/map.png");
const characterImage = Sprite.from("/character_ArrowDown.png");
app.stage.addChild(backgroundImage);
app.stage.addChild(characterImage);
앞에서 말했다시피 Application을 먼저 선언해주고 canvas에 올라갈 이미지를 Sprite object를 선언한다. Application에서는 width와 height를 설정해주지 않으면 default로 800x800으로 설정된다. Sprite에서는 from(source)
를 사용해서 원하는 화면을 띄울 수 있다.
source는 위와 같은 type을 허용한다고 한다.
useEffect(() => {
canvasRef.current?.appendChild(app.view);
app.start();
return () => {
app.stop();
};
}, []);
view
는 renderer의 canvas element를 리턴해준다. 따라서 app.view
를 canvasRef에 appendChild해주면 div 태그 내에 canvas 태그가 생성되어있는 것을 볼 수 있다.
start()
는 render를 시작한다는 의미, stop()
은 render를 끝낸다는 의미이다. 없어도 문제없이 돌아갔지만 혹시나 의도적으로 render를 멈춰야하는 로직이 필요할때 사용할 수 있을 것 같아서 우선 넣어뒀다.
useEffect(() => {
BackgroundImage.position.set(-900, -900);
}, [BackgroundImage]);
Sprite의 position은 defalt가 (0, 0)이라 원하는 위치에 이미지를 그려주려면 렌더링될때 position을 설정해주어야한다. 추가로 Sprite.position.set(x, y)
는 Sprite.x = x; Sprite.y = y
로 대체할 수 있다.
여기까지 하면 canvas에 이미지가 그려지는걸 볼 수 있다! 그런데
이렇게 묘하게 기분나쁜 여백이 있었다.
알아보니까 canvas의 default display가 inline-block
으로 설정되어있어서 그렇다고 한다. inline-block
은 태그들을 가로로 정렬해주는데 태그들 사이에 enter가 들어가게 되면서 font-size에 의한 글자 폭을 가져서 여백이 생성되게 된다.
결국 block
으로 display를 수정해주니까 여백이 사라졌다.
캐릭터 움직이는건 canvas에서 움직이는 것과 크게 다르지 않았다. 그런데 Pixi를 사용했을때 편한 점은 drawImage를 호출해줄 필요가 없다는 점이었다. 캐릭터가 추가되거나 움직일때 계속해서 drawImage를 해줄 필요가 없이 Sprite를 add해주거나 position을 조정해주면 알아서 draw를 해준다.
const handleKeyDown = (e: KeyboardEvent) => {
if (
e.key !== "ArrowDown" &&
e.key !== "ArrowUp" &&
e.key !== "ArrowLeft" &&
e.key !== "ArrowRight"
)
return;
setCharacterPosition({ ...characterPosition, dir: e.key });
let cnt = 0,
requestId;
const animation = () => {
setCharacterPosition((prevCharacter) => {
return {
...prevCharacter,
x: prevCharacter.x + MOVE_LENGTH[e.key].x / 10,
y: prevCharacter.y + MOVE_LENGTH[e.key].y / 10,
};
});
setBackgroundPosition((prevBackground) => {
return {
...prevBackground,
x: prevBackground.x + -MOVE_LENGTH[e.key].x / 10,
y: prevBackground.y + -MOVE_LENGTH[e.key].y / 10,
};
});
requestId = requestAnimationFrame(animation);
cnt++;
if (cnt === 10) cancelAnimationFrame(requestId);
};
requestAnimationFrame(animation);
};
handleKeyDown()
을 다음과 같이 수정하면 캐릭터가 이동하는 로직이 완성된다. 생략된 부분을 설명하자면 캐릭터의 절대적인 좌표를 저장하는 characterPosition
state를 선언해줬고, 배경의 좌표를 이동시키기 위한 좌표 backgroundPosition
state를 선언해줬다.
if (
e.key !== "ArrowDown" &&
e.key !== "ArrowUp" &&
e.key !== "ArrowLeft" &&
e.key !== "ArrowRight"
)
return;
KeyboardEvent
는 key를 속성으로 가지고 있다. 사용자가 누른 key의 value를 리턴하게 되는데, 여기서 방향키는 위와 같은 네 가지 이름을 가지고 있다. IE/Edge를 위해서는 Up, Down 등의 key도 고려해야 한다고 하는데 우선은,,,
setCharacterPosition({ ...characterPosition, dir: e.key });
캐릭터가 이동하는 방향에 따라 다른 이미지를 띄워주기 위해서는 direction 상태가 필요해서 다음과 같이 저장해줬다. (더 섬세한 캐릭터 움직임 이미지를 위해서는 연속된 이미지를 저장해서 보이는 이미지를 조정해주는 것 같은데 다음에 도전해볼것!!!!)
let cnt = 0, requestId;
const animation = () => {
setCharacterPosition((prevCharacter) => {
return {
...prevCharacter,
x: prevCharacter.x + MOVE_LENGTH[e.key].x / 10,
y: prevCharacter.y + MOVE_LENGTH[e.key].y / 10,
};
});
setBackgroundPosition((prevBackground) => {
return {
...prevBackground,
x: prevBackground.x + -MOVE_LENGTH[e.key].x / 10,
y: prevBackground.y + -MOVE_LENGTH[e.key].y / 10,
};
});
requestId = requestAnimationFrame(animation);
cnt++;
if (cnt === 10) cancelAnimationFrame(requestId);
};
requestAnimationFrame(animation);
여기서 MOVE_LENGTH
는 입력된 key에 따라 x좌표는 얼만큼, y좌표는 얼만큼 움직여야하는지 정의해놓은 것이다. 예를 들어서 ArrowLeft
가 입력되면 character는 { x: -40, y: 0 }
만큼 움직이게 되고, background는 character의 반대로 움직이게 보여져야하므로 { x: 40, y: 0 }
으로 움직이게 된다.
canvas로 구현할 때는 setInterval로 캐릭터의 자연스러운 움직임을 구현했는데, 이번에는 requestAnimationFrame
을 사용해보았다.
cancelAnimationFrame
으로 requestAnimationFrame
을 취소할 수 있다.움직여야하는 거리를 10만큼 나눠서 움직이게 만들어 애니메이션을 수행하게 해주었다.
뾰로롱
다음에는 socket.io을 사용해서 여러명의 캐릭터를 움직이게 만들어볼거다.
참고자료
https://webglfundamentals.org/webgl/lessons/ko/
https://pixijs.download/dev/docs/PIXI.html
https://developer.mozilla.org/ko/docs/Web/API/Window/requestAnimationFrame
https://blog.logrocket.com/getting-started-pixijs-react-create-canvas/
すごいウェブサイトを見つけました! https://geometrydashjp.com/ です!ジオメトリーダッシュのファンメイドバージョンです。音楽とリズムが組み合わさったゲームで、リズミカルな障害物が並ぶレベルをキャラクターで進んでいくのがとてもワクワクします。アクションプラットフォームゲームが好きな方は、ぜひチェックしてみてください!
🎯 まち針ゲームをプレイしよう - 精密性とタイミングが試される中毒性のある無料ブラウザゲーム!91以上のレベルに挑戦しよう。今すぐ https://coreballjp.com/ でプレイ 🎮
I just found this great website: https://raadsrtest.net/ It offers the RAADS-R test, a self-report questionnaire for those 16 and older to assess autism spectrum traits. It helps identify the presence and severity of traits related to autism spectrum disorder (ASD). It's a really useful tool. If you or someone you know might need such an assessment, do check it out.