
HTML Canvas 게임 컴포넌트
HTML Canvas 게임에서 컴포넌트는 게임의 기본 구성 요소이다. 이 튜토리얼에서는 게임 컴포넌트를 만들고 관리하는 방법을 알아본다.
기본 컴포넌트 만들기
게임에서 사용할 기본 컴포넌트를 만들어보자.
<canvas id="myCanvas1" width="480" height="270" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var myGamePiece;
var myGameArea = {
canvas: document.getElementById("myCanvas1"),
start: function() {
this.canvas.width = 480;
this.canvas.height = 270;
this.context = this.canvas.getContext("2d");
this.interval = setInterval(updateGameArea, 20);
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
function component(width, height, color, x, y) {
this.width = width;
this.height = height;
this.x = x;
this.y = y;
this.update = function() {
ctx = myGameArea.context;
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
function startGame() {
myGamePiece = new component(30, 30, "red", 10, 120);
myGameArea.start();
}
function updateGameArea() {
myGameArea.clear();
myGamePiece.update();
}
startGame();
</script>
component 생성자 함수는 게임 오브젝트의 기본 틀을 제공한다. 각 컴포넌트는 크기(width, height), 색상(color), 위치(x, y) 속성을 가진다.
움직임 속성 추가
컴포넌트에 속도 속성을 추가하여 움직임을 구현해보자.
<canvas id="myCanvas2" width="480" height="270" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var myGamePiece;
var myGameArea = {
canvas: document.getElementById("myCanvas2"),
start: function() {
this.canvas.width = 480;
this.canvas.height = 270;
this.context = this.canvas.getContext("2d");
this.interval = setInterval(updateGameArea, 20);
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
function component(width, height, color, x, y) {
this.width = width;
this.height = height;
this.speedX = 0;
this.speedY = 0;
this.x = x;
this.y = y;
this.update = function() {
ctx = myGameArea.context;
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
this.newPos = function() {
this.x += this.speedX;
this.y += this.speedY;
}
}
function startGame() {
myGamePiece = new component(30, 30, "red", 10, 120);
myGamePiece.speedX = 2;
myGamePiece.speedY = 1;
myGameArea.start();
}
function updateGameArea() {
myGameArea.clear();
myGamePiece.newPos();
myGamePiece.update();
}
startGame();
</script>
speedX와 speedY 속성을 추가하여 컴포넌트의 이동 속도를 제어한다. newPos() 메서드는 현재 위치에 속도를 더하여 새로운 위치를 계산한다.
여러 컴포넌트 관리
게임에서 여러 개의 컴포넌트를 동시에 관리해보자.
<canvas id="myCanvas3" width="480" height="270" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var myGamePiece;
var myObstacles = [];
var myGameArea = {
canvas: document.getElementById("myCanvas3"),
start: function() {
this.canvas.width = 480;
this.canvas.height = 270;
this.context = this.canvas.getContext("2d");
this.interval = setInterval(updateGameArea, 20);
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
function component(width, height, color, x, y) {
this.width = width;
this.height = height;
this.speedX = 0;
this.speedY = 0;
this.x = x;
this.y = y;
this.update = function() {
ctx = myGameArea.context;
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
this.newPos = function() {
this.x += this.speedX;
this.y += this.speedY;
}
}
function startGame() {
myGamePiece = new component(30, 30, "red", 10, 120);
myObstacles.push(new component(10, 200, "green", 300, 120));
myObstacles.push(new component(10, 200, "blue", 350, 120));
myObstacles.push(new component(10, 200, "yellow", 400, 120));
myGameArea.start();
}
function updateGameArea() {
myGameArea.clear();
for (i = 0; i < myObstacles.length; i += 1) {
myObstacles[i].speedX = -1;
myObstacles[i].newPos();
myObstacles[i].update();
}
myGamePiece.newPos();
myGamePiece.update();
}
startGame();
</script>[/code]
배열을 사용하여 여러 컴포넌트를 관리한다. 반복문을 통해 모든 장애물을 업데이트하고 렌더링한다.
컴포넌트 타입 구분
컴포넌트에 타입을 추가하여 다양한 종류의 오브젝트를 만들어보자.
[code lang="markup"]<canvas id="myCanvas4" width="480" height="270" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var myGamePiece;
var myObstacles = [];
var myGameArea = {
canvas: document.getElementById("myCanvas4"),
start: function() {
this.canvas.width = 480;
this.canvas.height = 270;
this.context = this.canvas.getContext("2d");
this.interval = setInterval(updateGameArea, 20);
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
function component(width, height, color, x, y, type) {
this.type = type;
this.width = width;
this.height = height;
this.speedX = 0;
this.speedY = 0;
this.x = x;
this.y = y;
this.update = function() {
ctx = myGameArea.context;
if (this.type == "text") {
ctx.font = this.width + " " + this.height;
ctx.fillStyle = color;
ctx.fillText(this.text, this.x, this.y);
} else if (this.type == "circle") {
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(this.x + this.width/2, this.y + this.height/2, this.width/2, 0, 2 * Math.PI);
ctx.fill();
} else {
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
this.newPos = function() {
this.x += this.speedX;
this.y += this.speedY;
}
}
function startGame() {
myGamePiece = new component(30, 30, "red", 10, 120);
myObstacles.push(new component(30, 30, "green", 300, 120, "circle"));
myObstacles.push(new component(10, 200, "blue", 350, 120));
myObstacles.push(new component("30px", "Arial", "black", 200, 50, "text"));
myObstacles[2].text = "Hello World!";
myGameArea.start();
}
function updateGameArea() {
myGameArea.clear();
for (i = 0; i < myObstacles.length; i += 1) {
if (myObstacles[i].type !== "text") {
myObstacles[i].speedX = -1;
myObstacles[i].newPos();
}
myObstacles[i].update();
}
myGamePiece.newPos();
myGamePiece.update();
}
startGame();
</script>[/code]
type 매개변수를 추가하여 다양한 형태의 컴포넌트를 만들 수 있다. 사각형, 원, 텍스트 등 다양한 타입을 지원한다.
이미지 컴포넌트
이미지를 사용하는 컴포넌트를 만들어보자.
[code lang="markup"]<canvas id="myCanvas5" width="480" height="270" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var myGamePiece;
var myGameArea = {
canvas: document.getElementById("myCanvas5"),
start: function() {
this.canvas.width = 480;
this.canvas.height = 270;
this.context = this.canvas.getContext("2d");
this.interval = setInterval(updateGameArea, 20);
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
function component(width, height, color, x, y, type) {
this.type = type;
this.width = width;
this.height = height;
this.speedX = 0;
this.speedY = 0;
this.x = x;
this.y = y;
if (type == "image") {
this.image = new Image();
this.image.src = color;
}
this.update = function() {
ctx = myGameArea.context;
if (this.type == "image") {
ctx.drawImage(this.image, this.x, this.y, this.width, this.height);
} else if (this.type == "text") {
ctx.font = this.width + " " + this.height;
ctx.fillStyle = color;
ctx.fillText(this.text, this.x, this.y);
} else if (this.type == "circle") {
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(this.x + this.width/2, this.y + this.height/2, this.width/2, 0, 2 * Math.PI);
ctx.fill();
} else {
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
this.newPos = function() {
this.x += this.speedX;
this.y += this.speedY;
}
}
function startGame() {
myGamePiece = new component(50, 50, "red", 10, 120);
myGameArea.start();
}
function updateGameArea() {
myGameArea.clear();
myGamePiece.speedX = 1;
myGamePiece.newPos();
myGamePiece.update();
}
startGame();
</script>
이미지 타입 컴포넌트는 Image 객체를 생성하고 drawImage() 메서드를 사용하여 렌더링한다. 이미지 파일의 URL을 color 매개변수로 전달한다.
회전 기능 추가
컴포넌트에 회전 기능을 추가해보자.
<canvas id="myCanvas6" width="480" height="270" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var myGamePiece;
var myGameArea = {
canvas: document.getElementById("myCanvas6"),
start: function() {
this.canvas.width = 480;
this.canvas.height = 270;
this.context = this.canvas.getContext("2d");
this.interval = setInterval(updateGameArea, 20);
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
function component(width, height, color, x, y, type) {
this.type = type;
this.width = width;
this.height = height;
this.speedX = 0;
this.speedY = 0;
this.x = x;
this.y = y;
this.angle = 0;
this.update = function() {
ctx = myGameArea.context;
ctx.save();
ctx.translate(this.x + this.width/2, this.y + this.height/2);
ctx.rotate(this.angle);
if (this.type == "text") {
ctx.font = this.width + " " + this.height;
ctx.fillStyle = color;
ctx.fillText(this.text, -this.width/2, this.height/2);
} else if (this.type == "circle") {
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(0, 0, this.width/2, 0, 2 * Math.PI);
ctx.fill();
} else {
ctx.fillStyle = color;
ctx.fillRect(-this.width/2, -this.height/2, this.width, this.height);
}
ctx.restore();
}
this.newPos = function() {
this.angle += this.speedX * Math.PI / 180;
this.x += this.speedY;
}
}
function startGame() {
myGamePiece = new component(30, 30, "red", 225, 135);
myGamePiece.speedX = 2;
myGameArea.start();
}
function updateGameArea() {
myGameArea.clear();
myGamePiece.newPos();
myGamePiece.update();
}
startGame();
</script>
angle 속성을 추가하고, save()와 restore() 메서드를 사용하여 변환 상태를 관리한다. translate()와 rotate() 메서드로 회전 효과를 구현한다.
경계 검사 기능
컴포넌트가 캔버스 경계를 벗어나지 않도록 하는 기능을 추가해보자.
<canvas id="myCanvas7" width="480" height="270" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var myGamePiece;
var myGameArea = {
canvas: document.getElementById("myCanvas7"),
start: function() {
this.canvas.width = 480;
this.canvas.height = 270;
this.context = this.canvas.getContext("2d");
this.interval = setInterval(updateGameArea, 20);
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
function component(width, height, color, x, y, type) {
this.type = type;
this.width = width;
this.height = height;
this.speedX = 0;
this.speedY = 0;
this.x = x;
this.y = y;
this.update = function() {
ctx = myGameArea.context;
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
this.newPos = function() {
this.x += this.speedX;
this.y += this.speedY;
if (this.x < 0) {
this.x = 0;
this.speedX = -this.speedX;
}
if (this.x > myGameArea.canvas.width - this.width) {
this.x = myGameArea.canvas.width - this.width;
this.speedX = -this.speedX;
}
if (this.y < 0) {
this.y = 0;
this.speedY = -this.speedY;
}
if (this.y > myGameArea.canvas.height - this.height) {
this.y = myGameArea.canvas.height - this.height;
this.speedY = -this.speedY;
}
}
}
function startGame() {
myGamePiece = new component(30, 30, "red", 10, 120);
myGamePiece.speedX = 3;
myGamePiece.speedY = 2;
myGameArea.start();
}
function updateGameArea() {
myGameArea.clear();
myGamePiece.newPos();
myGamePiece.update();
}
startGame();
</script>
newPos() 메서드에서 경계 검사를 수행한다. 컴포넌트가 캔버스 경계에 닿으면 속도 방향을 반전시켜 튕기는 효과를 만든다.
생명력 시스템
컴포넌트에 생명력 시스템을 추가해보자.
<canvas id="myCanvas8" width="480" height="270" style="border:1px solid #d3d3d3;">브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var myGamePiece;
var myObstacles = [];
var myGameArea = {
canvas: document.getElementById("myCanvas8"),
start: function() {
this.canvas.width = 480;
this.canvas.height = 270;
this.context = this.canvas.getContext("2d");
this.frameNo = 0;
this.interval = setInterval(updateGameArea, 20);
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
function component(width, height, color, x, y, type) {
this.type = type;
this.width = width;
this.height = height;
this.speedX = 0;
this.speedY = 0;
this.x = x;
this.y = y;
this.health = 100;
this.maxHealth = 100;
this.alive = true;
this.update = function() {
if (!this.alive) return;
ctx = myGameArea.context;
if (this.type == "text") {
ctx.font = this.width + " " + this.height;
ctx.fillStyle = color;
ctx.fillText(this.text, this.x, this.y);
} else {
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
if (this.health < this.maxHealth) { ctx.fillStyle = "red"; ctx.fillRect(this.x, this.y - 10, this.width, 5); ctx.fillStyle = "green"; ctx.fillRect(this.x, this.y - 10, this.width * (this.health / this.maxHealth), 5); } } } this.newPos = function() { if (!this.alive) return; this.x += this.speedX; this.y += this.speedY; } this.takeDamage = function(damage) { this.health -= damage; if (this.health <= 0) { this.health = 0; this.alive = false; } } } function startGame() { myGamePiece = new component(30, 30, "red", 10, 120); myObstacles.push(new component(10, 200, "green", 300, 120)); myObstacles.push(new component("20px", "Arial", "black", 200, 50, "text")); myObstacles[1].text = "체력: " + myGamePiece.health; myGameArea.start(); } function updateGameArea() { myGameArea.clear(); myGameArea.frameNo += 1; if (myGameArea.frameNo % 60 == 0) { myGamePiece.takeDamage(10); } for (i = 0; i < myObstacles.length; i += 1) { if (myObstacles[i].type !== "text") { myObstacles[i].speedX = -1; myObstacles[i].newPos(); } else { myObstacles[i].text = "체력: " + myGamePiece.health; } myObstacles[i].update(); } myGamePiece.newPos(); myGamePiece.update(); } startGame(); </script>[/code]
health, maxHealth, alive 속성을 추가하고, takeDamage() 메서드로 피해를 처리한다. 체력바를 시각적으로 표시하여 현재 상태를 보여준다.
💡 게임 컴포넌트 설계 팁:
• 컴포넌트는 재사용 가능하도록 설계해야 한다. 공통 속성과 메서드를 포함하자
• 타입 시스템을 사용하여 다양한 종류의 오브젝트를 효율적으로 관리하자
• 상태 관리(alive, health 등)를 통해 복잡한 게임 로직을 구현할 수 있다
• 성능을 위해 불필요한 연산을 피하고, 죽은 오브젝트는 렌더링하지 않도록 하자
컴포넌트 풀링 시스템
성능 최적화를 위한 오브젝트 풀링 시스템을 구현해보자.
<canvas id="myCanvas9" width="480" height="270" style="border:1px solid #d3d3d3;">브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var myGamePiece;
var bulletPool = [];
var activeBullets = [];
var myGameArea = {
canvas: document.getElementById("myCanvas9"),
start: function() {
this.canvas.width = 480;
this.canvas.height = 270;
this.context = this.canvas.getContext("2d");
this.frameNo = 0;
this.interval = setInterval(updateGameArea, 20);
this.keys = {};
window.addEventListener('keydown', function (e) {
myGameArea.keys[e.keyCode] = true;
})
window.addEventListener('keyup', function (e) {
myGameArea.keys[e.keyCode] = false;
})
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
function component(width, height, color, x, y, type) {
this.type = type;
this.width = width;
this.height = height;
this.speedX = 0;
this.speedY = 0;
this.x = x;
this.y = y;
this.active = false;
this.update = function() {
if (!this.active) return;
ctx = myGameArea.context;
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
this.newPos = function() {
if (!this.active) return;
this.x += this.speedX;
this.y += this.speedY;
if (this.x > myGameArea.canvas.width || this.x < 0 || this.y > myGameArea.canvas.height || this.y < 0) { this.deactivate(); } } this.activate = function(x, y, speedX, speedY) { this.x = x; this.y = y; this.speedX = speedX; this.speedY = speedY; this.active = true; } this.deactivate = function() { this.active = false; } } function createBulletPool(size) { for (var i = 0; i < size; i++) { bulletPool.push(new component(5, 10, "yellow", 0, 0, "bullet")); } } function getBullet() { for (var i = 0; i < bulletPool.length; i++) { if (!bulletPool[i].active) { return bulletPool[i]; } } return null; } function startGame() { myGamePiece = new component(30, 30, "red", 225, 225); createBulletPool(20); myGameArea.start(); } function updateGameArea() { myGameArea.clear(); myGameArea.frameNo += 1; myGamePiece.speedX = 0; myGamePiece.speedY = 0; if (myGameArea.keys && myGameArea.keys[37]) {myGamePiece.speedX = -2; } if (myGameArea.keys && myGameArea.keys[39]) {myGamePiece.speedX = 2; } if (myGameArea.keys && myGameArea.keys[38]) {myGamePiece.speedY = -2; } if (myGameArea.keys && myGameArea.keys[40]) {myGamePiece.speedY = 2; } if (myGameArea.keys && myGameArea.keys[32] && myGameArea.frameNo % 10 == 0) { var bullet = getBullet(); if (bullet) { bullet.activate(myGamePiece.x + myGamePiece.width/2, myGamePiece.y, 0, -5); } } myGamePiece.newPos(); myGamePiece.update(); for (var i = 0; i < bulletPool.length; i++) { bulletPool[i].newPos(); bulletPool[i].update(); } } startGame(); </script>[/code]
오브젝트 풀링을 사용하면 게임 실행 중에 새로운 오브젝트를 생성하고 삭제하는 오버헤드를 줄일 수 있다. 미리 생성된 오브젝트를 재사용하여 성능을 향상시킨다.
게임 컴포넌트 시스템은 복잡한 게임을 구조적으로 관리할 수 있게 해준다. 이러한 기본 구조를 바탕으로 더 정교한 게임 메커니즘을 구현할 수 있다.