내일배움캠프 Node.js 사전캠프 10일차
1. ZEP에서 이루어진 팀단위 JavaScript 스터디
(1) 스터디 계획
1) HP바 만들기
2) 폭주 게이지바 만들기
3) 총알 개수 늘리기
이전 시간에 구상해놓은 대로 기능을 추가해보기로 했다.
(2) 코드 작성하기
HP바 만들기
/** HP바 정의 */
const HP_BAR_WIDTH_COEFF = 2;
let HP_BAR_WIDTH = 100 * HP_BAR_WIDTH_COEFF;
const HP_bar = {
x: 20,
y: 20,
height: 30,
draw() {
const my_gradient = ctx.createLinearGradient(0, this.y, 0, this.y + this.height); // gradient
my_gradient.addColorStop(0, "#990000");
my_gradient.addColorStop(0.5, "#CC0000");
my_gradient.addColorStop(1, "#FF0000");
ctx.fillStyle = my_gradient;
ctx.strokeStyle = "black";
ctx.lineWidth = 3;
ctx.strokeRect(this.x, this.y, 100 * HP_BAR_WIDTH_COEFF, this.height);
ctx.fillRect(this.x, this.y, HP_BAR_WIDTH, this.height);
}
};
HP 바를 추가하기 위해 이용한 메서드는 크게 세 가지이다.
fillRect(x, y, width, height)
→ 색칠된 직사각형을 그린다.
strokeRect(x, y, width, height)
→ 직사각형의 윤곽을 그린다.
createLinearGradient(x, y, width, height)
→ 선형의 그라데이션 효과를 만들어준다.
세 메서드를 이용하여 그린 결과물이다. 아래에 있는 주황색 게이지 또한 같은 방식으로 만들 수 있었다.
폭주 게이지바 만들기
/** 게이지바 정의 */
const GAGE_BAR_WIDTH_COEFF = 2;
let GAGE_BAR_WIDTH = 100 * GAGE_BAR_WIDTH_COEFF;
let RAGE_GAGE = 0;
const GAGE_bar = {
x: 20,
y: 60,
height: 20,
draw() {
const my_gradient = ctx.createLinearGradient(0, this.y, 0, this.y + this.height); // gradient
my_gradient.addColorStop(0, "#FF8C00");
my_gradient.addColorStop(0.5, "#FF8C00");
my_gradient.addColorStop(1, "#FF8C00");
ctx.fillStyle = my_gradient;
ctx.strokeStyle = "black";
ctx.lineWidth = 3;
ctx.strokeRect(this.x, this.y, 100 * GAGE_BAR_WIDTH_COEFF, this.height);
ctx.fillRect(this.x, this.y, RAGE_GAGE, this.height);
}
};
폭주 게이지를 통해 구현하고 싶었던 건 특정한 시간 동안 발사체가 증가하는 기능이었다. 어떻게 코드를 짜야 내가 원하는 방식으로 작동할 수 있을까? 여러가지로 시도를 해봤다.
처음엔 20초 동안 함수가 실행된 뒤 if 문을 나가는 방식으로 구현하려 했으나 timer 변수가 프레임에 의해 영향을 받기에 내가 원하는 방식으로 작동하지 않았고, 비동기 처리와 관련된 버그를 발생시키기도 했다.
그래서 곰곰이 생각하다 떠올린 것은 RAGE_GAGE의 값을 프레임마다 감소시키는 방향이었다. 해당 방식으로 코드를 짜는 게 훨씬 간결해서 결과물이 더 만족스럽게 느껴진다.
// 게임 애니메이션 함수
function animate() {
// 중간 생략
/** 발사체 그리기 및 업데이트 */
bulletArray.forEach((bullet, bulletIndex) => {
bullet.draw();
bullet.update();
if (bullet.x > canvas.width) {
bulletArray.splice(bulletIndex, 1);
}
// 발사체와 적 충돌 검사
enemyArray.forEach((enemy, enemyIndex) => {
if (collision(bullet, enemy)) {
// 충돌한 총알과 적 오브젝트 없애기
enemyArray.splice(enemyIndex, 1);
bulletArray.splice(bulletIndex, 1);
// 점수 증가 및 표시
score += Math.floor(enemy.enemyScore);
scoreText.innerHTML = "현재점수: " + score;
scoreSound.pause();
scoreSound.currentTime = 0;
scoreSound.play();
// 폭주 상태가 아니라면 게이지 증가
if (!rtan.Israge) {
RAGE_GAGE += Math.floor(enemy.enemyScore) / 3;
}
}
});
});
// 중간 생략
// 폭주 모드에 진입해도 괜찮은지 검사
if (RAGE_GAGE >= 100 * GAGE_BAR_WIDTH_COEFF && !rtan.Israge) {
rtan.Israge = true;
}
// 폭주 모드에 진입하면 프레임당 게이지가 10씩 깎인다. 0이하가 되면 폭주상태가 끝난다.
if (rtan.Israge) {
RAGE_GAGE -= 0.7;
if (RAGE_GAGE <= 0) {
RAGE_GAGE = 0;
rtan.Israge = false;
}
}
// 중간 생략
/** 플레이어, HP바, 게이지바 그리기 */
rtan.draw();
HP_bar.draw();
GAGE_bar.draw();
}
폭주 기능 구현하기
폭주 상태를 불리언 값(true / false)으로 제어하는 게 가장 효율적이라고 판단했다.
르탄이 객체(rtan) 안에 Israge라는 이름의 key를 추가하고 value를 false로 지정해주었다.
/** 플레이어 객체 정의 */
const rtan = {
x: RTAN_X,
y: RTAN_Y,
hp: 100,
width: RTAN_WIDTH,
height: RTAN_HEIGHT,
Israge: false,
draw() {
// 달리는 애니메이션 구현
if (gameOver) {
// 게임 오버 시 충돌 이미지 그리기
ctx.drawImage(rtanCrashImage, this.x, this.y, this.width, this.height);
} else {
// 달리는 애니메이션 구현
if (timer % 60 > 30) {
ctx.drawImage(rtanAImage, this.x, this.y, this.width, this.height);
} else {
ctx.drawImage(rtanBImage, this.x, this.y, this.width, this.height);
}
}
}
};
이제 특정 조건을 만족할 시 Israge의 value를 true로 설정해줄 차례다.
앞서 내가 구상해두었던 폭주 조건은 적(enemy)을 잡아서 게이지를 모두 채웠을 때 해당 기능이 발동하는 것이었다.
// 게임 애니메이션 함수
function animate() {
// 중간 생략
/** 발사체 그리기 및 업데이트 */
bulletArray.forEach((bullet, bulletIndex) => {
bullet.draw();
bullet.update();
if (bullet.x > canvas.width) {
bulletArray.splice(bulletIndex, 1);
}
// 발사체와 적 충돌 검사
enemyArray.forEach((enemy, enemyIndex) => {
if (collision(bullet, enemy)) {
// 충돌한 총알과 적 오브젝트 없애기
enemyArray.splice(enemyIndex, 1);
bulletArray.splice(bulletIndex, 1);
// 점수 증가 및 표시
score += Math.floor(enemy.enemyScore);
scoreText.innerHTML = "현재점수: " + score;
scoreSound.pause();
scoreSound.currentTime = 0;
scoreSound.play();
// 폭주 상태가 아니라면 게이지 증가
if (!rtan.Israge) {
RAGE_GAGE += Math.floor(enemy.enemyScore) / 3;
}
}
});
});
// 중간 생략
// 폭주 모드에 진입해도 괜찮은지 검사
if (RAGE_GAGE >= 100 * GAGE_BAR_WIDTH_COEFF && !rtan.Israge) {
rtan.Israge = true;
}
// 폭주 모드에 진입하면 프레임당 게이지가 10씩 깎인다. 0이하가 되면 폭주상태가 끝난다.
if (rtan.Israge) {
RAGE_GAGE -= 0.7;
if (RAGE_GAGE <= 0) {
RAGE_GAGE = 0;
rtan.Israge = false;
}
}
// 중간 생략
/** 플레이어, HP바, 게이지바 그리기 */
rtan.draw();
HP_bar.draw();
GAGE_bar.draw();
}
총알 개수 늘리기
폭주 모드 진입 시 총알 세 개가 발사되도록(30도, 90도, 120도) 코드를 작성했다. 작성하고 나니 중복되는 코드가 많아 보인다. 총알을 발사하는 동작은 총알 객체의 메서드로 따로 만드는 게 좋을 것 같다.
class Bullet2 {
constructor() {
// 생략
}
draw() {
ctx.drawImage(bulletImage, this.x, this.y, this.width, this.height);
}
update() {
this.x += this.speed;
this.y += this.speed / 2;
}
}
class Bullet3 {
// 생략
}
// ... 생략...
window.addEventListener("keypress", function (e) {
if (gameStarted && e.code === "Space") {
const bullet = new Bullet();
if (rtan.Israge) {
bulletArray.push(bullet);
bulletSound.currentTime = 0;
bulletSound.play();
const bullet2 = new Bullet2();
const bullet3 = new Bullet3();
bullet2.draw();
bullet2.update();
bullet3.draw();
bullet3.update();
if (bullet.x > canvas.width) bulletArray.splice(bulletIndex, 1);
bulletArray.push(bullet2);
bulletArray.push(bullet3);
bulletSound.currentTime = 0;
bulletSound.play();
} else {
bulletArray.push(bullet);
bulletSound.currentTime = 0;
bulletSound.play();
}
}
});
깃 페이지 링크 :
르탄이 슈팅 게임 (ppiok-owo.github.io)
르탄이 슈팅 게임
르탄이와 함께 최고점을 향하여!! 😀 HP: 100 현재점수: 0
ppiok-owo.github.io
댓글