내일배움캠프 Node.js 트랙 10일차
1. ZEP에서 이루어진 로그라이크 게임 만들기 프로젝트
(1) 로그라이크 게임 기획
이번 프로젝트는 개별 과제로, 로그라이크 게임 개발이라는 큰 목표 아래 알고리즘 특강과 실습 시간이 주어졌다. 듣고 있던 자바스크립트 문법 강의를 마무리하고 본격적으로 게임 만들기에 앞서서 주요 골격부터 고민해봤다.
우선 로그라이크라는 장르는 너무 광범위하기 때문에 보다 세부적인 장르를 정할 필요가 있었다. 원래도 내가 개발하고 싶었던 건 턴제 덱빌딩 장르였기에 그리 어려운 선택지는 아니었다. 덱빌딩 기반의 로그라이크. 참 익숙하고 친근하다. 레퍼런스가 되어줄 게임들이 나의 스팀 라이브러리에 산재해있기 때문에 아이디어가 고갈날 염려는 없었다. 다만 '카드로 적을 물리친다'라는 기본 전제는 약간의 골칫덩이였다. 플레이어의 스탯이 증가하면 플레이어가 직접적으로 세지는 일반적인 게임과 달리, 카드 게임에서는 이 둘이 별개의 문제였기 때문이다.
뭐, 그래도 처음에는 스토리부터 구상하는 게 예의 아닐까. 첫 아이디어는 다음과 같은 메모로부터 출발했다.
다소 짬뽕스러운 컨셉으로 보인다면 정확하다. 슬더스+돌겜+유희왕을 적절히 섞은 게임을 만들어보고 싶었다.(그러고 싶었다는 것이지 만들었다는 얘기는 아니다.) 레벨 디자인에서 가장 중요한 변수가 있다면 '카드와의 유대감'일 것이다. 주인공 자체는 공격력도 뭣도 없고 카드 발동 확률조차 낮지만, 개발자(나)가 준 특권을 통해 '카드와의 유대감'은 높일 수 있다. 바로 그것이 카드의 발동 확률을 높여주고 카드가 가진 수치에 배수로 계산 될 예정이다.
(2) 객체 디자인
객체 디자인이라는 거창한 말로 써놨지만 사실은 별 게 아니다. 그냥 만들려는 객체의 구조를 자연어로 미리 적어본 게 전부였다.
플레이어(Class Player)
속성
이름 name
현재 체력 hp
최대 체력 maxHp
카드와의 유대감 bondingIndex(%p)
부주의함 carelessness(%p)
덱의 크기 deckSize
핸드의 크기 handSize
도망 확률 runAwayProb
보유한 장물 hasStolenGoods=[]
현재 턴에 카드를 사용했는가? isPlayCard
몬스터를 조우했는가? isThereAnyMonster
행동
카드를 뽑는다. drawCard()
카드를 사용한다. playCard()
장물을 잃어버린다. loseStolenGoods()
덱의 크기가 증가한다. incDeckSize() 최대 100
덱의 크기가 감소한다. decDeckSize() 최소 1
핸드의 크기가 증가한다. incHandSize() 최대 10
핸드의 크기가 감소한다. decHandSize() 최소 1
최대 체력이 증가한다. incMaxHp() 최대 5000
최대 체력이 감소한다. decMaxHp() 최소 1
현재 체력이 증가한다.(단, <=maxHp) incHp()
현재 체력이 감소한다.(단, >=0) decHp()
유대감 증가 incBondingIndex() 최대 1000
유대감 감소 decBondingIndex() 최소 -70
도망을 친다. runAway()
이 중에는 실제로 구현된 것도 있고, 아예 빼버린 것도 존재한다. 메소드 또한 너무 과하다 싶을 정도로 기능을 욱여 넣어 놓아서 일부는 다른 클래스에 양보하기도 했다. 다만, 자연어로 써둔 메모를 보면서 코드를 어떻게 짤지 미리 구상을 해뒀기 때문에 텅 빈 모니터를 보는 시간이 많이 줄어들었다. 확실한 방향을 정해준다는 면에서 객체를 미리 디자인했던 게 많은 도움이 되었던 것 같다.
(2) 의사 코드
인간은 기계가 아니고 나는 숙련자조차 아니기 때문에 아직까진 자연어가 친숙한 건 어쩔 수가 없는 것 같다. 객체 디자인을 미리 해놓았듯이 각종 함수, 메서드 등을 만들기에 앞서 의사 코드 또한 작성해두기로 결정했다.
이를 테면 이런 식이다.
유대감
유대감이 높아질 수록 카드 발동확률이 증가
유대감은 0부터 시작 최대 100까지 가능
발동확률은 카드 티어에 따라 결정됨 + 유대감 수치로 나머지를 채우는 형식
발동확률의 증감은 유대감 수치의 증갑이 퍼센트 포인트(%p)로 적용됨
즉, 유대감 수치를 25포인트를 획득했으면 발동 확률이 25퍼센트 포인트가 상승한다.
→ 노말 카드를 써도 발동률이 100퍼센트를 만족.
발동확률
노말 카드 - 75퍼
레어 카드 - 80퍼
에픽 카드 - 85퍼
레전더리 - 90퍼
100에서부터 초과한 유대감 수치가 카드의 위력에 퍼센트로 증가
예를 들어 발동 확률이 120%가 되면 카드의 위력이 1.2배 증가
만약 메모를 거치지 않고 바로 코드로 작성해보라고 했다면 게임을 완성하기가 무척 힘들었을 것이다. 수없이 많은 변수명을 써놓고 뭘 어디에 사용해야 할지 고민하느라 머리를 감쌌을지도 모르고. 그러나, 넣어야 하는 기능이나 컨셉을 글로 적어보면 수식을 어떻게 작성해야 할지 한 눈에 그려지기 시작한다.
위 노트의 내용을 수학적으로 표현해보면 이런 식의 수식이 곧장 떠오르게 될 것이다.
카드 발동 확률 = 카드 자체 발동 확률 + 카드와의 유대감
카드의 파워 지수 = 1 + (유대감 초과분)/100
이런 중간 과정을 거친 후, 결과물을 코드로 완성해보는 것이다.
👨💻 실제 코드로 구현한 결과물
cardPlay(playingCard, monster) {
// 100 미만의 랜덤한 밸류를 구하고 카드 발동 확률이랑 비교해보기
const randomValue = Math.random() * 100;
// 카드 발동 확률 = 카드 자체 발동 확률 + 카드와의 유대감
const cardActProb = playingCard.actProb + this.bondingIndex;
// 카드 발동 확률이 랜덤한 숫자를 이기면 발동!
if (randomValue <= cardActProb) {
if (cardActProb > 100) {
let cardPower = 1 + (cardActProb - 100) / 100;
monster.monsterLoseHp(playingCard, cardPower);
this.updateHpByCard(playingCard, cardPower);
this.updateDefenseByCard(playingCard, cardPower);
} else {
monster.monsterLoseHp(playingCard);
this.updateHpByCard(playingCard);
this.updateDefenseByCard(playingCard);
}
setMessage('카드 발동 성공!');
} else if (randomValue > cardActProb) {
setMessage('카드 발동 실패!');
}
}
// 몬스터 클래스...monsterLoseHp 메서드...
monsterLoseHp(playingCard, cardPower = 1) {
if (playingCard.attackDmg >= 0 && playingCard.spellDmg >= 0) {
this.hp -= (playingCard.attackDmg + playingCard.spellDmg) * cardPower;
}
}
유대감 초과분이 없을 시에는 monsterLoseHp메서드의 cardPower인자의 초기값을 1로 설정해두고 if문을 통해 초과분이 있을 때에만 수식이 적용되게끔 코드를 짜두었다. 적절한 인자를 외부 객체로부터 전달받아서 작동할 수 있게 만드는 게 가장 까다로운 부분이었다.(서로 다른 객체에서는 this난 변수가 참조하는 렉시컬 환경이 다르기 때문에) 배운 것을 되짚어 보면서 하나하나 적어내렸더니 의도대로 돌아가는 모습에 뿌듯함을 느끼게 되었다.
물론, 오늘의 TIL에서 강조하고 싶은 건 내가 짠 코드가 얼마나 효율적이고 완벽한가가 아니다. 게임에 원하는 기능을 넣기 위해 스스로 코드를 짜야할 때, 어떻게 시작하는 게 도움이 되었는지를 적어두고 싶었다. 모두가 완벽한 상태에서 코딩을 시작하는 건 아닐 테니까. 이렇게 연습을 해두면 언젠가는 완전히 익숙해져서 바로 코드를 칠 수 있는 날이 오기도 하지 않을까? 그날이 오기 전까지는 언제나 노트에 미리 적어보고 구상을 발전시키는 습관을 가지는 게 좋을 것 같다.
내일의 TIL에는 자바스크립트 디버그 터미널을 통해 버그의 원인을 어떻게 추측해낼 수 있는지 적어보도록 하겠다.
'개발일지 > TIL(Today I Learned)' 카테고리의 다른 글
2024-11-12 (1) | 2024.11.12 |
---|---|
2024-11-11 (5) | 2024.11.11 |
2024-11-07 (4) | 2024.11.07 |
2024-11-06 (0) | 2024.11.06 |
2024-11-05 (4) | 2024.11.05 |
댓글