[HTML5 Game] Parallxing Background Animation
지난 포스팅에서 백그라운드 애니메이션 처리 기법을 알아 보았다.
이 글에서는 하나의 배경이미지를 사용해서 애니메이션을 구현했는데,
이번 글에서는 더욱 현실감있게 하기 위해 몇 개의 배경을 중첩시켜서 서로 다른 속도로 애니메이션이 동작하도록 구현해 볼 것이다.
여러개의 배경이 서로 중첩되어 각기 다른 속도로 움직이도록 처리하면 게임을 더욱 실감나게 만들 수 있는데, 이를 시차 스크롤 방식이라 한다. 이에 대한 깔끔한 설명은 'LEARNING HTML5 온라인 게임 개발 프로그래밍'의 설명을 인용한다.
...
자동차를 타고 다리를 건너가면서 차창 밖으로 지나가는 풍경을 바라보는 것과 같다고 생각하면 쉽게 이해될 것이다. 저 멀리 보이는 산이라든가 도시의 스카이라인에 비해 자동차에서 가까운 다리의 기둥들은 훨씬 빠른 속도로 시야를 스쳐 지나간다.
즉 가까운 거리에 있는 배경은 상대적으로 빠르게 움직이고 멀리 있을 수록 천천히 움직이도록 배경에 시차 스크롤링 효과를 주면 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. 백그라운드, 생성자 함수와 메서드
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 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차원 배경 이미지 애니메이션도 구현할 수 있는 초석이 될 것이다. 개념이 정립되었으면 각자 취향에 맞는 배경 애니메이션을 살을 붙여 가며 구현해 보기 바란다.