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++;
}
})
어라..? 왜 오다 말아요..
- 다시 코드를 보니 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);
잘 작동한다!
플레이어 공격
- 현재 플레이어를 쫓아오는 몬스터들을 처리할 방법이 없어, 시간이 지날수록 매우 어려워 진다!
=> 플레이어의 공격에 의해 몬스터가 죽도록 만들어야 겠다!
- 일단 몬스터와 플레이어의 구조를 처음 설계한 스탯을 포함해서 선언해주어야 겠다!
(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);
}
});
- 중요한건 주위의 몬스터를 탐지해서 그 방향으로 이동해야 된다는 것이다..
-
근접한 몬스터를 찾아주는 로직! (거리 계산을 위해 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; } })
-
방향 계산을 해주는 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); } });
-
이제 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); }
-
-
이제 몬스터의 체력과 방어력을 비교하여 총알을 맞으면 죽게 만든다!
- 일단 총알과 충돌한 몬스터를 찾아 체력을 깎아주고 총알을 삭제 시켜준다
/* 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
=> 좀 괜찮아 진거 같다(아마 안 고쳐지는 건 현재 랜덤으로 크기를 만들어서 그런건가..?)
관성 적용
-
현재 모든 몬스터가 플레이어 방향으로 일괄적으로 오기 때문에, 인위적으로 느껴진다!
=> 움직임에 관성을 적용 시켜보자! (같은 방향이면 조금씩 가속되며 방향이 달라지면 감속되게)
-
몬스터에 이전 방향을 기억해주는 변수를 선언해준다.
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 }); }
-
몬스터의 이전 방향(각도)을 찾아 대입 해준다
// 방향 구하기(라디안 값) 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 }
-
만약 이전의 방향과의 차이로 가속/감속 설계
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();
}
-
몬스터에게 피격 시, 플레이어 체력이 닳게되는 로직 추가
let damaged = false; // 몬스터 피격 if (!damaged && monsters.some((monster) => colliedWith(monster, player))) { damaged = true; player.health-- // 무적시간 1.5초 적용 setInterval(() => damaged = false, 1500) }
-
플레이어 체력이 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에 합쳐져 있어 이를 분리해야 한다!
-
추가로 현재 일부 값들을 변수가 아닌 상수로 사용하고 있는데, 이 값들을 변수로 선언해주는 작업도 필요해 보인다.
-
index.html
- 사용자에게 전달되는 사이트로 디자인은 고민 중이다.
-
index.js
- 게임의 기본 로직을 관리한다 gameLoop(=현재는 update) 및 전역변수 선언
-
player.js
- player의 스탯 및 메서드를 관리한다 (이동 / 플레이어 생성(draw))
-
monster.js
- monster의 스탯 및 메서드를 관리한다 (충돌 감지로 플레이어 공격)
-
monstersController.js (현재의 monsters.forEach() 대용)
- monster class 생성 및 메서드를 관리한다 (이동 / 몬스터 생성(draw))
-
bullet.js
- bullet의 스탯 및 메서드를 관리한다 (충돌 감지로 몬스터 피격)
-
bulletsController.js (현재의 bullets.forEach() 대용)
- bullet class 생성 및 메서드를 관리한다 (몬스터 감지 / bullet 생성(draw))
-
item.js
- item의 스탯 및 메서드를 관리한다(충돌 감지로 획득 효과 적용)
-
itemsController.js
- item의 생성빈도 및 생성 아이템을 조절해준다
-
score.js
- Player의 최고 점수와 현재 점수를 관리한다
-
map.js
- 만약 map을 구현하게 된다면 관련 로직을 여기에 넣을 예정이다.
-
socket.js
- 서버의 Websocket과 연결하기 위해 설정해주는 파일이다.
-
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가 웹페이지 창에 맞춰 맵으로 사용하고 있는데..
원래 뱀파이어 서바이벌은 정해진 맵이 있으며, 그 안에서 움직이며 생존하는게 목표다. -
또한 플레이어가 화면의 중앙에 고정되어있어 맵을 이동하면서도 플레이어를 볼 수 있어야 한다!
-
정해진 크기가 있는 맵 구현하기
-
시점을 플레이어 고정으로 구현하기
-
시간이 된다면 벽을 만들어 변수창출하기( 나중에 원거리 몬스터를 만들 수도..?)
아이템 출현 빈도 / 플레이어 공격 주기 / 몬스터 스폰 주기 조절 + 스테이지 관리
-
현재는 setInterval에 의해 일괄적으로 시간마다 스폰되는 것들을 각 변수들을 이용해 조절을 해야될 것 같다.
=> 몬스터는 출현되는 범위도 플레이어 근처가 아니도록 조절해줘야한다!
-
파일 분리를 하며 관련 class(Controller) 에서 변수를 선언해 조절하면 좋을 것 같다
최고 점수 저장
-
현재 최고 점수를 저장해두는 로직이 없다!
-
파일 분리 때 score에서 관련 로직을 구현해야 겠다.
UI 개선
- 지금화면에선 체력과 점수를 제외한 나머지 요소들을 볼 수 없다!
남은 시간이라던가, 현재 Player 스탯이라던가, 최고 점이라던가
-
이러한 요소들을 볼 수 있도록 개선해 주어야 겠다.
-
또한 웹페이지 접속 또는 게임 오버 시 게임을 바로 시작하지 않고 돌아갈 수 있는 메인화면이 있으면 좋겠다.
-
시간이 허락한다면 몬스터와 플레이어 이미지도 넣어주고 싶다.
( 추가로 플레이어의 무적 상태도 알 수 있도록 설정)
서버와 연결 시
-
로컬 스토리지에 uuid를 저장해두어 기존의 최고점을 재방문 시에도 알 수 있도록 해줘야 겠다
-
채팅 기능을 사용할 수 있는 공간을 만들어야 겠다.
한줄 평 + 개선점
-
눈에 직접 보이는 코딩을 하는 건 오랜만이라 재밌었다! (근데 구현 해야할 기능들이 왤캐 많은 거 같지..)
-
GIF 캡쳐 도구를 사용해서 좀 더 보기쉽게 만들었는데, 좀 더 능숙하게 사용할 수 있도록 자주 써보자
-
개선/구현 해야할 기능들이 계속해서 떠오르는데 이를 위의 계획란처럼 미리 적어놓는 습관을 들여야 겠다.