나 개발자 진짜 되냐?

갑자기 만들어보게 된 웹게임 _ 장애물 피하면서 달리기 [ 2편 ] 본문

기타라고 해요!

갑자기 만들어보게 된 웹게임 _ 장애물 피하면서 달리기 [ 2편 ]

Snow Rabbit 2024. 9. 10. 16:46

2편에서는..

 

장애물 만들기

 

시작!

 


/** 2-1 장애물 설정 */
const OBSTACLE_WIDTH = 50; // 장애물 너비
const OBSTACLE_HEIGHT = 50; // 장애물 높이
const OBSTACLE_FREQUENCY = 90; // 장애물 생성 빈도
const OBSTACLE_SPEED = 4; // 장애물 이동 속도

/** 장애물 클래스 정의 */
class Obstacle {
  constructor() {
    this.x = canvas.width;
    this.y =
      Math.floor(Math.random() * (canvas.height - OBSTACLE_HEIGHT - 30)) + 30; // 장애물이 canvas의 상단과 하단에서 30px 이내에 생성되지 않도록 조정
    this.width = OBSTACLE_WIDTH;
    this.height = OBSTACLE_HEIGHT;
  }
  draw() {
    ctx.drawImage(obstacleImage, this.x, this.y, this.width, this.height); // 장애물 이미지 그리기
  }
}
/** end of 2-1 장애물 설정 */

 

장애물 코드는

 

 

애니메이션 시작 앞에 두도록 하자!!

( 둬야 하는 위치가 조금 중요할 때가 많다. )

 

 

여기서 장애물 클래스는

class로 정의되어있다.

 

앞에 runman이랑 차이점은

runman은 하나였기 때문에 변수 const를 사용하고

 

장애물은 여러 개 동시다발적으로 나와 야하기 때문에

class를 사용해 준다.

 

 

    this.y =
      Math.floor(Math.random() * (canvas.height - OBSTACLE_HEIGHT - 30)) + 30;

 

생소한 이 부분은

 

장애물이 나오는 과정에서

y값이 랜덤 하게 변하면서 나와야 하기 때문에 사용한다.

( 헷갈리면 주석 확인 )

 

 

 

자 장애물을 설치했으니

장애물도 움직일 수 있게 함수를 만들어 주어야 한다!!

 

  /**-- 장애물 --*/
  // 2-2 장애물 생성 및 업데이트
  if (timer % OBSTACLE_FREQUENCY === 0) {
    const obstacle = new Obstacle();
    obstacleArray.push(obstacle);
  }
  // 장애물 처리
  obstacleArray.forEach((obstacle) => {
    obstacle.draw();
    obstacle.x -= OBSTACLE_SPEED; // 장애물 왼쪽으로 이동
    });

 

이 함수는 timer가 있기 때문에

 

적어도 timer 뒤에 둬야 한다.

 그래서!

 

timer ++ 바로 위에 넣어주도록 하자!

 

 

제대로 넣었다면! 우리는 

 

 

이런 모습을 볼 수 있다.

 

 

지금까지 만든 것을 확인해 보면!

 

캐릭터가 점프한다.

장애물이 나온다.

장애물이 오른쪽에서 왼쪽으로 간다.

 

 

 

였다.

 

 

 

아직 구현이 안된 건

 

장애물이랑 캐릭터가 충돌할 때 게임이 끝난다.

장애물이 왼쪽으로 넘어가게 되면 점수를 획득한다.

 

 

 

이기 때문에 우리는 

충돌하는 함수도 적어주어야 한다!

/** 2-3 장애물 조건1 충돌함수 */
/** 충돌 체크 함수 */
function collision(rtan, obstacle) {
  return !(
    runman.x > obstacle.x + obstacle.width ||
    runman.x + rtan.width < obstacle.x ||
    runman.y > obstacle.y + obstacle.height ||
    runman.y + rtan.height < obstacle.y
  );
}
/** end of 2-3 장애물 조건1 충돌함수 */

 

 

자자! 

 

다음에 우리가 해야 할 건

 

장애물 충돌 조건 설정을 해주어야 한다.

 

이 친구는 장애물 생성 및 업데이트 안에서

가장 밑에 넣어주면 된다.

 

만약에 밖으로 나가면 장애물이 제거되고

점수가 증가하고

현재점수에서 점수가 추가되고

소리까지 추가가 된다.

 

     /** 2-3 장애물 조건 설정(충돌하기) */
        // 화면 밖으로 나간 장애물 제거 및 점수 증가
        if (obstacle.x < -OBSTACLE_WIDTH) {
            obstacleArray.shift(); // 장애물 제거
            score += 10; // 점수 증가
            scoreText.innerHTML = "현재점수: " + score;
            scoreSound.pause(); // 현재 재생 중인 점수 소리 중지
            scoreSound.currentTime = 0; // 소리 재생 위치를 시작으로 초기화
            scoreSound.play(); // 점수 획득 소리 재생
        }

 

 

그리고 여기서 점수가 얻는 것을 했으니

게임 오버되는 과정도 밑에 적어줘야 한다.

 

 

    /** 2-3 장애물 조건 설정(게임 오버) */
    if (gameOver) {
        drawGameOverScreen(); // 3-2 에서 다룰 게임오버 그리기
        return;
    }
    /** end of 2-3 장애물 조건 설정(게임 오버) */

이 친구는 애니메이션 안에서 제일 첫 번째에 둬준다.

 

     // 충돌 검사
        if (collision(runman, obstacle)) {
            timer = 0;
            gameOver = true;
            jump = false;
            bgmSound.pause(); // 배경 음악 중지
            defeatSound.play(); // 게임 오버 소리 재생
        }

 

충돌까지 해주면! 끝!

 

 

 

지금까지 한 코드를 다시 정리해 두었습니다!!

복사해서 

 

게임 애니메이션 함수를 싸악 고쳐봐요!

 

/* 게임 애니메이션 함수 */

function animate() {

     /** 2-3 장애물 조건 설정(게임 오버) */
    if (gameOver) {
       drawGameOverScreen(); // 3-2 에서 다룰 게임오버 그리기
        return; // 게임 오버되면 멈춰라!!
    }
        /** end of 2-3 장애물 조건 설정(게임 오버) */

    // 캔버스 클리어
    requestAnimationFrame(animate);
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    timer++;

  /**-- 장애물 --*/
  // 2-2 장애물 생성 및 업데이트
  if (timer % OBSTACLE_FREQUENCY === 0) { // 어떤 시간마다 만들어진다.
    const obstacle = new Obstacle(); // 장애물을 계속 새로 만든다
    obstacleArray.push(obstacle); // 장애물들을 리스트에 넣고 벽에 닿으면 비워주라 %5
  }
  // 장애물 처리
  obstacleArray.forEach((obstacle) => {
    obstacle.draw();
    obstacle.x -= OBSTACLE_SPEED; // 장애물 왼쪽으로 이동

     /** 2-3 장애물 조건 설정(충돌하기) */
        // 화면 밖으로 나간 장애물 제거 및 점수 증가
        if (obstacle.x < -OBSTACLE_WIDTH) { // 만약에 왼쪽 벽에 닿으면!!
            obstacleArray.shift(); // 장애물 제거 %5
            score += 10; // 점수 증가
            scoreText.innerHTML = "현재점수: " + score;
            scoreSound.pause(); // 현재 재생 중인 점수 소리 중지
            scoreSound.currentTime = 0; // 소리 재생 위치를 시작으로 초기화
            scoreSound.play(); // 점수 획득 소리 재생
        }

        // 충돌 검사
        if (collision(runman, obstacle)) { 
            timer = 0;
            gameOver = true;
            jump = false;
            bgmSound.pause(); // 배경 음악 중지
            defeatSound.play(); // 게임 오버 소리 재생
        }
    });


	  /**-- 르탄이 --*/
	  // 1-2 그리기
	  runman.draw();
	
	  // 1-3 점프 조건 설정하기
	  if (jump) {
	    runman.y -= 3; // 스페이스바를 누르고 있으면 runman의 y값 감소
	    if (runman.y < 20) runman.y = 20; // runman이 canvas 상단을 넘지 않도록 조정
	  } else {
	    if (runman.y < RUNMAN_Y) {
	      runman.y += 3; // 스페이스바를 떼면 runman의 y값 증가
	      if (runman.y > RUNMAN_Y) runman.y = RUNMAN_Y; // runman이 초기 위치 아래로 내려가지 않도록 조정
	    }
	  }
	  //** end of 르탄이 */
}
animate();
/** end of 게임 에니메이션 */

/** 1-3 점프하기 */
// 키보드 이벤트 처리 (스페이스바 점프)
document.addEventListener("keydown", function (e) {
    if (e.code === "Space" && !jump) {
        jump = true; // 스페이스바를 누르고 있을 때 점프 상태 유지
        jumpSound.play(); // 점프 소리 재생
    }
});

document.addEventListener("keyup", function (e) {
    if (e.code === "Space") {
        jump = false; // 스페이스바를 떼면 점프 상태 해제
    }
});
/** end of 1-3 점프하기 */

/** 2-3 장애물 조건1 충돌함수 */
/** 충돌 체크 함수 */
function collision(rtan, obstacle) {
    return !(
      runman.x > obstacle.x + obstacle.width ||
      runman.x + rtan.width < obstacle.x ||
      runman.y > obstacle.y + obstacle.height ||
      runman.y + rtan.height < obstacle.y
    );
  }

 

 

어려우셨다면!

이 친구들을 가지고 애니메이션에 붙이고 다시 따라오세요!

 

와우 와우 여러분

 

코드 쓰는 곳

숫자 옆에

>

이 친구 보이시나요?

 

이 친구를 누르면 최소화되면서

코드가 정리됩니다!

 

주석을 생활화해서

편하게 읽을 수 있도록 정리합시다!

 

 

자 이제 배경화면을 넣어봅시다!

 

 

배경화면은

게임 애니메이션 함수 위에 놓아야 합니다!

 

/** 3-1 배경 화면 그리기 */
function backgroundImg(bgX) { // 같은 화면이 반복적으로 지나가서 마치 연속되는거 처럼 사용하기위해 반복적으로 함수를 실행해야해서 function 사용
  ctx.drawImage(bgImage, bgX, 0, canvas.width, canvas.height);
}
// 시작 화면 그리기
function drawStartScreen() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  backgroundImg(0); // 숫자 0은 bgX에 들어가는거여서 활용하게 된다. 0인 이유는 X좌표에서 왼쪽이 0이기 때문! 왼쪽부터 오른쪽으로 화면을 그린다는 소리!
  const imageWidth = 473;
  const imageHeight = 316;
  const imageX = canvas.width / 2 - imageWidth / 2;
  const imageY = canvas.height / 2 - imageHeight / 2;
  ctx.drawImage(startImage, imageX, imageY, imageWidth, imageHeight);
}

// 게임 오버 화면 그리기
function drawGameOverScreen() {
  ctx.drawImage(
    gameoverImage, // 이미지
    canvas.width / 2 - 100, // x좌표
    canvas.height / 2 - 50, // y좌표
    200, // 크기
    100 // 크기
  );
  ctx.drawImage(
    restartImage, // 다시시작 버튼
    canvas.width / 2 - 50,
    canvas.height / 2 + 50,
    100,
    50
  );
}

//이미지 로딩 완료 시 게임 시작 화면 그리기
let bgImageLoaded = new Promise((resolve) => { //Promise는 약속, onload가 된다면! OK
  bgImage.onload = resolve;
});

let startImageLoaded = new Promise((resolve) => {
  startImage.onload = resolve;
});

Promise.all([bgImageLoaded, startImageLoaded]).then(drawStartScreen); // Promise가 두개지요..! 둘다 OK받았으면 그 다음거 실행해줘!, drawStartScreen 실행!
/** end of 3-1 게임 시작 화면을 그리는 함수 */

주석을 읽어보시면 도움이 되십니다 ㅎㅅㅎ

 

 

 

그렇다면 이제

왼쪽에 종이 두장짜리를 누르고

index에서 오른쪽 클릭

 

Open 어쩌고 누르면! 게임이 뜬다!

 

엇..

배경이 안 나온다고요?!

 

이유가 있습니다.

 

우리는 

중간중간 실험해 보기 위해 애니메이션을 강제로 실행하려고 

글자를 한 글자 넣었습니다.

 

그것은 바로

 

 

바로 여기..

animate(); 때문이지요

이 친구를 지우게 되면!!

 

배경화면이 나오게 됩니다!

 

여러분, 이 이미지를 공유하고 싶지만..

안될 수도 있기 때문에..

여러분들은 아무 사진을 가지고 오셔서

 

또는 요즘 ai로 배경사진을 만들어 주는 곳이 있습니다.

https://aitestkitchen.withgoogle.com/tools/image-fx

 

ImageFX

Prompt input cluttered artist studio, light shining through, welcoming

aitestkitchen.withgoogle.com

여기서, ai이니까 뭐 배경화면, 겨울, 눈오는중 이렇게 쓰시면 그거에 맞는 사진이 나오게 됩니다!!

그 사진들을 활용해보세요!

 

이미지 객체 생성 및 설정 < 여기에 들어가셔서

// (2) 게임 시작
const startImage = new Image();
startImage.src = "./images/gamestart.png";

 

"" 여기 안에 있는 사진을 본인이 저장했던 사진 주소, 이름으로 변경해 주시면 됩니다.

기타 사진들도.. 여기서 바꾸시면 됩니다.

 

 

 

자..

이제 배경을 설정해주어야 한다.

위에서 배경을 bgX로 설정해 두고 0으로 했듯

이제 게임을 시작할 테니 배경도 움직여야 할 것이다.

 

코드를 넣어주어야 한다.

어디에?

 

timer ++ 여기 바로 밑에!!!

 

    /** 배경 이미지 */
    // 3-1 배경 이미지 그리기 (무한 스크롤 효과)
    backgroundImg(bgX); // 그냥 그려줘
    backgroundImg(bgX + canvas.width); // 캔버스 넓이만큼 넓게펴줘, 즉 사진이 두장이고, 사진 한장이 왼쪽벽에 닿으면 다시 0으로 두장이 시작된다.
    bgX -= BG_MOVING_SPEED;
    if (bgX < -canvas.width) bgX = 0;
    // 배경 음악 재생
    bgmSound.play();
    /** end of 배경 이미지 */

 

배경까지 넣어줬으면 이제 게임 시작을 눌러줘야 하는데

어라라 안 눌린다!

 

 

우리는 게임시작 조건을 설정해야 한다.

왜냐하면 지금 여기서 게임시작 창만 뜨고 애니메이션이 실행이 안되기 때문!

 

 

쭈우욱 내려가서 제일 마지막 줄에

넣어준다.

 

/** 3-3 게임 시작 조건 설정하기 */ // 게임을 시작할 수 있다.
canvas.addEventListener("click", function (e) { // Listener 은 클릭을 하면 시작하겠다. 클릭 할때까지 나는 듣고있어!
  const rect = canvas.getBoundingClientRect();
  const x = e.clientX - rect.left; // x 좌표
  const y = e.clientY - rect.top; // y 좌표

  // 게임 시작 조건 확인
  if (
    !gameStarted && //!는 거짓을 나타낸다.
    x >= 0 &&
    x <= canvas.width &&
    y >= 0 &&
    y <= canvas.height
  ) {
    gameStarted = true;
    animate(); //이제는 클릭을 해야지만 실행이 된다!
  }

  // 게임 재시작 버튼 클릭 확인
  if (
    gameOver &&
    x >= canvas.width / 2 - 50 &&
    x <= canvas.width / 2 + 50 &&
    y >= canvas.height / 2 + 50 &&
    y <= canvas.height / 2 + 100
  ) {
    restartGame();
  }
});

/** 게임 재시작 함수 */
function restartGame() {
  gameOver = false; // 다시 바꿔주면서 게임오버가 아닌상태로 만들어주고
  obstacleArray = [];
  timer = 0; //시간 초기화
  score = 0; //점수 초기화
  scoreText.innerHTML = "현재점수: " + score;
  // 게임 오버 시 르탄이 위치 초기화
  runman.x = 10; // 이걸 안하면 다시 시작할때 죽은 위치에서 시작한다 ㅎㅎㅋ
  runman.y = 400;
  animate();
}
/** end of 3-3 마우스 클릭 이벤트 처리 (게임 시작 및 재시작) */

 

이렇게 하면!!!!!!

게임이 완성되게 된다!!!!!

 

 

추가적인 가벼운 마우스포인트 꾸미기 기능도 있다.

/** 4. 꾸미기 */
/** 마우스 이동 이벤트 처리 (커서 스타일 변경) */
canvas.addEventListener("mousemove", function (e) {
    const rect = canvas.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;

    // 게임오버 재시작 버튼 위에 있을 때
    if (
        gameOver &&
        x >= canvas.width / 2 - 50 &&
        x <= canvas.width / 2 + 50 &&
        y >= canvas.height / 2 + 50 &&
        y <= canvas.height / 2 + 100
    ) {
        canvas.style.cursor = "pointer";
    }
    // 게임 시작 전 캔버스 위에 있을 때
    else if (
        !gameStarted &&
        x >= 0 &&
        x <= canvas.width &&
        y >= 0 &&
        y <= canvas.height
    ) {
        canvas.style.cursor = "pointer";
    }
    // 그 외의 경우
    else {
        canvas.style.cursor = "default";
    }
});
/** end of 4.꾸미기 */

 

제일 마지막 줄에 넣어주면 되는데..!

안넣어줘도 되긴 한다!!

 

 

혹시 혹쉬

완성본이 궁금하시다구요?!

 

https://snowrabbit28.github.io/runman/

 

달리기 게임

최고점을 향하여!! 😀 현재점수: 0

snowrabbit28.github.io

 

저처럼 만들어졌으면 성공입니다!

 

링크에 들어오셔서 게임이 같은지 확인하세요!

 

그림이 다르다구요?!

저는 조금 수정했습니다 (윙크)

 

 

지금까지 

달려라 르탄이 웹 게임 만들기 였습니다 !!

 

 

< 본 프로젝트는 스파르타코딩클럽 수업내용을 복습한 내용입니다 >