개인과제는 어느정도 전체적으로 마무리한 것 같습니다. 일단은 여러번 테스트후 제출완료...
전투 기획
1. 플레이어에게는 여러 가지 선택지가 주어진다.
(공격, 연속공격, 방어, 도망 등…)
- 공격 - 100% 확률로 공격 성공, 몬스터에게 피격
- 연속공격 - 일정 확률로 2번 공격 성공, 몬스터에게 피격
- 방어 - 일정확률로 방어 성공, 몬스터에게 60%의 공격력으로 반격
- 도망 - 일정 확률로 해당 스테이지 클리어
2. 플레이어의 공격력은 최소공격력과 최대공격력이 존재하며, 최소공격력은 정수이고 최대공격력은 플레이어가 가지고 있는 최대 공격력 배율에 따라 달라진다.
3. 스테이지 클리어 시, 아래의 능력치 중 하나가 정해진 수치내에서 랜덤으로 증가한다. 증가하는 능력치는 아래와 같다.
- 체력 20~50
- 최소 공격력 5~20
- 최대 공격력 배율 (0.1 ~ 1)
- 도망 확률 1~3
- 연속 공격 확률 3~7
- 방어 수치 3~10
4. 스테이지 클리어 시, 체력이 일정수치 회복된다.
5. 기본 전투형태는 턴제 형식으로 진행된다.
6. 스테이지가 진행될 수록 몬스터의 체력과 공격력도 강해진다.이 전투기획에 맞게 로그라이크 텍스트게임을 만드는 과제입니다. 추가로 구현하고 싶거나 게임의 튜터님꼐선 흐름에 크게 벗어나지 않는선에서 로그라이크 장르에 기획을 새로 만들어보는 것도 좋다고 합니다.
저는 일단 기획에 맞게 구현하고 추가기능들을 기획에 추가하여 구현할려고 합니다.
제가 추가로 구현한 기능들은 스킬사용과 로컬로 업적시스템을 만들어 보았습니다.
오늘은 이 2가지 기능의 구현 과정을 적어 볼려고 합니다.
구현한 내용부터 보여드리겠습니다.
선택지에 '5. 스킬사용'을 추가해 입력을 받아 사용할 수 있게 만들었습니다.
'5' 를 입력하면 사용할 수 있는 스킬을 표시해 줍니다.
일단은 '1. 파이어볼'만 구현해 보았습니다. '1' 을 입력해 사용해 보겠습니다.
사용을하면 관련 로그를 띄우고 약간의 딜레이 후에 마법 공격을 하고 'Mp'수치가 1줄어 듭니다.
스킬사용에 이용된 함수들입니다.
원래 switch안에 처음입력을 받게 구현되어있습니다. 여기서 5를 입력했을때 다시
switch를 이용해 플레이어가 스킬을 선택해 그 입력에 맞게 사용하게 구상하여 만들었습니다.// 실제 배틀이 이루어지는 함수 const battle = async (stage, player, monster) => { let logs = []; let count = 0; // 행동한 횟수 음음 let result = false; // 확률의 성공여부를 확인할 변수 // 스테이지에 올라갈때마다 if (stage > 1) player.Heal(20, logs); logs.push(chalk.magenta(`야생의 몬스터와 마추졌습니다!!\n`)); let reLog = function () { console.clear(); displayStatus(stage, player, monster); logs.forEach((log) => console.log(log)); }; while (player.hp > 0) { reLog(); // 클리어 조건 if (monster.hp <= 0) break; console.log( chalk.green( `\n1. 공격한다 2. 연속 공격 (${player.doubleAtk}%) 3. 방어한다 (${player.defence}%) 4. 도망친다 (${player.run}%) 5. 스킬사용`, ), ); let choice = readlineSync.question('당신의 선택은? '); switch (choice) { case '1': // 일반 공격 count++; player.attack(monster, logs, count); monster.attack(player, logs, count); break; case '2': // 더블 공격 count++; if (Probability(player.doubleAtk)) { logs.push(chalk.green(`[${count}] 연속공격에 성공했습니다.`)); player.attack(monster, logs, count); player.attack(monster, logs, count); } else { logs.push(chalk.green(`[${count}] 연속공격에 실패했습니다.`)); } monster.attack(player, logs, count); break; case '3': // 방어하기 count++; result = Probability(player.defence); player.Defence(result, monster, logs, count); if (result === false) { monster.attack(player, logs, count); } break; case '4': // 도망가자-선우정아 count++; logs.push(chalk.green(`[${count}] 도망을 시도합니다....`)); reLog(); result = Probability(player.run); await Escape(result, monster, logs, count, player); if (result === false) { monster.attack(player, logs, count); } break; case '5': if (player.mp <= 0) { await LoadDelay(500, chalk.gray(`마나가 부족합니다.`)); break; } console.log( chalk.green( `\n1. 파이어볼`, ), ); choice = readlineSync.question('스킬을 선택하세요 '); switch (choice) { case '1': count++; logs.push(chalk.green(`[${count}] 화염구를 만들기 시작했습니다!!`)); reLog(); await player.Skill_Fireball(monster, logs, count); monster.attack(player, logs, count); break; default: logs.push(chalk.gray(`${choice}를 선택하셨습니다.`)); logs.push(chalk.red(`올바른 선택을 하세요`)); } break; default: logs.push(chalk.gray(`${choice}를 선택하셨습니다.`)); logs.push(chalk.red(`올바른 선택을 하세요`)); } } reLog(); };
Skill_Fireball(target, logs, count)
Player class 내부안에 만든 실제 파이어볼 실행 함수 입니다.
전에 만든LoadDelay함수를 이용해 딜레이 표현을 하였습니다.
공격력의 3배를 공격해 줍니다.
async Skill_Fireball(target, logs, count) { this.mp--; this.Num_of_skill_uses++; // 사용횟수 체크 AchievementFunc.SorcererAchievement(this); // 업적 await LoadDelay(2000, `..점점 커지는 중..`); const skillAtk = Math.floor((Math.random() * (this.maxAtt - this.minAtt) + this.minAtt)) * 3; // 3배 공격 target.hp -= skillAtk; logs.push(chalk.blueBright(`[${count}] 몬스터에게 ${skillAtk}의 마법피해를 입혔습니다.`)); }
구현한 내용부터 보여드리겠습니다.
첫 메인화면에서 '2'를 입력하여 업적을 확인해 보겠습니다.
아직 게임을 시작하지 않아 업적이 없는 것을 확인할 수 있습니다.
다시 '1'을 입력해 메인화면에서 게임을 시작해 업적을 얻어 보겠습니다.
첫 스킬사용시 '업적 : 당신은 마법사?'를 얻습니다.
첫 스테이지클리어시 '업적 : 첫 스테이지 클리어'를 얻을 수 있습니다.
이제 죽어보겠습니다.
(죽으면 메인화면으로 돌아오게 해놓았습니다.)
그러고 다시 업적 확인을 해보면 제대로 알맞는 업적이 생긴걸 확인할 수 있습니다.
업적시스템에 이용된 함수들입니다.
일단 메인화면에서 '2'를 입력받아 업적창을 띄울 수 있게 만들어 주었습니다.// 업적창을 띄어주는 함수입니다. function ChllengeSuccessDisplay() { console.clear(); console.log(chalk.bgRedBright('===================달성 업적===================\n')); Achievement.sort(); Achievement.forEach((log) => console.log(log)); console.log(chalk.yellow('\n메인 메뉴로 돌아가기 1을 입력하세요...')); const choice = readlineSync.question('입력: '); switch (choice) { case '1': console.log(chalk.green('게임을 시작합니다.')); // 여기에서 새로운 게임 시작 로직을 구현 start(); break; default: console.log(chalk.red('올바른 선택을 하세요.')); ChllengeSuccessDisplay(); // 유효하지 않은 입력일 경우 다시 입력 받음 } }
AchievementPush(str, AchievementNum, AchievementStr)
실제로 달성한 업적을 저장할 배열을 선언해주고,
함수를 통해 push하여 해당 업적을 저장합니다.
export로 외부파일 game.js파일(실제 전투가 이루어지는 로직이 있는 파일)에서도 사용가능하게 만듭니다. modules
// game.js에 이렇게 선언해 주어 AchievementPush()함수를 사용합니다.
import { AchievementPush } from "./server.js";
const Achievement = []; export function AchievementPush(str, AchievementNum, AchievementStr) { if (Achievement.includes(chalk.magenta(AchievementNum) + AchievementStr) === false) { console.log(chalk.bgGray(`\n업적 : ${str}\n`)); Achievement.push(chalk.magenta(AchievementNum) + AchievementStr); } }
AchievementFunc
한번에 관리하기 용이하게 적용할 업적들의 조건들을 검사하고
업적을 적용할 함수들을 객체에 저장하여 만들었습니다.
이제 이 함수는 업적에 맞게 적절한 구간에 실행해주어 업적을 저장해 줍니다.
// 업적 달성 조건을 보고 업적달성하는 함수 const AchievementFunc = { StageClearAchievement: async function (stage) { if (stage > 1 && stage <= 2) { AchievementPush('첫 스테이지 클리어', '1.', " 첫 스테이지 클리어") } else if (stage >= 11) { AchievementPush('킹 슬레이어', '2.', " 킹 슬레이어"); } }, SorcererAchievement: async function (player) { if (player.Num_of_skill_uses === 1) { AchievementPush('당신은 마법사?', '3.', " 당신은 마법사?"); } else if (player.Num_of_skill_uses === 5) { AchievementPush('대마법사', '4.', " 대마법사"); } }, RunAchievement: async function (player) { if (player.Num_of_run_uses === 5) { AchievementPush('도망자', '5.', " 도망가자-선우정아"); } } };
끝~~ 원래는 스킬사용까지만 구현하고 과제를 제출하였지만 다른분 블로그를 보고
레벨디자인을 재구성을 하시고 업적시스템을 구현 한것을 보고 자극을 받아 '나도 한번 해봐야겠다' 싶어 도전해보았습니다.
그리고 터미널창의 크기도 늘리면서 플레이에 좀 더 편하게 꾸며서 만드시게 신기했습니다.