여러분이 사용하고 계신 브라우저는 HTML5를 지원하지 않기 때문에 몇몇 요소가 제대로 보이도록 JScript를 사용하고 있습니다. 하지만 여러분의 브라우저 설정에서 스크립트 기능이 꺼져있으므로, 현재 페이지를 제대로 확인하시려면 스크립트 기능을 켜주셔야 합니다. HTML 게임 - 컴포넌트

HTML 게임 – 컴포넌트

2주전 작성

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]

오브젝트 풀링을 사용하면 게임 실행 중에 새로운 오브젝트를 생성하고 삭제하는 오버헤드를 줄일 수 있다. 미리 생성된 오브젝트를 재사용하여 성능을 향상시킨다.

게임 컴포넌트 시스템은 복잡한 게임을 구조적으로 관리할 수 있게 해준다. 이러한 기본 구조를 바탕으로 더 정교한 게임 메커니즘을 구현할 수 있다.

참고
Mingg`s Diary
밍구
공부 목적 블로그