JavaScript - 텍스트 RPG 예제와 class 문법

Jenna·2022년 12월 22일
1

javascript

목록 보기
12/16
post-thumbnail

class문법을 이용하여 텍스트로 RPG게임을 구현


📌순서도 그리기



게임 만들기는 비교적 복잡한 순서도를 가진다.
차근차근 생각해보면서 화면전환에 따라 기획한다.


📌 코드보기


<body>
    <form id="start-screen">
        <input id="name-input" placeholder="주인공 이름을 입력하세요!" />
        <button id="start">시작</button>
    </form>
    <div id="screen">
        <div id="hero-stat">
            <span id="hero-name"></span>
            <span id="hero-level"></span>
            <span id="hero-hp"></span>
            <span id="hero-xp"></span>
            <span id="hero-att"></span>
        </div>
        <form id="game-menu" style="display:none">
            <div id="menu-1">1.모험</div>
            <div id="menu-2">2.휴식</div>
            <div id="menu-3">3.종료</div>
            <input id="menu-input" />
            <button id="menu-button">입력</button>
        </form>
        <form id="battle-menu" style="display:none;">
            <div id="battle-1">1.공격</div>
            <div id="battle-2">2.회복</div>
            <div id="battle-3">3.도망</div>
            <input id="battle-input" />
            <button id="battle-button">입력</button>
        </form>
        <div id="message"></div>
        <div id="monster-stat">
            <span id="monster-name"></span>
            <span id="monster-hp"></span>
            <span id="monster-att"></span>
        </div>
    </div>

    <script>
        const $startScreen = document.querySelector('#start-screen');
        const $gameMenu = document.querySelector('#game-menu');
        const $battleMenu = document.querySelector('#battle-menu');
        const $heroName = document.querySelector('#hero-name');
        const $heroLevel = document.querySelector('#hero-level');
        const $heroHp = document.querySelector('#hero-hp');
        const $heroXp = document.querySelector('#hero-xp');
        const $heroAtt = document.querySelector('#hero-att');
        const $monsterName = document.querySelector('#monster-name');
        const $monsterHp = document.querySelector('#monster-hp');
        const $monsterAtt = document.querySelector('#monster-att');
        const $message = document.querySelector('#message');

        class Game {
            constructor(name) { // 초기설정값 
                this.monster = null;
                this.hero = null;
                this.monsterList = [
                    { name: '슬라임', hp: 25, att: 10, xp: 10 },
                    { name: '스켈레톤', hp: 50, att: 15, xp: 20 },
                    { name: '마왕', hp: 150, att: 35, xp: 50 },
                ];
                this.start(name);
                this.updateHeroStat();
            }
            start(name) { //특정동작 수행 부분
                $gameMenu.addEventListener('submit', this.onGameMenuInput);
                $battleMenu.addEventListener('submit', this.onBattleMenuInput);
                this.changeScreen('game');
                this.hero = new Hero(this, name);
            }
            changeScreen(screen) {
                if (screen === 'start') {
                    $startScreen.style.display = 'block';
                    $gameMenu.style.display = 'none';
                    $battleMenu.style.display = 'none';
                } else if (screen === 'game') {
                    $startScreen.style.display = 'none';
                    $gameMenu.style.display = 'block';
                    $battleMenu.style.display = 'none';
                } else if (screen === 'battle') {
                    $startScreen.style.display = 'none';
                    $gameMenu.style.display = 'none';
                    $battleMenu.style.display = 'block';
                }
            }
            onGameMenuInput = (event) => {
                event.preventDefault();
                const input = event.target['menu-input'].value;
                if (input === '1') { //모험
                    this.changeScreen('battle');
                    const randomIndex = Math.floor(Math.random() * this.monsterList.length);
                    const randomMonster = this.monsterList[randomIndex];
                    this.monster = new Monster(
                        this,
                        randomMonster.name,
                        randomMonster.hp,
                        randomMonster.att,
                        randomMonster.xp,
                    );
                    this.updateMonsterStat();
                    this.showMessage(`몬스터와 마주쳤다. ${this.monster.name}인 것 같다!`);
                } else if (input === '2') { //휴식
                    this.hero.hp = this.hero.maxHp;
                    this.updateHeroStat();
                    this.showMessage('충분한 휴식을 취했다.');
                } else if (input === '3') { //종료
                    this.showMessage(' ');
                    this.quit();
                }
            }
            onBattleMenuInput = (event) => {
                event.preventDefault();
                const input = event.target['battle-input'].value;
                if (input === '1') { //공격
                    const { hero, monster } = this;
                    hero.attack(monster);
                    monster.attack(hero);

                    if (hero.hp <= 0) {
                        this.showMessage(`${hero.lev}에서 전사. 새 주인공을 생성하세요.`);
                        this.quit();
                    } else if (monster.hp <= 0) {
                        this.showMessage(`몬스터를 잡아 ${monster.xp} 경험치를 얻었다.`);
                        hero.getXp(monster.xp);
                        this.monster = null;
                        this.changeScreen('game');
                    } else { //전투진행중 
                        this.showMessage(`${hero.att}의 데미지를 주고, ${monster.att}의 데미지를 받았다.`);
                    }

                    this.updateHeroStat();
                    this.updateMonsterStat();
                } else if (input === '2') { //회복
                    const { hero, monster } = this;
                    hero.hp = Math.min(hero.maxHp, hero.hp + 20);
                    //hero.hp 와 hero.maxHp 중 작은것이 내 체력이다. 
                    monster.attack(hero);
                    this.showMessage('체력을 조금 회복했다!');
                    this.updateHeroStat();
                } else if (input === '3') { // 도망 
                    this.changeScreen('game');
                    this.showMessage('부리나케 도망쳤다!');
                    this.monster = null;
                    this.updateMonsterStat();
                }
            }
            updateHeroStat() {
                const { hero } = this;
                if (hero === null) {
                    $heroName.textContent = '';
                    $heroLevel.textContent = '';
                    $heroHp.textContent = '';
                    $heroXp.textContent = '';
                    $heroAtt.textContent = '';
                    return
                }
                //hero상태 바뀔때마다 화면 다시 그려주기 
                $heroName.textContent = hero.name;
                $heroLevel.textContent = `${hero.lev}Lev`;
                $heroHp.textContent = `HP : ${hero.hp}/${hero.maxHp}`;
                $heroXp.textContent = `XP : ${hero.xp}/${15 * hero.lev}`;
                $heroAtt.textContent = `ATT: ${hero.att}`;
            }
            updateMonsterStat() {
                const { monster } = this;
                if (monster === null) {
                    $monsterName.textContent = '';
                    $monsterHp.textContent = '';
                    $monsterAtt.textContent = '';
                    return;
                }
                $monsterName.textContent = monster.name;
                $monsterHp.textContent = `HP : ${monster.hp}/${monster.maxHp}`;
                $monsterAtt.textContent = `ATT: ${monster.att}`;
            }
            showMessage(text) {
                $message.textContent = text;
            }
            quit() {
                this.hero = null;
                this.monster = null;
                this.updateHeroStat();
                this.updateMonsterStat();
                $gameMenu.removeEventListener('submit', this.onGameMenuInput);
                $battleMenu.removeEventListener('submit', this.onBattleMenuInput);
                this.changeScreen('start');
                game = null;
            }
        }
        class Unit {
            constructor(game, name, hp, att, xp) {
                this.game = game;
                this.name = name;
                this.maxHp = hp;
                this.hp = hp;
                this.xp = xp;
                this.att = att;
            }
            attack(target) {
                target.hp -= this.att;
            }
        }
        class Hero extends Unit {
            constructor(game, name) {
                super(game, name, 100, 10, 0); //부모클래스의 생성자 호출 겹치는거 괄호안에 넣어주기 
                this.lev = 1;

            }
            //unit에 attack있어서 상속받아 삭제해줌 
            heal(monster) {
                this.hp += 20;
                this.hp -= monster.att;
            }
            getXp(xp) {
                this.xp += xp;
                if (this.xp >= this.lev * 15) { //경험치를 다 채우면
                    this.xp -= this.lev * 15; // xp:20, lev:1, maxXp: 15 -> xp:5, lev2, maxXp: 15
                    this.lev += 1;
                    this.maxHp += 5; //레벨업에 따른 stat상승 
                    this.att += 5; //레벨업에 따른 stat상승 
                    this.hp = this.maxHp;
                    this.game.showMessage(`레벨업! 레벨 ${this.lev}`);
                }
            }
        }
        class Monster extends Unit {
            constructor(game, name, hp, att, xp) {
                super(game, name, hp, att, xp);
            }
            //unit에 attack있어서 상속받아 삭제해줌 
        }

        let game = null;
        //게임시작 
        $startScreen.addEventListener('submit', (event) => {
            event.preventDefault();
            const name = event.target['name-input'].value;
            game = new Game(name);
        });
    </script>
</body>

📌 코드공부하기


🖍 class 문법

게임은 상호작용이 많은 프로그램이기 때문에 class문법을 사용했다.


extends로 상속해주기


클래스 문법에서는 겹치는 부분을 따로 추출한 다음 extends를 이용하여 상속받을 수 있다.


class Unit {
            constructor(game, name, hp, att, xp) {
                this.game = game;
                this.name = name;
                this.maxHp = hp;
                this.hp = hp;
                this.xp = xp;
                this.att = att;
            }
            attack(target) {
                target.hp -= this.att;
            }
        }
        class Hero extends Unit {
            constructor(game, name) {
                super(game, name, 100, 10, 0); //부모클래스의 생성자 호출 겹치는거 괄호안에 넣어주기 
                this.lev = 1;

            }
            //unit에 attack있어서 상속받아 삭제해줌 
            heal(monster) {
                this.hp += 20;
                this.hp -= monster.att;
            }
            getXp(xp) {
                this.xp += xp;
                if (this.xp >= this.lev * 15) { //경험치를 다 채우면
                    this.xp -= this.lev * 15; // xp:20, lev:1, maxXp: 15 -> xp:5, lev2, maxXp: 15
                    this.lev += 1;
                    this.maxHp += 5; //레벨업에 따른 stat상승 
                    this.att += 5; //레벨업에 따른 stat상승 
                    this.hp = this.maxHp;
                    this.game.showMessage(`레벨업! 레벨 ${this.lev}`);
                }
            }
        }
        class Monster extends Unit {
            constructor(game, name, hp, att, xp) {
                super(game, name, hp, att, xp);
            }
            //unit에 attack있어서 상속받아 삭제해줌 
        }

다음과 같이 attack이라는 함수가 hero, monster 에 겹쳐있어 unit으로 따로 분리해준 후 상속시켰다.


🖍 this와 화살표함수


function(){this} 는 객체안의 this를 가르킨다.
화살표 함수에서는 밖의 this와 내부의 this가 같다.

  • a.addEventListener는 내부의 this가 a를 가리킨다.
    this는 함수가 호출될 때 결정된다.

🖍 bind 메서드


function a () {
console.log(this);
}

  • a.bind(document)(); -> document를 출력
  • 화살표 함수는 bind를 사용할 수 없다.

profile
connecting the dots 💫

0개의 댓글