p5.js를 이용하여 Vue.js에서 간단한 게임 만들기

Dev Smile·2024년 6월 9일
1

이전 글 (Vue+Canvas)에 더해서 p5.js를 활용하여 간단한 게임을 작성해보도록 하겠습니다.

1. vue.js 프로젝트 생성

  • yarn을 통한 vue create 사용
    yarn create cue
  • 위 안내를 따라서 라이브러리 설치 및 프로젝트 실행
    yarn 
    yarn format 
    yarn dev

2. src/app.vue 수정

  • 필요한 부분만 남기고 그 외 부분은 지우기
<script setup lang="ts">
import { RouterView } from 'vue-router'
</script>

<template>
  <RouterView />
</template>

3. src/views/AboutView.vue 수정

3-1. 완성 코드

  • 게임 구현 부분을 작성합니다.
<template>
  <div id="game-container" ref="gameContainer"></div>
</template>

<script>
export default {
  name: 'Game',  // Vue 컴포넌트의 이름을 'Game'으로 설정
  mounted() {
    this.sketch = new p5(this.gameSetup)  // 컴포넌트가 마운트될 때 p5 인스턴스를 생성하고 gameSetup 메서드로 초기화
  },
  beforeDestroy() {
    if (this.sketch) {
      this.sketch.remove()  // 컴포넌트가 파괴되기 전 p5 인스턴스가 존재하면 제거
    }
  },
  methods: {
    gameSetup(p) {
      let player
      let obstacles = []
      let score = 0
      let gameOver = false

      p.setup = function () {
        p.createCanvas(800, 400).parent('game-container')  // p5 캔버스를 생성하고 game-container div 요소에 붙임
        player = new Player()  // Player 인스턴스를 생성
      }

      p.draw = function () {
        if (gameOver) {
          p.background(200, 0, 0)  // 게임 오버 시 배경을 빨간색으로 설정
          p.textAlign(p.CENTER)
          p.textSize(32)
          p.fill(255)
          p.text('Game Over', p.width / 2, p.height / 2)  // 화면 중앙에 'Game Over' 텍스트 표시
          p.text(`Score: ${score}`, p.width / 2, p.height / 2 + 40)  // 점수 표시
          return
        }

        p.background(220)  // 게임 진행 중 배경을 회색으로 설정
        player.update()  // player 상태 업데이트
        player.show()  // player 그리기

        if (p.frameCount % 60 === 0) {
          obstacles.push(new Obstacle())  // 60프레임마다 새로운 장애물 생성
          score++  // 점수 증가
        }

        for (let i = obstacles.length - 1; i >= 0; i--) {
          obstacles[i].update()  // 각 장애물 상태 업데이트
          obstacles[i].show()  // 각 장애물 그리기

          if (player.hits(obstacles[i])) {
            gameOver = true  // 플레이어가 장애물과 충돌하면 게임 오버
          }

          if (obstacles[i].offscreen()) {
            obstacles.splice(i, 1)  // 화면 밖으로 나간 장애물 제거
          }
        }

        p.textSize(16)
        p.fill(0)
        p.text(`Score: ${score}`, p.width - 100, 30)  // 현재 점수를 화면에 표시
      }

      p.keyPressed = function () {
        if (p.key == ' ' && !gameOver) {
          player.jump()  // 스페이스바를 누르면 플레이어가 점프
        }
        if (gameOver && p.key == 'R') {
          score = 0
          gameOver = false
          obstacles = []
          player = new Player()  // 'R' 키를 누르면 게임이 리셋됨
        }
      }

      class Player {
        constructor() {
          this.r = 50  // 플레이어의 크기 설정
          this.x = 50  // 플레이어의 초기 x 위치 설정
          this.y = p.height - this.r  // 플레이어의 초기 y 위치 설정
          this.vy = 0  // 플레이어의 초기 속도 설정
          this.gravity = 1  // 중력 설정
        }

        jump() {
          if (this.y == p.height - this.r) {
            this.vy = -20  // 플레이어가 바닥에 있을 때 점프
          }
        }

        hits(obstacle) {
          return p.collideRectRect(
            this.x,
            this.y,
            this.r,
            this.r,
            obstacle.x,
            obstacle.y,
            obstacle.w,
            obstacle.h
          )  // 플레이어와 장애물의 충돌 검사
        }

        update() {
          this.y += this.vy  // 플레이어의 y 위치 업데이트
          this.vy += this.gravity  // 속도에 중력을 더함
          this.y = p.constrain(this.y, 0, p.height - this.r)  // 플레이어가 화면을 벗어나지 않도록 위치 제한
        }

        show() {
          p.fill(255, 50, 50)
          p.rect(this.x, this.y, this.r, this.r)  // 플레이어 그리기
        }
      }

      class Obstacle {
        constructor() {
          this.w = 40  // 장애물의 너비 설정
          this.h = p.random(50, 150)  // 장애물의 높이를 무작위로 설정
          this.x = p.width  // 장애물의 초기 x 위치 설정
          this.y = p.height - this.h  // 장애물의 y 위치 설정
          this.speed = 6  // 장애물의 이동 속도 설정
        }

        update() {
          this.x -= this.speed  // 장애물의 x 위치 업데이트
        }

        offscreen() {
          return this.x < -this.w  // 장애물이 화면 밖으로 나갔는지 확인
        }

        show() {
          p.fill(50, 50, 255)
          p.rect(this.x, this.y, this.w, this.h)  // 장애물 그리기
        }
      }
    }
  }
}
</script>

<style scoped>
#game-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;  /* 게임 컨테이너를 화면 중앙에 배치하고 전체 높이를 100vh로 설정 */
}
</style>

3-2. 상세한 설명

  • 이 코드는 Vue.js와 p5.js를 사용하여 간단한 게임을 만드는 예제
  • 이 게임은 장애물을 피하면서 플레이어가 점프하는 방식으로 진행

Template

<template>
  <div id="game-container" ref="gameContainer"></div>
</template>
  • template 태그 안에 div 요소가 있으며, idgame-container로 설정되어 있습니다.
  • ref="gameContainer"는 Vue.js에서 이 요소에 직접 접근할 수 있게 하는 참조(reference)를 설정합니다.

Script

<script>
export default {
  name: 'Game',  // Vue 컴포넌트의 이름을 'Game'으로 설정
  mounted() {
    this.sketch = new p5(this.gameSetup)  // 컴포넌트가 마운트될 때 p5 인스턴스를 생성하고 gameSetup 메서드로 초기화
  },
  beforeDestroy() {
    if (this.sketch) {
      this.sketch.remove()  // 컴포넌트가 파괴되기 전 p5 인스턴스가 존재하면 제거
    }
  },
  methods: {
    gameSetup(p) {
      let player
      let obstacles = []
      let score = 0
      let gameOver = false

      p.setup = function () {
        p.createCanvas(800, 400).parent('game-container')  // p5 캔버스를 생성하고 game-container div 요소에 붙임
        player = new Player()  // Player 인스턴스를 생성
      }

      p.draw = function () {
        if (gameOver) {
          p.background(200, 0, 0)  // 게임 오버 시 배경을 빨간색으로 설정
          p.textAlign(p.CENTER)
          p.textSize(32)
          p.fill(255)
          p.text('Game Over', p.width / 2, p.height / 2)  // 화면 중앙에 'Game Over' 텍스트 표시
          p.text(`Score: ${score}`, p.width / 2, p.height / 2 + 40)  // 점수 표시
          return
        }

        p.background(220)  // 게임 진행 중 배경을 회색으로 설정
        player.update()  // player 상태 업데이트
        player.show()  // player 그리기

        if (p.frameCount % 60 === 0) {
          obstacles.push(new Obstacle())  // 60프레임마다 새로운 장애물 생성
          score++  // 점수 증가
        }

        for (let i = obstacles.length - 1; i >= 0; i--) {
          obstacles[i].update()  // 각 장애물 상태 업데이트
          obstacles[i].show()  // 각 장애물 그리기

          if (player.hits(obstacles[i])) {
            gameOver = true  // 플레이어가 장애물과 충돌하면 게임 오버
          }

          if (obstacles[i].offscreen()) {
            obstacles.splice(i, 1)  // 화면 밖으로 나간 장애물 제거
          }
        }

        p.textSize(16)
        p.fill(0)
        p.text(`Score: ${score}`, p.width - 100, 30)  // 현재 점수를 화면에 표시
      }

      p.keyPressed = function () {
        if (p.key == ' ' && !gameOver) {
          player.jump()  // 스페이스바를 누르면 플레이어가 점프
        }
        if (gameOver && p.key == 'R') {
          score = 0
          gameOver = false
          obstacles = []
          player = new Player()  // 'R' 키를 누르면 게임이 리셋됨
        }
      }

      class Player {
        constructor() {
          this.r = 50  // 플레이어의 크기 설정
          this.x = 50  // 플레이어의 초기 x 위치 설정
          this.y = p.height - this.r  // 플레이어의 초기 y 위치 설정
          this.vy = 0  // 플레이어의 초기 속도 설정
          this.gravity = 1  // 중력 설정
        }

        jump() {
          if (this.y == p.height - this.r) {
            this.vy = -20  // 플레이어가 바닥에 있을 때 점프
          }
        }

        hits(obstacle) {
          return p.collideRectRect(
            this.x,
            this.y,
            this.r,
            this.r,
            obstacle.x,
            obstacle.y,
            obstacle.w,
            obstacle.h
          )  // 플레이어와 장애물의 충돌 검사
        }

        update() {
          this.y += this.vy  // 플레이어의 y 위치 업데이트
          this.vy += this.gravity  // 속도에 중력을 더함
          this.y = p.constrain(this.y, 0, p.height - this.r)  // 플레이어가 화면을 벗어나지 않도록 위치 제한
        }

        show() {
          p.fill(255, 50, 50)
          p.rect(this.x, this.y, this.r, this.r)  // 플레이어 그리기
        }
      }

      class Obstacle {
        constructor() {
          this.w = 40  // 장애물의 너비 설정
          this.h = p.random(50, 150)  // 장애물의 높이를 무작위로 설정
          this.x = p.width  // 장애물의 초기 x 위치 설정
          this.y = p.height - this.h  // 장애물의 y 위치 설정
          this.speed = 6  // 장애물의 이동 속도 설정
        }

        update() {
          this.x -= this.speed  // 장애물의 x 위치 업데이트
        }

        offscreen() {
          return this.x < -this.w  // 장애물이 화면 밖으로 나갔는지 확인
        }

        show() {
          p.fill(50, 50, 255)
          p.rect(this.x, this.y, this.w, this.h)  // 장애물 그리기
        }
      }
    }
  }
}
</script>

설명

  • export default { name: 'Game', ... }: Vue 컴포넌트를 정의합니다. 컴포넌트의 이름은 Game입니다.
  • mounted() { ... }: 컴포넌트가 DOM에 마운트될 때 호출됩니다. 이때 p5 인스턴스를 생성하여 gameSetup 메서드로 초기화합니다.
  • beforeDestroy() { ... }: 컴포넌트가 파괴되기 전에 호출됩니다. p5 인스턴스가 존재하면 제거합니다.
  • methods: { gameSetup(p) { ... } }: p5 인스턴스의 설정과 게임 로직을 정의하는 메서드입니다.
p5 라이브러리와 게임 로직
  • p.setup(): p5의 setup 함수로, 캔버스를 생성하고 플레이어 인스턴스를 초기화합니다.
  • p.draw(): p5의 draw 함수로, 매 프레임마다 게임 화면을 업데이트하고 그립니다. 게임이 진행 중이면 배경을 그리고 플레이어와 장애물을 업데이트 및 표시합니다. 게임 오버 시 "Game Over" 메시지를 표시합니다.
  • p.keyPressed(): 키가 눌렸을 때 호출됩니다. 스페이스바를 누르면 플레이어가 점프하고, 'R' 키를 누르면 게임이 리셋됩니다.
Player 클래스
  • constructor(): 플레이어의 속성을 초기화합니다.
  • jump(): 플레이어가 점프할 때 호출됩니다.
  • hits(obstacle): 플레이어가 장애물과 충돌했는지 확인합니다.
  • update(): 플레이어의 위치를 업데이트합니다.
  • show(): 플레이어를 화면에 그립니다.
Obstacle 클래스
  • constructor(): 장애물의 속성을 초기화합니다.
  • update(): 장애물의 위치를 업데이트합니다.
  • offscreen(): 장애물이 화면 밖으로 나갔는지 확인합니다.
  • show(): 장애물을 화면에 그립니다.

Style

<style scoped>
#game-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;  /* 게임 컨테이너를 화면 중앙에 배치하고 전체 높이를 100vh로 설정 */
}
</style>
  • #game-container: 게임 컨테이너를 화면 중앙에 배치하고 높이를 화면 전체 높이(100vh)로 설정합니다.

4. index.html 수정

  • p5.collide2d는 p5가 글로벌 스코프에 정의된 상태에서 동작하는데, vue 파일 내에서 정의하니 계속 에러가 발생하여 이를 해결하기 위해 p5와 p5.collide2d를 전역으로 정의
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite App</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/p5.collide2d@0.7.3/p5.collide2d.min.js"></script>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>

5. 결과

  • 빨간 네모가 캐릭터
    • 일정한 속도로 오른쪽으로 움직임
    • 스페이스바를 누르면 점프
    • 파란 막대에 부딪히면 게임 오버
  • 파란 막대는 장애물
    • 파란 막대가 등장한 갯수만큼 점수가 올라감

  • 장애물에 부딪히면 게임오버와 점수를 표시

0개의 댓글

관련 채용 정보