class로 만들어보는 간단한 2D 게임 (배웠으면 써먹어야하니까)
자바스크립트만으로도 간단한 게임을 만들 수 있다.
- HTMl, CSS, JS만으로 만든 게임 중 해볼만한 명작: candy box, Synergism
우리는 크롬 공룡 게임을 만들어 보자.
게임 개발 원리와 구현 방법
- 화면에 네모, 원 그릴 수 있어야 함 (네모 위에 공룡, 선인장 등 그림을 입힘)
-> <canvas>태그를 이용하면 자바스크립트로 간단하게 네모를 그릴 수 있다.
- 프레임마다 코드실행 가능해야 (애니메이션을 위해)
예로 들어서 공룡이 방향키를 누르면 60px 이동해야 한다. 근데 순간이동하면 안되겠지. 서서히 60px로 이동시켜야 한다. 그러면 1초에 60번 정도 +1px 해주면 된다. 그래서 1초에 60번 움직이도록 자바스크립트 코드를 짤 줄 알아야 한다. 브라우저 기본 함수가 있는데 그걸 쓰면 된다.
- collision check 할 수 있어야
충돌 시 처리를 해줘야 한다. 예로 들면 움직이다가 적과 충돌하면 내 체력이 -1 깎인다던지. 별건 앙니고 중학교 수학 정도만 할 줄 알면 된다.
네모 그리는 법
🕹️index.html
<body>
<canvas id="canvas"></canvas>
<script src="main.js"></script>
</body>
🕹️main.js - canvas 생성하는 기본 코드
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
canvas.width = window.innerWidth - 100;
canvas.height = window.innerHeight - 100;
공룡 만들기
ctx.fillStyle = "green";
ctx.fillRect(10, 10, 100, 100); // 위에서 10, 오른쪽에서 10 위치에서 100x100 사이즈의 네모 그리기
근데 이렇게 먼저 하지 말고, **등장 캐릭터의 속성부터 object 자료에 정리해두면 편리**하다.
var dino = {
// 공룡 등장 좌표
x: 10,
y: 200,
// 공룡 폭과 높이
width: 50,
height: 50,
// 공룡 그리는 함수
draw() {
ctx.fillStyle = "green";
ctx.fillRect(this.x, this.y, this.width, this.height);
},
};
dino.draw(); // 공룡 그리기 완료!
장애물 만들기
장애물들은 각각 width, height가 다른 특성을 가지고 있다. 비슷한 object가 많이 필요할 것 같으니 class로 만들어서 객체 생성 기계를 만들자.
class Cactus {
constructor() {
this.x = 500;
this.y = 200;
this.width = 50;
this.height = 50;
}
draw() {
ctx.fillStyle = "red";
ctx.fillRect(this.x, this.y, this.weight, this.height);
}
}
var cactus = new Cactus();
cactus.draw();
애니메이션 만들기
만약 100px만큼 이동하게 하려면, dino의 x값을 수정하면 된다.
dino.x = 100;
근데 이건 순간이동이지 애니메이션이 아니다. 애니메이션화하려면 1초에 60번 x++해줘야 한다.
// 1초에 60번 해당 코드를 실행해야 함
dino.x += 1;
(게임 개발을 본격적으로하려면 개임 개발 라이브러리를 쓰는게 나음) 근데 우린 쌩으로 개발해볼거다.
function executeByFrame() {
requestAnimationFrame(executeByFrame);
dino.x++; // 1초에 dino를 1px씩 움직이고
dino.draw(); // 이를 그림
}
executeByFrame();
- **requestAnimationFrame**(callback) : 기본 자바스크립트 함수. **1초에 60번 실행**함. 프레임마다 실행할거를 콜백함수 안에 넣는다.
1초에 60번 실행되어서 화면에 네모가 우측으로 커진다. 우측으로 커지는 게 아니라 실은 dino가 이동하면서 그리고 있는거다.
근데 왜 잔상이 남지? 잔상을 안남게하려면 그리기 전에 캔버스를 지우면 된다.
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height); // ⬅️
dino.x++;
dino.draw();
}
장애물도 애니메이션 생성하기
근데 크롬 게임은 공룡이 움직이는게 아니라, 장애물이 공룡쪽으로 다가간다. 그래서 공룡을 굳이 움직이게 할 필요는 없다.
장애물을 생성해보자.
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
var cactus = new Cactus(); // ⬅️
cactus.draw(); // ⬅️
dino.draw();
}
장애물은 2~3초에 하나씩 나와야 한다. 그래서 2~3초에 한 번 이걸 실행하도록 하자.
**게임 세상에선 항상 '초'단위로 움직이는 게 아니라 '프레임'단위로 움직인다.**
타이머를 생성하자. 1초에 60 프레임으로 진행이 된다, 하면 타이머에 조건문을 건다.
var timer = 0;
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 120 === 0) {
var cactus = new Cactus();
cactus.draw();
}
dino.draw();
}
장애물 여러개 관리하기
하지만 장애물은 하나가 아니라 여러개 존재한다. 그럼 장애물을 만들 때마다 array에 담아서 보관하면 관리하기 편하다.
var timer = 0;
var cactuses = [];
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 120 === 0) {
var cactus = new Cactus();
cactuses.push(cactus);
}
cactuses.forEach((a) => {
a.x--; // 1초에 60px 왼쪽으로 이동
a.draw();
});
dino.draw();
}
(정리) 120프레임마다 {장애물} 예쁘게 생성하고 array에 집어넣음. 그리고 마지막에 array에 있던거 다 draw()해줌.
Recap
- 네모 그리기
- 그릴 캐릭터의 정보 미리 object 자료로 정리하면 편함
- 코드를 1초에 60번 실행하면 애니메이션 만들 수 있음 (requestAnimationFrame)
- 120프레임마다 장애물도 소환해서 array에 보관했음
화면에서 없어져서 필요없어진 장애물 제거하기
cactuses.forEach((a) => {
// x 좌표가 0미만이면 제거하기
a.draw();
});
cactuses.forEach((a, i, o) => {
if (a.x < 0) {
o.splice(i, 1);
}
a.x--;
a.draw();
});
- 첫 번째 파라미터(a) : currentValue. 처리할 현재 요소 (=요소 값)
- 두 번째 파라미터(i) : index. 처리할 현재 요소의 인덱스 (=요소 인덱스)
- 세 번째 파라미터(o) : array. forEach()를 호출한 배열 (=**순회 중인 배열**)
스페이스바 누르면 점프 -> eventListener
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 120 === 0) {
var cactus = new Cactus();
cactuses.push(cactus);
}
cactuses.forEach((a, i, o) => {
if (a.x < 0) {
o.splice(i, 1);
}
a.x--;
a.draw();
});
if (jumping === true) { // ⬅️
dino.y--;
}
dino.draw();
}
executeByFrame();
var jumping = false;
document.addEventListener("keydown", function (e) {
if (e.code === "Space") {
jumping = true;
}
});
근데 이렇게 짜면 공룡이 무한히 하늘로 승천한다. y를 무한히 가는 게 아니라, 제한을 둬야한다. 100프레임 지나면 dino.y-- 점프를 그만하고 ++해준다던지.
jump 타이머를 만들자.
var timer = 0;
var cactuses = [];
var jumpTimer = 0; // ⬅️
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 120 === 0) {
var cactus = new Cactus();
cactuses.push(cactus);
}
cactuses.forEach((a, i, o) => {
if (a.x < 0) {
o.splice(i, 1);
}
a.x--;
a.draw();
});
if (jumping === true) {
dino.y--;
jumpTimer++; // ⬅️
}
if (jumpTimer > 100) { // ⬅️
jumping = false;
}
dino.draw();
}
공룡을 다시 내려오도록 하자.
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 120 === 0) {
var cactus = new Cactus();
cactuses.push(cactus);
}
cactuses.forEach((a, i, o) => {
if (a.x < 0) {
o.splice(i, 1);
}
a.x--;
a.draw();
});
if (jumping === true) {
dino.y--;
jumpTimer++;
} else { // ⬅️
if (jumpTimer > 0) {
dino.y++;
jumpTimer--;
}
}
if (jumpTimer > 100) {
jumping = false;
}
dino.draw();
}
collision detection
- 충돌체크하기 (충돌하면 뭔가 일어나야 함)
공룡과 장애물의 충돌을 어떻게 감지할 수 있을까?

장애물의 x좌표 - 공룡의 x좌표 < 0 면 충돌이 일어났다고 볼 수 있다.

근데 이건 x축이 만났을 때만 가정한거고, y축에서 만날 수도 있을거다.

그래서 x축과 y축이 모두 만났을 때 '충돌했다'고 볼 수 있다.
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 120 === 0) {
var cactus = new Cactus();
cactuses.push(cactus);
}
cactuses.forEach((a, i, o) => {
if (a.x < 0) {
o.splice(i, 1);
} else {
a.x--;
isCollapse(dino, a); // ⬅️ 충돌 체크는 여기서 해야
a.draw();
}
});
if (jumping === true) {
dino.y--;
jumpTimer++;
} else {
if (jumpTimer > 0) {
dino.y++;
jumpTimer--;
}
}
if (jumpTimer > 100) {
jumping = false;
}
dino.draw();
}
executeByFrame();
// 충돌 확인
function isCollapse(dino, cactus) {
var diffX = cactus.x - (dino.x + dino.width);
var diffY = cactus.y - (dino.y + dino.height);
}
var jumping = false;
document.addEventListener("keydown", function (e) {
if (e.code === "Space") {
jumping = true;
}
});
cactus의 x좌표 - 공룡의 x좌표를 구해야하는데, 공룡의 x좌표는 (dino.x + dino.width)다.

y의 경우에도 공룡의 y좌표는 (dino.y + dino.height)다.
충돌시 게임 중단
var animation;
function executeByFrame() {
animation = requestAnimationFrame(executeByFrame);
...
}
function isCollapse(dino, cactus) {
var diffX = cactus.x - (dino.x + dino.width);
var diffY = cactus.y - (dino.y + dino.height);
if (diffX < 0 && diffY < 0) { // 충돌 - 게임 중단
ctx.clearRect(0, 0, canvas.width, canvas.height);
cancelAnimationFrame(animation);
}
}
- **cancelAnimationFrame** : 애니메이션 중단
네모 대신 이미지 넣기
- 장애물
장애물을 그려보자. 먼저 이미지를 가져온다.
var img1 = new Image();
img1.src = "cactus.png";
그리고 장애물을 그리는 함수를
draw() {
ctx.fillStyle = "red";
ctx.fillRect(this.x, this.y, this.weight, this.height);
}
아래 코드로 변경한다.
draw() {
ctx.drawImage(img1, this.x, this.y);
}
- 공룡
var img2 = new Image();
img2.src = "dinosaur.png";
var dino = {
// 공룡 등장 좌표
x: 10,
y: 200,
// 공룡 폭과 높이
width: 50,
height: 50,
// 공룡 그리는 함수
draw() {
ctx.drawImage(img2, this.x, this.y, this.width, this.height); // ⬅️
},
};
dino.draw(); // 공룡 그리기 완료!
결과

🕹️전체 코드 (main.js)
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
canvas.width = window.innerWidth - 100;
canvas.height = window.innerHeight - 100;
var img2 = new Image();
img2.src = "dinosaur.png";
var dino = {
// 공룡 등장 좌표
x: 10,
y: 200,
// 공룡 폭과 높이
width: 50,
height: 50,
// 공룡 그리는 함수
draw() {
ctx.drawImage(img2, this.x, this.y, this.width, this.height);
},
};
dino.draw(); // 공룡 그리기 완료!
var img1 = new Image();
img1.src = "cactus.png";
class Cactus {
constructor() {
this.x = 600;
this.y = 200;
this.width = 50;
this.height = 50;
}
draw() {
ctx.drawImage(img1, this.x, this.y);
}
}
var timer = 0;
var cactuses = [];
var jumpTimer = 0;
var animation;
function executeByFrame() {
animation = requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 200 === 0) {
var cactus = new Cactus();
cactuses.push(cactus);
}
timer++;
cactuses.forEach((a, i, o) => {
if (a.x < 0) {
o.splice(i, 1);
}
a.x--;
isCollapse(dino, a); // 충돌 체크는 여기서 해야
a.draw();
});
if (jumping === true) {
dino.y--;
jumpTimer++;
} else if (jumping === false) {
if (dino.y < 200) {
jumpTimer = 0;
dino.y++;
}
}
if (jumpTimer > 100) {
jumping = false;
}
dino.draw();
}
executeByFrame();
// 충돌 확인
function isCollapse(dino, cactus) {
var diffX = cactus.x - (dino.x + dino.width);
var diffY = cactus.y - (dino.y + dino.height);
// 충돌 - 게임 중단
if (diffX < 0 && diffY < 0) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
cancelAnimationFrame(animation);
}
}
var jumping = false;
document.addEventListener("keydown", function (e) {
if (e.code === "Space") {
jumping = true;
}
});
class로 만들어보는 간단한 2D 게임 (배웠으면 써먹어야하니까)
자바스크립트만으로도 간단한 게임을 만들 수 있다.
- HTMl, CSS, JS만으로 만든 게임 중 해볼만한 명작: candy box, Synergism
우리는 크롬 공룡 게임을 만들어 보자.
게임 개발 원리와 구현 방법
- 화면에 네모, 원 그릴 수 있어야 함 (네모 위에 공룡, 선인장 등 그림을 입힘)
-> <canvas>태그를 이용하면 자바스크립트로 간단하게 네모를 그릴 수 있다.
- 프레임마다 코드실행 가능해야 (애니메이션을 위해)
예로 들어서 공룡이 방향키를 누르면 60px 이동해야 한다. 근데 순간이동하면 안되겠지. 서서히 60px로 이동시켜야 한다. 그러면 1초에 60번 정도 +1px 해주면 된다. 그래서 1초에 60번 움직이도록 자바스크립트 코드를 짤 줄 알아야 한다. 브라우저 기본 함수가 있는데 그걸 쓰면 된다.
- collision check 할 수 있어야
충돌 시 처리를 해줘야 한다. 예로 들면 움직이다가 적과 충돌하면 내 체력이 -1 깎인다던지. 별건 앙니고 중학교 수학 정도만 할 줄 알면 된다.
네모 그리는 법
🕹️index.html
<body>
<canvas id="canvas"></canvas>
<script src="main.js"></script>
</body>
🕹️main.js - canvas 생성하는 기본 코드
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
canvas.width = window.innerWidth - 100;
canvas.height = window.innerHeight - 100;
공룡 만들기
ctx.fillStyle = "green";
ctx.fillRect(10, 10, 100, 100); // 위에서 10, 오른쪽에서 10 위치에서 100x100 사이즈의 네모 그리기
근데 이렇게 먼저 하지 말고, **등장 캐릭터의 속성부터 object 자료에 정리해두면 편리**하다.
var dino = {
// 공룡 등장 좌표
x: 10,
y: 200,
// 공룡 폭과 높이
width: 50,
height: 50,
// 공룡 그리는 함수
draw() {
ctx.fillStyle = "green";
ctx.fillRect(this.x, this.y, this.width, this.height);
},
};
dino.draw(); // 공룡 그리기 완료!
장애물 만들기
장애물들은 각각 width, height가 다른 특성을 가지고 있다. 비슷한 object가 많이 필요할 것 같으니 class로 만들어서 객체 생성 기계를 만들자.
class Cactus {
constructor() {
this.x = 500;
this.y = 200;
this.width = 50;
this.height = 50;
}
draw() {
ctx.fillStyle = "red";
ctx.fillRect(this.x, this.y, this.weight, this.height);
}
}
var cactus = new Cactus();
cactus.draw();
애니메이션 만들기
만약 100px만큼 이동하게 하려면, dino의 x값을 수정하면 된다.
dino.x = 100;
근데 이건 순간이동이지 애니메이션이 아니다. 애니메이션화하려면 1초에 60번 x++해줘야 한다.
// 1초에 60번 해당 코드를 실행해야 함
dino.x += 1;
(게임 개발을 본격적으로하려면 개임 개발 라이브러리를 쓰는게 나음) 근데 우린 쌩으로 개발해볼거다.
function executeByFrame() {
requestAnimationFrame(executeByFrame);
dino.x++; // 1초에 dino를 1px씩 움직이고
dino.draw(); // 이를 그림
}
executeByFrame();
- **requestAnimationFrame**(callback) : 기본 자바스크립트 함수. **1초에 60번 실행**함. 프레임마다 실행할거를 콜백함수 안에 넣는다.
1초에 60번 실행되어서 화면에 네모가 우측으로 커진다. 우측으로 커지는 게 아니라 실은 dino가 이동하면서 그리고 있는거다.
근데 왜 잔상이 남지? 잔상을 안남게하려면 그리기 전에 캔버스를 지우면 된다.
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height); // ⬅️
dino.x++;
dino.draw();
}
장애물도 애니메이션 생성하기
근데 크롬 게임은 공룡이 움직이는게 아니라, 장애물이 공룡쪽으로 다가간다. 그래서 공룡을 굳이 움직이게 할 필요는 없다.
장애물을 생성해보자.
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
var cactus = new Cactus(); // ⬅️
cactus.draw(); // ⬅️
dino.draw();
}
장애물은 2~3초에 하나씩 나와야 한다. 그래서 2~3초에 한 번 이걸 실행하도록 하자.
**게임 세상에선 항상 '초'단위로 움직이는 게 아니라 '프레임'단위로 움직인다.**
타이머를 생성하자. 1초에 60 프레임으로 진행이 된다, 하면 타이머에 조건문을 건다.
var timer = 0;
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 120 === 0) {
var cactus = new Cactus();
cactus.draw();
}
dino.draw();
}
장애물 여러개 관리하기
하지만 장애물은 하나가 아니라 여러개 존재한다. 그럼 장애물을 만들 때마다 array에 담아서 보관하면 관리하기 편하다.
var timer = 0;
var cactuses = [];
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 120 === 0) {
var cactus = new Cactus();
cactuses.push(cactus);
}
cactuses.forEach((a) => {
a.x--; // 1초에 60px 왼쪽으로 이동
a.draw();
});
dino.draw();
}
(정리) 120프레임마다 {장애물} 예쁘게 생성하고 array에 집어넣음. 그리고 마지막에 array에 있던거 다 draw()해줌.
Recap
- 네모 그리기
- 그릴 캐릭터의 정보 미리 object 자료로 정리하면 편함
- 코드를 1초에 60번 실행하면 애니메이션 만들 수 있음 (requestAnimationFrame)
- 120프레임마다 장애물도 소환해서 array에 보관했음
화면에서 없어져서 필요없어진 장애물 제거하기
cactuses.forEach((a) => {
// x 좌표가 0미만이면 제거하기
a.draw();
});
cactuses.forEach((a, i, o) => {
if (a.x < 0) {
o.splice(i, 1);
}
a.x--;
a.draw();
});
- 첫 번째 파라미터(a) : currentValue. 처리할 현재 요소 (=요소 값)
- 두 번째 파라미터(i) : index. 처리할 현재 요소의 인덱스 (=요소 인덱스)
- 세 번째 파라미터(o) : array. forEach()를 호출한 배열 (=**순회 중인 배열**)
스페이스바 누르면 점프 -> eventListener
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 120 === 0) {
var cactus = new Cactus();
cactuses.push(cactus);
}
cactuses.forEach((a, i, o) => {
if (a.x < 0) {
o.splice(i, 1);
}
a.x--;
a.draw();
});
if (jumping === true) { // ⬅️
dino.y--;
}
dino.draw();
}
executeByFrame();
var jumping = false;
document.addEventListener("keydown", function (e) {
if (e.code === "Space") {
jumping = true;
}
});
근데 이렇게 짜면 공룡이 무한히 하늘로 승천한다. y를 무한히 가는 게 아니라, 제한을 둬야한다. 100프레임 지나면 dino.y-- 점프를 그만하고 ++해준다던지.
jump 타이머를 만들자.
var timer = 0;
var cactuses = [];
var jumpTimer = 0; // ⬅️
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 120 === 0) {
var cactus = new Cactus();
cactuses.push(cactus);
}
cactuses.forEach((a, i, o) => {
if (a.x < 0) {
o.splice(i, 1);
}
a.x--;
a.draw();
});
if (jumping === true) {
dino.y--;
jumpTimer++; // ⬅️
}
if (jumpTimer > 100) { // ⬅️
jumping = false;
}
dino.draw();
}
공룡을 다시 내려오도록 하자.
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 120 === 0) {
var cactus = new Cactus();
cactuses.push(cactus);
}
cactuses.forEach((a, i, o) => {
if (a.x < 0) {
o.splice(i, 1);
}
a.x--;
a.draw();
});
if (jumping === true) {
dino.y--;
jumpTimer++;
} else { // ⬅️
if (jumpTimer > 0) {
dino.y++;
jumpTimer--;
}
}
if (jumpTimer > 100) {
jumping = false;
}
dino.draw();
}
collision detection
- 충돌체크하기 (충돌하면 뭔가 일어나야 함)
공룡과 장애물의 충돌을 어떻게 감지할 수 있을까?

장애물의 x좌표 - 공룡의 x좌표 < 0 면 충돌이 일어났다고 볼 수 있다.

근데 이건 x축이 만났을 때만 가정한거고, y축에서 만날 수도 있을거다.

그래서 x축과 y축이 모두 만났을 때 '충돌했다'고 볼 수 있다.
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 120 === 0) {
var cactus = new Cactus();
cactuses.push(cactus);
}
cactuses.forEach((a, i, o) => {
if (a.x < 0) {
o.splice(i, 1);
} else {
a.x--;
isCollapse(dino, a); // ⬅️ 충돌 체크는 여기서 해야
a.draw();
}
});
if (jumping === true) {
dino.y--;
jumpTimer++;
} else {
if (jumpTimer > 0) {
dino.y++;
jumpTimer--;
}
}
if (jumpTimer > 100) {
jumping = false;
}
dino.draw();
}
executeByFrame();
// 충돌 확인
function isCollapse(dino, cactus) {
var diffX = cactus.x - (dino.x + dino.width);
var diffY = cactus.y - (dino.y + dino.height);
}
var jumping = false;
document.addEventListener("keydown", function (e) {
if (e.code === "Space") {
jumping = true;
}
});
cactus의 x좌표 - 공룡의 x좌표를 구해야하는데, 공룡의 x좌표는 (dino.x + dino.width)다.

y의 경우에도 공룡의 y좌표는 (dino.y + dino.height)다.
충돌시 게임 중단
var animation;
function executeByFrame() {
animation = requestAnimationFrame(executeByFrame);
...
}
function isCollapse(dino, cactus) {
var diffX = cactus.x - (dino.x + dino.width);
var diffY = cactus.y - (dino.y + dino.height);
if (diffX < 0 && diffY < 0) { // 충돌 - 게임 중단
ctx.clearRect(0, 0, canvas.width, canvas.height);
cancelAnimationFrame(animation);
}
}
- **cancelAnimationFrame** : 애니메이션 중단
네모 대신 이미지 넣기
- 장애물
장애물을 그려보자. 먼저 이미지를 가져온다.
var img1 = new Image();
img1.src = "cactus.png";
그리고 장애물을 그리는 함수를
draw() {
ctx.fillStyle = "red";
ctx.fillRect(this.x, this.y, this.weight, this.height);
}
아래 코드로 변경한다.
draw() {
ctx.drawImage(img1, this.x, this.y);
}
- 공룡
var img2 = new Image();
img2.src = "dinosaur.png";
var dino = {
// 공룡 등장 좌표
x: 10,
y: 200,
// 공룡 폭과 높이
width: 50,
height: 50,
// 공룡 그리는 함수
draw() {
ctx.drawImage(img2, this.x, this.y, this.width, this.height); // ⬅️
},
};
dino.draw(); // 공룡 그리기 완료!
결과

🕹️전체 코드 (main.js)
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
canvas.width = window.innerWidth - 100;
canvas.height = window.innerHeight - 100;
var img2 = new Image();
img2.src = "dinosaur.png";
var dino = {
// 공룡 등장 좌표
x: 10,
y: 200,
// 공룡 폭과 높이
width: 50,
height: 50,
// 공룡 그리는 함수
draw() {
ctx.drawImage(img2, this.x, this.y, this.width, this.height);
},
};
dino.draw(); // 공룡 그리기 완료!
var img1 = new Image();
img1.src = "cactus.png";
class Cactus {
constructor() {
this.x = 600;
this.y = 200;
this.width = 50;
this.height = 50;
}
draw() {
ctx.drawImage(img1, this.x, this.y);
}
}
var timer = 0;
var cactuses = [];
var jumpTimer = 0;
var animation;
function executeByFrame() {
animation = requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 200 === 0) {
var cactus = new Cactus();
cactuses.push(cactus);
}
timer++;
cactuses.forEach((a, i, o) => {
if (a.x < 0) {
o.splice(i, 1);
}
a.x--;
isCollapse(dino, a); // 충돌 체크는 여기서 해야
a.draw();
});
if (jumping === true) {
dino.y--;
jumpTimer++;
} else if (jumping === false) {
if (dino.y < 200) {
jumpTimer = 0;
dino.y++;
}
}
if (jumpTimer > 100) {
jumping = false;
}
dino.draw();
}
executeByFrame();
// 충돌 확인
function isCollapse(dino, cactus) {
var diffX = cactus.x - (dino.x + dino.width);
var diffY = cactus.y - (dino.y + dino.height);
// 충돌 - 게임 중단
if (diffX < 0 && diffY < 0) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
cancelAnimationFrame(animation);
}
}
var jumping = false;
document.addEventListener("keydown", function (e) {
if (e.code === "Space") {
jumping = true;
}
});
class로 만들어보는 간단한 2D 게임 (배웠으면 써먹어야하니까)
자바스크립트만으로도 간단한 게임을 만들 수 있다.
- HTMl, CSS, JS만으로 만든 게임 중 해볼만한 명작: candy box, Synergism
우리는 크롬 공룡 게임을 만들어 보자.
게임 개발 원리와 구현 방법
- 화면에 네모, 원 그릴 수 있어야 함 (네모 위에 공룡, 선인장 등 그림을 입힘)
-> <canvas>태그를 이용하면 자바스크립트로 간단하게 네모를 그릴 수 있다.
- 프레임마다 코드실행 가능해야 (애니메이션을 위해)
예로 들어서 공룡이 방향키를 누르면 60px 이동해야 한다. 근데 순간이동하면 안되겠지. 서서히 60px로 이동시켜야 한다. 그러면 1초에 60번 정도 +1px 해주면 된다. 그래서 1초에 60번 움직이도록 자바스크립트 코드를 짤 줄 알아야 한다. 브라우저 기본 함수가 있는데 그걸 쓰면 된다.
- collision check 할 수 있어야
충돌 시 처리를 해줘야 한다. 예로 들면 움직이다가 적과 충돌하면 내 체력이 -1 깎인다던지. 별건 앙니고 중학교 수학 정도만 할 줄 알면 된다.
네모 그리는 법
🕹️index.html
<body>
<canvas id="canvas"></canvas>
<script src="main.js"></script>
</body>
🕹️main.js - canvas 생성하는 기본 코드
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
canvas.width = window.innerWidth - 100;
canvas.height = window.innerHeight - 100;
공룡 만들기
ctx.fillStyle = "green";
ctx.fillRect(10, 10, 100, 100); // 위에서 10, 오른쪽에서 10 위치에서 100x100 사이즈의 네모 그리기
근데 이렇게 먼저 하지 말고, **등장 캐릭터의 속성부터 object 자료에 정리해두면 편리**하다.
var dino = {
// 공룡 등장 좌표
x: 10,
y: 200,
// 공룡 폭과 높이
width: 50,
height: 50,
// 공룡 그리는 함수
draw() {
ctx.fillStyle = "green";
ctx.fillRect(this.x, this.y, this.width, this.height);
},
};
dino.draw(); // 공룡 그리기 완료!
장애물 만들기
장애물들은 각각 width, height가 다른 특성을 가지고 있다. 비슷한 object가 많이 필요할 것 같으니 class로 만들어서 객체 생성 기계를 만들자.
class Cactus {
constructor() {
this.x = 500;
this.y = 200;
this.width = 50;
this.height = 50;
}
draw() {
ctx.fillStyle = "red";
ctx.fillRect(this.x, this.y, this.weight, this.height);
}
}
var cactus = new Cactus();
cactus.draw();
애니메이션 만들기
만약 100px만큼 이동하게 하려면, dino의 x값을 수정하면 된다.
dino.x = 100;
근데 이건 순간이동이지 애니메이션이 아니다. 애니메이션화하려면 1초에 60번 x++해줘야 한다.
// 1초에 60번 해당 코드를 실행해야 함
dino.x += 1;
(게임 개발을 본격적으로하려면 개임 개발 라이브러리를 쓰는게 나음) 근데 우린 쌩으로 개발해볼거다.
function executeByFrame() {
requestAnimationFrame(executeByFrame);
dino.x++; // 1초에 dino를 1px씩 움직이고
dino.draw(); // 이를 그림
}
executeByFrame();
- **requestAnimationFrame**(callback) : 기본 자바스크립트 함수. **1초에 60번 실행**함. 프레임마다 실행할거를 콜백함수 안에 넣는다.
1초에 60번 실행되어서 화면에 네모가 우측으로 커진다. 우측으로 커지는 게 아니라 실은 dino가 이동하면서 그리고 있는거다.
근데 왜 잔상이 남지? 잔상을 안남게하려면 그리기 전에 캔버스를 지우면 된다.
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height); // ⬅️
dino.x++;
dino.draw();
}
장애물도 애니메이션 생성하기
근데 크롬 게임은 공룡이 움직이는게 아니라, 장애물이 공룡쪽으로 다가간다. 그래서 공룡을 굳이 움직이게 할 필요는 없다.
장애물을 생성해보자.
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
var cactus = new Cactus(); // ⬅️
cactus.draw(); // ⬅️
dino.draw();
}
장애물은 2~3초에 하나씩 나와야 한다. 그래서 2~3초에 한 번 이걸 실행하도록 하자.
**게임 세상에선 항상 '초'단위로 움직이는 게 아니라 '프레임'단위로 움직인다.**
타이머를 생성하자. 1초에 60 프레임으로 진행이 된다, 하면 타이머에 조건문을 건다.
var timer = 0;
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 120 === 0) {
var cactus = new Cactus();
cactus.draw();
}
dino.draw();
}
장애물 여러개 관리하기
하지만 장애물은 하나가 아니라 여러개 존재한다. 그럼 장애물을 만들 때마다 array에 담아서 보관하면 관리하기 편하다.
var timer = 0;
var cactuses = [];
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 120 === 0) {
var cactus = new Cactus();
cactuses.push(cactus);
}
cactuses.forEach((a) => {
a.x--; // 1초에 60px 왼쪽으로 이동
a.draw();
});
dino.draw();
}
(정리) 120프레임마다 {장애물} 예쁘게 생성하고 array에 집어넣음. 그리고 마지막에 array에 있던거 다 draw()해줌.
Recap
- 네모 그리기
- 그릴 캐릭터의 정보 미리 object 자료로 정리하면 편함
- 코드를 1초에 60번 실행하면 애니메이션 만들 수 있음 (requestAnimationFrame)
- 120프레임마다 장애물도 소환해서 array에 보관했음
화면에서 없어져서 필요없어진 장애물 제거하기
cactuses.forEach((a) => {
// x 좌표가 0미만이면 제거하기
a.draw();
});
cactuses.forEach((a, i, o) => {
if (a.x < 0) {
o.splice(i, 1);
}
a.x--;
a.draw();
});
- 첫 번째 파라미터(a) : currentValue. 처리할 현재 요소 (=요소 값)
- 두 번째 파라미터(i) : index. 처리할 현재 요소의 인덱스 (=요소 인덱스)
- 세 번째 파라미터(o) : array. forEach()를 호출한 배열 (=**순회 중인 배열**)
스페이스바 누르면 점프 -> eventListener
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 120 === 0) {
var cactus = new Cactus();
cactuses.push(cactus);
}
cactuses.forEach((a, i, o) => {
if (a.x < 0) {
o.splice(i, 1);
}
a.x--;
a.draw();
});
if (jumping === true) { // ⬅️
dino.y--;
}
dino.draw();
}
executeByFrame();
var jumping = false;
document.addEventListener("keydown", function (e) {
if (e.code === "Space") {
jumping = true;
}
});
근데 이렇게 짜면 공룡이 무한히 하늘로 승천한다. y를 무한히 가는 게 아니라, 제한을 둬야한다. 100프레임 지나면 dino.y-- 점프를 그만하고 ++해준다던지.
jump 타이머를 만들자.
var timer = 0;
var cactuses = [];
var jumpTimer = 0; // ⬅️
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 120 === 0) {
var cactus = new Cactus();
cactuses.push(cactus);
}
cactuses.forEach((a, i, o) => {
if (a.x < 0) {
o.splice(i, 1);
}
a.x--;
a.draw();
});
if (jumping === true) {
dino.y--;
jumpTimer++; // ⬅️
}
if (jumpTimer > 100) { // ⬅️
jumping = false;
}
dino.draw();
}
공룡을 다시 내려오도록 하자.
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 120 === 0) {
var cactus = new Cactus();
cactuses.push(cactus);
}
cactuses.forEach((a, i, o) => {
if (a.x < 0) {
o.splice(i, 1);
}
a.x--;
a.draw();
});
if (jumping === true) {
dino.y--;
jumpTimer++;
} else { // ⬅️
if (jumpTimer > 0) {
dino.y++;
jumpTimer--;
}
}
if (jumpTimer > 100) {
jumping = false;
}
dino.draw();
}
collision detection
- 충돌체크하기 (충돌하면 뭔가 일어나야 함)
공룡과 장애물의 충돌을 어떻게 감지할 수 있을까?

장애물의 x좌표 - 공룡의 x좌표 < 0 면 충돌이 일어났다고 볼 수 있다.

근데 이건 x축이 만났을 때만 가정한거고, y축에서 만날 수도 있을거다.

그래서 x축과 y축이 모두 만났을 때 '충돌했다'고 볼 수 있다.
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 120 === 0) {
var cactus = new Cactus();
cactuses.push(cactus);
}
cactuses.forEach((a, i, o) => {
if (a.x < 0) {
o.splice(i, 1);
} else {
a.x--;
isCollapse(dino, a); // ⬅️ 충돌 체크는 여기서 해야
a.draw();
}
});
if (jumping === true) {
dino.y--;
jumpTimer++;
} else {
if (jumpTimer > 0) {
dino.y++;
jumpTimer--;
}
}
if (jumpTimer > 100) {
jumping = false;
}
dino.draw();
}
executeByFrame();
// 충돌 확인
function isCollapse(dino, cactus) {
var diffX = cactus.x - (dino.x + dino.width);
var diffY = cactus.y - (dino.y + dino.height);
}
var jumping = false;
document.addEventListener("keydown", function (e) {
if (e.code === "Space") {
jumping = true;
}
});
cactus의 x좌표 - 공룡의 x좌표를 구해야하는데, 공룡의 x좌표는 (dino.x + dino.width)다.

y의 경우에도 공룡의 y좌표는 (dino.y + dino.height)다.
충돌시 게임 중단
var animation;
function executeByFrame() {
animation = requestAnimationFrame(executeByFrame);
...
}
function isCollapse(dino, cactus) {
var diffX = cactus.x - (dino.x + dino.width);
var diffY = cactus.y - (dino.y + dino.height);
if (diffX < 0 && diffY < 0) { // 충돌 - 게임 중단
ctx.clearRect(0, 0, canvas.width, canvas.height);
cancelAnimationFrame(animation);
}
}
- **cancelAnimationFrame** : 애니메이션 중단
네모 대신 이미지 넣기
- 장애물
장애물을 그려보자. 먼저 이미지를 가져온다.
var img1 = new Image();
img1.src = "cactus.png";
그리고 장애물을 그리는 함수를
draw() {
ctx.fillStyle = "red";
ctx.fillRect(this.x, this.y, this.weight, this.height);
}
아래 코드로 변경한다.
draw() {
ctx.drawImage(img1, this.x, this.y);
}
- 공룡
var img2 = new Image();
img2.src = "dinosaur.png";
var dino = {
// 공룡 등장 좌표
x: 10,
y: 200,
// 공룡 폭과 높이
width: 50,
height: 50,
// 공룡 그리는 함수
draw() {
ctx.drawImage(img2, this.x, this.y, this.width, this.height); // ⬅️
},
};
dino.draw(); // 공룡 그리기 완료!
결과

🕹️전체 코드 (main.js)
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
canvas.width = window.innerWidth - 100;
canvas.height = window.innerHeight - 100;
var img2 = new Image();
img2.src = "dinosaur.png";
var dino = {
// 공룡 등장 좌표
x: 10,
y: 200,
// 공룡 폭과 높이
width: 50,
height: 50,
// 공룡 그리는 함수
draw() {
ctx.drawImage(img2, this.x, this.y, this.width, this.height);
},
};
dino.draw(); // 공룡 그리기 완료!
var img1 = new Image();
img1.src = "cactus.png";
class Cactus {
constructor() {
this.x = 600;
this.y = 200;
this.width = 50;
this.height = 50;
}
draw() {
ctx.drawImage(img1, this.x, this.y);
}
}
var timer = 0;
var cactuses = [];
var jumpTimer = 0;
var animation;
function executeByFrame() {
animation = requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 200 === 0) {
var cactus = new Cactus();
cactuses.push(cactus);
}
timer++;
cactuses.forEach((a, i, o) => {
if (a.x < 0) {
o.splice(i, 1);
}
a.x--;
isCollapse(dino, a); // 충돌 체크는 여기서 해야
a.draw();
});
if (jumping === true) {
dino.y--;
jumpTimer++;
} else if (jumping === false) {
if (dino.y < 200) {
jumpTimer = 0;
dino.y++;
}
}
if (jumpTimer > 100) {
jumping = false;
}
dino.draw();
}
executeByFrame();
// 충돌 확인
function isCollapse(dino, cactus) {
var diffX = cactus.x - (dino.x + dino.width);
var diffY = cactus.y - (dino.y + dino.height);
// 충돌 - 게임 중단
if (diffX < 0 && diffY < 0) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
cancelAnimationFrame(animation);
}
}
var jumping = false;
document.addEventListener("keydown", function (e) {
if (e.code === "Space") {
jumping = true;
}
});
class로 만들어보는 간단한 2D 게임 (배웠으면 써먹어야하니까)
자바스크립트만으로도 간단한 게임을 만들 수 있다.
- HTMl, CSS, JS만으로 만든 게임 중 해볼만한 명작: candy box, Synergism
우리는 크롬 공룡 게임을 만들어 보자.
게임 개발 원리와 구현 방법
- 화면에 네모, 원 그릴 수 있어야 함 (네모 위에 공룡, 선인장 등 그림을 입힘)
-> <canvas>태그를 이용하면 자바스크립트로 간단하게 네모를 그릴 수 있다.
- 프레임마다 코드실행 가능해야 (애니메이션을 위해)
예로 들어서 공룡이 방향키를 누르면 60px 이동해야 한다. 근데 순간이동하면 안되겠지. 서서히 60px로 이동시켜야 한다. 그러면 1초에 60번 정도 +1px 해주면 된다. 그래서 1초에 60번 움직이도록 자바스크립트 코드를 짤 줄 알아야 한다. 브라우저 기본 함수가 있는데 그걸 쓰면 된다.
- collision check 할 수 있어야
충돌 시 처리를 해줘야 한다. 예로 들면 움직이다가 적과 충돌하면 내 체력이 -1 깎인다던지. 별건 앙니고 중학교 수학 정도만 할 줄 알면 된다.
네모 그리는 법
🕹️index.html
<body>
<canvas id="canvas"></canvas>
<script src="main.js"></script>
</body>
🕹️main.js - canvas 생성하는 기본 코드
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
canvas.width = window.innerWidth - 100;
canvas.height = window.innerHeight - 100;
공룡 만들기
ctx.fillStyle = "green";
ctx.fillRect(10, 10, 100, 100); // 위에서 10, 오른쪽에서 10 위치에서 100x100 사이즈의 네모 그리기
근데 이렇게 먼저 하지 말고, **등장 캐릭터의 속성부터 object 자료에 정리해두면 편리**하다.
var dino = {
// 공룡 등장 좌표
x: 10,
y: 200,
// 공룡 폭과 높이
width: 50,
height: 50,
// 공룡 그리는 함수
draw() {
ctx.fillStyle = "green";
ctx.fillRect(this.x, this.y, this.width, this.height);
},
};
dino.draw(); // 공룡 그리기 완료!
장애물 만들기
장애물들은 각각 width, height가 다른 특성을 가지고 있다. 비슷한 object가 많이 필요할 것 같으니 class로 만들어서 객체 생성 기계를 만들자.
class Cactus {
constructor() {
this.x = 500;
this.y = 200;
this.width = 50;
this.height = 50;
}
draw() {
ctx.fillStyle = "red";
ctx.fillRect(this.x, this.y, this.weight, this.height);
}
}
var cactus = new Cactus();
cactus.draw();
애니메이션 만들기
만약 100px만큼 이동하게 하려면, dino의 x값을 수정하면 된다.
dino.x = 100;
근데 이건 순간이동이지 애니메이션이 아니다. 애니메이션화하려면 1초에 60번 x++해줘야 한다.
// 1초에 60번 해당 코드를 실행해야 함
dino.x += 1;
(게임 개발을 본격적으로하려면 개임 개발 라이브러리를 쓰는게 나음) 근데 우린 쌩으로 개발해볼거다.
function executeByFrame() {
requestAnimationFrame(executeByFrame);
dino.x++; // 1초에 dino를 1px씩 움직이고
dino.draw(); // 이를 그림
}
executeByFrame();
- **requestAnimationFrame**(callback) : 기본 자바스크립트 함수. **1초에 60번 실행**함. 프레임마다 실행할거를 콜백함수 안에 넣는다.
1초에 60번 실행되어서 화면에 네모가 우측으로 커진다. 우측으로 커지는 게 아니라 실은 dino가 이동하면서 그리고 있는거다.
근데 왜 잔상이 남지? 잔상을 안남게하려면 그리기 전에 캔버스를 지우면 된다.
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height); // ⬅️
dino.x++;
dino.draw();
}
장애물도 애니메이션 생성하기
근데 크롬 게임은 공룡이 움직이는게 아니라, 장애물이 공룡쪽으로 다가간다. 그래서 공룡을 굳이 움직이게 할 필요는 없다.
장애물을 생성해보자.
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
var cactus = new Cactus(); // ⬅️
cactus.draw(); // ⬅️
dino.draw();
}
장애물은 2~3초에 하나씩 나와야 한다. 그래서 2~3초에 한 번 이걸 실행하도록 하자.
**게임 세상에선 항상 '초'단위로 움직이는 게 아니라 '프레임'단위로 움직인다.**
타이머를 생성하자. 1초에 60 프레임으로 진행이 된다, 하면 타이머에 조건문을 건다.
var timer = 0;
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 120 === 0) {
var cactus = new Cactus();
cactus.draw();
}
dino.draw();
}
장애물 여러개 관리하기
하지만 장애물은 하나가 아니라 여러개 존재한다. 그럼 장애물을 만들 때마다 array에 담아서 보관하면 관리하기 편하다.
var timer = 0;
var cactuses = [];
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 120 === 0) {
var cactus = new Cactus();
cactuses.push(cactus);
}
cactuses.forEach((a) => {
a.x--; // 1초에 60px 왼쪽으로 이동
a.draw();
});
dino.draw();
}
(정리) 120프레임마다 {장애물} 예쁘게 생성하고 array에 집어넣음. 그리고 마지막에 array에 있던거 다 draw()해줌.
Recap
- 네모 그리기
- 그릴 캐릭터의 정보 미리 object 자료로 정리하면 편함
- 코드를 1초에 60번 실행하면 애니메이션 만들 수 있음 (requestAnimationFrame)
- 120프레임마다 장애물도 소환해서 array에 보관했음
화면에서 없어져서 필요없어진 장애물 제거하기
cactuses.forEach((a) => {
// x 좌표가 0미만이면 제거하기
a.draw();
});
cactuses.forEach((a, i, o) => {
if (a.x < 0) {
o.splice(i, 1);
}
a.x--;
a.draw();
});
- 첫 번째 파라미터(a) : currentValue. 처리할 현재 요소 (=요소 값)
- 두 번째 파라미터(i) : index. 처리할 현재 요소의 인덱스 (=요소 인덱스)
- 세 번째 파라미터(o) : array. forEach()를 호출한 배열 (=**순회 중인 배열**)
스페이스바 누르면 점프 -> eventListener
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 120 === 0) {
var cactus = new Cactus();
cactuses.push(cactus);
}
cactuses.forEach((a, i, o) => {
if (a.x < 0) {
o.splice(i, 1);
}
a.x--;
a.draw();
});
if (jumping === true) { // ⬅️
dino.y--;
}
dino.draw();
}
executeByFrame();
var jumping = false;
document.addEventListener("keydown", function (e) {
if (e.code === "Space") {
jumping = true;
}
});
근데 이렇게 짜면 공룡이 무한히 하늘로 승천한다. y를 무한히 가는 게 아니라, 제한을 둬야한다. 100프레임 지나면 dino.y-- 점프를 그만하고 ++해준다던지.
jump 타이머를 만들자.
var timer = 0;
var cactuses = [];
var jumpTimer = 0; // ⬅️
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 120 === 0) {
var cactus = new Cactus();
cactuses.push(cactus);
}
cactuses.forEach((a, i, o) => {
if (a.x < 0) {
o.splice(i, 1);
}
a.x--;
a.draw();
});
if (jumping === true) {
dino.y--;
jumpTimer++; // ⬅️
}
if (jumpTimer > 100) { // ⬅️
jumping = false;
}
dino.draw();
}
공룡을 다시 내려오도록 하자.
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 120 === 0) {
var cactus = new Cactus();
cactuses.push(cactus);
}
cactuses.forEach((a, i, o) => {
if (a.x < 0) {
o.splice(i, 1);
}
a.x--;
a.draw();
});
if (jumping === true) {
dino.y--;
jumpTimer++;
} else { // ⬅️
if (jumpTimer > 0) {
dino.y++;
jumpTimer--;
}
}
if (jumpTimer > 100) {
jumping = false;
}
dino.draw();
}
collision detection
- 충돌체크하기 (충돌하면 뭔가 일어나야 함)
공룡과 장애물의 충돌을 어떻게 감지할 수 있을까?

장애물의 x좌표 - 공룡의 x좌표 < 0 면 충돌이 일어났다고 볼 수 있다.

근데 이건 x축이 만났을 때만 가정한거고, y축에서 만날 수도 있을거다.

그래서 x축과 y축이 모두 만났을 때 '충돌했다'고 볼 수 있다.
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 120 === 0) {
var cactus = new Cactus();
cactuses.push(cactus);
}
cactuses.forEach((a, i, o) => {
if (a.x < 0) {
o.splice(i, 1);
} else {
a.x--;
isCollapse(dino, a); // ⬅️ 충돌 체크는 여기서 해야
a.draw();
}
});
if (jumping === true) {
dino.y--;
jumpTimer++;
} else {
if (jumpTimer > 0) {
dino.y++;
jumpTimer--;
}
}
if (jumpTimer > 100) {
jumping = false;
}
dino.draw();
}
executeByFrame();
// 충돌 확인
function isCollapse(dino, cactus) {
var diffX = cactus.x - (dino.x + dino.width);
var diffY = cactus.y - (dino.y + dino.height);
}
var jumping = false;
document.addEventListener("keydown", function (e) {
if (e.code === "Space") {
jumping = true;
}
});
cactus의 x좌표 - 공룡의 x좌표를 구해야하는데, 공룡의 x좌표는 (dino.x + dino.width)다.

y의 경우에도 공룡의 y좌표는 (dino.y + dino.height)다.
충돌시 게임 중단
var animation;
function executeByFrame() {
animation = requestAnimationFrame(executeByFrame);
...
}
function isCollapse(dino, cactus) {
var diffX = cactus.x - (dino.x + dino.width);
var diffY = cactus.y - (dino.y + dino.height);
if (diffX < 0 && diffY < 0) { // 충돌 - 게임 중단
ctx.clearRect(0, 0, canvas.width, canvas.height);
cancelAnimationFrame(animation);
}
}
- **cancelAnimationFrame** : 애니메이션 중단
네모 대신 이미지 넣기
- 장애물
장애물을 그려보자. 먼저 이미지를 가져온다.
var img1 = new Image();
img1.src = "cactus.png";
그리고 장애물을 그리는 함수를
draw() {
ctx.fillStyle = "red";
ctx.fillRect(this.x, this.y, this.weight, this.height);
}
아래 코드로 변경한다.
draw() {
ctx.drawImage(img1, this.x, this.y);
}
- 공룡
var img2 = new Image();
img2.src = "dinosaur.png";
var dino = {
// 공룡 등장 좌표
x: 10,
y: 200,
// 공룡 폭과 높이
width: 50,
height: 50,
// 공룡 그리는 함수
draw() {
ctx.drawImage(img2, this.x, this.y, this.width, this.height); // ⬅️
},
};
dino.draw(); // 공룡 그리기 완료!
결과

🕹️전체 코드 (main.js)
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
canvas.width = window.innerWidth - 100;
canvas.height = window.innerHeight - 100;
var img2 = new Image();
img2.src = "dinosaur.png";
var dino = {
// 공룡 등장 좌표
x: 10,
y: 200,
// 공룡 폭과 높이
width: 50,
height: 50,
// 공룡 그리는 함수
draw() {
ctx.drawImage(img2, this.x, this.y, this.width, this.height);
},
};
dino.draw(); // 공룡 그리기 완료!
var img1 = new Image();
img1.src = "cactus.png";
class Cactus {
constructor() {
this.x = 600;
this.y = 200;
this.width = 50;
this.height = 50;
}
draw() {
ctx.drawImage(img1, this.x, this.y);
}
}
var timer = 0;
var cactuses = [];
var jumpTimer = 0;
var animation;
function executeByFrame() {
animation = requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 200 === 0) {
var cactus = new Cactus();
cactuses.push(cactus);
}
timer++;
cactuses.forEach((a, i, o) => {
if (a.x < 0) {
o.splice(i, 1);
}
a.x--;
isCollapse(dino, a); // 충돌 체크는 여기서 해야
a.draw();
});
if (jumping === true) {
dino.y--;
jumpTimer++;
} else if (jumping === false) {
if (dino.y < 200) {
jumpTimer = 0;
dino.y++;
}
}
if (jumpTimer > 100) {
jumping = false;
}
dino.draw();
}
executeByFrame();
// 충돌 확인
function isCollapse(dino, cactus) {
var diffX = cactus.x - (dino.x + dino.width);
var diffY = cactus.y - (dino.y + dino.height);
// 충돌 - 게임 중단
if (diffX < 0 && diffY < 0) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
cancelAnimationFrame(animation);
}
}
var jumping = false;
document.addEventListener("keydown", function (e) {
if (e.code === "Space") {
jumping = true;
}
});
class로 만들어보는 간단한 2D 게임 (배웠으면 써먹어야하니까)
자바스크립트만으로도 간단한 게임을 만들 수 있다.
- HTMl, CSS, JS만으로 만든 게임 중 해볼만한 명작: candy box, Synergism
우리는 크롬 공룡 게임을 만들어 보자.
게임 개발 원리와 구현 방법
- 화면에 네모, 원 그릴 수 있어야 함 (네모 위에 공룡, 선인장 등 그림을 입힘)
-> <canvas>태그를 이용하면 자바스크립트로 간단하게 네모를 그릴 수 있다.
- 프레임마다 코드실행 가능해야 (애니메이션을 위해)
예로 들어서 공룡이 방향키를 누르면 60px 이동해야 한다. 근데 순간이동하면 안되겠지. 서서히 60px로 이동시켜야 한다. 그러면 1초에 60번 정도 +1px 해주면 된다. 그래서 1초에 60번 움직이도록 자바스크립트 코드를 짤 줄 알아야 한다. 브라우저 기본 함수가 있는데 그걸 쓰면 된다.
- collision check 할 수 있어야
충돌 시 처리를 해줘야 한다. 예로 들면 움직이다가 적과 충돌하면 내 체력이 -1 깎인다던지. 별건 앙니고 중학교 수학 정도만 할 줄 알면 된다.
네모 그리는 법
🕹️index.html
<body>
<canvas id="canvas"></canvas>
<script src="main.js"></script>
</body>
🕹️main.js - canvas 생성하는 기본 코드
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
canvas.width = window.innerWidth - 100;
canvas.height = window.innerHeight - 100;
공룡 만들기
ctx.fillStyle = "green";
ctx.fillRect(10, 10, 100, 100); // 위에서 10, 오른쪽에서 10 위치에서 100x100 사이즈의 네모 그리기
근데 이렇게 먼저 하지 말고, **등장 캐릭터의 속성부터 object 자료에 정리해두면 편리**하다.
var dino = {
// 공룡 등장 좌표
x: 10,
y: 200,
// 공룡 폭과 높이
width: 50,
height: 50,
// 공룡 그리는 함수
draw() {
ctx.fillStyle = "green";
ctx.fillRect(this.x, this.y, this.width, this.height);
},
};
dino.draw(); // 공룡 그리기 완료!
장애물 만들기
장애물들은 각각 width, height가 다른 특성을 가지고 있다. 비슷한 object가 많이 필요할 것 같으니 class로 만들어서 객체 생성 기계를 만들자.
class Cactus {
constructor() {
this.x = 500;
this.y = 200;
this.width = 50;
this.height = 50;
}
draw() {
ctx.fillStyle = "red";
ctx.fillRect(this.x, this.y, this.weight, this.height);
}
}
var cactus = new Cactus();
cactus.draw();
애니메이션 만들기
만약 100px만큼 이동하게 하려면, dino의 x값을 수정하면 된다.
dino.x = 100;
근데 이건 순간이동이지 애니메이션이 아니다. 애니메이션화하려면 1초에 60번 x++해줘야 한다.
// 1초에 60번 해당 코드를 실행해야 함
dino.x += 1;
(게임 개발을 본격적으로하려면 개임 개발 라이브러리를 쓰는게 나음) 근데 우린 쌩으로 개발해볼거다.
function executeByFrame() {
requestAnimationFrame(executeByFrame);
dino.x++; // 1초에 dino를 1px씩 움직이고
dino.draw(); // 이를 그림
}
executeByFrame();
- **requestAnimationFrame**(callback) : 기본 자바스크립트 함수. **1초에 60번 실행**함. 프레임마다 실행할거를 콜백함수 안에 넣는다.
1초에 60번 실행되어서 화면에 네모가 우측으로 커진다. 우측으로 커지는 게 아니라 실은 dino가 이동하면서 그리고 있는거다.
근데 왜 잔상이 남지? 잔상을 안남게하려면 그리기 전에 캔버스를 지우면 된다.
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height); // ⬅️
dino.x++;
dino.draw();
}
장애물도 애니메이션 생성하기
근데 크롬 게임은 공룡이 움직이는게 아니라, 장애물이 공룡쪽으로 다가간다. 그래서 공룡을 굳이 움직이게 할 필요는 없다.
장애물을 생성해보자.
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
var cactus = new Cactus(); // ⬅️
cactus.draw(); // ⬅️
dino.draw();
}
장애물은 2~3초에 하나씩 나와야 한다. 그래서 2~3초에 한 번 이걸 실행하도록 하자.
**게임 세상에선 항상 '초'단위로 움직이는 게 아니라 '프레임'단위로 움직인다.**
타이머를 생성하자. 1초에 60 프레임으로 진행이 된다, 하면 타이머에 조건문을 건다.
var timer = 0;
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 120 === 0) {
var cactus = new Cactus();
cactus.draw();
}
dino.draw();
}
장애물 여러개 관리하기
하지만 장애물은 하나가 아니라 여러개 존재한다. 그럼 장애물을 만들 때마다 array에 담아서 보관하면 관리하기 편하다.
var timer = 0;
var cactuses = [];
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 120 === 0) {
var cactus = new Cactus();
cactuses.push(cactus);
}
cactuses.forEach((a) => {
a.x--; // 1초에 60px 왼쪽으로 이동
a.draw();
});
dino.draw();
}
(정리) 120프레임마다 {장애물} 예쁘게 생성하고 array에 집어넣음. 그리고 마지막에 array에 있던거 다 draw()해줌.
Recap
- 네모 그리기
- 그릴 캐릭터의 정보 미리 object 자료로 정리하면 편함
- 코드를 1초에 60번 실행하면 애니메이션 만들 수 있음 (requestAnimationFrame)
- 120프레임마다 장애물도 소환해서 array에 보관했음
화면에서 없어져서 필요없어진 장애물 제거하기
cactuses.forEach((a) => {
// x 좌표가 0미만이면 제거하기
a.draw();
});
cactuses.forEach((a, i, o) => {
if (a.x < 0) {
o.splice(i, 1);
}
a.x--;
a.draw();
});
- 첫 번째 파라미터(a) : currentValue. 처리할 현재 요소 (=요소 값)
- 두 번째 파라미터(i) : index. 처리할 현재 요소의 인덱스 (=요소 인덱스)
- 세 번째 파라미터(o) : array. forEach()를 호출한 배열 (=**순회 중인 배열**)
스페이스바 누르면 점프 -> eventListener
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 120 === 0) {
var cactus = new Cactus();
cactuses.push(cactus);
}
cactuses.forEach((a, i, o) => {
if (a.x < 0) {
o.splice(i, 1);
}
a.x--;
a.draw();
});
if (jumping === true) { // ⬅️
dino.y--;
}
dino.draw();
}
executeByFrame();
var jumping = false;
document.addEventListener("keydown", function (e) {
if (e.code === "Space") {
jumping = true;
}
});
근데 이렇게 짜면 공룡이 무한히 하늘로 승천한다. y를 무한히 가는 게 아니라, 제한을 둬야한다. 100프레임 지나면 dino.y-- 점프를 그만하고 ++해준다던지.
jump 타이머를 만들자.
var timer = 0;
var cactuses = [];
var jumpTimer = 0; // ⬅️
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 120 === 0) {
var cactus = new Cactus();
cactuses.push(cactus);
}
cactuses.forEach((a, i, o) => {
if (a.x < 0) {
o.splice(i, 1);
}
a.x--;
a.draw();
});
if (jumping === true) {
dino.y--;
jumpTimer++; // ⬅️
}
if (jumpTimer > 100) { // ⬅️
jumping = false;
}
dino.draw();
}
공룡을 다시 내려오도록 하자.
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 120 === 0) {
var cactus = new Cactus();
cactuses.push(cactus);
}
cactuses.forEach((a, i, o) => {
if (a.x < 0) {
o.splice(i, 1);
}
a.x--;
a.draw();
});
if (jumping === true) {
dino.y--;
jumpTimer++;
} else { // ⬅️
if (jumpTimer > 0) {
dino.y++;
jumpTimer--;
}
}
if (jumpTimer > 100) {
jumping = false;
}
dino.draw();
}
collision detection
- 충돌체크하기 (충돌하면 뭔가 일어나야 함)
공룡과 장애물의 충돌을 어떻게 감지할 수 있을까?

장애물의 x좌표 - 공룡의 x좌표 < 0 면 충돌이 일어났다고 볼 수 있다.

근데 이건 x축이 만났을 때만 가정한거고, y축에서 만날 수도 있을거다.

그래서 x축과 y축이 모두 만났을 때 '충돌했다'고 볼 수 있다.
function executeByFrame() {
requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 120 === 0) {
var cactus = new Cactus();
cactuses.push(cactus);
}
cactuses.forEach((a, i, o) => {
if (a.x < 0) {
o.splice(i, 1);
} else {
a.x--;
isCollapse(dino, a); // ⬅️ 충돌 체크는 여기서 해야
a.draw();
}
});
if (jumping === true) {
dino.y--;
jumpTimer++;
} else {
if (jumpTimer > 0) {
dino.y++;
jumpTimer--;
}
}
if (jumpTimer > 100) {
jumping = false;
}
dino.draw();
}
executeByFrame();
// 충돌 확인
function isCollapse(dino, cactus) {
var diffX = cactus.x - (dino.x + dino.width);
var diffY = cactus.y - (dino.y + dino.height);
}
var jumping = false;
document.addEventListener("keydown", function (e) {
if (e.code === "Space") {
jumping = true;
}
});
cactus의 x좌표 - 공룡의 x좌표를 구해야하는데, 공룡의 x좌표는 (dino.x + dino.width)다.

y의 경우에도 공룡의 y좌표는 (dino.y + dino.height)다.
충돌시 게임 중단
var animation;
function executeByFrame() {
animation = requestAnimationFrame(executeByFrame);
...
}
function isCollapse(dino, cactus) {
var diffX = cactus.x - (dino.x + dino.width);
var diffY = cactus.y - (dino.y + dino.height);
if (diffX < 0 && diffY < 0) { // 충돌 - 게임 중단
ctx.clearRect(0, 0, canvas.width, canvas.height);
cancelAnimationFrame(animation);
}
}
- **cancelAnimationFrame** : 애니메이션 중단
네모 대신 이미지 넣기
- 장애물
장애물을 그려보자. 먼저 이미지를 가져온다.
var img1 = new Image();
img1.src = "cactus.png";
그리고 장애물을 그리는 함수를
draw() {
ctx.fillStyle = "red";
ctx.fillRect(this.x, this.y, this.weight, this.height);
}
아래 코드로 변경한다.
draw() {
ctx.drawImage(img1, this.x, this.y);
}
- 공룡
var img2 = new Image();
img2.src = "dinosaur.png";
var dino = {
// 공룡 등장 좌표
x: 10,
y: 200,
// 공룡 폭과 높이
width: 50,
height: 50,
// 공룡 그리는 함수
draw() {
ctx.drawImage(img2, this.x, this.y, this.width, this.height); // ⬅️
},
};
dino.draw(); // 공룡 그리기 완료!
결과

🕹️전체 코드 (main.js)
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
canvas.width = window.innerWidth - 100;
canvas.height = window.innerHeight - 100;
var img2 = new Image();
img2.src = "dinosaur.png";
var dino = {
// 공룡 등장 좌표
x: 10,
y: 200,
// 공룡 폭과 높이
width: 50,
height: 50,
// 공룡 그리는 함수
draw() {
ctx.drawImage(img2, this.x, this.y, this.width, this.height);
},
};
dino.draw(); // 공룡 그리기 완료!
var img1 = new Image();
img1.src = "cactus.png";
class Cactus {
constructor() {
this.x = 600;
this.y = 200;
this.width = 50;
this.height = 50;
}
draw() {
ctx.drawImage(img1, this.x, this.y);
}
}
var timer = 0;
var cactuses = [];
var jumpTimer = 0;
var animation;
function executeByFrame() {
animation = requestAnimationFrame(executeByFrame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (timer % 200 === 0) {
var cactus = new Cactus();
cactuses.push(cactus);
}
timer++;
cactuses.forEach((a, i, o) => {
if (a.x < 0) {
o.splice(i, 1);
}
a.x--;
isCollapse(dino, a); // 충돌 체크는 여기서 해야
a.draw();
});
if (jumping === true) {
dino.y--;
jumpTimer++;
} else if (jumping === false) {
if (dino.y < 200) {
jumpTimer = 0;
dino.y++;
}
}
if (jumpTimer > 100) {
jumping = false;
}
dino.draw();
}
executeByFrame();
// 충돌 확인
function isCollapse(dino, cactus) {
var diffX = cactus.x - (dino.x + dino.width);
var diffY = cactus.y - (dino.y + dino.height);
// 충돌 - 게임 중단
if (diffX < 0 && diffY < 0) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
cancelAnimationFrame(animation);
}
}
var jumping = false;
document.addEventListener("keydown", function (e) {
if (e.code === "Space") {
jumping = true;
}
});
'Front-End: Web > JavaScript' 카테고리의 다른 글
[js] Math.floor vs trunc vs parseInt (0) | 2022.11.16 |
---|---|
[코딩애플] js part 3-13. Optional Chanining(?.) / Nullish Coalescing (??) (0) | 2022.10.22 |
[코딩애플] js part 3-11. shadow DOM과 template으로 HTML 모듈화하기 (0) | 2022.10.22 |
[코딩애플] js part 3-10. Web Components: 커스텀 HTML 태그 만들기 (0) | 2022.10.22 |
[코딩애플] js part 3-9. Map, Set 자료형 (0) | 2022.10.21 |