[HTML5 Game] Parallxing Background Animation

Posted in 모바일/HTML5 // Posted at 2013. 9. 21. 17:10
728x90

지난 포스팅에서 백그라운드 애니메이션 처리 기법을 알아 보았다.

=> http://m.mkexdev.net/239

 

이 글에서는 하나의 배경이미지를 사용해서 애니메이션을 구현했는데,

이번 글에서는 더욱 현실감있게 하기 위해 몇 개의 배경을 중첩시켜서 서로 다른 속도로 애니메이션이 동작하도록 구현해 볼 것이다.

 

여러개의 배경이 서로 중첩되어 각기 다른 속도로 움직이도록 처리하면 게임을 더욱 실감나게 만들 수 있는데, 이를 시차 스크롤 방식이라 한다. 이에 대한 깔끔한 설명은 'LEARNING HTML5 온라인 게임 개발 프로그래밍'의 설명을 인용한다.

 

 수퍼 마리오나 소닉과 같은 측면 스크롤 방식의 게임에서 3차원 공간감을 더욱 실감나게 만드는 기술의 하나가 바로 시차(Parallxing)다.

...

 

자동차를 타고 다리를 건너가면서 차창 밖으로 지나가는 풍경을 바라보는 것과 같다고 생각하면 쉽게 이해될 것이다. 저 멀리 보이는 산이라든가 도시의 스카이라인에 비해 자동차에서 가까운 다리의 기둥들은 훨씬 빠른 속도로 시야를 스쳐 지나간다.

 

즉 가까운 거리에 있는 배경은 상대적으로 빠르게 움직이고 멀리 있을 수록 천천히 움직이도록 배경에 시차 스크롤링 효과를 주면 2D게임의 3차원 효과를 줄 수 있게 된다.

 

* 간단한 개념

시차효과를 구현하기 위해서는 각각의 배경을 별도의 이미지로 만들고 이를 중첩시켜서 서로 다른 속도로 애니메이션이 동작하도록 구현해야 한다. 즉 Canvas에 배경 이미지가 계층을 이루어 그려져야 하는데 이때 다른 이미지를 덮게 되는 이미지는 투명효과를 지원하도록 하여 밑에 깔린 이미지를 가리지 않도록 해야한다. 이것은 이미지를 제작할 때 고려되어야 하는 부분으로 디자이너의 몫이라 하겠다. 이번 예제에서는 개인적으로 투명이미지를 만들 수 있는 디자인 역량이 없는 관계로 앞에서 언급한 책의 이미지를 사용할 것이다.

 

 

총 4개의 배경 이미지가 사용되는데 이 이미지들은 자신의 표현부분을 제외하고는 모두 투명효과를 지원해서 차례대로 중첩시키면 다음과 같은 모양이 나온다.

 

 

이 4개의 배경이미지를 서로 다른 속도로 동작하도록 구현하면 시차 효과가 적용된 배경 애니메이션이 완성되는 것이다. 이때 서로 다른 속도를 구현하기 위한 다음과 같은 방법이 사용될 수 있다.

 

1) fps 조절

초당 프레임수를 조정하여 가까이 있는 배경일수록 더 빠른 fps를 사용한다. 다만 그다지 권장하고 싶지는 않다. 배경 이미지 애니메이션을 위해 fps가 따로 관리되어야 한다면 배보다 배꼽이 더 크지는 느낌이다.

 

2) 이미지 자르기 위치 조절

이전 글에서 살펴본대로 배경 애니메이션은 게임 루프상에서 이미지 자르기와 두 번의 이미지 그리기 작업을 반복함으로써 구현할 수 있었다. 즉 배경 이미지의 이동 간격을 서로 다르게 하여 시차 효과를 구현할 수 있다.

이 방법도 다음과 같이 두 가지로 나눠볼 수 있겠다.

 

- 이미지 크기 조절

앞에서 언급한 책인, 'LEARNING HTML5 온라인 게임 개발 프로그래밍'에서 구현한 방식이다. 이미지 크기를 배수로 조절하여 계층간 이미지 이동 간격을 별도로 계산하지 않아도 되도록 한다. 즉 첫번째 계층은 320px, 두 번째는 640px, 세 번째는 960px 너비를 갖게 함으로써 동일 시간 내에 점점 더 많은 부분을 보여주도록 한다.

 

- 이동 간격 조절

앞서, 이미지 크기 조절과 개념적으로는 동일하지만 이미지 크기에 제약을 두지는 않는다. 이미지 크기 조절 방식이 편리한 측면이 있으나 예제의 포괄성(?)을 위해 이미지 크기를 직접 손대지 않고 이동 간격을 조절할 수 있도록 한다. 이번 글의 예제에서 사용하는 방식이다.

 

* HTML 

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

 

 

* Javascript

'LEARNING HTML5 온라인 게임 개발 프로그래밍' 책의 예제 이미지를 사용했지만 코드는 이전 글들과의 맥락을 위해 사용하지 않겠다. 이 책에서는 Trident.js를 사용해 시차 효과를 구현하고 있다. 여기서는 이전 글들의 코드 구조를 유지하면서 라이브러리 없이 직접 구현하도록 한다.

 

1. 백그라운드, 생성자 함수와 메서드

/* Define Background Class */
function Background(assets,canvasElement){
   this.assets = assets; 
   this.canvasSize = {width: canvasElement.width, height: canvasElement.height}; //Canvas Size
   this.canvasContext = canvasElement.getContext('2d');                                   //Canvas Context
   this.spritesX = [];
   for(var i = 0; i < assets.length; i++){
      this.spritesX.push(0);
   }
}

 

Background.prototype.startAnimation = function(){ 
 //Clear Canvas  
 this.canvasContext.clearRect(0, 0, this.canvasSize.width, this.canvasSize.height);
 
 for(var i = 0; i < this.assets.length; i++){  
  //Draw Background Image
  var drawX = this.spritesX[i] * this.assets[i].bgImage.width;
  var drawWidth = this.assets[i].bgImage.width - drawX;
  this.canvasContext.drawImage(this.assets[i].bgImage,

                   drawX, 0, drawWidth, this.assets[i].bgImage.height,

                   0, 0, drawWidth, this.assets[i].bgImage.height);     
         
  //Fill Cut Out area     
  if(drawWidth < this.assets[i].bgImage.width) {
    var fillDrawWidth = this.assets[i].bgImage.width - drawWidth;
    this.canvasContext.drawImage(this.assets[i].bgImage,

                    0, 0, fillDrawWidth, this.assets[i].bgImage.height,

                    drawWidth, 0, fillDrawWidth, this.assets[i].bgImage.height);
  }      
 
  this.spritesX[i] = (this.spritesX[i] + this.assets[i].spritesRate) % 1;   
 } 
}

 

배경 이미지를 다뤘던 이전 글의 흐름과 거의 동일하다. 다만 이번 글에서는 중첩된 배경 이미지들이 사용될 것이기에 생성자 함수에 커스텀 객체 배열(assets)을 받는 부분과 이 배열을 기반으로 그리기 좌표 등이 계산되는 부분만 변경되었다. assets 배열의 개별 객체에는 배경 이미지 이외에도 이동 간격을 나타내는 속성인 spritesRate가 정의되어 있는데 이 값을 기준으로 자르기 X좌표(spritesX) 값의 수정이 이뤄진다. 생성자 함수에서는 spritesX를 배경 이미지 수 만큼 지정할 수 있도록 하였으며 처음에는 모두 0으로 초기화한다.

 

2. 기타 프로그램 로직

역시 이전 포스팅의 예제와 거의 동일하다. 중요한 것은 assets 배열에 담기는 객체의 spritesRate 속성과 그 값이다. 이 값은 이미지 자르기 x좌표를 계산하기 위한 값인데, 이 값의 크기를 배경 이미지마다 다르게 지정하여 애니메이션 속도를 조절하게 된다. 가장 밑에 깔리는 배경은 움직임이 없게 하기 위해 값을 '0'으로 지정하고 이어지는 배경이미지들은 직전 이미지보다 2배 빠른 속도로 애니에미션이 될 수 있도록 값을 지정했다.

var fps = 60;                           //frame per second
var background;                        //Character Instance
var canvasElement;                    //Canvas Element
var assetfiles;                            //Asset Image File Array
var assets = [];                        //Custom Asset Object Array  
var currentAssetLoadCount = 0;  //Asset Image File Load Count  

 

function init(){ 
 canvasElement = document.getElementById("GameCanvas"); 
 
 //Define Asset Image File Array
 assetfiles = ['image/Parallax0.gif', 'image/Parallax1.gif', 'image/Parallax2.gif', 'image/Parallax3.gif'];
 
 //Create Custom Literal Object(Define spritesRate Property) & Insert into Asset Image Array
 assets.push({spritesRate: 0});
 assets.push({spritesRate: 0.001});
 assets.push({spritesRate: 0.002});
 assets.push({spritesRate: 0.004});
  
 for (var i = 0; i < assetfiles.length; i++) {
    //Create Asset Image Ojbect
    var asset = new Image(); 
    asset.src = assetfiles[i];     
  
    //Assign Asset Image Object to the bgImage property that newly created
    assets[i].bgImage = asset;    
      
    //Assign Imgae Load Event
    asset.onload = onAssetLoadComplete;      
  }     
}

function onAssetLoadComplete(){ 
   //Check Load Complete of All Images
   if(++currentAssetLoadCount >= assetfiles.length){ 
     //Create Character Instance    
     background = new Background(assets,canvasElement);
     //Run Game Loop     
     setInterval(animationLoop, 1000 / fps);   
 } 
}

 

function animationLoop(){
   background.startAnimation();
}

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

 

 

* 실행화면 

 

 

* 마무리하며...

두 번의 포스팅을 통해 배경 이미지 애니메이션과 시차 효과가 적용된 애니메이션 구현 기법에 대해 알아보았다. 캐릭터 애니메이션이나 배경 애니메이션 모두 게임 루프상에서 이미지 그리기를 지속적으로 업데이트 하는, 즉 궁극적으로는 동일한 매커니즘이 사용됨을 알 수 있었다. 이 지식은 어떠한 2차원 배경 이미지 애니메이션도 구현할 수 있는 초석이 될 것이다. 개념이 정립되었으면 각자 취향에 맞는 배경 애니메이션을 살을 붙여 가며 구현해 보기 바란다.

 

 

[HTML5 Game] Background Animation

Posted in 모바일/HTML5 // Posted at 2013. 9. 20. 17:47
728x90

이번 글에서는 HTML5 게임의 백그라운드 애니메이션 구현 기법을 알아보자.

 

백그라운드 애니메이션은 배경이 고정되어 있지 않고 지속적으로 움직이는 것으로, 여기서는 마치 자동차를 타고 지나가는 것과 같이 배경이 우측에서 좌측으로 계속 움직이는 애니메이션 효과를 구현해 볼 것이다.

 

실제 게임에서는 캐릭터와 배경 애니메이션이 적절히 조화된 상태로 구현되지만 간단하게는 캐릭터는 가만히 있어도 배경이 계속 흐르듯 움직이면 마치 캐릭터가 앞으로 나아가는 것같은 느낌을 줄수도 있다.

 

* 간단한 개념

배경이 움직인다고 해서 여러개의 이미지가 사용되는 것은 아니다.(물론 여러개의 배경이라면 그 만큼 이미지가 필요하다). 여기서는 하나의 배경이 우측에서 좌측으로 움직이는 것이므로 하나의 배경 이미지만 사용하면 된다. 이전 글, Image Sprites의 개념에서 설명했듯이 HTML5 Canvas에는 원본 이미지에서 원하는 부분만 잘라내어 그릴 수 있다고 했다. 배경 애니메이션 역시 Canvas의 이러한 특징을 이용하는 것으로 총 두 번의 그리기 작업을 지속적으로 수행함으로써 배경이 움직이도록 할 수 있다.

 

먼저 배경이 좌측으로 움직인만큼 X좌표를 이동시켜서 첫 번째 그리기를 수행하고, 우측에 남은 공간만큼 원본에서 다시 잘라내어 두 번째 그리기를 수행하면 된다. 이것은 마치 회전목마(carousel)를 연상시킨다. 만일 회전목마의 한 단면만 볼 수 있다면 그 단면은 지나간 말의 꼬리와 새로운 말의 머리가 만나는 것의 연속이라 할 수 있다. 

 

결국 배경이 움직인다는 것은 시간의 흐름에 따라 배경의 그리기 좌표를 조정하고 이를 빠르게 전환시키면서 그려주면 자연스러운 애니메이션 처리가 된다는 것이다.

 

애니메이션으로 사용되는 배경 이미지

하나의 이미지를 사용하여 그림을 붙여 나가는 방식이기 때문에 배경 이미지는 시작과 끝이 자연스럽게 연결되는 모습이어야 한다. 그렇지 않을 경우, 애니메이션에는 지장이 없으나 배경이 끝나고 시작되는 지점의 시각적 연결이 부자연스러워 게임의 퀄리티가 손상될 것이다. 이렇게 이미지의 시작과 끝 부분을 자연스럽게 연결해서 반복하는 것을 타일링 방식이라 한다.

 

 

이제 본격적으로 Background Animation 구현방법을 알아보자.

 

* HTML

HTML 코드는 설명할 것도 없이 간단하다. 캔버스가 있고 대체 텍스트와 스크립트 참조가 전부이다.

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

 

 

* Javascript

늘 그래왔듯이 모든 마법(?)은 자바스크립트에서 이뤄진다. 전체적인 구조는 지금까지 구현해 왔던 것과 유사하다. 다만 캐릭터 객체가 아닌 백그라운드 객체로 변화가 있으며 두 번의 그리기 작업이 필요하다는 것이다.

애니메이션을 위하 Trident.js를 사용할 수도 있으나 이번에는 라이브러리 의존없이 구현해 보도록 한다.

 

1. 백그라운드, 생성자 함수와 메서드

/* Define Background Class */
function Background(assetObj,canvasElement){
   this.assetObj = assetObj; 
   this.canvasSize = {width: canvasElement.width, height: canvasElement.height};   //Canvas Size
   this.canvasContext = canvasElement.getContext('2d');                                     //Canvas Context
   this.spritesX = 0;                                                                                       //Image X Position 
}

 

Background.prototype.startAnimation = function(){ 
  //Clear Canvas  
  this.canvasContext.clearRect(0, 0, this.canvasSize.width, this.canvasSize.height);
 
  //Draw Background Image
  var drawX = this.spritesX * this.assetObj.bgImage.width;
  var drawWidth = this.assetObj.bgImage.width - drawX;
  this.canvasContext.drawImage(this.assetObj.bgImage,
                    drawX, 0, drawWidth, this.assetObj.bgImage.height,
                    0, 0, drawWidth, this.assetObj.bgImage.height);     
    
   //Fill Cut Out area
  if(drawWidth < this.assetObj.bgImage.width) {
        var fillDrawWidth = this.assetObj.bgImage.width - drawWidth;
        this.canvasContext.drawImage(this.assetObj.bgImage
                          0, 0, fillDrawWidth, this.assetObj.bgImage.height,
                          drawWidth, 0, fillDrawWidth, this.assetObj.bgImage.height);
    }
   
    this.spritesX = (this.spritesX + this.assetObj.spritesRate) % 1;

 

먼저 Background 생성자 함수에서는 배경이미지를 표현하는 에셋 인스턴스와 Canvas 요소를 전달받아서 초기화 작업을 수행한다. spritesX 변수는 배경이미지의 이동을 처리하기 위해 원본 이미지에서 이동할 크기를 저장하는 변수이다.

 

다음으로 실제 애니메이션 처리가 이뤄지는 startAnimation 메서드에서는 캔버스를 다 지우면서 시작한다.

drawX 변수는 원본 이미지 크기에서 spritesX를 곱한 값으로, 원본 이미지에서 원한는 부분을 자르게 될 X좌표 값이 된다. spritesX의 값은 이후 나오게 될 spritesRate값을 계속 더하여 원본 이미지의 x 좌표를 spritesRate 간격만큼 이동시키는데, 예를 들어 원본 이미지 너비가 100px이고 spritesRate가 0.1일 경우 spritesX와 drawX값의 변화는 다음과 같다.

 

 spretesX

 0

 0.1

 0.2

 0.3

 drawX

 0

 10

 20

 30

 

즉, X좌표가 10px 만큼 이동하게 되는 것이다.

이어지는 drawWidth는 원본 이미지에서 drawX르 뺀 값으로 drawX부터 이미지 나머지 부분의 크기를 나타낸다. 이렇게 첫번째 그리기 작업이 수행된다.

 

이후 두번째 그리기 작업이 수행되는데, 이 시점에 브라우저로 확인해 보는 것도 프로그램 로직 이해에 도움이 될 것이다. 두 번째 그리기 작업은 앞서 첫번째 그리기 작업에서 비워진 우측 공간을 메우는 작업으로 역시 동일한 원본 이미지의 첫 부분에서 비워진 공간만큼 잘라내어 그리는 것이다.

 

두번째 그리기 작업에서 중요한 것은 fillDrawWidth 변수인데, 이 변수에는 비워진 공간의 너비를 계산해서 그 값을 저장하게 된다. 그리고 비워진 공간의 X좌표에 두번째 그리기 작업을 수행하면 두 개의 그리기 작업이 자연스럽게 연결됨으로써 애니메이션 효과가 구현되는 것이다.

 

마지막으로 spritesX 값을 spritesRate만큼 계속 더해가는데, 최대 크기 비율인 1이 될 시점에 다시 0으로 만들어 줌으로써 이미지가 처음부터 다시 그려지도록 한다.

 

2. 기타 프로그램 로직

나머지 코드는 이전에 알아봤던 캐릭터 애니메이션의 그것과 거의 유사하다.

var fps = 60;            //frame per second
var background;        //Character Instance
var canvasElement;    //Canvas Element
var asset;                 //Asset Image Ojbect    

 

function init(){ 
 canvasElement = document.getElementById("GameCanvas"); 
  
 //Create Asset Image Ojbect
 asset = new Image(); 
 asset.src = 'image/background.png';       
 //Assign Imgae Load Event
 asset.onload = onAssetLoadComplete
}

 

function onAssetLoadComplete(){ 
 //Create Custom Asset Object 
 var assetObj = {bgImage:asset, spritesRate:0.01}; 
 //Create Character Instance    
 background = new Background(assetObj,canvasElement);
 //Run Game Loop     
 setInterval(animationLoop, 1000 / fps); 
}

function animationLoop(){
  background.startAnimation();
}

 

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

 

fps 값을 60 즉, 1초에 60번 프레임을 교체하도록 하여 캐릭터 애니메이션 보다 조금 더 빠른 속도를 지정했다.

나머지 코드는 캐릭터 애니메이션 예제와 동일하므로 이전 글을 참고하면 되고 다만 spritesRate가 추가되었는데 이것은 배경 이미지의 이동 간격을 나타낸다. 코드에서는 0.01을 지정했는데 이것의 의미는 프레임이 이동할 때마다 원본 이미지에서 0.01px 이동한다는 의미가 된다. fps와 함께 이 값의 변화는 배경 애니메이션의 속도에 영향을 준다. 이 값이 클수록 한 프레임에 이동하는 배경 이미지의 간격이 크지므로 더욱 빠른 애니메이션 효과를 낼 수 있다. 즉 이 값의 조정을 통해 동일한 fps 상에서 캐릭터의 속도를 증가시킬수도 있다는 것이다.

 

 

실행화면

인터넷에서 background image를 검색해서 이미지의 좌측 시작부분과 우측 끝 부분이 매끄럽게 연결되는 이미지를 다운로드 해서 예제를 실행해 보자.

 

마무리하며...

이 글에서는 횡스크롤 형태의 게임에서 사용할법한 배경 애니메이션을 구현해 봤다.

원본 이미지의 좌표를 이동해 가며 두 번의 그리기 작업을 반복함으로써 배경이 우측에서 좌측으로 이동하는 효과를 구현했는데 이는 유명한 고전 오락실 게임인 1945와 같이 위에서 아래로 흐르는 배경 애니메이션 처리에도 동일한 개념으로 접근할 수 있을 것이다. 애니메이션의 방향을 바꿔서 직접 구현해 보면 더욱 빠른 이해가 될 것이다.

 

 

[HTML5 Game] Character Animation Using Sprites

Posted in 모바일/HTML5 // Posted at 2013. 9. 17. 07:44
728x90

이전 글에서 캐릭터의 움직임을 위한 애니메이션 기법을 알아봤다.

이전 글: http://m.mkexdev.net/235

 

이 글에서는 캐릭터 애니메이션을 처리하기 위해 각각의 동작을 분리된 개별 이미지로 처리했었다.

하지만 이런 방식은 많은 이미지 리소스를 다운로드 해야 하기 때문에 비효율적이다.

 

이번 글에서는 Image Sprites 기법을 이용해 애니메이션을 구현하는 예를 살펴 보겠다.

Image Sprites 모든 캐릭터의 모든 동작을 하나의 이미지로 만들어 필요한 부분만 잘라내서 사용하는 방식으로 리소스의 다운로드 비용을 줄이고 효율성을 높이는 기법으로 실제 게임 개발에 많이 사용되고 있기도 하다.

 

아래 그림은 http://opengameart.org 의 샘플 이미지로 8 * 36 즉, 총 8가지 패턴의 서로 다른 동작 36개를 하나의 이미지로 모아놓은 것이다. 이번 예에서는 이 이미지를 Sprites 해서 애니메이션을 구현해 볼 것이다.

 

 

 

* 간단한 개념

HTML5 Canvas에 이미지를 그릴 때, 이미지 원본의 특정 좌표에서 원하는 너비,높이 만큼 이미지를 그릴 수 있다. 예를 들어 아래 그림과 같이 4개의 서로 다른 모습이 하나의 이미지에 포함되어 있을 때 개별 이미지의 너비/높이 값으로 특정 이미지 좌표와 크기를 산출할 수 있으며 이렇게 산출된 값으로 원하는 영역의 이미지를 Canvas에 그릴 수 있게 된다. 즉 아래 그림과 같이 4개의 동작일 경우, (0,0) ~ (384,0)까지 128*128 크기로 차례대로 그려주면 되는 것이다. 

* HTML

HTML은 이전 글과 동일한 구조다.

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

 

* JavaScript

자바스크립트 역시 전체적인 흐름은 이전 글의 내용과 동일하다.

다만 이전 글에서는 개별 이미지를 담는 배열과 이 배열의 인덱스를 탐색하기 위한 프레임 값의 업데이트가 있었던 것에 반해, 여기서는 하나의 이미지에서 원하는 부분을 잘라내기 위한 이미지 X좌표 값의 업데이트가 구현된다.

 

1. 캐릭터, 생성자 함수와 메서드

/* Define Character Class */
function Character(assetObj,canvasElement){
   this.assetObj = assetObj;                                                                           //Custom Asset Object
   this.canvasSize = {width: canvasElement.width, height: canvasElement.height}; //Canvas Size
   this.canvasContext = canvasElement.getContext('2d');                                  //Canvas Context
   this.spritesX = 0;                                                                                      //Image X Position
}

Character.prototype.startAnimation = function(){ 
  //Clear Canvas  
  this.canvasContext.clearRect(0, 0, this.canvasSize.width, this.canvasSize.height);

 
  //Draw Image on Canvas
  this.canvasContext.drawImage(this.assetObj.assetImage,
       this.spritesX * this.assetObj.spritesWidth, this.assetObj.spritesY,
       this.assetObj.spritesWidth, this.assetObj.spritesHeight,
       100, 100,
       this.assetObj.spritesWidth, this.assetObj.spritesHeight);
   
 //Update Sprite Image X Position       
 this.spritesX  = ++this.spritesX  % this.assetObj.spritesCount;  
}

 

이번 코드의 생성자 함수에서는 이미지 배열이 아닌 하나의 이미지 정보를 담고 있는 커스텀 객체를 매개변수로 전달받는다. 그리고 원본 이미지에서 잘라 낼 X 좌표 값을 위한 spritesX 변수를 선언하였다. 이 변수는 총 36개의 동작의 X좌표 계산을 위한 변수이며 '0 에서 35'의 값을 가지게 된다. 이 값에 개별 동작의 너비 값인 128px를 곱해 주면 0 ~ 4480 즉, 실제 잘라 낼 이미지의 X좌표를 계산할 수 있게 된다.

 

HTML5의 Canvas는 원본 이미지에서 원하는 부분을 잘라내어 그릴 수 있도록 아래와 같은 매개변수를 가진 drawImage()함수를 제공한다.

 

drawImage 매개변수>

 - image, in float sx, in float sy, in float sw, in float sh, in float dx, in float dy, in float dw, in float dh

 

drawImage 매개변수 설명>

 - 이미지 객체, 자르기 X , 자르기 Y , 자를 너비, 자를 높이, 그릴 위치 X 값, 그릴 위치 Y 값, 그릴 너비, 그릴 높이

(실제 제대로 된 해석은 원본 X값, 대상 X값.. 이런 식이지만 원본 이미지를 자른다는 개념을 명확히 하기 위해 임의대로 설명함)

 

 

2. 프로그램 로직

하나의 이미지만을 사용하기 때문에 이전 코드에 비해 전역변수가 줄어 들었다. 그리고 초당 프레임 수인 fps도 15로 작게 설정되었다. 샘플을 실행해 보면 알겠지만 초당 30 프레임으로 동작시키면 캐릭터가 너무 빠르게 움직이므로 적절한 값으로 변경해 주었다.

var fps = 15;             //frame per second
var character;             //Character Instance
var canvasElement;     //Canvas Element
var asset;                  //Asset Image Ojbect

 

그리고 초기화 함수를 보면 단 한개의 이미지만을 로딩하는 것을 확인할 수 있으며 이미지 객체와 개별 동작의 수, 개별 이미지의 너비/높이 및 Y 좌표 값을 담은 커스텀 객체를 생성해서 캐릭터 객체의 생성자 함수로 전달해 주고 있다. 이 값들을 이용해 원본 이미지에서 어떤 부분의 이미지들을 애니메이션 할 것인가가 결정된다.

function init(){ 
 canvasElement = document.getElementById("GameCanvas"); 
  
  //Create Asset Image Ojbect
  asset = new Image(); 
  asset.src = 'image/zombie.png';       
  //Assign Imgae Load Event
  asset.onload = onAssetLoadComplete
}

 

function onAssetLoadComplete(){ 
  //Create Custom Asset Object 
var assetObj = 
   {assetImage:asset, spritesCount:36, spritesWidth:128, spritesHeight:128,
     spritesY: 128 * 0}; 


  //Create Character Instance    
  character = new Character(assetObj,canvasElement);
  //Run Game Loop     
  setInterval(animationLoop, 1000 / fps); 
}

 

function animationLoop(){
  character.startAnimation();
}

 

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

 

 

실행화면

코드를 실행하면 좀비가 걸어가다 머리가 터지며 죽는 모습의 애니메이션을 확인할 수 있다. 

 

마무리 하며...

지금까지 캐릭터 이미지를 구현하기 위한 두 가지 방법을 알아 보았다. 사실 두 가지 방법은 이미지를 몇 개 사용하는지만 다를 뿐 동일한 프로그램 로직을 가지고 있다. 실제 게임 개발에서는 Image Sprites 기법을 많이 사용하므로 이 기법으로 여러가지 테스트 프로그램을 작성해 보는 것이 도움이 될 것이다.

 

두개의 글을 통해 애니메이션 구현 기법을 충분히 숙지하였으니, 다음 글에서는 공개된 라이브러리를 사용해서 애니메이션을 구현해 보도록 하겠다. 라이브러리를 활용하면 좀 더 안정적으로 그리고 생산성 높게 개발할 수 있으므로 자신의 개발 무기로 장착해 두는 것이 좋을 것이다.

 

 

 

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

[HTML5 Game] Background Animation  (1) 2013.09.20
[HTML5 Game] Character Animation Using Trident.js  (3) 2013.09.17
[HTML5 Game] Character Animation  (1) 2013.09.14
CSS3 3D Effect  (0) 2013.08.02
...  (0) 2013.07.23

[HTML5 Game] Character Animation

Posted in 모바일/HTML5 // Posted at 2013. 9. 14. 08:30
728x90

HTML5의 Canvas 요소를 활용하여 그럴싸한 2D 게임을 개발할 수 있다.

 

UDACITY에서도 HTML5 기반의 게임 개발 강의를 무료로 제공하고 있다

=> https://www.udacity.com/course/cs255

 

여기서 제공하는 캐릭터 애니메이션 구현 기법을 살펴보자. 코드는 내 입맛에 맞게 수정을 가했지만 전체적인 맥락은 동일하다.

 

대략적은 구현 흐름을 보면, 캐릭터의 움직임을 위해서 각각의 컷을 개별 이미지로 준비하고 이 이미지들을 Canvas에 차례대로 반복해서 그려주는 것이다. 이미지들은 매우 빠른 속도로 Canvas에 그려지는데 이때 적용되는 개념이 초당 프레임(FPS, frame per second)이다. 

 

FPS는 게임 개발 영역에서는 자연스러운 개념으로 인간이 느끼지 못하는 속도로 프레임을 교체하여 자연스러운 움직임이 가능하도록 하는 것으로 영화 필름의 재생을 생각해 보면 이해하기 쉽다.

 

인간의 눈은 초당 인식할 수 있는 프레임의 수가 제한적이기 때문에 보통 초당 30에서 60회 사이에서 화면을 다시 그리는 반면 사용자 입력값에 대한 확인은 이보다 훨씬 많은 초당 100회 정도 수행한다. 하지만 이 모든 것은 게임의 상태에 따라 달라진다. - LEARNING HTML5 온라인 게임 개발 프로그래밍

 

아래 그림은 캐릭터 움직임을 19개 이미지로 분할한 모습을 보여준다.

 

 

 

* HTML

애니메이션 처리는 모두 자바스크립트에서 구현한다. HTML 파일은 단지 Canvas 요소와 자바스크립트을 참조하는 코드만 존재한다.

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

 

 

* JavaScript

자바스크립트로 이미지를 다운로드하여 애니메이션을 구현한다. 애니메이션 처리를 위한 자바스크립트의 핵심 함수는 setInterval()이다. 이 함수는 주어진 간격으로 함수를 반복(loop) 실행시켜주는 함수로 setTimeout() 함수와 함께 HTML5 게임 개발에 필수적으로 사용되는 함수이다. 먼저 캐릭터 객체를 위한 코드부터 살펴보자

 

1. 캐릭터, 생성자 함수와 메서드 

function Character(assets,canvasElement){
  this.assets = assets;                                                                                   //Asset Image Array
  this.canvasSize = {width: canvasElement.width, height: canvasElement.height}; //Canvas Size
  this.canvasContext = canvasElement.getContext('2d');                                   //Canvas Context
  this.currentFrame= 0;                                                                                //Current Frame
}

 

Character.prototype.startAnimation = function(){
    //Clear Canvas  
    this.canvasContext.clearRect(0, 0, this.canvasSize.width, this.canvasSize.height);
    //Draw Image on Canvas
    this.canvasContext.drawImage(this.assets[this.currentFrame], 100, 100);
    //Update Current Frame
    this.currentFrame = ++this.currentFrame % this.assets.length;  

 

생성자 함수에서는 게임 캐릭터의 개별 이미지를 담을 assets 배열과, 이 이미지를 그려줄 Canvas 요소를 매개변수로 받아서 초기화 작업을 수행한다. CurrentFrame는 개별 이미지를 담고 있는 assets 배열의 인덱스로 사용될 변수로 총 19개의 이미지를 순서대로 교체하기 위해 0~18까지의 값을 가지게 된다.

 

그리고 실제 애니메이션 동작을 처리하는 startAnimation 메서드에서는 먼저 Canvas를 초기화시킨다. Canvas를 초기화하지 않으면 이전에 그린 그림과 새로 그리는 그림이 서로 겹쳐지게 되어 잔상이 남게 되기 때문에 초기화 하는 작업을 제일 먼저 수행한다.

다음으로 19개의 캐릭터 이미지를 담고 있는 assets 배열에서 CurrentFrame에 해당하는 이미지를 선택해서 Canvas에 그려준다. 마지막으로 19개의 이미지를 순회하기 위해 CurrentFrame 값을 업데이트 하는데 0~18까지 순회하도록 나머지 연산을 수행한다. 결국 이 startAnimation 메서드가 끊임없이 반복 수행됨으로써 캐릭터의 움직임을 구현할 수 있게 되는 것이다.

 

2. 프로그램 로직

이제 이미지를 다운로드해서 setInterval()함수와 캐릭터 객체를 활용하는 코드를 살펴보자.

먼저 전역변수를 정의한다.

var fps = 30;          //frame per second
var character;         //Character Instance
var canvasElement;     //Canvas Element
var assetfiles;        //Asset Image File Array
var assets = [];       //Asset Image Ojbect Array
var currentAssetLoadCount = 0;  //Asset Image File Load Count

 

fps는 frame per count 약자로 초당 프레임 수를 나타내는데 30이라는 값은 1초에 30번 함수를 호출하겠다는 의미이다. 즉 1초에 30번 프레임이 이동 시키겠다는 의미이다.

 

다음으로 초기화함수를 정의한다.

function init(){ 
 canvasElement = document.getElementById("GameCanvas"); 
 
 //Define Asset Image File Array
 assetfiles = 
  [ 'image/robowalk/robowalk00.png', 'image/robowalk/robowalk01.png',            
    'image/robowalk/robowalk02.png',  'image/robowalk/robowalk03.png',        'image/robowalk/robowalk04.png', 'image/robowalk/robowalk05.png',
    'image/robowalk/robowalk06.png', 'image/robowalk/robowalk07.png',     'image/robowalk/robowalk08.png', 'image/robowalk/robowalk09.png',     'image/robowalk/robowalk10.png', 'image/robowalk/robowalk11.png',
    'image/robowalk/robowalk12.png', 'image/robowalk/robowalk13.png',     'image/robowalk/robowalk14.png', 'image/robowalk/robowalk15.png',     'image/robowalk/robowalk16.png','image/robowalk/robowalk17.png',
    'image/robowalk/robowalk18.png'
   ];
         
  for (var i = 0; i < assetfiles.length; i++) {
    //Create Asset Image Ojbect
    var asset = new Image(); 
    asset.src = assetfiles[i];  
    //Insert Asset Image in Asset Image Array
    assets.push(asset);        
    //Assign Imgae Load Event
    asset.onload = onAssetLoadComplete;      
  } 
}

 

페이지가 로딩되면 제일 처음 실행될 초기화 함수에서는 HTML DOM에서 Canvas 요소를 선택하고 총 19개의 캐릭터 이미지를 파일을 불러와서 이미지 객체를 생성하여 assets 배열에 담는다. 실제 웹 환경에서는 이미지 다운로드 시간에 지연이 있을 수 있으므로 이미지 로드가 모두 완료된 후 다음 로직을 수행해야 한다. 이미지 로딩이 완료되면 발생하는 onload 이벤트에 onAssetLoadComplete 함수를 할당한다.

 

onAssetLoadComplete 함수 코드는 다음과 같다.

function onAssetLoadComplete(){   
   //Check Load Complete of All Images
   if(++currentAssetLoadCount >= assetfiles.length){ 
      //Create Character Instance    
      character = new Character(assets,canvasElement);
      //Run Game Loop     
      setInterval(animationLoop, 1000 / fps);   
 }
}

 

function animationLoop(){
     character.startAnimation();
} 

 

이 함수에서는 모든 캐릭터 이미지가 로딩 완료되었는지 체크하고 완료되었다면 캐릭터 객체를 생성하고 setInterval() 함수로 반복 루프를 시작시킨다. setInterval() 함수는 반속 실행을 위한 시간 간격을 밀리세컨트 단위로 설정하게 되는데 1000/fps 즉 1000/30은 0.33초 마다 함수를 수행하라는 의미가 되므로 결과적으로 1초에 30번 Character 객체의 startAnimation() 함수가 수행시키게 된다.

 

그리고 마지막으로 페이지가 로딩되면 초기화 함수가 수행될 수 있도록 이벤트를 연결시킨다.

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

 

 

실행화면

코드를 모두 작성하고 HTML5를 지원하는 브라우저(크롬 or 사파리)로 확인하면 로봇 캐릭터가 움직이는 애니메이션을 확인할 수 있다. 다음 그림은 화면을 캡쳐한 것이다.

 

마무리 하며...

이번 글에서는 UDACITY 강의를 기반으로 HTML5 기반의 애니메이션 처리를 살펴봤다. 여기서는 총 19개에 달하는 각각의 캐릭터 이미지를 사용했지만 실제 게임 개발에서는 이렇게 많은 이미지를 사용한다는 것은 매우 비효율적인 것이 된다. 그래서 대부분 Image Sprite 라는 기법을 이용해 모든 에셋이 포함된 하나의 이미지를 기반으로 필요한 에셋만 잘라내서 활용하는 방식이 사용된다. 또한 이러한 애니메이션 처리를 위한 오픈소스 라이브러리도 많이 존재한다. 다음 글에서는 Image Sprite 기법과 애니메이션 라이브러리를 사용하여 애니메이션을 구현하는 기법을 알아보자.

 

참고> 이 글에서 사용된 게임 에셋 이미지(UDACITY 이미지) 다운로드 경로

=> https://www.udacity.com/media/js/standalone/libs/gamedev_assets/robowalk/robowalk01.png

 

 

 

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

[HTML5 Game] Character Animation Using Trident.js  (3) 2013.09.17
[HTML5 Game] Character Animation Using Sprites  (0) 2013.09.17
CSS3 3D Effect  (0) 2013.08.02
...  (0) 2013.07.23
HTML5 개발을 도와주는 도구들  (4) 2010.11.01

[CSS3] Animation

Posted in 모바일/HTML5 // Posted at 2010. 9. 13. 14:06
728x90
Animation (개체에 애니메이션 효과를 주다)
CSS3 에는 웹 페이지 개체에 동적인 효과를 주기 위한 흥미로운 기능들이 추가되었는데

앞서 살펴본 Transition 이나 Transform 과 같은 특성들이 바로 그것이다

여기에 더불어 Animation 이라는 특성도 추가되었는데 이것은 말 그대로 웹 페이지에 애니메이션을 구현할 수 있는 특성이다. 물론 Transition 이나 Transform 과 같은 특성으로 애니메이션 효과를 줄 수는 있지만 이는 임의의 (정해진) 한 상태에서 다른 상태로의 변화를 주어 동적 효과를 주는 것인 반면 Animation 을 이용하면 동적 효과의 시작점과 종료점을 명시적으로 제어할 수 있다는 특징이 있다

즉 Transition 이나 Transform 은 변화된 상태, 변화하는 과정은 제어할 수 있지만 변화의 시작부터 종료까지의 제어는 별도의 스크립트를 통해서만이 가능하다는 것이다

Animation 에서 제어할 수 있는 변화의 시작점과 종료점은 키프레임(keyframe)이라는 개념으로 설명된다. 플래시나 다른 동적 효과를 다루는 툴을 다뤄봤다만 키프레임이라는 단어가 익숙할 것이다
키프레임을 간단히 설명하면, 애니메이션을 구성하는 동작의 키가 되는 프레임을 말하며 이 프레임들의 연결 및 이동으로 자연스러운 동적 효과의 처리가 가능하다

웹킷 기반 브라우저에서만 가능
Transitino 이나 Transform 은 비록 그 스펙이 완성되지는 않았지만 브라우저별 접두어를 사용하는 임시특성을 이용할 수 있었지만 Animation 은 현재까지 웹킷 기반 브라우저만 지원하고 있다
즉 사파리와 크롬 브라우저에서만 Animation 을 지원하고 있다
(따라서 이 글에서는 W3C의 표준명 대신 -webkit- 접두어를 이용하여 설명하기로 한다)

키프레임
Animation 효과 중심이 되는 개념이라 할 수 있다
애니메이션 효과를 위한 CSS속성을 각각의 프레임으로 정의하고 이 프레임들이 연결되어 동적효과를 주게 된다. 즉 애니메이션을 위한 시작~종료 사이의 동적 효과를 각각의 프레임으로 정의하는 것이다

키프레임의 간단한 예를 보도록 하자. 아래 코드는 두개의 키프레임을 정의한 것이며 각각은
투명도와 너비 속성의 변화를 주는 것이며 from ~ to 즉 시작과 종료 프레임을 정의한 것이다
(키프레임 정의 오른쪽의 myAnimation 은 이 프레임셋의 이름(name)이다)

@-webkit-keyframes myAnimation {
  from opacity: 0.1; width: 150px;  }
  to    opacity: 1.0; width: 300px; }
 }

키프레임은 비율로도 나눌 수 있는데 다음과 같이 정의할 수 있다. 애니매이션 효과를 위한 4개의 프레임이 비율로 정의되었으며 정해진 시간 내 비율에 맞게 변화가 이어져 나갈 것이다
@-webkit-keyframes myAnimation  {
  0%     { left: 100px; }
  40%   { left: 150px; }
  60%   { left: 75px;  }
  100% { left: 100px; }
}

form, to, 그리고 비율을 혼합하여 정의할 수도 있다

애니메이션 속성
키프레임으로 동적효과를 위한 각 변화의 지점을 정의한 후 이 프레임들이 어떤식으로 연결되는지를 설정하는 애니메이션 속성을 정의해야 한다. 애니메이션 속성으로 애니메이션이 실행되는 시간, 반복 횟수, 변화의 정도, 프레임 연결 방향등을 지정하게 된다

-webkit-animation-name
키 프레임을 지정한다. 앞서 키프레임의 이름을 여기에 매핑 시킨다

-webkit-animation-duration
애니메이션이 실행되는 총 시간을 지정한다. 이 시간내에 키프레임이 연결되어 애니메이션이 실행된다
(기본 값: 0)

-webkit-animation-iteration-count
애니메이션의 반복횟수를 지정한다. 기본적로 키프레임은 시작~종료까지 한번만 실행된다
두번이상 실행하고 싶으면 이 속성에 반복하고자 하는 횟수를 지정하면 된다
만일 계속 실행(무한 실행)하고 싶을 경우 infinite 를 지정하면 된다 (기본 값: 1)

-webkit-animation-direction
키프레임의 연결 방향을 지정한다. 기본적으로 키프레임은 from(혹은 0%) ~ to(혹은 100%) 방향으로 연결된다. 만일 역방향으로의 연결을 원한다면 alternate 를 지정하면 된다 (기본 값: normal)

-webkit-animation-timing-function
키프레임간 변화의 정도를 지정한다. Transition의 그것과 동일하며 다음 글을 참고 바란다
(기본 값: ease) [CSS3] Transition

-webkit-animation-play-state
애니메이션의 실행 상태를 지정한다. running 와 paused 값이 있다(기본값: running)
애니메이션 실행 도중 일시정지, 재시작 시킬 경우 이용한다

-webkit-animation-delay
애니메이션이 시작되기 전 대기시간을 지정한다 (기본 값: 0, 즉시 시작)


애니메이션의 간단한 예
키프레임과 애니메이션 속성을 이용하여 간단한 동적 효과를 처리해 보자
이미지의 투명도와 너비 그리고 회전각의 변화를 두 개의 프레임으로 정의하였다
애니메이션 속성은, 2초 동안 정방향<->역방향을 번갈아 가면서 무한대로 실행하도록 한다


<img src="son1.bmp">
.....
<style>
@-webkit-keyframes myAnimation {
 from {
    opacity: 0.1;
    width: 150px;
    -webkit-transform: rotate(15deg);
  }
 to {
    opacity: 1.0;
    width: 300px;
    -webkit-transform: rotate(0deg);
  }
}

 img {
   -webkit-animation-name: myAnimation ;
   -webkit-animation-duration: 2s;
   -webkit-animation-iteration-count: infinite;
   -webkit-animation-timing-function: ease-in-out;
   -webkit-animation-direction: alternate;
 }
</style>


데모를 실행하면 이미지의 크기와 투명도 그리고 회전각도의 변화가 지속적으로 번갈아 가며 실행되는 것을 확인 할 수 있을 것이다
 
              è  


참고>
http://www.w3.org/TR/css3-animations/
http://robertnyman.com/2010/05/06/css3-animations/
http://webkit.org/blog/324/css-animation-2/