whatisthis?
javaScript. 턴제 게임 - 텍스트 기반 RPG (中) 본문
턴제 게임 - 텍스트 기반 RGP
- 지난번 진행사항
index.html
<!DOCTYPE html>
<html>
<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>턴제 게임</title>
<link rel="styleshee" href="./style.css">
</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 src="./turn.js"></script>
</body>
</html>
turn.js
let TurnGame = (function() {
let instance;
let initiate = function(heroName) { // initiate 변수에 익명함수 저장
let hero = {
name: heroName,
lev: 1,
maxHp: 100,
hp: 100,
xp: 0,
att: 10
};
return { // showLevel, showXp, showHp, toogleMenu, setMessage 메서드를 가진 객체 반환
showLevel: function() {
document.getElementById('hero-level').innerHTML = hero.lev + 'lev';
return this;
},
showXp: function() {
let self = this; // setTimeout에선 this가 소멸되니까 미리 저장해둠
if (hero.xp > 15 * hero.lev) {
hero.xp -= 15 * hero.lev;
hero.maxHp += 10;
hero.hp = hero.maxHp;
hero.att += hero.lev;
hero.lev++;
window.setTimeout(function() { // setTimeout에선 저장해둔 this(self변수) 사용
self.setMessage('레벨업!');
}, 1000); // 1000ms 후에 실행
}
document.getElementById('hero-xp').innerHTML = 'XP: ' + hero.xp + '/' + 15 * hero.lev;
document.getElementById('hero-att').innerHTML = 'ATT: ' + hero.att;
return this.showLevel().showHp(); // 💡 메소드 체이닝
},
showHp: function() {
if (hero.hp < 0) {
return this.gameOver();
}
document.getElementById('hero-hp').innerHTML = 'HP: ' + hero.hp + '/' + hero.maxHp;
return this;
}, ////// 여기서부턴 toggleMenu , setMessage메서드
toggleMenu: function () { // toggleMenu 메서드
document.getElementById('hero-name').innerHTML = hero.name;
document.getElementById('start-screen').style.display = 'none'; // 시작화면 안보이게
if (document.getElementById('game-menu').style.display === 'block') { // 1️⃣ 게임메뉴 보이면
document.getElementById('game-menu').style.display = 'none'; // 게임메뉴 가리고
document.getElementById('battle-menu').style.display = 'block'; // 배틀메뉴 보이게
document.getElementById('battle-input').focus(); // 배틀 input태그에 포커스되게
} else { // 2️⃣ 게임메뉴 안보이면
document.getElementById('game-menu').style.display = 'block'; // 게임메뉴 보이게
document.getElementById('battle-menu').style.display = 'none'; // 배틀메뉴 가리고
document.getElementById('menu-input').focus(); // 메뉴 input태그에 포커스되게
}
return this;
},
setMessage: function(msg) {
document.getElementById('message').innerHTML = msg;
return this;
},
};
}; ////// 여기까지 객체 return
return {
getInstance: function(name) { // instance가 비어있으면(즉, 맨처음 호출시)에만
if (!instance) {
instance = initiate(name); // 맨처음 호출시에만 ㅡinitiate를 거침 ㅡ instance에 저장
}
return instance; // instance 반환
}
};
})();
document.getElementById('start-screen').onsubmit = function(e) { // 🎲 시작화면 폼 제출시 ㅡ 이름
var name = document.getElementById('name-input').value; // input의 value
e.preventDefault(); // submit 기본이벤트 막음
if (name && name.trim() && confirm(name + '으로 하시겠습니까?')) { // 확인, 취소중 선택
TurnGame.getInstance(name).showXp().toggleMenu(); // 💡 메소드 체이닝 ㅡ getInstance함수 호출
// showXp엔 return this.showLevel().showHp()가 되어있음
// toggleMenu엔 if-else문이 있음 (메뉴 없앴다가 생겼다가)
} else {
alert('이름을 입력해주세요'); // 취소 선택시
}
};
document.getElementById('game-menu').onsubmit = function(e) { // 🎲 게임메뉴 폼 제출시
var input = document.getElementById('menu-input');
var option = input.value; // option변수에 input태그 값 저장
e.preventDefault(); // submit 기본이벤트 막음 (순서 주의❗)
input.value = ''; // input값 초기화 (빈칸)
};
document.getElementById('battle-menu').onsubmit = function(e) { // 🎲 배틀메뉴 폼 제출시
var input = document.getElementById('battle-input');
var option = input.value; // 상동
e.preventDefault();
input.value = '';
};
<주요 개념>
1. Turngame 싱글턴 객체
2. 이벤트리스너 (HTMLElements.onsubmit)
계속해서 같은 객체(Turngame)를 리턴하기 때문에
그 객체의 메소드를 연속으로 체이닝 하듯이 사용할 수 있음.
>> 이런 패턴을 메소드 체이닝(Method Chaining) 이라고 한다.
- 메뉴 선택
<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>
위 부분에서 메뉴 입력 버튼에 이벤트 리스너를 달아야 한다. (#menu-button)
document.getElementById('menu-button').onsubmit = function(e) {
let input = document.getElementById('menu-input');
let option = input.value;
e.preventDefault();
input.value = '';
TurnGame.getInstance().menuInput(option); // 새로 추가
}
document.getElementById('battle-button').onsubmit = function(e) {
let input = document.getElementById('battle-input');
let option = input.value;
e.preventDefault();
input.value = '';
TurnGame.getInstance().menuInput(option); // 새로 추가
}
menuInput 메서드와 battleInput 메서드를 instance에 추가한다.
- 아래에 메서드 추가 예정임.
- 몬스터 객체 리스트
var TurnGame = (function() {
var instance;
var initiate = function(heroName) {
var hero = {
name: heroName,
lev: 1,
maxHp: 100,
hp: 100,
xp: 0,
att: 10
};
var monsters = [{
name: '슬라임',
hp: 25 + hero.lev * 3,
att: 10 + hero.lev,
xp: 10 + hero.lev,
}, {
name: '스켈레톤',
hp: 50 + hero.lev * 5,
att: 15 + hero.lev * 2,
xp: 20 + hero.lev * 2,
}, {
name: '찬호[보스]',
hp: 100 + hero.lev * 10,
att: 25 + hero.lev * 5,
xp: 50 + hero.lev * 5,
}];
var monster = null;
var turn = true;
return {
... // 이전과 동일 다음 메소드 추가
generateMonster: function() {
monster = JSON.parse(JSON.stringify(monsters[Math.floor(Math.random() * monsters.length)]));
document.getElementById('monster-name').innerHTML = monster.name;
document.getElementById('monster-hp').innerHTML = 'HP: ' + monster.hp;
document.getElementById('monster-att').innerHTML = 'ATT: ' + monster.att;
this.setMessage(monster.name + '이(가) 공격해옵니다');
return this.toggleMenu();
},
menuInput: function(input) {
if (input === '1') {
return this.generateMonster();
} else if (input === '2') {
hp = maxHp;
return this.updateStat().setMessage('체력을 회복했습니다');
} else if (input === '3') {
return this.exit();
} else {
alert('잘못된 입력');
}
},
battleInput: function(input) {}, // 구현필요
attackMonster: function() {}, // 구현필요
attackHero: function() {}, // 구현필요
nextTurn: function() {}, // 구현필요
win: function() {}, // 구현필요
clearMonster: function() {}, // 구현필요
gameOver: function() {}, // 구현필요
exit: function(input) {
document.getElementById('screen').innerHTML = '이용해주셔서 감사합니다.새로 시작하려면 새로고침하세요';
}
};
};
return {
getInstance: function(name) {
if (!instance) {
instance = initiate(name);
}
return instance;
}
};
})();
지금까지 완성본 🔻
#start-screen이 onsubmit일때
name-input의 value가 name 변수에 저장되고,
confirm창에서 확인을 클릭하면 ㅡ 메소드체이닝) TurnGame.getInstance(name).showXp().toggleMenu() 실행.
>> 여기서 showXp메서드는 내부적으로 showLevel과 showHp를 실행함.
return this.showLevel().showHp();
- 전투메뉴 (battleInput, attackMonster, attackHero, nextTurn, win, clearMonster, gameOver)
battleInput: function (input) {
if (input === '1') {
return this.attackMonster();
} else if (input === '2') {
if (hero.hp + hero.lev * 20 < hero.maxHp) {
hero.hp += hero.lev * 20;
} else {
hero.hp = hero.maxHp;
}
return this.showHp().setMessage('체력을 회복했습니다').nextTurn();
} else if (input === '3') {
return this.clearMonster().setMessage('도망쳤습니다');
} else {
alert('잘못된 입력');
}
},
attackMonster: function () {
monster.hp -= hero.att;
document.getElementById('monster-hp').innerHTML = 'HP: ' + monster.hp;
if (monster.hp > 0) {
return this.setMessage(hero.att + '의 데미지를 입혔습니다.').nextTurn();
}
return this.win();
},
attackHero: function () {
hero.hp -= monster.att;
return this.showHp();
},
nextTurn: function () {
var self = this;
turn = !turn;
document.getElementById('battle-button').disabled = true;
if (!turn) {
window.setTimeout(function () {
self.setMessage(monster.name + '의 턴입니다');
window.setTimeout(function () {
document.getElementById('battle-button').disabled = false;
if (self.attackHero()) {
self.setMessage(monster.att + '의 데미지를 입었습니다');
window.setTimeout(function () {
self.setMessage(hero.name + '의 턴입니다');
}, 1000);
}
}, 1000);
}, 1000);
return this.nextTurn();
}
return this;
},
win: function () {
this.setMessage(monster.name + ' 사냥에 성공해 경험치 ' + monster.xp + '을 얻었습니다');
hero.xp += monster.xp;
return this.clearMonster().showXp();
},
clearMonster: function () {
monster = null;
document.getElementById('monster-name').innerHTML = '';
document.getElementById('monster-hp').innerHTML = '';
document.getElementById('monster-att').innerHTML = '';
return this.toggleMenu();
},
gameOver: function () {
document.getElementById('screen').innerHTML = hero.name + '은 레벨' + hero.lev + '에서 죽었습니다. 새로 시작하려면 새로고침하세요';
return false;
},
setTimeout부분 이해 🔻
nextTurn 메소드
nextTurn: function () {
var self = this;
turn = !turn;
document.getElementById('battle-button').disabled = true; //
if (!turn) {
window.setTimeout(function () {
self.setMessage(monster.name + '의 턴입니다');
window.setTimeout(function () {
document.getElementById('battle-button').disabled = false;
if (self.attackHero()) {
self.setMessage(monster.att + '의 데미지를 입었습니다');
window.setTimeout(function () {
self.setMessage(hero.name + '의 턴입니다');
}, 1000);
}
}, 1000);
}, 1000);11
return this.nextTurn(); // 지금 !turn이므로 다시 nextTurn()으로 가면 ㅡ turn이 된다
}
return this; // 지금이 turn이면
}
우선, setTimeout에 들어가면 콜백에 의해 this가 소멸되므로, self라는 변수에 this값을 미리 저장해둔다.
그리고, battle-button을 disabled하게 한다. ( html 요소를 안보이게 처리하는 것 )
즉, turn이 아닐때는 다음과 같은 루프가 돈다.
몬스터의 턴일때 (즉, !turn일때)
setMessage 메서드 (몬스터의 턴입니다) >> 버튼 disabled + attackHero 메서드 >> setMessage (00데미지 입음)
>> setMessage (본인 턴)
style.css
* {
font-family: 'SUIT-Medium';
margin: 0;
box-sizing : border-box;
}
@font-face {
font-family: 'SUIT-Medium';
src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_suit@1.0/SUIT-Medium.woff2') format('woff2');
font-weight: normal;
font-style: normal;
}
html {
font-size: 16px;
line-height: 2;
color: #1f2d3d;
}
body {
/* display: flex;
flex-direction: column;
justify-content: center;
align-items: center; */
width: 100%;
height: 100vh;
background-color: #f2f4ff;
}
.container {
display: flex;
justify-content: center;
align-items: center;
background-color: #c1c1c1;
margin: 50px;
border-radius: 20px;
}
#hero-stat {
background-color: rgb(241, 241, 241);
}
#hero-name {
font-size: 20px;
font-weight: bold;
}
#hero-level {
background-color: #ae1e1e;
color: rgb(255, 253, 243);
font-weight: bold;
border-radius: 50%;
}
.menu-container {
text-align: center;
}
- js위주 실습이다보니 대충 짬.
- 추후에 수정예정임.
실행 결과
Test 화면 (gif 파일) 🔻
다음 포스팅에서는 js 코드 리뷰 + 이해를 위한 부가설명 진행.
REFERENCE
'PRACTICE > SELF' 카테고리의 다른 글
javaScript. Painting App 구현 - (1) HTML/CSS (0) | 2022.02.02 |
---|---|
javaScript. 턴제 게임 - 텍스트 기반 RPG (下) (0) | 2022.01.31 |
javaScript. 턴제 게임 - 텍스트 기반 RPG (上) (0) | 2022.01.30 |
project. (js) 자동 텍스트 RPG 게임 - (수정) (0) | 2022.01.29 |
project. (js) 숫자야구게임 - 웹 ver (수정) (0) | 2022.01.28 |