Front-End 개발

Back-End 부분은 기존의 자료들을 이용할 예정이기에, Front-End 부분을 먼저 구현하기로 하였다
(Back-End 는 스켈레톤 코드가 있지만 Front-End는 없으니까..)

일단 AI를 통해 스켈레톤 코드를 구성하고, 이를 분석해 가며 원하는 기능들을 추가할 예정이다!

Player와 Monster

Canvas

일단 플레이어와 적을 그려주기 위해 html의 canvas로 활동영역을 구현해준다.

<head>
    <!-- 브라우저의 최신 표준을 준수하여 렌더링하도록 강제 -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge" /> 
    <!-- 사용자의 화면에 맞추어 content의 크기를 지정해줌 -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
    <canvas id="gameCanvas"></canvas>
</body>
// Body의 canvas를 동적으로 수정하기위해 가져옴
const canvas = document.getElementById('gameCanvas');
// 캔버스에 그래픽을 그리거나 조작하는데 이용
const ctx = canvas.getContext('2d');
// 동적 캔버스 크기 조정
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

// player와 monster 및 입력키 생성 (후에 class로 만들 예정)
let player = { x: canvas.width / 2, y: canvas.height / 2, size: 20, score: 0 };
let monster = [];
let keys = {};

// 랜덤으로 monster 생성 함수
function spawnMonster() {
    const size = Math.random() * 20 + 10;
    monster.push({ x: Math.random() * canvas.width, y: Math.random() * canvas.height, size: size , speed: 2});
}

Moving

  • 플레이어와 몬스터를 캔버스에 그려주고, 움직임을 처리해주는 업데이트 함수를 생성해준다
// 실시간으로 게임로직을 실행 시켜주는 함수
function update() {
    // 캔버스 초기화
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    // 플레이어 색상
    ctx.fillStyle = 'blue';
    // player 그리기
    ctx.fillRect(player.x, player.y, player.size, player.size);

    // monster 그리기
    monsters.forEach((monster, index) => {
        // 임시로 speed에 따라 움직이도록
        monster.y += monster.speed
        monster.x += monster.speed
        // 적의 색
        ctx.fillStyle = 'red';
        // 캔버스에 적 생성
        ctx.fillRect(monster.x, monster.y, monster.size, monster.size);

        // 화면 밖으로 나가면 제거 (임시)
        if (monster.y > canvas.height) {
            monsters.splice(index, 1);
            player.score++;
        }
    });
    // 플레이어의 이동 WASD 와 키보드 화살표로 이동
    if ((keys['ArrowUp'] || keys["w"] )&& player.y > 0) player.y -= player.speed;
    if ((keys['ArrowDown'] || keys["s"]) && player.y < canvas.height - player.size) player.y += player.speed;
    if ((keys['ArrowLeft'] || keys["a"] )&& player.x > 0) player.x -= player.speed;
    if ((keys['ArrowRight'] || keys["d"] )&& player.x < canvas.width - player.size) player.x += player.speed;

    // **애니메이션화 60fps + 재귀호출로 반복
    requestAnimationFrame(update);
}

// 입력 키 맵핑 이벤트
window.addEventListener('keydown', (e) => {
    keys[e.key] = true;
});
window.addEventListener('keyup', (e) => {
    keys[e.key] = false;
});

Collision

  • 플레이어와 몬스터의 충돌을 감지해주는 함수와 게임오버 상태를 만들어 준다!
let gameOver = false;

// canvas 에서 기준점(0, 0)은 왼쪽 위고 y는 위에서 아래로 갈수록 증가한다!
const colliedWith = (monster, player) => {
    // 플레이어의 왼쪽이 몬스터의 오른쪽보다 왼쪽에 있을 때
    return (player.x < monster.x + monster.size &&
    // 플레이어의 오른쪽이 몬스터 왼쪽보다 오른쪽에 있을 때
    player.x + player.size > monster.x &&
    // 플레이어의 위가 몬스터의 아래보다 위에 있을 때
    player.y < monster.y + monster.size &&
    // 플레이어의 아래가 몬스터의 위보다 아래에 있을 때
    player.y + player.size > monster.y)
}

/*update 내부에 적용*/

// 충돌 시 게임오버
if (!gameOver && monsters.some((monster) => colliedWith(monster, player))) {
alert('Game Over! Your score: ' + player.score);
// 게임 오버가 여러 번 뜨는 버그 방지
gameOver = true;
// 재시작
document.location.reload();
}

Player를 쫓아가는 Monster

  • 현재는 +방향으로만 몬스터들이 움직이지만, 뱀서류(Vampire Survival Like)는 몬스터들이 플레이어를 쫓아가야한다!

=> 그렇기에 플레이어의 위치에 따라 방향을 설정해주는 변수를 만들었다!

monsters.forEach((monster, index) => {
    //X 방향
    let directionY = 0;
    //Y 방향
    let directionX = 0;
    // player가 monster 보다 아래에 있으면 아래로!
    if (player.y > monster.y ) directionY = 1;
    // player가 monster 보다 위에 있으면 위로!
    else if (player.y < monster.y) directionY = -1;
    // player가 monster 보다 오른쪽에 있으면 오른쪽으로!
    if (player.x > monster.x ) directionX = 1;
    // player가 monster 보다 왼쪽에 있으면 왼쪽으로!
    else if (player.x < monster.x) directionX = -1;
    // 방향을 speed와 곱해주어 계산한다!
    monster.x += directionY * monster.speed
    monster.y += directionX * monster.speed
    
    ctx.fillStyle = 'red';
    ctx.fillRect(monster.x, monster.y, monster.size, monster.size);

    // 화면 밖으로 나가면 제거(임시)
    if (monster.y > canvas.height || monster.x > canvas.width) {
        monsters.splice(index, 1);
        player.score++;
    }
})

Animation

어라..? 왜 오다 말아요..

  • 다시 코드를 보니 X,Y 방향이 반대로 되어있었네요..ㅋㅋ;
//정상화
monster.x += directionY * monster.speed
monster.y += directionX * monster.speed

방향정상화

성공!

점수 획득 방식

  • 원래 스켈레톤 코드에선 몬스터가 화면밖으로 나갔을때 점수를 획득하도록 되어있는데,
    몬스터가 플레이어를 쫓아옴으로써 점수를 획득하지 못하게 되었다!

  • 이를 원래 계획한대로 지난 시간에 따라 올라갈 수 있도록 설계 해준다!

let previousTime = null;

//requestAnimationFrame 로 함수가 호출될 시, 인자로 performance.now() = ms 단위의 타임스태프 를 전달해준다.
function update(currentTime) {
    // 첫 동작일 시
    if (previousTime === null) {
        // currentTime을 previousTime에 대입하고
        previousTime = currentTime;
        // 함수를 재시작함
        requestAnimationFrame(update);
        return;
    }
    // 모든 환경에서 같은 게임 속도를 유지하기 위해 구하는 값
    // 프레임 렌더링 속도
    const deltaTime = (currentTime - previousTime) * 0.001;
    previousTime = currentTime;

    // 1초마다 1 점이 추가되도록  
    player.score += deltaTime 
}
  • 예전에 언리얼 엔진으로 게임을 만드는 강좌를 보며 Frame과 렌더링 시간(delta-time)의 개념과
    이를 이용해서 시간이 일정하게 흐르도록 만들어야 된다는 것을 배웠다.

  • 그래서 이를 반영하여 Frame Drop이 생겨도 1초당 점수가 올라가도록 설계를 하였다.

추가 개선안

  • 몬스터를 죽이는 방법을 아래에서 구현하면서, 몬스터를 죽였을 때도 score가 오르도록 하고 싶어졌다!
    (물론 서버에서 검증하는게 귀찮아지겠지만)
/* 전역 변수 */
let monsters = [];
function spawnMonster() {
    const size = Math.trunc(Math.random() * 20) + 10;
    monsters.push({
    x: Math.random() * canvas.width,
    y: Math.random() * canvas.height,
    health: 3,
    defense: 1,
    speed: 150,
    preDirection: 0,
    score: 10,
    size
    });
}
/* update() 내부 monsters.forEach() 내부 */
// 체력이 0이 될 경우 삭제
if (monster.health < 1) {
    player.score += monster.score
    monsters.splice(mIndex, 1);
}

중간 Play

  • 이제 본격적으로 게임을 실행해주도록 함수를 실행시켜주고 플레이 해본다!
// 1초마다 적 생성
setInterval(spawnMonster, 600); 
// 처음부터 currentTime을 인자로 전달해주어야 하기에 requestAnimationFrame을 바로 사용
requestAnimationFrame(update); 

중간Play

잘 작동한다!


플레이어 공격

  • 현재 플레이어를 쫓아오는 몬스터들을 처리할 방법이 없어, 시간이 지날수록 매우 어려워 진다!

=> 플레이어의 공격에 의해 몬스터가 죽도록 만들어야 겠다!

  • 일단 몬스터와 플레이어의 구조를 처음 설계한 스탯을 포함해서 선언해주어야 겠다!
    (player.score는 나중에 분리하자)
let player = { 
    x: canvas.width / 2,
     y: canvas.height / 2, 
    size: 20,
    score: 0,
    health: 3,
    damage: 2,
    speed: 3,
}
let monsters = [];

function spawnMonster() {
    const size = Math.random() * 20 + 10;
    monsters.push({ 
        x: Math.random() * canvas.width,
        y: Math.random() * canvas.height,
        health: 3,
        defense: 1,
        speed: 2
        size 
    });
}
  • 플레이어의 공격 방식을 가까운 몬스터를 향해 탄환을 쏘는 것으로 결정했다!

    => 이를 위해 탄환을 선언해준다!

let bullets = [];

function shootBullets() {
    const size = 5
    bullets.push({
    x: player.x,
    y: player.y,
    damage: player.damage,
    speed: 5,
    size
    });
}
  • 이제 탄환을 몬스터처럼 게임에서 생성되도록 만들어준다
/* update 내부 */
// 탄환 그리기
bullets.forEach((bullet, index) => {
    let directionY = 0;
    let directionX = 0;
    bullet.x += directionX * bullet.speed
    bullet.y += directionY * bullet.speed

    ctx.fillStyle = 'yellow';
    ctx.fillRect(bullet.x, bullet.y, bullet.size, bullet.size);

    // 화면 밖으로 나가면 제거
    if (bullet.y > canvas.height || bullet.x > canvas.width) {
        bullet.splice(index, 1);
    }
});
  • 중요한건 주위의 몬스터를 탐지해서 그 방향으로 이동해야 된다는 것이다..
  1. 근접한 몬스터를 찾아주는 로직! (거리 계산을 위해 Math.hypot()를 사용했다! )

     let closestMonster = null;
     let closestDistance = Infinity;
    
     monsters.forEach((monster, index) => {
             const dx = monster.x - player.x;
             const dy = monster.y - player.y;
             const distance = Math.hypot(dx, dy);
             if (distance < closestDistance) {
                 closestDistance = distance;
                 closestMonster = monster;
             }
     })
    
  2. 방향 계산을 해주는 Math.atan2() 와 방향(각도)을 x,y 죄표로 바꿔주는 Math.cos() Math,sin()을 사용!

     function shootBullets(shooter, target) {
         const size = 5
         bullets.push({
         x: player.x,
         y: player.y,
         damage: player.damage,
         speed: 5,
         // 방향 확인
         angle: Math.atan2(target.y - shooter.y, target.x - shooter.x),
         size
         });
     }
    
     /* update 내부 */
     // 탄환 그리기
     bullets.forEach((bullet, index) => {
         let directionY = 0;
         let directionX = 0;
         bullet.x += Math.cos(bullet.angle) * bullet.speed
         bullet.y += Math.sin(bullet.angle) * bullet.speed
    
         ctx.fillStyle = 'white';
         ctx.fillRect(bullet.x, bullet.y, bullet.size, bullet.size);
    
         // 화면 밖으로 나가면 제거
         if (bullet.y > canvas.height || bullet.x > canvas.width) {
             bullet.splice(index, 1);
         }
     });
    
  3. 이제 0.5초마다 발사되게 세팅!

     setInterval(shootBullets(player,closeMonster), 500);
    

    => ?? 총알이 안나간다..

     // 총알 목록 확인
     setInterval(() => {console.log(bullets)}, 500);
    

    총알발사

    아 맞다 setInterval은 함수자체만 넣어줘야지..실행 결과를 주는게 아니라

     setInterval(() => {shootBullets(player, closestMonster)}, 500); // 0.5초마다 탄환 발사
    
    • 근데 총알이 화면밖으로 나갈 때마다 게임이 멈춘다..?

      => 콘솔을 보니 bullets 대신 bullet으로 splice를 실행해서 그렇다..

     // 화면 밖으로 나가면 제거
     if (bullet.y > canvas.height || bullet.x > canvas.width) {
         // bullet은 그냥 총알 하나의 객체를 뜻하고 bullets가 발사된 모든 총알을 모아둔 배열이다..
         bullet.splice(index, 1); 
     }
    
  4. 이제 몬스터의 체력과 방어력을 비교하여 총알을 맞으면 죽게 만든다!

    • 일단 총알과 충돌한 몬스터를 찾아 체력을 깎아주고 총알을 삭제 시켜준다
     /* update() 내부의 bullets.forEach 내부 */
     //몬스터와 충돌 감지
     const monsterIndex = monsters.findIndex((monster) => colliedWith(bullet, monster))
     if(monsterIndex !== -1) { 
         // 몬스터 방어력 계산
         const damage = bullet.damage - monsters[monsterIndex].defense;
         // 만약 방어력이 높아서 음수가 되면 데미지를 안받는 형식으로
         monsters[monsterIndex].health -=  damage > 0 ? damage : 0;
         //총알 제거 
         bullets.splice(bIndex, 1);
     }
    
    • 몬스터의 체력이 0 이하가 되면 삭제되도록 해준다
     /* update() 내부의 monsters.forEach 내부 */
     // 체력이 0이 될 경우 삭제
     if (monster.health < 1) {
         monsters.splice(mIndex, 1);
     }
    

Front-End 개선안

Delta-time 적용

  • 생각해보니 점수를 추가해줄 때만 deltaTime을 사용 했지만, 플레이어와 몬스터/총알 의 위치를 그릴 때도 사용해 주어야 한다!

    => 프레임 드랍이 일어나면 채감되는 speed가 달라짐

      // 몬스터
      monster.x += directionX *monster.speed* deltaTime
      monster.y += directionY *monster.speed* deltaTime
      // 총알
      bullet.x += Math.cos(bullet.angle) * bullet.speed * deltaTime
      bullet.y += Math.sin(bullet.angle) * bullet.speed * deltaTime
      // 플레이어
      if ((keys['ArrowUp'] || keys["w"]) && player.y > 0) player.y -= player.speed  * deltaTime;
      if ((keys['ArrowDown'] || keys["s"]) && player.y < canvas.height - player.size) player.y += player.speed * deltaTime;
      if ((keys['ArrowLeft'] || keys["a"]) && player.x > 0) player.x -= player.speed * deltaTime;
      if ((keys['ArrowRight'] || keys["d"]) && player.x < canvas.width - player.size) player.x += player.speed * deltaTime;
    

    느려

    놀랍게도 현재 움직이고 있는 겁니다.

    => 이론상 초당 2px 을 이동한다는게 너무 느리단 생각이 들어 스피드값을 전체적으로 수정해 주었다.

      let player = {
          x: canvas.width / 2,
          y: canvas.height / 2,
          size: 20,
          score: 0,
          health: 3,
          damage: 2,
          speed: 300,
      }
    
      let monsters = [];
      function spawnMonster() {
          const size = Math.random() * 20 + 10;
          monsters.push({
          x: Math.random() * canvas.width,
          y: Math.random() * canvas.height,
          health: 3,
          defense: 1,
          speed: 200,
          size
          });
      }
    
      let bullets = [];
      function shootBullets(shooter, target) {
          // 추가로 너무 안맞는 느낌이여서 상향했다
          const size = 10
          if(("x" in target)) {
          bullets.push({
              x: shooter.x,
              y: shooter.y,
              damage: shooter.damage,
              speed: 2000,
              angle: Math.atan2(target.y - shooter.y, target.x - shooter.x),
              size
          });
          }
      }
    

몬스터 무빙

좌표 오류

  • 몬스터들이 몸을 흔들며 플레이어에게 다가온다..(무서워)

무빙치네

아마도 좌표에 소수점이 있어 이루어져 방향을 정하는 directionX,Y 부분들이 계속 바껴서 그런가보다

  • Math.trunc()로 소수점을 삭제하여 비교하는걸로 테스트 해봐야겠다.
if (Math.trunc(player.y) > Math.trunc(monster.y)) directionY = 1;
else if (Math.trunc(player.y) < Math.trunc(monster.y)) directionY = -1;
if (Math.trunc(player.x) > Math.trunc(monster.x)) directionX = 1;
else if (Math.trunc(player.x) < Math.trunc(monster.x)) directionX = -1;
monster.x += directionX * monster.speed * deltaTime
monster.y += directionY * monster.speed * deltaTime

괜찮아진듯

=> 좀 괜찮아 진거 같다(아마 안 고쳐지는 건 현재 랜덤으로 크기를 만들어서 그런건가..?)

관성 적용

  • 현재 모든 몬스터가 플레이어 방향으로 일괄적으로 오기 때문에, 인위적으로 느껴진다!

    => 움직임에 관성을 적용 시켜보자! (같은 방향이면 조금씩 가속되며 방향이 달라지면 감속되게)

  1. 몬스터에 이전 방향을 기억해주는 변수를 선언해준다.

     let monsters = [];
     function spawnMonster() {
         const size = Math.trunc(Math.random() * 20) + 10;
         monsters.push({
         x: Math.random() * canvas.width,
         y: Math.random() * canvas.height,
         health: 3,
         defense: 1,
         speed: 150,
         preDirection: 0,
         size
         });
     }
    
  2. 몬스터의 이전 방향(각도)을 찾아 대입 해준다

     // 방향 구하기(라디안 값)
     const direction = Math.atan2(player.y - monster.y, player.x - monster.x)
     // 방향을 이용해 각도 구하기
     const currentDirection = direction * (180 / Math.PI)
      if (monster.preDirection === 0) {
         monster.preDirection = currentDirection
     } 
    
  3. 만약 이전의 방향과의 차이로 가속/감속 설계

     let accSpeed = 0;
     // 방향 각도 차이가 30도 아래이면 가속
     if ( Math.abs(monster.preDirection - currentDirection) <= 3ㅇ0) {
     accSpeed = 20
     // 방향 각도 차이가 100도 이상이면 감속
     } else if (Math.abs(monster.preDirection - currentDirection) > 100) {
     accSpeed = -10
     // 적절한 진로 변경은 상관 없음
     } else {
     accSpeed = 0
     }
     // 이동속도가 0이 되는 걸 방지
     if (monster.speed + accSpeed < 0) {
     accSpeed = 1 - monster.speed 
     }
    
     monster.x += Math.cos(direction) * (monster.speed + accSpeed) * deltaTime
     monster.y += Math.sin(direction) * (monster.speed + accSpeed) * deltaTime
    

    => 나중에 시간이 된다면 가속/감속 부분도 변수로 지정 해두어야 겠다.

총알이 너무 안맞는다

  • 현재 총알이 몬스터를 죽이면 다음 몬스터를 찾는데 시간이 걸린다(?)
/* 전역 변수 */
let closestMonster = {};
// 근접 몬스터 찾기용 변수
let closestDistance = Infinity;
/* update() 내부의 monsters.forEach() 내부*/
//플레이어와 가까운 몬스터 찾기
const dx = monster.x - player.x;
const dy = monster.y - player.y;
const distance = Math.hypot(dx, dy);
if (distance < closestDistance) {
    closestDistance = distance;
    closestMonster = monster;
}

=> 생각을 해보니 가장 가까운 몬스터가 죽으면 그 몬스터와 제일 가까웠던 거리가 closeDistance에 전역 변수로 남는다..

=> 그러면 다른 몬스터와 더 가까워지기 전까지 죽은 몬스터 방향으로 총알을 쏜다

=> closeDistance를 update() 내부로 이동시키면, 계속해서 가장 가까운 적을 찾을 수 있을 것 이다!

/* 전역 변수 */
let closestMonster = {};

/* update() 내부*/
// 근접 몬스터 찾기용 변수
let closestDistance = Infinity;
/* monsters.forEach() 내부*/
//플레이어와 가까운 몬스터 찾기
const dx = monster.x - player.x;
const dy = monster.y - player.y;
const distance = Math.hypot(dx, dy);
if (distance < closestDistance) {
    closestDistance = distance;
    closestMonster = monster;
}

=> 해결

플레이어 체력 시스템

  • 현재는 플레이어가 한 번이라도 맞으면 게임 오버가 되는데, 이를 체력 시스템으로 구현해야겠다

게임오버 로직 변경

// 변경전 게임오버 로직 ( 피격 시 게임 오버 )
if (!gameOver && monsters.some((monster) => colliedWith(monster, player))) {
    alert('Game Over! Your score: ' + Math.floor(player.score).toString().padStart(6, 0));
    gameOver = true;
    document.location.reload();
}
  1. 몬스터에게 피격 시, 플레이어 체력이 닳게되는 로직 추가

     let damaged = false;
     // 몬스터 피격
     if (!damaged && monsters.some((monster) => colliedWith(monster, player))) {
         damaged = true;
         player.health--
         // 무적시간 1.5초 적용
         setInterval(() => damaged = false, 1500)
     }
    
  2. 플레이어 체력이 0이 되면 게임오버되도록 변경

     // 게임오버
     if (!gameOver && player.health < 1) {
         alert('Game Over! Your score: ' + Math.floor(player.score).toString().padStart(6, 0));
         gameOver = true;
         document.location.reload();
     }
    

체력바 생성

  • 플레이어의 체력을 사용자가 알 수 있도록 플레이어 위에 체력바를 추가해보려 한다.
// 체력바 배경
ctx.fillStyle = 'black';
ctx.fillRect(player.x, player.y - 10, player.size, 5);
// 체력 1칸당 비율
const healthPerUnit = player.size / 3; 
// 체력바 그리기
for (let i = 0;i < 3;i++) {
    // 현재 체력 
    if ( i < player.health) {
        ctx.fillStyle = 'green';
        ctx.fillRect(player.x + i * healthPerUnit, player.y - 10, healthPerUnit, 5);  
    }
    if (i < 2) {
        // 구분 줄
        ctx.fillStyle = 'black';
        ctx.fillRect(player.x + (i + 1) * healthPerUnit, player.y - 10, 2, 5); 
    }
}

=> 칸을 구분해주기 위해 for 문을 이용해 주었다

아이템 시스템 구현

  • 이전에 기획 때 생각해둔 아이템들을 생성하여 플레이어가 획득할 수 있도록 해야겠다!

아이템 생성

  • 위의 플레이어,몬스터,총알 처럼 새로운 변수를 선언해준다(나중에 class로 분리할 예정)
let getItem = false;
let items = [];
function spawnItem() {
    const size = 25
    items.push({
    x: Math.random() * canvas.width,
    y: Math.random() * canvas.height,
    damage: 1,
    health: 1,
    defense: 0,
    speed: 5,
    score: 5,
    size
    });
}
/* update() 내부 */
// 아이템 그리기
items.forEach((item) => {
    ctx.fillStyle = 'purple';
    ctx.fillRect(item.x, item.y, item.size, item.size);
});

아이템 적용

  • 아이템 획득 시(=충돌 시) 일어나는 이벤트를 작성해준다
/* update() 내부 */
// 아이템 획득
const itemIndex = items.findIndex((item) => colliedWith(item, player))
if (!getItem && itemIndex !== -1 ) {
    getItem = true;
    // 아이템 스탯 적용
    player.damage += items[itemIndex].damage
    // 최대 체력인 3보다 적을 때만 회복
    player.health += player.health < 3 ? items[itemIndex].health : 0
    player.defense += items[itemIndex].defense
    player.speed += items[itemIndex].speed
    player.score += items[itemIndex].score
    // 아이템 삭제
    items.splice(itemIndex, 1)
    getItem = false;
}
  • setInterval()로 일정시간마다 아이템이 스폰되도록 해준다

Last Play 및 계획

플레이영상

  • 현재 하루만에 여러 기능을 추가한 것은 좋지만, 아직도 구현하고픈 아이디어가 많다!

파일 분리

  • 지금의 파일 구조는 index.html에 합쳐져 있어 이를 분리해야 한다!

  • 추가로 현재 일부 값들을 변수가 아닌 상수로 사용하고 있는데, 이 값들을 변수로 선언해주는 작업도 필요해 보인다.

  1. index.html

    • 사용자에게 전달되는 사이트로 디자인은 고민 중이다.
  2. index.js

    • 게임의 기본 로직을 관리한다 gameLoop(=현재는 update) 및 전역변수 선언
  3. player.js

    • player의 스탯 및 메서드를 관리한다 (이동 / 플레이어 생성(draw))
  4. monster.js

    • monster의 스탯 및 메서드를 관리한다 (충돌 감지로 플레이어 공격)
  5. monstersController.js (현재의 monsters.forEach() 대용)

    • monster class 생성 및 메서드를 관리한다 (이동 / 몬스터 생성(draw))
  6. bullet.js

    • bullet의 스탯 및 메서드를 관리한다 (충돌 감지로 몬스터 피격)
  7. bulletsController.js (현재의 bullets.forEach() 대용)

    • bullet class 생성 및 메서드를 관리한다 (몬스터 감지 / bullet 생성(draw))
  8. item.js

    • item의 스탯 및 메서드를 관리한다(충돌 감지로 획득 효과 적용)
  9. itemsController.js

    • item의 생성빈도 및 생성 아이템을 조절해준다
  10. score.js

    • Player의 최고 점수와 현재 점수를 관리한다
  11. map.js

    • 만약 map을 구현하게 된다면 관련 로직을 여기에 넣을 예정이다.
  12. socket.js

    • 서버의 Websocket과 연결하기 위해 설정해주는 파일이다.
  13. assets folder

    • 기본적으로 클라이언트에 필요한 data 들을 파일명으로 관리하며, 필요에 따라 이미지 폴더도 만들 예정이다.

constants.js

  • 클라이언트의 버전(검증용)을 관리해준다

assets.js

  • 서버와 공통으로 이용하는 Data Table을 가져와주는 파일이다.
.
└── public                      
    ├── index.html
    ├── index.js    
    └── assets             
        ├── js
        │    ├── class
        │    │   ├── player.js
        │    │   ├── monster.js
        │    │   ├── monstersController.js
        │    │   ├── bullet.js
        │    │   ├── bulletsController.js
        │    │   ├── item.js
        │    │   ├── itemsController.js
        │    │   ├── map.js
        │    │   └── score.js   
        │    ├── socket.js
        │    ├── assets.js
        │    └── constants.js
        ├── css
        │    └── main.css
        └── images

맵 구현

  • 현재는 canvas가 웹페이지 창에 맞춰 맵으로 사용하고 있는데..
    원래 뱀파이어 서바이벌은 정해진 맵이 있으며, 그 안에서 움직이며 생존하는게 목표다.

  • 또한 플레이어가 화면의 중앙에 고정되어있어 맵을 이동하면서도 플레이어를 볼 수 있어야 한다!

  1. 정해진 크기가 있는 맵 구현하기

  2. 시점을 플레이어 고정으로 구현하기

  3. 시간이 된다면 벽을 만들어 변수창출하기( 나중에 원거리 몬스터를 만들 수도..?)

아이템 출현 빈도 / 플레이어 공격 주기 / 몬스터 스폰 주기 조절 + 스테이지 관리

  • 현재는 setInterval에 의해 일괄적으로 시간마다 스폰되는 것들을 각 변수들을 이용해 조절을 해야될 것 같다.

    => 몬스터는 출현되는 범위도 플레이어 근처가 아니도록 조절해줘야한다!

  • 파일 분리를 하며 관련 class(Controller) 에서 변수를 선언해 조절하면 좋을 것 같다

최고 점수 저장

  • 현재 최고 점수를 저장해두는 로직이 없다!

  • 파일 분리 때 score에서 관련 로직을 구현해야 겠다.

UI 개선

  • 지금화면에선 체력과 점수를 제외한 나머지 요소들을 볼 수 없다!

남은 시간이라던가, 현재 Player 스탯이라던가, 최고 점이라던가

  • 이러한 요소들을 볼 수 있도록 개선해 주어야 겠다.

  • 또한 웹페이지 접속 또는 게임 오버 시 게임을 바로 시작하지 않고 돌아갈 수 있는 메인화면이 있으면 좋겠다.

  • 시간이 허락한다면 몬스터와 플레이어 이미지도 넣어주고 싶다.
    ( 추가로 플레이어의 무적 상태도 알 수 있도록 설정)

서버와 연결 시

  • 로컬 스토리지에 uuid를 저장해두어 기존의 최고점을 재방문 시에도 알 수 있도록 해줘야 겠다

  • 채팅 기능을 사용할 수 있는 공간을 만들어야 겠다.

한줄 평 + 개선점

  • 눈에 직접 보이는 코딩을 하는 건 오랜만이라 재밌었다! (근데 구현 해야할 기능들이 왤캐 많은 거 같지..)

  • GIF 캡쳐 도구를 사용해서 좀 더 보기쉽게 만들었는데, 좀 더 능숙하게 사용할 수 있도록 자주 써보자

  • 개선/구현 해야할 기능들이 계속해서 떠오르는데 이를 위의 계획란처럼 미리 적어놓는 습관을 들여야 겠다.