[HTML5 Game] Game State

Posted in 모바일/HTML5 // Posted at 2013. 9. 27. 21:00
728x90

게임은 상태의 집합이라 할 수 있다. 보통 우리가 게임을 할 때 대략 다음과 같은 수순의 단계를 만나게 된다.

 

게임 준비 -> 게임 실행 -> 게임 종료

 

물론 복잡한 게임에서는 더 다양한 단계가 있을 수 있지만, 개념을 이해하는 수준에서 위와 같이 간단히 세 가지 단계이 있고 한 번에 하나의 단계가 활성화되어 게임이 실행된다는 것을 이해하는게 중요하다.

 

이러한 게임 흐름의 각 단계를 '게임 상태'라 하며 HTML5 Game 개발에서도 이러한 상태의 전이를 통해 전반적인 게임 흐름을 관리하게 된다.

 

다음 그림은 게임 루프상에서 실행되는 게임 상태를 표현한 것이다. 게임 상태는 특정한 이벤트에 의해 서로 교체되며 한 번에 하나의 상태가 게임 루프 상에서 동작하여 게임이 진행된다.

 

 

 

게임 상태를 잘 분리해서 독립화시켜 적절히 모듈화한다면 개발과 유지보수에 좋은 영향을 미치게 된다. 따라서 게임 개발 시 각각의상태를 객체화 시켜서 관리하는 것이 권장된다.

 

 

그럼 이제 게임 상태와 상태의 변경을 통한 게임 흐름을 체험해 볼만한 간단한 샘플을 작성해 보자.

총 3가지 게임상태(ready, running, end)를 객체로 관리하고 키보드의 스페이스 바를 누르면 다음 상태로 이동하는 예인데, running 상태에서는 캔버스에 랜덤한 사격형을 그리는 이전 글(Game Loop, http://m.mkexdev.net/242)의 샘플을 그대로 사용할 것이다.

 

먼저 다음과 같이 HTML 파일을 준비한다.

<!DOCTYPE html>
<html lang="en">
 <head>  
  <script type="text/javascript" src="js/gameState.js"></script>
 </head> 
 <body>  
  <canvas id="GameCanvas" width="500" height="400" style="border: 1px solid #000;">
    HTML5 Canvas를 지원하지 않습니다. 크롬 또는 사파리와 같은 HTML5 지원 브라우저를 이용해 주세요
  </canvas>        
 </body>
</html>

 

 

다음으로 '준비/실행/종료'에 해당하는 각각의 게임상태를 객체로 관리하기 위한 3개의 생성자 함수를 정의하는데 공통 속성을 재사용하기 위해 프로토타입의 상속을 활용한다. 3가지 상태는 모두 게임 루프상에서 실행되기 위한 update(), display() 함수를 가지고 있으며 각 상태에 맞는 업데이트와 화면 갱신을 처리하도록 한다.

 

/* Define Base Game State Class */
function GameState(canvasElement){
 if(canvasElement != undefined){
     this.canvasSize = {width: canvasElement.width, height: canvasElement.height};  
     this.canvasContext = canvasElement.getContext('2d');                                    
   }
}

 

/* Define Ready State Class */
function Ready(canvasElement){
   //Call Parent Constract Function
   GameState.call(this,canvasElement);
}

 

Ready.prototype = new GameState(); //inherit

 

Ready.prototype.update = function(){ 
  this.canvasContext.fillStyle = '#000000'; 
}

 

Ready.prototype.display = function(){ 
   this.canvasContext.font = '18pt Arial';        
   this.canvasContext.textBaseline = 'top';
   this.canvasContext.fillText("Ready..." ,200, 150);
}

 

/* Define Running State Class */
function Running(canvasElement){
  //Call Parent Constract Function
  GameState.call(this,canvasElement);

  this.position = {};
}

 

Running.prototype = new GameState(); //inherit

 

Running.prototype.update = function(){ 
  this.position.x = Math.floor(Math.random() * (canvasElement.width - 20));
  this.position.y = Math.floor(Math.random() * (canvasElement.height - 20));
  
  this.canvasContext.fillStyle = 'rgb(' + Math.floor(Math.random() * 255) + ','
                    + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ')'; 
}

 

Running.prototype.display = function(){ 
   this.canvasContext.fillRect(this.position.x, this.position.y, 20, 20);
}

 

/* Define End Class */
function End(canvasElement){
   //Call Parent Constract Function
   GameState.call(this,canvasElement);
}

 

End.prototype = new GameState(); //inherit

 

End.prototype.update = function(){ 
   this.canvasContext.fillStyle = '#000000'; 
}

 

End.prototype.display = function(){ 
   this.canvasContext.font = '18pt Arial';        
   this.canvasContext.textBaseline = 'top';
   this.canvasContext.fillText("End!!!" ,200, 150);
}

 

 

 

게임 상태가 준비되었으니, 각 상태가 실행되도록 다음과 같이 작성한다. 게임상태를 저장하기 위한 gameState 배열과 이 배열에서 특정 상태를 선택하기 위한 인덱스 값을 저장하는 currentGameStateIndex 변수를 선언한다. 키보드의 스페이스 바를 누르면 이 변수 값을 차례대로(0~2) 변경되도록 하여 게임 상태가 순환되도록 한다. 나머지 코드는 지금까지 학습했던 내용과 동일하므로 설명을 생략한다.

 

var fps = 10;
var canvasElement;
var gameContext;
var gameState = [];
var currentGameStateIndex = 0;

 

function init(){
   canvasElement = document.getElementById('GameCanvas');
   gameContext = canvasElement.getContext('2d'); 
 
   //Create Game State Instance & Push in gameState Array
   gameState = [new Ready(canvasElement),new Running(canvasElement),new End(canvasElement)];
 
   setInterval(gameLoop, 1000/fps);
}

 

function gameLoop(){ 
   gameState[currentGameStateIndex].update();
   gameState[currentGameStateIndex].display();
}

 

function ChangeGameState(e){
   if(e.keyCode == 32){ //32: Space Key
      gameContext.clearRect(0, 0, canvasElement.width, canvasElement.height);  
      //Change Game State Index(0 ~ 2) 
      currentGameStateIndex = (currentGameStateIndex + 1) % 3; 
   }
}

 

window.addEventListener('load', init, false);
window.addEventListener("keydown", ChangeGameState, false);

 

샘플을 브라우저로 확인해 보면 스페이스 키로 인해 다음의 3 단계가 순환되는 것을 확인할 수 있다.

 

 

 

 

 

 

 

 

 

 

 

 

 

'모바일 > HTML5' 카테고리의 다른 글

[HTML5 Game] Moving Object  (0) 2013.10.02
[HTML5 Game]Calculating FPS  (0) 2013.09.30
[HTML5 Game] Game Loop  (0) 2013.09.25
[HTML5 Game] Collision Detection  (2) 2013.09.23
[HTML5 Game] Parallxing Background Animation  (0) 2013.09.21

[HTML5 Game] Game Loop

Posted in 모바일/HTML5 // Posted at 2013. 9. 25. 01:30
728x90

모든 마법은 게임루프(Game Loop)에서 일어난다!!!

 

처음 HTML5 Game 개발에 대한 학습을 시작할 때 가장 충격적(?)이었던 것이 바로 게임루프라는 개념이었다. 좀 과장된 표현이긴 하지만, 서버 측 어플리케이션 개발에 대부분의 시간을 보낸 나에게는 꽤나 신선한 느낌을 주었다. 물론 서버 측 어플리케이션에도 무한 반복 실행을 위한 루프 로직이 없는 것은 아니다. TCP 서버에서 입력 소켓을 지속적으로 리스닝 하는 것도 일종의 루프라 할 수 있고 주기적인 폴링을 위한 루프 로직도 심심찮게 구현해왔다. 하지만 초당 프레임 수에 기반한 매우 빠른 속도의 루프와 이벤트 처리, 상태 업데이트, 랜더링 등 거의 모든 로직이 스스로 살아 움직이는 게임 루프상에서 이뤄지는 개념은 적어도 나에게는 입가에 미소를 짓게 만들만큼 신선한 느낌이었다.

 

게임루프는 비단 HTML5 게임개발에만 적용되는 개념이 아니다. 어떤 프로그래밍 환경의 게임이라도 게임루프가 구현되며 큰 맥락에서는 동일한 개념과 로직이라고 할 수 있다.

 

혹자는 게임루프를 게임의 심장박동이라고 표현한다. 심장이 계속 뛰어야 하는 것처럼 게임이 살아있기 위해서는 게임루프가 지속적으로 실행되어야 하며 분당 심박 수처럼 게임 루프도 초당 프레임 수에 기반하므로 꽤 그럴싸한 비유라 하겠다.

 

 

업데이트와 디스플레이

게임 루프에서 모든 마법이 일어나지만 개념을 단순화 시키면 크게 두 가지 마법(?)이 일어난다고 할 수 있다.

바로 업데이트와 디스플레이다. 게임은 실행 중에 캐릭터가 이동하거나 레벨이 업데이트 되거나 하는 여러가지 상태 값의 업데이트가 필요하며 이렇게 업데이트 된 데이터를 기반으로 화면을 갱신해 줘야 한다. 이러한 게임 루프의 로직을 의사코드로 표현하면 다음과 같다.

 

gameLoop(){

update()

display()

}

 

 

FPS

FPS는 Frame Per Second의 약자로 초당 프레임 수를 말한다. 1초 동안 보여주는 화면의 수를 일컫는데 보통 영상을 상영할 때 필름의 프레임이 교체되는 속도 즉 프레임률(frame rate)과 동일한 개념이다. 보통 인간은 초당 15프레임 이상이면 깜빡임 현상을 거의 느끼지 못한다고 하며 25프레임 이상이면 비교적 자연스러운 영상으로 인식한다고 한다. 60프레임이상이면 잔상을 느끼지 못하게 된다.

 

프레임 수가 높을수록 더 부드러운 처리가 가능하지만 과도하게 높은 프레임 수는 게임 실행에 지장을 주게 되므로 성능과 하드웨어 환경 및 게임 상황을 고려해 적절한 프레임 수를 지정할 필요가 있다.

 

1) 고정 프레임 방식

초당 프레임 수를 일정하게 고정시키는 것을 고정 프레임 방식이라 한다. 이 방식은 한 프레임이 수행되는 시간이 아니라 초당 프레임 수행 횟수에 의존하는 방식이며 이 프레임 수행 횟수를 매 초 마다 고정시키는 방식이다.자료에 의하면 고정 프레임 방식은 하드웨어 환경을 미리 파악할 수 있어서, 하나의 프레임에 걸리는 시간을 미리 결정할 수 있는 콘솔 게임들에서 흔히 사용되어 왔다고 한다. HTML5 게임 개발에서 고정 프레임 방식을 적용할 경우 자바스크립트의 setInterval() 함수를 사용할 수 있다.

 

2) 가변 프레임 방식

초당 프레임 수가 일정하지 않고 유동적으로 변하는 것을 가변 프레임 방식이라 한다. 가변 프레임 환경에서 한 프레임의 수행 시간은 그 프레임에서 그려야 할 화면의 디스플레이 시간에 의존적이기 때문에 게임 상황에 따라 달라질 수 있다. 즉 초당 프레임 횟수가 아니라 프레임 수행 시간에 의존적인 방식이다. 따라서 하드웨어의 성능이나 게임 상황에 따라 초당 프레임 수가 달라질 수 있다는 것이다. 대부분의 PC게임이 이 방식으로 구현된다고 한다. HTML5 게임 개발에서 고정 프레임 방식을 적용할 경우 자바스크립트의 setTimeout() 함수를 사용할 수 있다.

 

고정 프레임 방식의 게임 루프를 코드로 표현하면 대략 다음과 같다.

var fps = 10;

setInterval
(gameLoop, 1000/fps);

 

자바스크립트의 setInterval() 함수는, 두 번째 매개변수로 지정된 시간 간격으로 함수를 반복해서 실행한다. 이때 시간 단위는 1/1000초 즉, 밀리세컨드 단위로 지정하게 된다.

 

코드에서는 1000/fps = 1000/10은 1초에 10번 gameLoop() 함수를 실행하라는 의미가 된다. 다시말해 1000/10 = 100 즉, 100ms (0.1초) 마다 함수를 호출하도록 한다.

 

게임 루프 구현

게임 루프와 FPS의 개념을 알아봤으니 이 개념을 기반으로 간단한 게임 루프를 HTML5 기반으로 구현해보자.

여기서 구현해 볼 예제는 게임 루프 상에서 Canvas 영역안에 임의의 색상의 사각형을 랜덤하게 반복적으로 그려주는 예이다.

 

먼저 HTML 파일을 다음과 같이 작성한다.

<!DOCTYPE html>
<html lang="en">
 <head>  
  <script type="text/javascript" src="js/gameLoop.js"></script>
 </head> 
 <body>  
  <canvas id="GameCanvas" width="500" height="400" style="border: 1px solid #000;">
   HTML5 Canvas를 지원하지 않습니다. 크롬 또는 사파리와 같은 HTML5 지원 브라우저를 이용해 주세요
  </canvas>        
 </body>
</html>

 

그리고 다음과 같이 자바스크립트를 작성한다. 게임 루프가 어떤 식으로 구현되는지 자세히 살펴보기 바라며 사각형의 위치와 색상이 랜덤하게 지정되도록 했다.

var fps = 10;
var canvasElement;
var gameContext;
var position = {};

function init(){
   canvasElement = document.getElementById('GameCanvas');
   gameContext = canvasElement.getContext('2d'); 
   setInterval(gameLoop, 1000/fps);
}

 

function gameLoop(){ 
   update();
   display();
}

 

function update(){
   //Set Rectangle Position(Random Positioning In Canvas)
   position.x = Math.floor(Math.random() * (canvasElement.width - 20));  //0~480
   position.y = Math.floor(Math.random() * (canvasElement.height - 20)); //0~380
 
   //Set Random Coloring
   gameContext.fillStyle = 'rgb(' + Math.floor(Math.random() * 255) + ','
                       + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ')';  
}

 

function display(){  
   gameContext.fillRect(position.x, position.y, 20, 20);
}

 

window.addEventListener('load', init, false);

 

 

이제 예제를 브라우저로 실행해보면 다음과 같은 화면을 볼 수 있을 것이다.

fps를 10으로 지정했기 때문에 1초에 10번 사각형이 그려질 것이며 이를 무한 반복하게 되는 것을 확인할 수 있다.

 

 

'모바일 > HTML5' 카테고리의 다른 글

[HTML5 Game]Calculating FPS  (0) 2013.09.30
[HTML5 Game] Game State  (0) 2013.09.27
[HTML5 Game] Collision Detection  (2) 2013.09.23
[HTML5 Game] Parallxing Background Animation  (0) 2013.09.21
[HTML5 Game] Background Animation  (1) 2013.09.20