Javascript

[JavaScript] 자바스크립트 10장. 클래스_텍스트 RPG 게임 만들기

양치개발자 2023. 6. 9. 23:28
반응형

10-1. 텍스트 RPG 순서도 그리기

🔆 순서도 작성

게임에는 두 가지 모드가 있음모험, 휴식, 종료 중에서 선택하는 일반 모드적을 만나게 될 때 돌입하는 전투 모드입니다.전투 모드에서는 적을 공격하거나 체력을 회복하거나 도망갑니다.

 

 

🔆 화면 구성

  1. 주인공 생성하는 화면
  2. 주인공 스텟 화면
  3. 모험 모드 화면
  4. 전투 모드 화면
  5. 몬스터 스텟 화면

 

<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" type="number" />
            <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 $startsScreen = document.querySelector('#start-screen');
        const $gameMenu = document.querySelector('#game-menu');
        const $battleMenu = document.querySelector('#battle-menu');
        const $heroName = document.querySelector('#hero-name');
        
        $startsScreen.addEventListener('submit', (event) => {
            event.preventDefault(); // 중복 방지
            const name = event.target['name-input'].value; // name은 'name-input'라는 id를 가진 이벤트 타겟의 값
            $startsScreen.style.display = 'none';  // display 스타일을 'none'으로 바꿈
            $gameMenu.style.display = 'block'; // display 스타일을 'block'으로 바꿈
            $heroName.textContent = name; // heroName의 이름은 input창에 입력한 이름
         });
    </script>

 

 

초기 화면

10-2. 주인공이나 몬스터 만들기

🔆 주인공 및 몬스터 스텟 변수 만들기

	<script>
        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');
     </script>

 

🔆 hreo 및 monster 객체 형태로 스텟을 만듬

// hero 객체
        const hero = {
            name: '',
            lev: 1,
            maxHp: 100,
            hp: 100,
            xp: 0,
            att: 10,
            attack(monster) {
                monster.hp -= this.att; // 몬스터 체력에서 내 공격력 만큼 까임
                this.hp -= monster.att; // 내 채력에서 몬스터 공격력 만큼 까임
            },
            heal(monster) {
                this.hp += 20; // hp를 20 회복
                this.hp -= monster.att; // 몬스터가 떄린 만큼 체력이 깍임
            },
        };
        let monster = null; // 몬스터가 처음에는 생성이 안되서
        // 임의로 몬스터 리스트
        const monsterList = [
            { name: '슬라임', hp: 25, att: 10, xp: 10 },
            { name: '스켈레톤', hp: 50, att: 15, xp: 20 },
            { name: '마왕', hp: 150, att: 35, xp: 50 },
        ];

 

시작 화면 코드

// 시작 화면 
        $startsScreen.addEventListener('submit', (event) => {
            event.preventDefault(); // 중복 방지
            const name = event.target['name-input'].value; // 'name-input'라는 id를 가진 이벤트 타겟의 값
            $startsScreen.style.display = 'none';  // display 스타일을 'none'으로 바꿈
            $gameMenu.style.display = 'block'; // display 스타일을 'block'으로 바꿈
            $heroName.textContent = 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}`;
            hero.name = name;
        });

시작 화면 구현

 

게임 모드 코드

// 게임 모드
        $gameMenu.addEventListener('submit', (event) => {
            event.preventDefault();
            const input = event.target['menu-input'].value;
            if (input === '1') { // 모험
                $gameMenu.style.display = 'none';
                $battleMenu.style.display = 'block';
                monster = JSON.parse(
                    JSON.stringify(monsterList[Math.floor(Math.random() * monsterList.length)])
                );
                monster.maxHp = monster.hp;
                $monsterName.textContent = monster.name;
                $monsterHp.textContent = `HP: ${monster.hp}/${monster.maxHp}`;
                $monsterAtt.textContent = `ATT: ${monster.att}`;
            } else if (input === '2') { // 휴식
            } else if (input === '3') { // 종료
            }
        });

게임 모드 구현

10-3. 깊은 복사와 얕은 복사

🔆 깊은 복사(Deep Copy)

'실제 값'을 새로운 메모리 공간에 복사하는 것을 의미한다.

예시

 

가장 대표적인 방법

 

 

🔆 얕은 복사(Shallow Copy)

  • 중첩된 객체가 있을 때 가장 바깥 객체만 복사되고, 내부 객체는 참조 관계를 유지하는 복사
  • 객체의 참조값(주소 값)을 복사한다.

const array = [ { j: 'k' }, {l: 'm'}];
const shallowCopy = [...array];
console.log(array === shallowCopy); // false
console.log(array[0] === shallowCopy[0]); // true

 

얕은 복사를 할 때는 ... 연산자를 사용합니다.
... 연사자를 spread문법이라도 하는데, spread문법은 기존 객체의 속성을 새 객체에 할당할 때 사용합니다.
배열은 [...배열] / 객체라면 {...객체}

 

🔆 1분 퀴즈

다섯 개의 값을 각각 복사해주세요. 복사라고 하는 함은 복사본은 수정할 때 원본이 바뀌지 않은 것

객체라면 복사한 객체 내부의 값을 바꿔도 원본 객체의 값이 바뀌지 않는다.

const a = 'b';
const c = ['d', true, 1];
const e = { g: 'h' };
const i = [ { j: 'k'}, { l: 'm' }];
const n = { o: { p: 'q' }};

// 답
const a1 = a;
const c1 = c.slice();
const e1 = [...e];
const i1 = JSON.parse(JSON.stringify(i));
const d1 = JSON.parse(JSON.stringify(d));

 

10-4. 서로 공격하기(this, 클래스가 필요한 이유)

🔆 this란?

객체 리터럴 안에서는 함수를 값으로 입력할 때 function 예약어를 생략할 수 있다
엄밀하게 봤을 때는 다른 부분이 있다고 하지만 무시해도 좋을 정도라고 한다

const obj = {
	name = "JH"
    func1 : function(){} // 생략하지 않고 메서드를 입력한 경우
    func2(){} // :function를 생략하고 메서드를 입력한 경우
}

this는 기본적으로 브라우저를 가르키는 window객체를 나타낸다
window는 생략할 수 있기 때문에 window객체인

window.document, window.console를 document나 console로 간략하게 사용할 수 있다

 

this는 객체 메서드 안에서는 자신이 속한 객체를 나타낸다
단 객체.메서드(); 로 호출하는 경우에만
this가 자신이 속한 객체를 나타내므로 이 점을 주의해야 한다
예를 들어,
const a = {
	name: "JH"
    sayName() {
    console.log(this === a);
    }
};
a.sayName(); // true가 반환되며 this가 자신이 속한 객체를 나타내는 것을 확인할 수 있다
const ab = a.sayName;
ad(); // false가 반환되며 메서드를 변수로 담았을 때는, this가 자신이 속한 객체를 나타내지 않는 것을 확인할 수 있다

 

🔆 예시

함수 선언문으로 선언이 된 경우

const hero = {
            name : '',
            lev: 1,
            maxHp : 100,
            hp : 100,
            xp : 0,
            att: 10,
            attck(monster) {
                monster.hp -= this.att;
                this.hp -= monster.att;
            },
            heal(monster) {
                this.hp += 20;
                this.hp -= monster.att;
            },
        };

 

화살표 함수로 된 경우

const hero = {
            name : '',
            lev: 1,
            maxHp : 100,
            hp : 100,
            xp : 0,
            att: 10,
            attck : (monster) => {
                monster.hp -= this.att;
                this.hp -= monster.att;
            },
            heal : (monster) => {
                this.hp += 20;
                this.hp -= monster.att;
            },
        };

 

10-5. 클래스 사용하기(팩토리, 생성자)

🔆 Class(클래스)

  • 객체를 생성하기 위한 템플릿(서식) 문법이다.
  • class 예약어로 클래스 선언, constructor 메서드 안에 기존 코드를 넣는다
  • new를 붙여서 호출하면 constructor 함수가 실행되고 객체가 반환된다
  • 이 때 this는 생성된 객체 자신을 가리킨다

클래스를 사용하는 목적

  • 코드를 보기 쉽게 작성 하기 위해서
  • 코드를 상호 작용하는 것을 보고 체계적으로 작성을 하기 위해서
  • 객체를 공장처럼 찍어내기 위해서

 

팩토리 함수(factory function)

function creatMonster(name, hp, att, xp){
return {name, hp, att, xp};
}
const monster1 = creatMonster('슬라임', 25, 10, 11);
const monster2 = creatMonster('슬라임', 28, 9, 13);

생성자 함수(constructor function)

function Monster(name, hp, att, xp){
	this.name = name;
    this.hp = hp;
    this.att = att;
    this.xp = xp;
}
const monster1 = new Monster('슬라임', 25, 10, 11);
const monster2 = new Monster('슬라임', 27, 11, 13);

객체의 프로퍼티에서 this는 window를 가리킨다
하지만 new 예약어를 사용하여 생성자 함수를 선언하면 새로운 객체가 생성되고
this는 생성된 객체를 가리키게 된다

 

class 문법:

// class 다음은 첫문자는 무조건 대문자로 한다.
class Monster {
	// constructor만 추가를 한 문법이다.
	constructor(name, hp, att, xp){
    	this.name = name;
        this.hp = hp;
        this.att = att;
        this.xp = xp;
    }
}
const monster1 = new Monster('슬라임', 25, 10, 11);
const monster2 = new Monster('슬라임', 26, 12, 10);

class문법에서는 new예약어를 사용하지 않으면 에러가 발생하므로
생성자 함수에서 확인하기 어려웠던 에러를 쉽게 찾을 수 있게 되었다

 

생성자 함수에서는 new를 사용하지 않았을 때,
별도의 에러 표기 없이 this가 window를 가리키게 되어
window객체의 프로퍼티에 영향을 주는 문제가 있었다

 

또한 생성자 함수는 관련 코드들이 한 곳에 모여있지 않았기 때문에
가독성에도 문제가 있었는데 그런 문제들이 class문법에서 개선되었다

 

this와 화살표함수를 알 수 있는 포스트:

https://www.zerocho.com/category/JavaScript/post/5b0645cc7e3e36001bf676eb

 

(JavaScript) 자바스크립트의 this는 무엇인가?

안녕하세요. 이번 시간에는 자바스크립트 this에 대해 알아보겠습니다. 사실 이미 실행 컨텍스트 강좌 에 다 설명해둔 것이긴 한데요. 그 강좌는 실행 컨텍스트(전체적인 흐름)에 더 집중해서 쓰

www.zerocho.com

 

옛날 공장 함수와 생성자 함수의 차이

// 공장 함수
function createMonster(name, hp, att, xp) {
	return {
    	name, hp, att, xp,
        attack(monster) {
        	monster.hp -= this.att;
            this.hp -= monster.att;
        },
        heal: (monster) => {
                this.hp += 20;
                this.hp -= monster.att;
            },
    }
}

 

// 생성자 함수
function Monster(name, hp, att, xp) {
	this.name = name;
    this.hp = hp;
    this.att = att;
    this.xp = xp;
}
// 메서드를 추가할 때는 prototype이라는 속성 안에 추가해야 한다.
Monster.prototype.attack = function(monster) {
	 monster.hp -= this.att;
     this.hp -= monster.att;
}
Monster.prototype.heal = function(monster) {
	  this.hp += 20;
      this.hp -= monster.att;
};

 

10-6. 클래스로 재구성하기

class를 이용을 해서 Game 및  Hero 및 Monster 생성

//  Game 클래스 생성
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 },
                ];
            }
        }

// Hero 클래스 생성
        class Hero {
            constructor(game, name) {
                this.game = game;
                this.name = name;
                this.lev = 1;
                this.maxHp = 100;
                this.hp = 100;
                this.xp = 0;
                this.att = 10;
            }
            attack(target) {
                target.hp -= this.att;
            }
            heal(monster) {
                this.hp += 20;
                this.hp -= monster.att;
            }
        
	// Monster 클래스 생성
        class Monster {
            constructor(game, name, hp, att, xp) {
                this.game = game;
                this.name = name;
                this.maxHp = hp;
                this.xp = xp;
                this.att = att;
            }
            attack(target) {
                target.hp -= this.att;
            }
        }

게임 안에서 히어로랑 몬스터가 생성되는게 상호 작용이 되는 것임.

 

// 화면 모드
$startsScreen.addEventListener('sumbmit', (event) => {
	event.preventDefault();
	const name = event.target['name-input'].value; // Hero 이름 저장 됨
	game = new Game(name); // new 함수로 Game이라는 함수를 실행한다.
});

 

최종 코드

<script>
	...
    	 class Game {
            constructor(name) {
                this.monster = null;
                this.hero = new Hero(this, name);
                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);
            }
            start(name) {
                $gameMenu.addEventListener('submit', this.onGameMenuInput);
                $battleMenu.addEventListener('submit', this.onBattleMenuInput);
                this.changeScreen('game');
            }
            changeScreen(screen) {
                if (screen === 'start') {
                    $startsScreen.style.display = 'block';
                    $gameMenu.style.display = 'none';
                    $battleMenu.style.display = 'none';
                } else if (screen === 'game') {
                    $startsScreen.style.display = 'none';
                    $gameMenu.style.display = 'block';
                    $battleMenu.style.display = 'none';
                } else if (screen === 'battle') {
                    $startsScreen.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') { // 모험
                } else if (input === '2') { // 휴식
                } else if (input === '3') { // 종료
                }
            }
            onBattleMenuInput = (event) => {
                event.preventDefault();
                const input = event.target['battle-input'].value;
                if (input === '1') { // 공격
                } else if (input === '2') { // 회복
                } else if (input === '3') { // 도망
                }
            }
        }

        class Hero {
            constructor(game, name) {
                this.game = game;
                this.name = name;
                this.lev = 1;
                this.maxHp = 100;
                this.hp = 100;
                this.xp = 0;
                this.att = 10;
            }
            attack(target) {
                target.hp -= this.att;
            }
            heal(monster) {
                this.hp += 20;
                this.hp -= monster.att;
            }
        }

        class Monster {
            constructor(game, name, hp, att, xp) {
                this.game = game;
                this.name = name;
                this.maxHp = hp;
                this.xp = xp;
                this.att = att;
            }
            attack(target) {
                target.hp -= this.att;
            }
        }

        let game = null;
        // 시작 화면 
        $startsScreen.addEventListener('submit', (event) => {
            event.preventDefault();
            const name = event.target['name-input'].value;
            game = new Game(name);
        });

        // 게임 모드
        $gameMenu.addEventListener('submit', (event) => {
            event.preventDefault();
            const input = event.target['menu-input'].value;
            if (input === '1') { // 모험
            } else if (input === '2') { // 휴식
            } else if (input === '3') { // 종료
            }
        });

        // 배틀 모드
        $battleMenu.addEventListener('submit', (event) => {
            const input = event.target['menu-input'].valueAsNumber;
            if (input === '1') { // 공격
            } else if (input === '2') { // 회복

            } else if (input === '3') { // 도발

            }
        });
    
    ...
</script>

 

10-7. 화살표 함수와 this

this는 호출자에 따라 계속해서 가리키는 대상이 변한다
숙달되기 전까지 console.log()로 this가 무엇을 가리키고 있는지 계속 확인하는 것이 좋다

 

elem.addEventListener의 콜백 함수의 this는 elem을 가리킨다
따라서 클래스내에서 this를 사용하더라도
클래스 내의 이벤트리스너 콜백에서의 this는 class객체가 아닌 소속된 이벤트요소를 가리킨다
이렇게 클래스 내에서 this의 대상이 바뀌는 것을 막아주기 위하여 화살표 함수를 사용할 수 있다

 

화살표 함수를 사용하면 자체적인 this를 가지지 못하기 때문에 외부의 this와 동일한 대상을 가리킨다
function예약어를 사용하면 자체적인 this를 가지기 때문에 this의 대상이 호출자에 따라 달라진다
여기서 주의할 점은, 객체.메서드()의 경우 함수 블록에서 this는 객체를 가리킨다
객체.메서드()의 모양이 아니면서
eventListener과 같이 특정한 this를 가지는 경우에 this가 바뀌게 되는 것이다

 

화살표 함수의 특징은 크게 다음과 같다
1. 생성자 함수로 호출할 수 없다
2. 중복된 매개변수를 선언할 수 없다
3. this, arguments, new.target, super 바인딩을 갖지 않는다

🔆 예시

함수식 표현로 만든 경우

start() {
                console.log(this); // Game을 지칭함
                const game = this;
                $gameMenu.addEventListener('submit', function (event) {
                    event.preventDefault();
                    const input = event.target['menu-input'].value;
                    if (input === '1') { // 모험
                        console.log(game); // form(즉,$gameMenu)를 지칭한다.
                        game.changeScreen('battle');
                    } else if (input === '2') { // 휴식
                    } else if (input === '3') { // 종료
                    }
                });

자기만의 this를 생성을 한다.

 

화살표 함수로 만든 경우

start() {
                console.log(this); // Game을 지칭함
                const game = this;
                $gameMenu.addEventListener('submit', (event) => {
                    event.preventDefault();
                    const input = event.target['menu-input'].value;
                    if (input === '1') { // 모험
                        console.log(game); // Game을 칭한다.
                        game.changeScreen('battle');
                    } else if (input === '2') { // 휴식
                    } else if (input === '3') { // 종료
                    }
                });

 바깥쪽 this와 안쪽 this를 같게 만들라고 하면 화살표 함수 를 하면 된다

 

10-8. 클래스간에 상호작용하기

영웅이랑 몬스터랑 서로 공격하는 것을 코드화

<script>
...
	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);
            }
            start(name) {
                console.log(this); // Game을 지칭함
                $gameMenu.addEventListener('submit', this.onGameMenuInput);
                $battleMenu.addEventListener('submit', this.onBattleMenuInput);
                this.changeScreen('game');
                this.hero = new Hero(this, name);
                this.updateHeroStat();
            }
            // 화면 변하는 함수
            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.showMessage('충분한 휴식을 취했습니다.');
                } else if (input === '3') { // 종료
                }
            }

            // 배틀 메뉴 함수
            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} 레벨에서 전사했습니다. 새 주인공을 생성하세요.`);
                    } 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') { // 회복 
                } else if (input === '3') { // 도망
                }
            }
            // 영웅 상태 업데이트
            updateHeroStat() {
                const { hero } = this;
                if (hero === null) {
                    $heroName.textContent = '';
                    $heroLevel.textContent = '';
                    $heroHp.textContent = '';
                    $heroXp.textContent = '';
                    $heroAtt.textContent = '';
                    return;
                }
                $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;
            }
        }
...
</script>

 

결과 화면

 

🔆 1분 퀴즈

사람을 컴퓨터 세상 속에 구현을 해봅시다. 사람(Human) 클래스를 만들고, 생성자 메서드에서는 이름과 나이를 속성으로 입력받으세요. 또한 자신의 이름과 나이를 콘솔에 출력하는 메소드도 두개 만드세요

class Human {
            constructor(name, age) {
                this.name = name;
                this.age = age;
            }
            sayName() {
                console.log(this.name)
            }
            sayAge() {
                console.log(this.naageme)
            }
        }

 

경험치를 얻는 과정

코드화

<script>
...
	class Hero {
            constructor(game, name) {
                this.game = game;
                this.name = name;
                this.lev = 1;
                this.maxHp = 100;
                this.hp = 100;
                this.xp = 0;
                this.att = 10;
            }
            attack(target) {
                target.hp -= this.att;
            }
            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:5, lev: 2, maxXp: 15
                    this.lev += 1; // 레벨업
                    this.maxHp += 5; // 최대채력증가
                    this.att += 5; // 공격력증가
                    this.hp = this.maxHp; // 현재 채력이 최대체력
                    this.game.showMessage(`레벨업! 레벨 ${this.lev}`);
                }
            }
        }
...
</script>

 

결과 화면

10-9. 클래스 상속

공통인 요소를 하나의 클래스로 묶는다.

js는 여러 부모로부터 상속을 받는 다중 상속은 지원하지 않는다

class 자식클래스 extends 부모클래스 {
	constructor(para1, para2, para3){
    	super(para1, para2); // 부모에게 상속받을 매개변수는 super에 넣는다
        this.para3 = value; // 부모와 다른 값을 사용한다면 별도로 입력해준다
    }
  	func1(para){
    	super.func1(para1); // 부모클래스의 func1을 상속
        func2(para2)// 부모 클래스의 func1 외의 동작을 입력
    }
}

class A extends B {
	method() {
    	super.method(); // 메서드 내에서 부모의 메서드만 호출하므로 생략 가능
    }
}

 

🔆 1분 퀴즈

Human 클래스를 상속하면 조금 더 구체적인 사람을 만들 수 있습니다.

HTML, CSS, JS를 할 줄 아는 개발자를 만들어 봅시다.

class Developer extends Human {
            constructor(name, age, languages) {
                super(name, age);
                this.languages = languages;
            }
            writeCode() {
                console.log(this.languages.join() + '(으)로 코딩해요.');
            }
        }

        const Developer = new Developer(
            '차니',
            27,
            ['html', 'css', 'js']
        );
        Developer.writeCode();

 

최종 코드

<!DOCTYPE html>
<html lang="ko">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>텍스트 RPG</title>
</head>

<body>
    <form id="start-screen">
        <input id="name-input" placeholder="영웅 이름을 입력하세요!" />
        <button id="start">시작</button>
    </form>
    <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>
    <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);
            }
            start(name) {
                console.log(this); // Game을 지칭함
                $gameMenu.addEventListener('submit', this.onGameMenuInput);
                $battleMenu.addEventListener('submit', this.onBattleMenuInput);
                this.changeScreen('game');
                this.hero = new Hero(this, name);
                this.updateHeroStat();
            }
            // 화면 변하는 함수
            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.showMessage('충분한 휴식을 취했습니다.');
                } else if (input === '3') { // 종료
                    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') { // 회복 
                } else if (input === '3') { // 도망
                }
            }
            // 영웅 상태 업데이트
            updateHeroStat() {
                const { hero } = this;
                if (hero === null) {
                    $heroName.textContent = '';
                    $heroLevel.textContent = '';
                    $heroHp.textContent = '';
                    $heroXp.textContent = '';
                    $heroAtt.textContent = '';
                    return;
                }
                $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;  // 그 외 속성
            }
            // 공격 함수
            attack(target) {
                super.attack(target); // 부모 클래스의 attack
                //  부모 클래스의 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;
                    this.lev += 1;
                    this.maxHp += 5;
                    this.att += 5;
                    this.hp = this.maxHp;
                    this.game.showMessage(`레벨업! 레벨 ${this.lev}`);
                }
            }
        }

        // Monster 객체 생성
        class Monster extends Unit {
            constructor(game, name, hp, att, xp) {
                super(game, name, hp, att, xp);
            }
        }

        let game = null;
        // 시작 화면모드
        $startScreen.addEventListener('submit', (event) => {
            event.preventDefault();
            const name = event.target['name-input'].value;
            game = new Game(name);
        });

        // 게임 모드
        $gameMenu.addEventListener('submit', (event) => {
            event.preventDefault();
            const input = event.target['menu-input'].value;
            if (input === '1') { // 모험
            } else if (input === '2') { // 휴식
            } else if (input === '3') { // 종료
            }
        });

        // 배틀 모드
        $battleMenu.addEventListener('submit', (event) => {
            const input = event.target['menu-input'].valueAsNumber;
            if (input === '1') { // 공격
            } else if (input === '2') { // 회복

            } else if (input === '3') { // 도발

            }
        });

    </script>
</body>

</html>

결과 화면

반응형