whatisthis?

javaScript. 턴제 게임 - 텍스트 기반 RPG (上) 본문

PRACTICE/SELF

javaScript. 턴제 게임 - 텍스트 기반 RPG (上)

thisisyjin 2022. 1. 30. 14:13

턴제 게임 - 텍스트 기반 RGP

 

skill

  • html / css
  • javascript 

 

 

 

javaScript. 자동 텍스트 RPG 게임

자동 텍스트 RPG 게임 index.html <!DOCTYPE html> 텍스트 RPG style.css * { margin: 0; box-sizing : border-box; } html { font-family:Impact, Haettenschweiler, 'Arial Narrow Bold', sans-serif; font-s..

mywebproject.tistory.com

 

지난 실습으로 해봤던 자동 텍스트 RPG 게임을 토대로 제작했음.

 

 

< 변경 사항 >

 

- 자동텍스트 RPG게임은 게임과 사용자의 interaction이 '이름 입력' 하나밖에 없었다면,

이번에 제작할 게임은 '턴(turn)'의 개념을 도입하여 매 턴마다 사용자와 인터렉션하도록 제작함.

 

- 게임이 자동으로 실행되었다면, 이번에는 싱글턴 객체를 활용하여 게임 시작을 하도록 함.

 

- 데이터 처리는 메소드 체이닝 패턴을 이용함. ( = 디자인패턴 中 자주 쓰임)

 

 

 

 

 

1 / 싱글턴 객체

 

- 객체 하나만 생성함.

- 전체 게임 객체를 싱글턴 객체로 만들어, 한 번만 실행되도록 함.

 

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)

 

- 이벤트리스너는 각각

#start-screen

#game-menu

#battle-menu 에 연결하였다.

>> 이는 <form>태그이고, submit시 해당 함수가 동작하도록 함.

 

 

#game-menu와 #battle-menu는

마크업은 이미 해두었지만,

css의 display:none을 이용해 숨겨두고

필요한 경우에 보이도록 코딩한다.

 

TurnGame.getInstance(name).showXp().toggleMenu();

이 부분의 코드를 보면, instance 객체의 메소드를 연속해서 쓰고 있다.

>> Turngame.getInstance를 먼저 사용

>> Turngame.showXp를 사용

>> Turngame.toggleMenue를 사용

 

모두 instance 또는 this를 return 하도록 되어있음.

** 단, showXp는 showHP와 showLevel을 내부적으로 리턴함

 return this.showLevel().showHp();      // showXp의 return값

 

- 계속해서 같은 객체(Turngame)를 리턴하기 때문에

그 객체의 메소드를 연속으로 체이닝 하듯이 사용할 수 있음.

 

>> 이런 패턴을 메소드 체이닝(Method Chaining) 이라고 한다.

 

 

 


 

 

index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>턴제 게임</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 src="./turn.js"></script>
</body>
</html>

 

 

 

 

다음 포스팅에서는

  • 메뉴 선택 (#battle-menu중 1,2,3번을 선택하여 연동)
  • 몬스터 데이터 처리 (monster)

에 대해 진행함.

 

 


 

 

REFERENCE

ZeroCho Blog

 

ZeroCho Blog

ZeroCho의 Javascript와 Node.js 그리고 Web 이야기

www.zerocho.com