순서도 그리기
게임에는 크게 두 가지 모드가 있다. 모험, 휴식, 종료 중에서 선택하는 일반 모드와 모험을 떠나 적을 만나면 전투를 벌이는 전투 모드이다. 전투 모드에서는 적을 공격하거나 체력을 회복하거나 도망간다. 내용에 맞춰 그린 텍스트 RPG의 순서도는 다음과 같다.


일반 모드와 전투 모드, 두 가지 경우를 순서도로 만들었다. 첫 화면에서는 먼저 사용자에게 주인공 이름을 입력받아 캐릭터를 만든다.
<head>
<meta charset="UTF-8">
<title>텍스트 RPG</title>
</head>
<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>
</script>
</body>
</html>

html 코드는 다음과 같다. form 태그들에게 display: none 을 설정해놨기 때문에 코드에 비해 초기 화면이 매우 간단하다. 나중에 display:block을 통해 화면에 보이게 할 수 있다.
아래 코드를 추가하면 사용자가 주인공 이름을 입력하고 시작 버튼을 클릭해 초기 화면을 일반 메뉴 화면으로 전환한다.
<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');
$startScreen.addEventListener('submit', (event) => {
event.preventDefault();
const name = event.target['name-input'].value;
$startScreen.style.display = 'none';
$gameMenu.style.display = 'block';
$heroName.textContent = name;
});
</script>
주인공과 몬스터 만들기
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');
const hero = {
name: '',
lev: 1,
maxHp: 100,
hp: 100,
xp: 0,
att: 10,
};
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 },
];
몬스터와 주인공을 만드는데 필요한 태그들을 모두 변수에 할당하고 주인공을 만들었다. 몬스터 정보는 monserList에 저장했다.
일반 메뉴에서 1번을 눌러 모험을 선택하면 화면이 전투 메뉴로 바뀌고 상대할 몬스터를 무작위로 선택한다. 이때 몬스터는 monsterList에서 가져온다.
$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') {
}
});
전투 메뉴로 바뀌며 monster에 랜덤으로 몬스터를 하나 지정한다. 몬스터를 지정한 후에 몬스터의 최대 체력을 지정해주고 몬스터에 관한 정보를 화면에 표시한다.
JSON.parse(JSON.stringify(객체)) 는 깊은 복사를 사용할 때 쓰는 것이다, 간단한 객체는 이를 사용해도 크게 문제는 없으나, 성능이 느리고 함수나 Math, Date 같은 객체를 복사할 수 없다는 단점이 있다. 따라서 실무에서는 lodash 같은 라이브러리를 사용한다.
서로 공격하기
이제 전투 모드에서 서로 공격을하고 회복하거나 도망가는 것을 구현할 것이다. 이때 주인공 객체와 몬스터 객체 간에 상호 작용이 일어난다. 이런 상호 작용은 어떤 객체가 다른 객체에 특정한 기능을 하는 것이므로 함수로 구현한다.
객체 안에 쓰인 함수는 메서드이다. 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;
this.hp -= monster.att;
},
};
객체의 메서드에서는 fucntion 예약어를 생략할 수 있다. 또한 코드에 있는 예약어인 this는 지금 상황에서는 객체 자신(hero)을 가리킨다.
이제 전투 메뉴를 구성할 것이다. 하지만 앞선 게임 메뉴를 구성하듯이 구성하게 되면 코드가 흩어져 있어서 어떤 부분에 어떤 코드를 써야할지 헷갈릴 수 있다. 그리고 구현할수록 코드가 복잡해질 것이다. 그래서 클래스를 도입해서 코드를 깔끔하게 만들 것이다.
클래스로 재구성하기
클래스는 객체를 생서하기 위한 템플릿이다. 클래스 개념이 추가되지 이전에는 함수로 객체를 만들었는데 이렇게 객체를 반환하는 함수를 공장 함수 라고 한다. 또 다른 방법으로는 객체의 속성에 this을 this에 대입하고 함수를 호출할 때 new를 붙여 호출한다. 이렇게 되면 this는 객체 자신을 가리키는데 이런 함수를 생성자 함수 라고 한다. new를 붙이지 않고 호출하게 되면 window의 프러퍼티 값이 바뀌게 되므로 오류가 발생할 수 있다.
이런 생성자 함수를 조금 더 편하게 쓸 수 있도록 클래스 문법을 도입했다.
이 클래스 문법의 장점은 객체의 메서드를 같이 묶어서 작성할 수 있다는 점에 있다. 공장 함수의 경우, 메서드를 추가하게 되면 객체를 생성할 때마다 재사용해도 될 메서드도 새로 생성하여 비효율적이다. 생성자 함수의 경우에느 prototype이라는 새로운 속성 안에 추가해야하는데, 이렇게 하면 메서드를 재사용할 수는 있지만, 프로토타입 메서드가 함수와 묶여있지는 않는다.
이런 문제를 모두 해결한 것이 클래스 문법이다.
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 },
];
}
}
먼저 게임 객체를 만들어 게임의 요소들과 게임 자체의 객체가 상호작용하는 것을 구현한다.
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.hp = hp;
this.xp = xp;
this.att = att;
}
attack(target) {
target.hp -= this.att;
}
}
그 다음은 게임 객체 안에 들어갈 Hero와 Monster 객체를 만든다.
let game = null;
$startScreen.addEventListener('submit', (event) => {
event.preventDefault();
const name = event.target['name-input'].value;
game = new Game(name);
});
다음 코드를 통해 시작 화면에서 이름을 제출하면 Game 객체의 constructor가 실행된다.
이제 전체 총괄부인 Game 객체를 좀더 자세히 구현할 것이다.
일단 constructor에 this.start(); 를 호출하여 객체를 생성함과 동시에 처리해야 하는 작업들을 처리한다.
또 밖에서는 start 메서드와 다른 처리들을 메서드를 통해 구현한다.
start() {
$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');
} 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') { // 도망
}
}
'javascript > JS웹게임' 카테고리의 다른 글
| 이벤트 루프 이해하기-카드 짝 맞추기 게임(1) (0) | 2023.02.28 |
|---|---|
| 클래스_텍스트 RPG 게임 만들기(2) (0) | 2023.02.25 |
| 이차원 배열 다루기-틱택토 게임 (0) | 2023.02.05 |
| Date 사용하기-반응속도 테스트 (0) | 2023.02.03 |
| 객체 다루기-가위바위보 게임 (0) | 2023.01.29 |