오늘의 삽질
RogueLike TextGame
플레이어 행동(버프) 만들기
-
player.buff 메서드 구현!
- 적이 방어하는 경우, 힐을 할 수도있지만.. 좀 더 공격적으로 할 수 있는 선택지를 주고 싶었다!
//복습하기 buff(logs) { this.buff = Math.round(Math.random() * 10) / 10 logs.push(chalk.greenBright(`문제를 풀기 전, 배웠던 내용을 복습합니다..`)); logs.push(chalk.greenBright(`다음 문제 풀기의 Page 수 ${this.buff}배로 증가!(중첩불가)`)); }
-
buff 값 저장 변수 생성 + 연결
class Player { constructor() { // 정신력 this.maxHp = 100; this.hp = this.maxHp; // 무기 this.weapon = Weapons[0]; // 몰입도 this.minDmg = 20 + this.weapon.damage; this.maxDmg = 30 + this.weapon.damage; // 이해력 this.lev = 1; // 수면의 질 this.minHeal = 8 + this.weapon.heal; this.maxHeal = 15 + this.weapon.heal; // 통계 값 this.kills = 0; this.totalDmg = 0; this.totalHeal = 0; this.maxLev = this.lev; this.shield = false; //버프 배수 저장! this.buff = 0; } //문제 풀기 attack(monster, logs) { this.shield = false; logs.push(chalk.greenBright('문제를 풀기시작합니다!')); //랜덤 값 추출 let playerDmg = Math.floor(Math.random() * (this.maxDmg - this.minDmg)) + this.minDmg; // 버프 배수 적용 playerDmg = playerDmg * (1 + this.buff); this.buff = 0; //통계 값 기록 if (monster.hp > 0) { this.totalDmg += playerDmg; } else { this.totalDmg += playerDmg + monster.hp; } return playerDmg; } } // battle.js // 복습(버프) 눌렀을 경우 case '4': player.buff(logs); monster.action(player, logs); monster.hp > 0 ? monster.Day(player, logs) : 0;
-
오류!
-
왜인지 모르게 player의 메서드가 인식이 안됐다!(buff만..)
=> 이름을 buffer로 바꿔주자 되었다(?? 왜지)
-
-
추가 수정
- 버프를 통해 데미지가 어느정도 증가했는지 플레이어에게 제공하는게 좋아보여 수정하게 되었다!
-
player.attack() 수정
- 추가된 데미지를 알려주기 위해 두 변수를 배열로 묶어 반환해주게 변경하였다!
attack(monster, logs) { this.shield = false; let playerDmg2 = 0; logs.push(chalk.greenBright('문제를 풀기시작합니다!')); //랜덤 값 추출 let playerDmg = Math.round(Math.random() *(this.maxDmg - this.minDmg)) + this.minDmg; //버프 수치가 0이 아닐때! if (this.buff > 0) { //버프된 수치를 저장 playerDmg2 = Math.round(playerDmg* this.buff); this.buff = 0; } //통계 값 기록 if (monster.hp > 0) { this.totalDmg += playerDmg; } else { this.totalDmg += playerDmg + monster.hp; } //배열로 전환하여 반환! return [playerDmg, playerDmg2]; }
-
monster.damaged() 변경
- 반환된 값을 받아 플레이어 화면에 보여주기 위해 메서드 수정을 하였다
//Page 소모 damaged(value1, value2, logs) { // 방어 여부 if (this.shield) { logs.push(chalk.redBright(`머리에 아무것도 들어오지 않았습니다!`)); this.shield = false; } else if (this.hp - value1 > 0) { this.hp -= value1; logs.push(chalk.greenBright(`문제집을 ${value1} Page 만큼 풀었습니다!`)); // 만약 추가 데미지가 0보다 클시 if (value2 > 0) { // 데미지를 받으며 this.hp -= value2; // 플레이어에게 전달! logs.push(chalk.greenBright(`추가로 문제집을 ${value2} Page 만큼 풀었습니다!`)); } } else { this.hp = 0; logs.push(chalk.green('문제집을 전부 풀었습니다!')); } }
-
battle.js의 switch(입력받는) 함수 변경
- 반환된 player.attack() 을 monster.damaged()에 알맞게 연결해준다!
case '1': const Dmg = player.attack(monster, logs); monster.action(player, logs); if (player.hp > 0) { // 배열 값을 알맞게 배치하여 매개변수 입력! monster.damaged(Dmg[0], Dmg[1], logs); monster.hp > 0 ? monster.Day(player, logs) : 0; } break;
뽑기 시스템 구현 (+ 무기 추가)
- 보상 중 뽑기를 통해 이해력을 소모하며 무기를 뽑는 기능이 있으면 좋겠다고 생각했다!
-
reward class에 gamble 메서드 생성!
- reward class에 다른 메서드를 참조하여 구조를 조금 바꾸고 기능을 추가하는 형식으로 구현했다.
-
뽑기 등급별 확률 조정 및 비용 설정
- 등급별 확률로 등급을 먼저 정하고, 그 다음 같은 등급의 무기들 중 랜덤으로 뽑는 형식으로 구현을 해야겠다!
//뽑기 case '1': //비용이 부족할 때 if (player.lev < coast) { logs.push( chalk.redBright( `뽑기에 필요한 이해력이 부족합니다.`, ), ); logs.push( chalk.yellowBright( `이전 선택지로 이동합니다.`, ), ); results = false; break; //뽑기 기능 } else { // 등급 확정 const prob = Math.round(Math.random() * 100); //히든 if (prob < 2) { //등급이 H인 무기들을 찾아 ratingWeapons = Weapons.filter((a) => a.rating === "H"); //INDEX를 랜덤으로 선정해 가져온다! newWpn = ratingWeapons[Math.round(Math.random() * (ratingWeapons.length - 1))]; //S급 } else if (prob < 7) { ratingWeapons = Weapons.filter((a) => a.rating === "S"); newWpn = ratingWeapons[Math.round(Math.random() * (ratingWeapons.length - 1))]; //A급 } else if (prob < 15) { ratingWeapons = Weapons.filter((a) => a.rating === "A"); newWpn = ratingWeapons[Math.round(Math.random() * (ratingWeapons.length - 1))]; //B급 } else if (prob < 25) { ratingWeapons = Weapons.filter((a) => a.rating === "B"); newWpn = ratingWeapons[Math.round(Math.random() * (ratingWeapons.length - 1))]; //C급 } else if (prob < 40) { ratingWeapons = Weapons.filter((a) => a.rating === "C"); newWpn = ratingWeapons[Math.round(Math.random() * (ratingWeapons.length - 1))]; //D급 } else if (prob < 65) { ratingWeapons = Weapons.filter((a) => a.rating === "D"); newWpn = ratingWeapons[Math.round(Math.random() * (ratingWeapons.length - 1))]; //E급 } else if (prob <= 100) { ratingWeapons = Weapons.filter((a) => a.rating === "E"); newWpn = ratingWeapons[Math.round(Math.random() * (ratingWeapons.length - 1))]; } //뽑기 비용 지불 player.levelSet(-(coast), logs); //뽑기 비용 증가 coast++ // 여러 뽑을 수 있도록 choice 초기화로 while 지속 choice = 0; continue; }
-
무기 교체
- 이전에 강화를 적용시키기 위해 만들어둔 player.changeUpdate(무기교체) 메서드를 이용해 간단하게 연결 해둘 것이다!
//교체 case '2': if (newWpn) { logs.push( chalk.greenBright( `뽑은 무기와 교체합니다!`, ), ); player.changeUpdate(newWpn) //무기 교체 시, 다음 라운드 진행 results = true; break; } else { logs.push( chalk.redBright( `뽑은 무기가 존재하지 않습니다!`, ), ); choice = 0; continue; }
-
뽑은 무기 플레이어 전달
- 무기를 뽑고 플레이어에게 전달될 때, 뽑기 전의 값이 출력되어 확인이 안됀다!
=> 해결을 위해 뽑은 무기를 화면에 출력해주는 코드의 배열을 바꿔 주었다!
(원래 화면 출력 함수아래에 배치되어 이전 값을 불러왔던 오류 해결!)//뽑은 무기 값을 화면출력해주는 기능은 위로 logs.push( chalk.yellowBright( `| 뽑힌 필기구 | 등급 : ${rating} | 이름 : ${name} | 몰입도 상승 : ${damage} Page | 수면효과 상승 : ${heal} |`, ), ); // 애니메이션 효과로 화면을 출력하는 함수를 아래로 for await (const log of logs) { console.log(log); // 애니메이션 효과 딜레이 await new Promise((resolve) => setTimeout(resolve, 200)); } // 로그 초기화 logs = [];
-
뽑아둔 무기 + 코스트 증가 증발 오류
- 무기를 뽑고 뽑기 화면에서 나가면 증가한 코스트하고 뽑아둔 무기가 증발해버리는 상황이 생긴다! 이를 방지하기 위해 reward 이벤트에 값을 저장할 수 있도록 설계 해야겠다!
-
reward 이벤트에 변수 추가
-
return 값을 배열로 받아 구조분해 할당 기능을 이용
//reward 이벤트 // 초기값 let coast = stage * 2; let gambleWeapon = {}; // 뽑기 기능 case '3': [exit, coast, gambleWeapon] = await reward.gamble(player, coast, gambleWeapon); break; //reward class 메서드 async gamble(player, coast, newWpn) { // 선택이 되었을 때, 최종 값을 출력 if (results) { return [results, coast, newWpn]; } else if (choice) { const back = readlineSync.question('Back<<'); return [results, coast, newWpn]; } }
플레이어 선턴
-
만들어둔 구조에 의하면.. 플레이어와 몬스터가 서로 공격을 하면 플레이어가 먼저 피격당하여 죽게되는 경우가 생긴다!(분명 플레이어가 몬스터를 죽이는 턴인데..)
-
해결 방법으로 몬스터가 데미지를 입을 시 죽게되는 경우, 방어를 제외한 행동을 제한 해야겠다!
-
monster.action()에 제한 조건 걸기
-
action에 player의 데미지를 확인하기 위해 매개변수 추가
-
action에서 현재 HP와 받을 DMG를 계산하여 죽었는지 확인
-
죽었을 시, protect를 제외한 나머지 행동을 제한시키기!
-
// 몬스터의 랜덤 행동
action(player, logs, Dmg) {
//몬스터가 죽었는지 여부 확인
const damaged = this.hp - (Dmg[0] + Dmg[1])
//랜덤 값 추출
this.shield = false;
const monsterAct = Math.round(Math.random() * 100);
if (monsterAct <= 30) {
//damaged가 양수 일때만 행동을 실행한다!
(damaged > 0) ? this.heal(logs) : 0;
} else if (monsterAct <= 75) {
(damaged > 0) ? this.attack(player, logs) : 0;
} else if (monsterAct <= 90) {
this.protect(logs);
} else if (monsterAct <= 100) {
(damaged > 0) ? this.skills(player, logs) : 0;
} else {
logs.push('오류!');
}
}
등급에 따른 차등 강화 비용
- 간혹 높은 등급의 무기를 얻었을 때, 강화비용이 너무 싸서 게임이 쉬워지는 것 같아 고등급에 따라 강화비용이 달라졌으면 좋겠다!
-
weapon class 에 강화비용 매개변수 설정
class Weapon { constructor(name, damage, heal, rating, type, plus, prob, dayDmg) { this.upgradeCoast = 2; //[고]등급 무기는 기본 강화 비용 증가 switch (this.rating) { case "B": this.upgradeCoast = 4; break; case "A": this.upgradeCoast = 6; break; case "S": this.upgradeCoast = 8; break; case "H": this.upgradeCoast = 10; break; } } }
-
reward class의 강화 메서드에 연결
//reward class async upgrade(player, stage) { //강화 비용에 업그레이드 코스트 연관성 부여 let upgradeCoast = player.weapon.upgradeCoast + player.weapon.plus * 2 + stage; }
보스 시스템 구현 + 승리 조건 설정
- 10라운드 이후 보스몬스터와의 전투를 구현하고 싶었다!
-
몬스터 클래스를 복사하여 보스몹 클래스를 생성해준다! (+기본 스탯 뻥튀기 시키기)
-
game.js의 논리를 약간 수정하여 10스테이지 이상부턴 보스몬스터만 나오고, 일정 스코어를 만족하면 승리하게 설정하기
export async function startGame() { console.clear(); const player = new Player(); let stage = 1; let status; let end = false; // 일정 스코어를 넘기면 엔딩 const maxscore = Math.round(Math.random() * 500) + 2000 while (!end) { let monster; //특정 조건 만족시 시험 생성 if (stage > 10) { monster = new bossMonster(); // 이외엔 일반 문제집 생성 } else { monster = new Monster(stage); } status = await battle(stage, player, monster, maxscore); // 도주 시 if (status === 'run') { continue; // 문제집을 풀었을 시 } else if (status) { const reward = new Rewards(player, monster, stage); await rewardEvent(stage, player, reward, maxscore); stage++; //엔딩 조건 end = (player.score > maxscore) // 죽었을 시 } else { return await endgame(stage, player); } } return await win(player); }
리팩토링 + 밸런스 패치
-
현재 reward class에 이벤트 관련 메서드가 있는데.. 이걸 reward(event) 에 옮겨도 문제가 없어보이고, 훨씬 덜 헷갈릴 것 같아 이동하였다!
=> 추가로 함수 선언식보다 함수 표현식이 hoisting 과정에서 오류가 나올 확률이 적다들어 함수들을 표현식으로 변경해주었다!
-
밸런스 상 최대체력 증가비율보다 회복률 증가 비율이 높아 이를 조정하였고, type에 따라 증가하는 데미지도 좀 크게 상향을 하였다!
-
무기도 여럿 추가하였으나.. 좀더 넣어봐야 겠다
개선점 분석
-
현재 상황에 만족하고 있다!
-
내일은 추가 기능 (아이템 서버 연동 / 엔딩 시 이름을 적고 스코어 올리기) 및 추가 리팩토링과 밸런스 패치로 완성도를 높여야 겠다!