[HTML5 Game] Background Animation
이번 글에서는 HTML5 게임의 백그라운드 애니메이션 구현 기법을 알아보자.
백그라운드 애니메이션은 배경이 고정되어 있지 않고 지속적으로 움직이는 것으로, 여기서는 마치 자동차를 타고 지나가는 것과 같이 배경이 우측에서 좌측으로 계속 움직이는 애니메이션 효과를 구현해 볼 것이다.
실제 게임에서는 캐릭터와 배경 애니메이션이 적절히 조화된 상태로 구현되지만 간단하게는 캐릭터는 가만히 있어도 배경이 계속 흐르듯 움직이면 마치 캐릭터가 앞으로 나아가는 것같은 느낌을 줄수도 있다.
* 간단한 개념
배경이 움직인다고 해서 여러개의 이미지가 사용되는 것은 아니다.(물론 여러개의 배경이라면 그 만큼 이미지가 필요하다). 여기서는 하나의 배경이 우측에서 좌측으로 움직이는 것이므로 하나의 배경 이미지만 사용하면 된다. 이전 글, Image Sprites의 개념에서 설명했듯이 HTML5 Canvas에는 원본 이미지에서 원하는 부분만 잘라내어 그릴 수 있다고 했다. 배경 애니메이션 역시 Canvas의 이러한 특징을 이용하는 것으로 총 두 번의 그리기 작업을 지속적으로 수행함으로써 배경이 움직이도록 할 수 있다.
먼저 배경이 좌측으로 움직인만큼 X좌표를 이동시켜서 첫 번째 그리기를 수행하고, 우측에 남은 공간만큼 원본에서 다시 잘라내어 두 번째 그리기를 수행하면 된다. 이것은 마치 회전목마(carousel)를 연상시킨다. 만일 회전목마의 한 단면만 볼 수 있다면 그 단면은 지나간 말의 꼬리와 새로운 말의 머리가 만나는 것의 연속이라 할 수 있다.
결국 배경이 움직인다는 것은 시간의 흐름에 따라 배경의 그리기 좌표를 조정하고 이를 빠르게 전환시키면서 그려주면 자연스러운 애니메이션 처리가 된다는 것이다.
하나의 이미지를 사용하여 그림을 붙여 나가는 방식이기 때문에 배경 이미지는 시작과 끝이 자연스럽게 연결되는 모습이어야 한다. 그렇지 않을 경우, 애니메이션에는 지장이 없으나 배경이 끝나고 시작되는 지점의 시각적 연결이 부자연스러워 게임의 퀄리티가 손상될 것이다. 이렇게 이미지의 시작과 끝 부분을 자연스럽게 연결해서 반복하는 것을 타일링 방식이라 한다.
이제 본격적으로 Background Animation 구현방법을 알아보자.
* HTML
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. 백그라운드, 생성자 함수와 메서드
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 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와 같이 위에서 아래로 흐르는 배경 애니메이션 처리에도 동일한 개념으로 접근할 수 있을 것이다. 애니메이션의 방향을 바꿔서 직접 구현해 보면 더욱 빠른 이해가 될 것이다.