[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

[HTML5 Game] Collision Detection

Posted in 모바일/HTML5 // Posted at 2013. 9. 23. 21:30
728x90

거의 대부분의 게임은 충돌감지를 필요로 한다.

 

플레이어를 향해 달려오는 적(enemy), 적을 향해 쏘는 총알 등 게임 요소들간 충돌을 구현하기 위해서는 개별 요소들의 시각적 겹침을 처리해야 한다.

 

2D 게임에서의 충돌 감지는, 충돌 감지 대상이 되는 게임 요소의 형태에 따라 충돌박스, 충돌구, 충돌점 등 상황에 맞는 감지 로직을 구현해야 한다.

 

여기서는 가장 기본이 되는 충돌 박스에 대해 알아 보겠다.

 

* 간단한 개념

앞서 언급했듯이, 충돌 감지는 두 그래픽 요소의 시각적 겹침을 처리하면 된다. 겹침은 그래픽 요소의 X, Y 좌표와 요소의 크기(너비/높이) 값이 기준이 되는데 다음과 같이 크게 두 영역의 겹첨을 감지하면 된다.

 

1) X 좌표 겹침

player 와 enemy 라는 두 그래픽 요소가 있다고 가정하자.

player를 기준으로 보면, enemy가 player의 X좌표 영역 안에 있을 경우 충돌되었다고 할 수 있다.

 

아래 그림을 보면 이해가 쉬울 것이다.

먼저 enemy의 우측 끝 X좌표(enemy.x + enemy.width)가 player의 좌측 끝 X좌표(player.x)보다 크고,

enemy의 좌측 끝 X좌표(enemy.x)가 player의 우측 끝 X좌표(player.x + player.width)보다 작으면 서로 겹침 즉, 충돌되었다고 할 수 있다.

 

2) Y 좌표 겹침

같은 개념으로 Y 좌표 겹침을 감지할 수 있다.

먼저 enemy의 하단 끝 Y좌표(enemy.y + enemy.height)가 player의 상단 끝 Y좌표(player.y)보다 크고,

enemy의 상단 끝 Y좌표(enemy.y)가 player의 하단 끝 Y 좌표(player.y + player.height)보다 작으면 서로 겹침 즉, 충돌되었다고 할 수 있다.

 

결론적으로 두 요소의 X 좌표 겹침과 Y 좌표 겹침이 둘다 만족한다면 서로 충돌되었다고 감지하면 되는 것이다.이 개념을 코드화 하면 다음과 같다.

function IsCollision(enemy, player) {
  return enemy.x < player.x + player.width &&
           enemy.x + enemy.width > player.x &&
           enemy.y < player.y + player.height &&
           enemy.y + enemy.height > player.y;

}

 

 

* HTML

충돌 박스에 근거한 간단한 예제를 만들어 보겠다. 먼저 다음과 같이 HTML 파일을 구성한다. 캔버스와 마우스 좌표를 표시하기 위한 span 요소를 정의한다.

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

 

 

* Javascript

먼저 Player 객체를 위한 Box 생성자 함수를 정의하는데, Player가 위치할 X,Y 좌표와 크기(너비/높이)를 매개변수로 전달받도록 한다.

function Box(x, y, width, height){
 this.x = x;
 this.y = y;
 this.width = width;
 this.height = height; 
}

 

다음으로 문서의 로딩 완료와 마우스 움직임을 처리하기 위한 이벤트 리스너를 등록하고 필요한 전역 변수를 정의한다. 예제의 단순함을 위해 enemy 요소는 따로 정의하지 않고 마우스 포인트를 사용할 것이다. 즉 Player 박스와 마우스 포인터의 겹침(충돌)을 감지하게 되므로 마우스 이벤트가 필요하다.

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

 

var canvasClientRect;  //Canvas Client Bounding Rect
var gameContext;      //Canvas Context
var box;                   //Player Box

 

그리고 초기화 함수와 Player 박스를 그리는 함수를 정의한다. 여기서 주의깊게 봐야 할 것은 Canvas의 getBoundingClientRect() 함수이다. Canvas가 문서에 표시될 때 div와 같은 부모요소에 포함될 수도 있고 문서 자체의 margin, padding 값에 의해 문서의 좌측 상단에서 떨어져 있을 수 있다.

 

다시말해, Canvas의 좌측 상단의 좌표가(0,0)이 아닐 수 있다는 것이다. Canvas에 포함된 게임 요소들의 충돌 감지를 위해서는 Canvas의 좌측 상단의 좌표가 (0,0)이어야 하므로 문서와 Canvas의 간격을 좌표계산에서 제거해야 한다. 이를 위해 문서 자체의 margin값을 0으로 설정하거나 Canvas의 offSet값을 계산에 포함할 수도 있지만 좀더 범용적으로 적용하기 위해서 getBoundingClientRect()함수를 사용하는 것이 좋다. 이 함수를 이용하면 Canvas가 문서로부터 얼마나 떨어져 있는지 알 수 있으므로 쉽게 계산할 수 있게 된다.

function init(){ 
  var canvasElement = document.getElementById("GameCanvas");
  //Get Canvas Bounding ClientRect
  canvasClientRect = canvasElement.getBoundingClientRect();  
  gameContext = canvasElement.getContext('2d');  

  //Draw Player Box
  drawBox();
}

 

function drawBox(){
  //Create Player Box Instance
  box = new Box(150,100,100,100); 
  gameContext.fillStyle = '#000000';
  //Draw Player Box
  gameContext.fillRect(box.x,box.y,box.width,box.height);
}

 

이제 남은건 마우스 무브 이벤트와 충돌감지 로직이다. 이 코드가 핵심인데 다음과 같이 구현한다.

앞서 설명한대로 Player 요소와 마우스 포인터의 겹침을 감지하고 충돌 시 이를 표시해 준다.

function onMousemove(e){
 //Clear Canvas  
 gameContext.clearRect(0, 0, canvasClientRect.width, canvasClientRect.height);
  
 //Draw Player Box
 drawBox();
   
 //Calculate Mouse Position based on Canvas
 mouseXinCanvas = e.clientX - canvasClientRect.left;
 mouseYinCanvas = e.clientY- canvasClientRect.top;
     
 //Collision Detection 
 if(IsCollision(box,mouseXinCanvas,mouseYinCanvas)){
    gameContext.fillStyle = '#ffffff';  
    gameContext.font = '18pt Arial';        
    gameContext.textBaseline = 'top';
    gameContext.fillText("collision!", box.x, box.y);
 }
 
 //Display Mouse Position
 document.getElementById("mousePositionDisplay").innerText =
                                                                   "x:" + mouseXinCanvas + ", y:" + mouseYinCanvas;
}

function IsCollision(box, x, y){    
   return  x > box.x  && x < box.x + box.width &&
              y > box.y  && y < box.y + box.height      
}

 

 

* 실행 화면

예제를 브라우저로 실행해 보면 마우스 포인터가 Player 박스 안에 들어가면 충돌되었다고 표시하는 것을 확인할 수 있을 것이다. 물론 이 예제에서는 마우스 포인트가 enemy를 대신하기 때문에 enemy의 박스 계산(너비/높이 계산)이 필요없게 되지만 개념은 동일하다. 

 

 

 

 

 

 

 

 

 

 

 

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

[HTML5 Game] Game State  (0) 2013.09.27
[HTML5 Game] Game Loop  (0) 2013.09.25
[HTML5 Game] Parallxing Background Animation  (0) 2013.09.21
[HTML5 Game] Background Animation  (1) 2013.09.20
[HTML5 Game] Character Animation Using Trident.js  (3) 2013.09.17

[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 Trident.js

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

앞서 두 개의 글을 통해 HTML5 상에서 애니메이션 구현을 알아봤다.

이 두 글에서는 자바스크립트의 setInterval() 함수를 통해 애니메이션 처리를 직접 구현했었다.

 

> http://m.mkexdev.net/235

> http://m.mkexdev.net/237

 

이번에는 잘 알려진 오픈소스 라이브러리를 이용해 애니메이션을 구현해 보도록 한다.

 

이 글에서 사용할 라이브러리는 Kirill Grouchnikov라는 스마트한 개발자에 의해 개발된,

Trident.js 라는 자바스크립트 애니메이션 라이브러리로 ProcessingJS 처럼 기존 JAVA 라이브러리를 자바스크립트 버전으로 포팅한 것으로 타임라인과 키프레임에 기반한 애니메이션 처리를 지원하고 Easing 함수를 통해 비선형 타임라인을 지원하는 등 애니메이션 처리와 관련된 안정되고 다양한 기능을 제공한다.

 

이 라이브러리는 github에 공개되어 있다.

> https://github.com/kirillcool/trident-js

 

공개된 라이브러리를 사용한다는 것은 개발의 생산성과 안정성을 높이고 유용하고 다양한 기능을 큰 시행착오 없이 적용할 수 있는 등 많은 장점을 가져다 준다. 따라서 대부분의 개발환경에서 이미 구현된 라이브러리 사용을 권장하고 있다.

 

다만 기본 개념이 전혀 갖춰져 있지 않은 상태에서 무작정 라이브러리에만 의존하다보면 프로그램의 원리적 동작의 이해가 부족하게 되고 복잡한 문제를 대응하는 능력이 떨어져 전반적인 프로그램 개발 역량을 성장시키지 못하게 되는 원인이 되기도 한다. 따라서 이전 두 글에서와 같이 라이브러리 의존 없이 기본적인 구현을 직접 해 봄으로써 애니메이션 처리의 기본 구현 원리를 숙지하고 난후 유용한 라이브러리에 관심을 가지는 수순을 따르는게 좋겠다.

 

그럼. 본격적으로 Trident.js를 사용해보게 될텐데, 캐릭터 애니메이션 구현을 해 보기 전에 기본적인 라이브러리 사용법을 간단히 알아보자.

 

* Trident.js 기본

Trident.js 사용해서 HTML 이미지요소의 투명도를 타임라인에 기반해 서서히 변경해 보는 간단한 예를 살펴봄으로써 이 라이브러리의 기본 사용법을 익혀보자.

 

먼저 다음과 같이 github에서 다운받은 trident.js와 이미지요소가 정의된 간단한 HTML파일을 만들자.

<html>
  <head>
    <script src="js/trident.js"></script>
    <script type="text/javascript" src="js/tridentBasic.js"></script>
  </head>
  <body>  
    <img id="myImg" src="image/iu.png" style="opacity:0.3" /> 
  </body>
</html>

 

그리고 자바스크립트에서 다음과 같은 로직을 구현한다.

function init(){
  //Create TimeLine Instance
  var rolloverTimeline = new Timeline(myImg.style);
    
  //Add Interpolator
  rolloverTimeline.addPropertiesToInterpolate([
    {
      property: "opacity", from:0.3, to:1.0, interpolator: new FloatPropertyInterpolator()     
    }
  ]);
  
  //Define Animation Duration
  rolloverTimeline.duration = 500;
    
  //Add Event Listener of Image
  myImg.addEventListener("mouseover", function(){rolloverTimeline.play()}, false);
  myImg.addEventListener("mouseout", function(){rolloverTimeline.playReverse()}, false);
}

 

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

 

Trident.js는 그래픽 편집 툴의 그것과 동일한 개념의 타임라인 객체를 제공한다. 이 객체에 설정한 각종 정보를 기반으로 애니메이션 처리가 이뤄지는데 먼저 생성자 함수로 애니메이션할 요소와 대상 속성을 설정한다.

여기서는 이미지 요소의 style속성을 애니메이션 대상 속성으로 지정했다.

var rolloverTimeline = new Timeline(myImg.style);

 

그리고 애니메이션할 속성과 시작-끝 값 그리고 시작과 끝 값 사이에 변경해야 할 값(보간값)의 형태를 지정한다. 여기서는 이미지의 투명도에 해당하는 opacity 속성을 지정하고 0.3 ~ 1.0 사이의 값을 실수 형태의 보간(FloatPropertyInterpolator)이 이뤄지도록 지정했다.

rolloverTimeline.addPropertiesToInterpolate([
   {
      property: "opacity", from:0.3, to:1.0, interpolator: new FloatPropertyInterpolator()
     }
]);

 

참고로 보간형태는 FloatPropertyInterpolator외에도 정수값 보간인 IntPropertyInterpolator과 RGB값 보간인 RGBPropertyInterpolator가 제공된다.

 

마지막으로 애니메이션이 동작할 시간을 밀리초단위로 지정하는데 여기서는 0.5초로 지정했다

rolloverTimeline.duration = 500;

 

결국 0.5초 동안 이미지 투명도가 0.3에서 1.0으로 점점 밝아 지도록 하는 것이다. 여기까지해서 Trident.js의 애니메이션 설정은 모두 끝이 났다.

 

이어지는 코드는 실제 애니메이션 처리를 하기 위한 이벤트를 등록하는 것이다.

코드에서는 이미지에 마우스를 올리면 애니메이션이 시작하도록 하고 마우스를 떼면 역방향으로 애니메이션이 진행하도록 구현했다.

myImg.addEventListener("mouseover", function(){rolloverTimeline.play()}, false);
myImg.addEventListener("mouseout", function(){rolloverTimeline.playReverse()}, false);

 

브라우저로 샘플을 실행해서 애니메이션을 확인해 보자.(눈의 즐거움을 위해 국민 여동생 등장시킴!)

 

                =>               

 

 

* Trdent.js를 활용한 캐릭터 애니메이션 구현

이제 이 라이브러리를 이용해서 캐릭터 애니메이션을 구현해 보자. 전체적인 흐름은 이전과 유사하며 이미지는 Image Sprites 기법으로 처리한다.

 

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;                                                                                   //Sprite Image X Position
}

 

Character.prototype.startAnimation = function()
{
   //Assign This Instance Context('this' keyword Changed according to the Effective Range)
   var self = this; 
 
   //Create TimeLine
   var spriteTimeline = new Timeline(self);  
   //Add Interpolate
   spriteTimeline.addPropertiesToInterpolate([
    { 
      //Interpolate Property: spritesX, Interpolate Value: 0 ~ 36, Interpolate Type: Int
      property: "spritesX", from:0, to: 36, interpolator: new IntPropertyInterpolator()
     }
   ]);    
        
   //Define Animation Duration
   spriteTimeline.duration = 2500;      
   //Add onpulse Event Listener 
   spriteTimeline.addEventListener("onpulse",

            function (timeline, durationFraction, timelinePosition) {
                    self.drawCanvas();   
             }

   );  
   //Play Animation(Infinite Looping)
   spriteTimeline.playInfiniteLoop();
}

 

Character.prototype.drawCanvas = 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;
 
}

 

캐릭터 메서드의 구조가 이전과는 조금 달라졌다.

startAnimation() 메서드에서 Trident.js의 타임라인을 생성하고 애니메이션을 시작하도록 구현했으며 캔버스에 그리는 작업인 drawCanvas() 메서드를 분리시켰다.

 

타임라인 인스턴스를 생성할 때 앞서 기본 예제와는 달리 HTML 요소가 아니라 객체 자체를 매개변수로 전달하고 있다. 이렇게 하면 객체의 특정 속성을 기준으로 애니메이션 처리를 할 수 있게 된다.

var self = this;

var spriteTimeline = new Timeline(self);

 

또한 여기서 캐릭터 인스턴스의 문맥을 나타내는 'this' 값을 self라는 지역변수에 할당하고 있는데 이렇게 하는 이유는 this 키워드가 다른 유효범위에서는 다른 문맥을 가르키기 때문에 프로그램이 정상 동작하지 않을 수 있다. 이 예제에서는 타임라인에 이벤트 리스너를 등록할 때 문제가 발생한다. 따라서 self라는 변수를 통해 캐릭터 인스턴스의 문맥이 유효할 수 있도록 하며 타임라인 위치가 변경할 때마다 동작할 이벤트 리스너를 다음과 같이 등록한다.

spriteTimeline.addEventListener("onpulse", function (timeline, durationFraction, timelinePosition) {
   self.drawCanvas();
});

 

그리고 원본 이미지의 자르기 x좌표인 spritesX 값은 더이상 직접 업데이트 해 줄 필요가 없게 되었다. Trident.js의 보간값 설정에서 다음과 같이 이 변수의 변경될 값 범위(0~36)를 지정했기 때문에 타임라인이 실행되면서 자동으로 spritesX값이 업데이트 되기 때문이다.

spriteTimeline.addPropertiesToInterpolate([
   {
      //Interpolate Property: spritesX, Interpolate Value: 0 ~ 36, Interpolate Type: Int
      property: "spritesX", from:0, to: 36, interpolator: new IntPropertyInterpolator()
   }
]);

 

마지막으로 애니메이션을 2.5초간 동작하도록 하여 이전 두 글의 fps와 값을 유사하게 맞췄다. 더불어 애니메이션이 무한 반복되도록 다음과 같이 playInfiniteLoop() 메서드를 호출한다.

spriteTimeline.playInfiniteLoop();

 

2. 프로그램 로직

나머지는 이전 코드와 모두 동일하며 onAssetLoadComplete() 함수에 약간의 변화가 있다.

이전 코드에서는 자바스크립트의 setInterval() 메서드를 이용해 직접 루프를 동작시켰지만 Trideng.js에서는 자체 타임라인을 기반으로 루프가 동작하기 때문에 더 이상 사용할 필요가 없다.

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);
 
  
   character.startAnimation();  
}

 

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

 

 

실행화면

브라우저로 실행해 보면 이전 예제와 동일한 애니메이션을 확인할 수 있다. 

 

마무리하며...

지금까지 총 3차례에 걸쳐 캐릭터 애니메이션을 구현해 보았다. 첫번째는 동작별로 분리된 이미지들을 이용해 애니메이션을 구현해 봤으며 두번째는 하나의 이미지를 기반으로 Image Sprites 기법을 활용했으며 마지막으로 이 글에서는 Image Sprites 기법과 더불어 Trident.js 라는 오픈소스 라이브러리를 활용해서 동일한 애니메이션을 구현해 보았다. 이 중에서 제일 권장되는 방법은 1 < 2 < 3 즉, 이 Trident.js를 활용하는 것이 여러모로 좋을 것이다. 라이브러리의 안정성이 곧 프로그램의 안정성으로 직결되며 반복 카운트 및 되감기 애니메이션 지원과 타임라인과 키프레임의 조합, 비선형 타임라인 지원 등 더 강력한 기능을 공짜로(?) 얻을 수 있기 때문이다. 하지만 자바스크립트의 애니메이션 처리를 위한 기본 원리를 알기 위해서는 이전 두 과정을 필수로 거쳐보기 바란다. 기본기 없이 라이브러리에만 의존한다면 언젠가 더 높은 벽을 만나게 될 것이다.

 

 

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

[HTML5 Game] Parallxing Background Animation  (0) 2013.09.21
[HTML5 Game] Background Animation  (1) 2013.09.20
[HTML5 Game] Character Animation Using Sprites  (0) 2013.09.17
[HTML5 Game] Character Animation  (1) 2013.09.14
CSS3 3D Effect  (0) 2013.08.02

[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

OOP in Javascript

Posted in 모바일/Javascript // Posted at 2013. 8. 16. 14:34
728x90

 

   /* 클래스, 인스턴스/클래스 변수, 메서드 */
   function Person(name){      
      this.name = name;                              //인스턴스 멤버변수(public)
      var nickname = 'pretty_' + name;         //함수 지역변수(private)  
      this.getNickName = function(){            //메서드(지역변수 엑세스)
           return nickname;
      }
    } 
   
    Person.cName = "인간";                                     //클래스 멤버변수  
    Person.prototype.age = 39;                               //인스턴스 멤버변수 추가   
    Person.prototype.getSummary = function(){        //메서드 추가
        return this.name + '(' + this.getNickName() + ')';  
    }
  
   var person = new Person('박종명');        //인스턴스 생성
   console.log(person.name);                    //인스턴스 변수 액세스
   console.log(person.getNickName());       //메서드 엑세스(메서드를 통해 함수의 지역변수 액세스) 
   console.log(Person.cName);                  //클래스 변수 액세스  
   console.log(person.getSummary());   
  
  
   /* 상속  */
   function Korean(name,age){
       Person.call(this,name);          //부모 생성자 함수 호출
       this.age = age;
   }


   Korean.prototype = new Person();        //상속


   Korean.prototype.getSummary = function(){     //오버라이딩
     return this.name + '(' + this.getNickName() + ')' + '(' +  this.age + ')';  
    }
      
   var koreanPerson = new Korean('한국사람',20);     //자식 인스턴스 생성
   console.log(koreanPerson.name);                         //부모 멤버변수 엑세스
   console.log(koreanPerson.getSummary());             //자식 오버라이딩 메서드 엑세스
  
  
   /* 객체 타입 확인 */
   console.log(person instanceof Person);
   console.log(koreanPerson instanceof Person);

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

[AngularJS] 일주일 탐방기  (0) 2016.07.06
knockout.js  (0) 2013.11.14
함수에 대하여  (5) 2013.07.31
null과 undefined 그리고 NaN  (0) 2013.07.29
스크립트 실행 지연  (0) 2013.07.23

CSS3 3D Effect

Posted in 모바일/HTML5 // Posted at 2013. 8. 2. 18:55
728x90

CSS3 만으로 이런 UI가 가능하다뉘... 놀~라~울 따름이다

 

소스를 다운받아서 보면,

CSS3의 TransformTransition만으로 3D 효과 및 애니메이션을 구현한 것을 확인할 수 있다

 

멋쪄부러~

 

http://tympanus.net/codrops/2013/08/01/3d-effect-for-mobile-app-showcase/

Demo: http://tympanus.net/Development/3DEffectMobileShowcase/

 

참고: [CSS3] Transform   [CSS3]Transition

 

 

 

그리고 하나 더... !

 

이미지를 사용하지 않고, CSS3를 활용한 다양한 버튼 스타일 !

http://alexwolfe.github.io/Buttons/

 

 

 

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

[HTML5 Game] Character Animation Using Sprites  (0) 2013.09.17
[HTML5 Game] Character Animation  (1) 2013.09.14
...  (0) 2013.07.23
HTML5 개발을 도와주는 도구들  (4) 2010.11.01
주요 브라우저 HTML5 지원 점수  (0) 2010.10.27

함수에 대하여

Posted in 모바일/Javascript // Posted at 2013. 7. 31. 10:33
728x90

자바스크립트를 공부하다 보면 함수의 독특함에 많이 놀라곤 한다

이 언어가 가지는 유연함의 중심에 함수가 있으며 과히 함수기반 언어라 할 수 있다

 

먼저 모든 프로그래밍 언어에 존재하는 함수는 수학의 그것과 개념적으로 완전히 동일하다

즉 입력 값(x)에 대응하는 출력 값(y)이라는 개념 즉, f(x) = y라는 수학적 개념이 프로그래밍 언어에도 그대로 계승되었다는 것이다

 

 

 

먼저 가장 일반적인 함수 형태를 살펴 보자

//함수 정의
function Size(width,height){
    var size = width * height;
    return size;
}
       
//함수 호출
var size = Size(10,10);   
console.log(size); //100

 

function 이라는 키워드를 사용하여 함수를 정의할 수 있는데 위의 예에서는 너비와 높이를 입력 값으로 받아서 두 값의 곱 즉 사이즈를 출력(반환)하는 함수를 정의하고 호출하는 코드를 작성했다

 

return 문은 없어도 된다(이 경우 undefined가 반환된다)

대부분의 프로그래밍 언어처럼 자바스크립트 함수에서도 반드시 return으로 반환해야 하는 것은 아니다

대체로 void 함수로 정의하는데 자바스크립트에서는 반환 값의 타입을 지정하지 않기 때문에 다음 코드와 같이 return 반환이 없는 함수를 정의할 수 있다

 

function writeLog(message){
   console.log(message);
}

 

이 경우, 한가지 참고할 사항은 return 없는 함수의 호출 결과는 자동으로 undefined가 반환된다는 점이다.

return문을 통한 명시적 반환이 없는 함수가 자동으로 뱉는 undefined는 실제 프로그램에서 사용할 일은 거의 없다고 봐도 개념적으로는 기억해 두는 것이 좋다

//return문이 없는 함수 정의
function writeLog(message){
    console.log(message);
}
            
var returnValue = writeLog('Hello'); //Hello
console.log(returnValue);               //undefined

 

 

매개변수의 데이터 타입을 명시하지 않아도 된다.

심지어 매개변수의 개수가 달라도 된다

C#과 같은 언어에서는 타입 안정성을 위해 데이터 타입 제약을 강하게 둔다. 즉 사용하고자 하는 데이터의 타입이 사전에 정확히 명시되어야만 컴파일을 허용한다(C#의 var 선언도 결국 컴파일 시점에 타입이 결정된다)

 

반면, 자바스크립트는 타입 제약이 느슨한 언어이다. 앞서 코드들에서 봤겠지만 함수의 출력값은 물론 입력 매개변수에 대해서도 타입을 지정하지 않는다

 

또한 더욱 심오한(?) 것은 입력 매개변수의 개수도 개의치 않는다는 점이다. 앞서 작성했던 Size() 함수를 호출할 때 함수에 정의된 입력 매개변수 보다 더 많은 매개변수를 전달해 보자

function Size(width,height){
   var size = width * height;
   return size;
}

 

//더 많은 매개변수 전달
var size = Size(10,10,10);
console.log(size); //100

 

위 코드는 여전히 잘 동작하며 원하던 100이라는 결과를 내뱉는다.

자바스크립트에서는 함수에 정의된 것 보다 더 많은 매개변수가 전달될 경우 그것들은 무시하게 된다

 

그럼 더 적은 매개변수를 전달하면 어떻게 될까?

var size = Size(10);
console.log(size); //NaN

 

결과는 NaN이다. 오류가 아니라 정상적으로(?) 결과를 반환하는 것이다.

자바스크립트에서는 함수에 정의된 것 보다 더 적은 매개변수가 전달될 경우 나머지 값은 undefined로 할당된다.

 

즉 위의 예에서는 width = 10, height = undefined 가 되어 다음 식으로 계산된 결과를 반환한 것이다.

 

10 * undefined = NaN

 

NaN이 반환된 것은 undefined가 숫자 문맥에서는 NaN으로 평가되고 NaN과 숫자의 연산은 모두 NaN이기 때문이다

 

함수 리터럴

자바스크립트엣는 이름 없는 함수를 정의할 수 있다. 이를 함수 리터릴이라 한다. 앞서 Size 함수를 함수 리터럴로 정의하면 다음과 같다

var f = function(width,height){
       var size = width * height;
       return size;
}
            
var size = f(10,10);
console.log(size); //100

 

위의 코드를 보면 함수의 이름이 생략되었다. 여기소 var f 는 함수의 이름이 아니라 변수이며 함수 리터럴을 변수에 할당한 것이다.

 

함수 정의와 호출을 동시에

다음 코드와 같이 자바스크립트는 함수 정의와 호출을 동시에 할 수 있도록 지원한다. 아래 코드는 함수 리터럴을 선언하고 바로 호출했지만 이름을 가진 함수도 동일하게 작성할 수 있다

var result = (function(width,height){
       var size = width * height;
       return size;
})(10,10);
            
console.log(result); //100

 

 

데이터로써의 함수

앞서 함수 리터럴에서 본 것 처럼, 함수를 변수에 할당할 수 있다는 것은 함수 자체가 데이터로써의 역할을 할 수 있다는 예기가 된다.  이번 예에서는 두 개의 함수를 정의하고 이를 배열에 담아 배열로 함수를 호출하는 예를 살펴보자.

function Size(width,height){
      var size = width * height;
      return size;

   
function Sum(value1,value2){
      return value1 + value2;      
}   
  
var functionArray = new Array();
functionArray[0] = Size;
functionArray[1] = Sum;

  
for(var i = 0; i < functionArray.length; i++){
   console.log(functionArray[i](i,i));

 

위 코드는, 함수 자체를 배열로 담을 수 있다는 것으로 함수가 데이터로써의 역할을 한다는 것을 다시 한번 확인 가능하며 더불어 자바스크립트의 배열 유연성을 덤으로 눈치 챌 수 있게 하는 예이다.

 

데이터로써의 함수 특징을 볼 수 있는 마지막 예는 함수의 매개변수로 사용되는 경우이다.

function Size(width,height){
      var size = width * height;
      return size;

      
function DoubleSize(value1,value2,func){
   return func(value1,value2) * func(value1,value2);
}  
   
//함수의 매개변수로 또 다른 함수를 전달
var result = DoubleSize(10,10,Size);
console.log(result); //10000

 

C#과 같은 언어에서도 delegate라는 개념을 기반으로 한 함수(메서드)의 데이터화를 지원하지만 자바스크립트의 그것에 비하면 역사적으로 늦은 편에 속한다.

 

객체로써의 함수

C#과 Java와 같이 자바스크립트에서도 객체 기반의 프로그래밍이 가능하다.

언어적인 특징으로만 보면 자바스크립트는 생성자 함수와 프로토타입을 기반으로 한 객체 지향 프로그래밍을 지원하며 클로저를 이용한 private 멤버 구현하는 등 C#과 Java의 그것과는 다르다 할 수 있지만 상속/오버라이딩, 캡슐화와 같은 객체의 특징을 비롯해 객체에 기반한 구조적인 프로그래밍이 가능하기 때문에 객체 기반 장점을 누릴 수 있다.

 

자바스크립트에서는 클래스 생성 구문이 없다. 다만 생성자 함수를 통해 클래스와 같은 원형을 만들 수 있다

다음의 코드는 Rect라는 생성자 함수를 정의해서 객체로 사용되는 예를 보여준다.

 

이렇게 객체로 사용하는 함수를 메서드라고 부르며 메서드에는 this라는 키워드로 자신의 객체에 접근할 수 있게 된다. 내부적으로 보면 메서드는 그 메서드가 속해 있는 객체를 묵시적으로 전달받게 되는데 이 객체를 this로 접근할 수 있다는 것이다.

 

//생성자 함수 정의
function Rect(width, height){
    this.width = width;
    this.height = height;
    
    this.Size = function(){
      return this.width * this.height;
    }
}
     
//객체 생성 및 활용
var rect = new Rect(10,10);
var size = rect.Size();
   
console.log(size); //100

 

여기서 한가지 주의할 점은, 생성자 함수를 객체로 생성해서 호출(메서드 호출)하는 것이 아닌 일반 함수로 호출할 경우에는 함수 안의 this는 전역 객체를 가리키게 된다는 것이다. 다음의 예를 살펴 보자.

//생성자 함수 정의
function Rect(width){
    this.width = width;    //this는 메서드와 일반함수로 호출될 경우 대상이 달라진다
}
   
//객체(메서드)로 호출(Rect객체의 멤버 변수 width 값을 수정한다)
var rectObj = new Rect(10);
       
//일반 함수로 호출(전역 객체의 width 값을 수정한다)
var rectFun = Rect(20)
   
console.log(rectObj.width); //10          
console.log(width);            //20

 

 

 

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

knockout.js  (0) 2013.11.14
OOP in Javascript  (0) 2013.08.16
null과 undefined 그리고 NaN  (0) 2013.07.29
스크립트 실행 지연  (0) 2013.07.23
Pie.js  (0) 2013.07.16

null과 undefined 그리고 NaN

Posted in 모바일/Javascript // Posted at 2013. 7. 29. 12:10
728x90

대부분의 프로그래밍 언어는 null 이라는 특별한 값을 지원한다.

 

ex> var temp = null;

 

이 값(null)의 의미는 '아무런 값도 나타내지 않는다'라는 의미이며 다음의 구문과는 구분된다.

 

ex> var temp;

 

즉, null 은 어떠한 유효한 값도 아니라는 의미이지 값 자체가 없다는 의미가 아니라는 말이다. 다시 말해 변수 temp에는 어떤 유효한 데이터를 담지 않을 뿐, null 값을 가진다는 의미이다. 따라서 선언만 하고 값을 할당하지 두 번재 예와는 구분되는 것이다.

 

자바스크립트는 값의 비유효성과 관련한 또 하나의 의미를 제공하는데 바로 undefined이다.

앞서 두 번째 예가 바로 undefined의 예가 되겠다. 즉 선언은 되었지만 값이 할당되지 않는 변수에 접근할 경우 undefined가 된다.

 

var temp;
console.log(temp); //선언은 되었으나 값이 할당되지 않은 변수: undefined
            
temp = null;
console.log(temp); //여전히 유효한 값은 없으나 null로 할당된 변수: null

 

결론적으로 null은 null이라는 값이 할당된 경우이지만, undefined의 경우 선언은 되었지만 아무런 값도 할당되지 않은 경우로 요약할 수 있다.

 

좀 더 나아가 객체 환경에서의 undefined를 살펴볼 필요가 있다.

선언은 되었지만 값이 할당되지 않은 변수에 접근할 경우 undefined라 했다. 이 개념은 객체환경에서도 동일한다. 즉 선언은 되었지만 값이 할당되지 않는 객체 프로퍼티 역시 undefined이다.

 

여기에 한가지 더 기억해야 할 것은, 객체 환경에서는 선언되지 않는 객체 프로퍼티도 undefined라는 점이다.

일반 변수의 경우 선언되지 않은 경우 오류(Exception)을 내뱉지만 객체 프로퍼티일 경우에는 오류가 아니라 undefined라는 점이다.

 

function TempObject(){
    this.i;       
}
   
var tempObject = new TempObject();
console.log(tempObject.i); //선언은 되었으나 값이 할당되지 않은 객체 프로퍼티: undefined
console.log(tempObject.j); //존재하지 않는 객체 프로퍼티 역시...: undefined

 

문맥에 따른 null, undefined 평가

자바스크립트는 유연한 자동 형변환이 지원되는 언어이다. 자동 형변환이란 어떤 데이터 타입이 숫자와 함께 사용될 경우 자동으로 (원래 숫자 타입이 아닌 경우에도) 자동으로 정해진 숫자로 변환되는 것이다.

여기서 숫자와 함께 사용된 경우라는 의미가 바로 프로그램 문맥상 숫자 문맥으로 평가된다는 것이다.

 

null과 undefined 역시 문맥에 따른 자동 형변환이 된다.

 

Boolean 문맥

null과 undefined는 실제로 다른 값이지만 참/거짓(Boolean)으로 평가되어야 하는 경우 모두 false로 형변환이 된다.

var temp;      
console.log(Boolean(temp)); //강제 형변환: false
   
if(!temp) //자동 현변환: undefined가 자동으로 false로 변환됨
{
   console.log('temp 변수가 false로 평가되었습니다');
}

 

숫자 문백

숫자 문맥에서는 두 값이 차이를 보이는데 null의 경우 0으로, undefined의 경우 NaN으로 변환된다.

var temp1 = null;
var temp2;    
console.log(Number(temp1)); //강제 형변환: 0
console.log(Number(temp2)); //강제 형변환: NaN
   
console.log(temp1 + 2); //자동형변환: 2
console.log(temp2 + 2); //자동형변환: NaN

 

실제로 자바스크립트 모듈을 개발할 경우, 이러한 문맥상 자동형변환의 특징을 이용하는 경우가 많다.

만일 어떤 객체에 정의된 함수의 존재여부를 식별한 후 해당 함수를 호출하고 싶을 경우 다음과 같이 작성할 수 있다.

function TempObject(){    
     this.doWork = function(){
       console.log('doWork...');
     }              
}
  
var temp = new TempObject();
if(temp.doWork){
     temp.doWork();  //조건식이 true이므로 호출됨
}
if(temp.doWork2){
    temp.doWork2(); //조건식이 false이므로 호출되지 않음
}

 

그리고 외부 모듈을 가져다 쓰는 복잡한 스크립트 환경에서 전역 네임스페이스의 충돌을 방지하기 위한 조건식으로 사용되기도 한다.

 

다음의 경우를 살펴보자. 동일한 이름의 객체를 생성하면 오류는 나지 않지만 뒤에 선언된 이름이 앞선 이름을 덮어쓰게 된다.

var someName = { i : 10 };     
console.log(someName.i);  //10
   
var someName = { i : 20 };
console.log(someName.i);  //20

 

문제는 만일 someName이라는 심벌이 외부 모듈에서 전역 네임스페이스로 이미 사용중일 경우 이름 중복은 예상치 못한 결과를 초래하기 때문에 다음과 같이 전역 네임스페이스에 동일한 심벌이 존재하는지 여부를 판단하는 것이 좋다.

var someName = { i : 10 };     
console.log(someName.i);  //10
    
if(!someName){      //만일 someName이라는 심벌이 정의되지 않았다면 새로 정의한다.   
   var someName = { i : 20 };    
}
   
console.log(someName.i); //10

 

물론 이름 충돌 방지는 도메인 네임과 같은 고유성이 보장된 이름 규칙을 사용하고 나아가 익명함수로 전역 네임스페이스를 전혀 어지럽히지 않도록 하는 기법이 존재하지만 여기서는 그 평가로 사용되는 undefined 특징을 살펴본 것이다.

 

참고로 null과 undefined의 문맥에 따른 자동 형변환 표를 정리한다.

 

 Boolean 문맥 

 숫자 문맥 

 문자열 문맥 

  null

 false 

 0 

 "null" 

  undefined

 false

 NaN

 "undefined"

 

 

 

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

OOP in Javascript  (0) 2013.08.16
함수에 대하여  (5) 2013.07.31
스크립트 실행 지연  (0) 2013.07.23
Pie.js  (0) 2013.07.16
Zepto.js  (0) 2013.07.15

스크립트 실행 지연

Posted in 모바일/Javascript // Posted at 2013. 7. 23. 11:52
728x90

기본적으로 HTML 파서는 문서 파싱 도중에 자바스크립트를 만나면,

파싱 작업을 멈추고 자바스크립트를 다운로드하고 실행되기를 기다린다.

 

이는 덩치 큰 외부 스크립트를 문서로 삽입할 때 문서 로딩 시간의 지연을 초래하는데,

이를 해결하기 위한 메커니즘 중 하나가 deferasync 속성이다.

 

<script> 태그에 사용할 수 있는 속성으로 단어 그대로 defer은 지연, async는 비동기 특징을 지닌다.

 

보다 자세한 내용은 아래 블로그 글에서 확인할 수 있다.

 

=> script 태그의 async와 defer 속성

 

defer/async와 HTML 파서의 상호작용은 다음 포스트에서 시각적으로 확인 가능하다

=> http://peter.sh/experiments/asynchronous-and-deferred-javascript-execution-explained/

 

그리고 이 두 속성의 브라우저 지원 여부는 다음 포스트에서 바로 확인 가능한데...

=> http://peter.sh/experiments/asynchronous-and-deferred-javascript-execution/

 

문제는 이들 속성을 지원하는 브라우저 상황이 일관되지 않아 실제 적용을 위해서는 고민이 필요하다.

 

DOM과 밀접히 상호작용하고 스크립트간 유효범위에 대한 액세스 규칙을 포함하는 등 복잡한 스크립트 환경에서는 로딩과 실행 순서에 민감할 수 있으니 자~알 보고 적용해야 할 것이다.

 

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

함수에 대하여  (5) 2013.07.31
null과 undefined 그리고 NaN  (0) 2013.07.29
Pie.js  (0) 2013.07.16
Zepto.js  (0) 2013.07.15
자바스크립트 상속  (0) 2013.07.12

...

Posted in 모바일/HTML5 // Posted at 2013. 7. 23. 07:52
728x90

http://www.columbia.edu/~sss31/html/html-mean.html

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

[HTML5 Game] Character Animation  (1) 2013.09.14
CSS3 3D Effect  (0) 2013.08.02
HTML5 개발을 도와주는 도구들  (4) 2010.11.01
주요 브라우저 HTML5 지원 점수  (0) 2010.10.27
어도비, HTML5 디자인 개발툴 엣지(Edge) 공개  (0) 2010.10.26

Pie.js

Posted in 모바일/Javascript // Posted at 2013. 7. 16. 19:00
728x90

border-radius와 같은 CSS3의 새로운 효과를 IE에서도 적용되도록 해 주는 라이브러리이다.

IE6 ~ 9까지 지원되는데 다음의 CSS3 효과가 지원된다.

 

지원되는 CSS3 효과: border-radius, box-shadow, linear-gradient

 

조건부 주석을 통한 스크립트 참조  또는 htc behavior 방식 둘 중 하나를 선택해서 구현할 수 있는데, 
IE에서만 별도의 동작을 요구하기 때문에 가볍게 적용할 수 있을 듯 하다.

 

점진적 퇴보 전략 대신, IE 사용자들에게도 CSS3 경험을 선사해 주겠다면 이 라이브러리 적용을 고려해 볼 하다. 아쉬운건 좀 더 많은 CSS3 효과가 지원되지 않는다는 점이다.

 

=> http://css3pie.com/

 

 

 

 

 

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

null과 undefined 그리고 NaN  (0) 2013.07.29
스크립트 실행 지연  (0) 2013.07.23
Zepto.js  (0) 2013.07.15
자바스크립트 상속  (0) 2013.07.12
ProcessingJS, 자바스크립트 Canvas 라이브러리  (0) 2013.07.12

Zepto.js

Posted in 모바일/Javascript // Posted at 2013. 7. 15. 17:11
728x90

모바일 환경에서 사용할 만한 경량의 자바스크립트 라이브러리를 찾았다

 

Zepto.js 라는 라이브러리로 jQuery의 문법 구조를 그대로 유지하면서 꼭 필요한 기능만 제공하여 크기를 최소화했다.

 

모바일 환경에서 jQuery를 사용하기엔 좀 무거운 감이 있을 경우 대체안으로 적합해 보인다.

또한 UI는 jQuery Mobile과 같은 템플릿에서 벗어나고 싶지만 swipe, taphold와 같은 터치 관련 이벤트는 사용하고 싶을 경우에도 접합하다 하겠다.

 

Zepto.js는 Core 모듈과 모바일 운영체제를 식별할 수 있는 Detect 모듈, 비동기 통신을 위한 Ajax 모듈, 에니메이션 효과를 위한 Effects 모듈, 터치 이벤트 지원을 위한 Touch 모듈 등으로 구성되어 있다.

 

=> http://zeptojs.com

 

 

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

스크립트 실행 지연  (0) 2013.07.23
Pie.js  (0) 2013.07.16
자바스크립트 상속  (0) 2013.07.12
ProcessingJS, 자바스크립트 Canvas 라이브러리  (0) 2013.07.12
평점에서 별표 마킹  (0) 2012.11.16