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

HTML 게임 – 장애물

1주전 작성

HTML Canvas 게임 장애물

HTML Canvas 게임에서 장애물을 만들고 관리하는 방법을 알아본다. 동적으로 생성되는 장애물과 충돌 감지 시스템을 구현하여 흥미진진한 게임을 만들어보자.

기본 장애물 만들기

게임에 첫 번째 장애물을 추가해보자.

<canvas id="myCanvas1" width="480" height="270" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var myGamePiece;
var myObstacle;
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.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);
myObstacle = new component(10, 200, "green", 300, 120);
myGameArea.start();
}
function updateGameArea() {
myGameArea.clear();
myObstacle.speedX = -1;
myObstacle.newPos();
myObstacle.update();
myGamePiece.newPos();
myGamePiece.update();
}
startGame();
</script>

기본 장애물은 녹색 사각형으로, 왼쪽으로 이동하여 스크롤링 효과를 만든다. 플레이어는 빨간 사각형으로 표시된다.

여러 장애물 관리

배열을 사용하여 여러 개의 장애물을 관리해보자.

<canvas id="myCanvas2" width="480" height="270" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var myGamePiece;
var myObstacles = [];
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);
myObstacles.push(new component(10, 200, "green", 300, 120));
myObstacles.push(new component(10, 200, "green", 350, 120));
myObstacles.push(new component(10, 200, "green", 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]

myObstacles 배열에 여러 장애물을 저장하고, 반복문을 사용하여 모든 장애물을 업데이트한다.

동적 장애물 생성

게임이 진행되면서 새로운 장애물이 계속 생성되도록 해보자.

<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.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) {
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);
myGameArea.start();
}
function updateGameArea() {
var x, height, gap, minHeight, maxHeight, minGap, maxGap;
for (i = 0; i < myObstacles.length; i += 1) { myObstacles[i].speedX = -1; myObstacles[i].newPos(); myObstacles[i].update(); } myGameArea.frameNo += 1; if (myGameArea.frameNo == 1 || everyinterval(150)) { x = myGameArea.canvas.width; minHeight = 20; maxHeight = 200; height = Math.floor(Math.random()*(maxHeight-minHeight+1)+minHeight); minGap = 50; maxGap = 200; gap = Math.floor(Math.random()*(maxGap-minGap+1)+minGap); myObstacles.push(new component(10, height, "green", x, 0)); myObstacles.push(new component(10, x - height - gap, "green", x, height + gap)); } myGameArea.clear(); myGamePiece.newPos(); myGamePiece.update(); for (i = 0; i < myObstacles.length; i += 1) { myObstacles[i].update(); } } function everyinterval(n) { if ((myGameArea.frameNo / n) % 1 == 0) {return true;} return false; } startGame(); </script>[/code]

everyinterval() 함수를 사용하여 일정 간격마다 새로운 장애물을 생성한다. 무작위 높이와 간격을 가진 위아래 장애물 쌍을 만들어 파이프 게임과 같은 효과를 연출한다.

충돌 감지 시스템

플레이어와 장애물 간의 충돌을 감지하는 시스템을 추가해보자.

<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.frameNo = 0;
this.interval = setInterval(updateGameArea, 20);
window.addEventListener('keydown', function (e) {
myGameArea.key = e.keyCode;
})
window.addEventListener('keyup', function (e) {
myGameArea.key = false;
})
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
},
stop: function() {
clearInterval(this.interval);
}
}
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;
}
this.crashWith = function(otherobj) {
var myleft = this.x;
var myright = this.x + (this.width);
var mytop = this.y;
var mybottom = this.y + (this.height);
var otherleft = otherobj.x;
var otherright = otherobj.x + (otherobj.width);
var othertop = otherobj.y;
var otherbottom = otherobj.y + (otherobj.height);
var crash = true;
if ((mybottom < othertop) || (mytop > otherbottom) || (myright < otherleft) || (myleft > otherright)) {
crash = false;
}
return crash;
}
}
function startGame() {
myGamePiece = new component(30, 30, "red", 10, 120);
myGameArea.start();
}
function updateGameArea() {
var x, height, gap, minHeight, maxHeight, minGap, maxGap;
for (i = 0; i < myObstacles.length; i += 1) { if (myGamePiece.crashWith(myObstacles[i])) { myGameArea.stop(); return; } } myGameArea.clear(); myGameArea.frameNo += 1; if (myGameArea.frameNo == 1 || everyinterval(150)) { x = myGameArea.canvas.width; minHeight = 20; maxHeight = 200; height = Math.floor(Math.random()*(maxHeight-minHeight+1)+minHeight); minGap = 50; maxGap = 200; gap = Math.floor(Math.random()*(maxGap-minGap+1)+minGap); myObstacles.push(new component(10, height, "green", x, 0)); myObstacles.push(new component(10, x - height - gap, "green", x, height + gap)); } for (i = 0; i < myObstacles.length; i += 1) { myObstacles[i].speedX = -1; myObstacles[i].newPos(); myObstacles[i].update(); } myGamePiece.speedX = 0; myGamePiece.speedY = 0; if (myGameArea.key && myGameArea.key == 37) {myGamePiece.speedX = -1; } if (myGameArea.key && myGameArea.key == 39) {myGamePiece.speedX = 1; } if (myGameArea.key && myGameArea.key == 38) {myGamePiece.speedY = -1; } if (myGameArea.key && myGameArea.key == 40) {myGamePiece.speedY = 1; } myGamePiece.newPos(); myGamePiece.update(); } function everyinterval(n) { if ((myGameArea.frameNo / n) % 1 == 0) {return true;} return false; } startGame(); </script>[/code]

crashWith() 메서드는 두 사각형이 겹치는지 확인한다. 충돌이 감지되면 게임이 중지된다. 키보드 컨트롤도 추가하여 플레이어가 장애물을 피할 수 있게 했다.

장애물 정리 시스템

화면을 벗어난 장애물을 제거하여 메모리 사용량을 최적화해보자.

<canvas id="myCanvas5" width="480" height="270" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var myGamePiece;
var myObstacles = [];
var myGameArea = {
canvas: document.getElementById("myCanvas5"),
start: function() {
this.canvas.width = 480;
this.canvas.height = 270;
this.context = this.canvas.getContext("2d");
this.frameNo = 0;
this.interval = setInterval(updateGameArea, 20);
window.addEventListener('keydown', function (e) {
myGameArea.key = e.keyCode;
})
window.addEventListener('keyup', function (e) {
myGameArea.key = false;
})
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
},
stop: function() {
clearInterval(this.interval);
}
}
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;
}
this.crashWith = function(otherobj) {
var myleft = this.x;
var myright = this.x + (this.width);
var mytop = this.y;
var mybottom = this.y + (this.height);
var otherleft = otherobj.x;
var otherright = otherobj.x + (otherobj.width);
var othertop = otherobj.y;
var otherbottom = otherobj.y + (otherobj.height);
var crash = true;
if ((mybottom < othertop) || (mytop > otherbottom) || (myright < otherleft) || (myleft > otherright)) {
crash = false;
}
return crash;
}
}
function startGame() {
myGamePiece = new component(30, 30, "red", 10, 120);
myGameArea.start();
}
function updateGameArea() {
var x, height, gap, minHeight, maxHeight, minGap, maxGap;
for (i = 0; i < myObstacles.length; i += 1) { if (myGamePiece.crashWith(myObstacles[i])) { myGameArea.stop(); return; } } myGameArea.clear(); myGameArea.frameNo += 1; if (myGameArea.frameNo == 1 || everyinterval(150)) { x = myGameArea.canvas.width; minHeight = 20; maxHeight = 200; height = Math.floor(Math.random()*(maxHeight-minHeight+1)+minHeight); minGap = 50; maxGap = 200; gap = Math.floor(Math.random()*(maxGap-minGap+1)+minGap); myObstacles.push(new component(10, height, "green", x, 0)); myObstacles.push(new component(10, x - height - gap, "green", x, height + gap)); } for (i = myObstacles.length - 1; i >= 0; i -= 1) {
myObstacles[i].speedX = -1;
myObstacles[i].newPos();
if (myObstacles[i].x + myObstacles[i].width < 0) { myObstacles.splice(i, 1); } else { myObstacles[i].update(); } } myGamePiece.speedX = 0; myGamePiece.speedY = 0; if (myGameArea.key && myGameArea.key == 37) {myGamePiece.speedX = -1; } if (myGameArea.key && myGameArea.key == 39) {myGamePiece.speedX = 1; } if (myGameArea.key && myGameArea.key == 38) {myGamePiece.speedY = -1; } if (myGameArea.key && myGameArea.key == 40) {myGamePiece.speedY = 1; } myGamePiece.newPos(); myGamePiece.update(); } function everyinterval(n) { if ((myGameArea.frameNo / n) % 1 == 0) {return true;} return false; } startGame(); </script>[/code]

역순으로 배열을 순회하면서 화면을 벗어난 장애물을 splice()로 제거한다. 이렇게 하면 메모리 누수를 방지할 수 있다.

다양한 장애물 타입

크기와 색상이 다른 여러 종류의 장애물을 만들어보자.

<canvas id="myCanvas6" width="480" height="270" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var myGamePiece;
var myObstacles = [];
var myGameArea = {
canvas: document.getElementById("myCanvas6"),
start: function() {
this.canvas.width = 480;
this.canvas.height = 270;
this.context = this.canvas.getContext("2d");
this.frameNo = 0;
this.interval = setInterval(updateGameArea, 20);
window.addEventListener('keydown', function (e) {
myGameArea.key = e.keyCode;
})
window.addEventListener('keyup', function (e) {
myGameArea.key = false;
})
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
},
stop: function() {
clearInterval(this.interval);
}
}
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 == "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;
}
this.crashWith = function(otherobj) {
var myleft = this.x;
var myright = this.x + (this.width);
var mytop = this.y;
var mybottom = this.y + (this.height);
var otherleft = otherobj.x;
var otherright = otherobj.x + (otherobj.width);
var othertop = otherobj.y;
var otherbottom = otherobj.y + (otherobj.height);
var crash = true;
if ((mybottom < othertop) || (mytop > otherbottom) || (myright < otherleft) || (myleft > otherright)) {
crash = false;
}
return crash;
}
}
function startGame() {
myGamePiece = new component(30, 30, "red", 10, 120);
myGameArea.start();
}
function updateGameArea() {
var x, y, type, color, width, height;
for (i = 0; i < myObstacles.length; i += 1) { if (myGamePiece.crashWith(myObstacles[i])) { myGameArea.stop(); return; } } myGameArea.clear(); myGameArea.frameNo += 1; if (myGameArea.frameNo == 1 || everyinterval(120)) { x = myGameArea.canvas.width; y = Math.floor(Math.random() * (myGameArea.canvas.height - 50)); if (Math.random() > 0.5) {
type = "circle";
width = Math.floor(Math.random() * 30 + 20);
height = width;
color = "blue";
} else {
type = "rectangle";
width = Math.floor(Math.random() * 20 + 10);
height = Math.floor(Math.random() * 80 + 40);
color = "green";
}
myObstacles.push(new component(width, height, color, x, y, type));
}
for (i = myObstacles.length - 1; i >= 0; i -= 1) {
myObstacles[i].speedX = -2;
myObstacles[i].newPos();
if (myObstacles[i].x + myObstacles[i].width < 0) { myObstacles.splice(i, 1); } else { myObstacles[i].update(); } } myGamePiece.speedX = 0; myGamePiece.speedY = 0; if (myGameArea.key && myGameArea.key == 37) {myGamePiece.speedX = -2; } if (myGameArea.key && myGameArea.key == 39) {myGamePiece.speedX = 2; } if (myGameArea.key && myGameArea.key == 38) {myGamePiece.speedY = -2; } if (myGameArea.key && myGameArea.key == 40) {myGamePiece.speedY = 2; } myGamePiece.newPos(); myGamePiece.update(); } function everyinterval(n) { if ((myGameArea.frameNo / n) % 1 == 0) {return true;} return false; } startGame(); </script>[/code]

무작위로 원형 또는 사각형 장애물이 생성된다. 각 타입마다 다른 크기와 색상을 가지며, 다양한 위치에서 나타난다.

움직이는 장애물

수직으로 움직이는 장애물을 추가해보자.

<canvas id="myCanvas7" width="480" height="270" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var myGamePiece;
var myObstacles = [];
var myGameArea = {
canvas: document.getElementById("myCanvas7"),
start: function() {
this.canvas.width = 480;
this.canvas.height = 270;
this.context = this.canvas.getContext("2d");
this.frameNo = 0;
this.interval = setInterval(updateGameArea, 20);
window.addEventListener('keydown', function (e) {
myGameArea.key = e.keyCode;
})
window.addEventListener('keyup', function (e) {
myGameArea.key = false;
})
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
},
stop: function() {
clearInterval(this.interval);
}
}
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.direction = 1;
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.type == "moving") {
if (this.y <= 0 || this.y >= myGameArea.canvas.height - this.height) {
this.direction *= -1;
}
this.speedY = this.direction * 1;
}
}
this.crashWith = function(otherobj) {
var myleft = this.x;
var myright = this.x + (this.width);
var mytop = this.y;
var mybottom = this.y + (this.height);
var otherleft = otherobj.x;
var otherright = otherobj.x + (otherobj.width);
var othertop = otherobj.y;
var otherbottom = otherobj.y + (otherobj.height);
var crash = true;
if ((mybottom < othertop) || (mytop > otherbottom) || (myright < otherleft) || (myleft > otherright)) {
crash = false;
}
return crash;
}
}
function startGame() {
myGamePiece = new component(30, 30, "red", 10, 120);
myGameArea.start();
}
function updateGameArea() {
var x, y, type, color, width, height;
for (i = 0; i < myObstacles.length; i += 1) { if (myGamePiece.crashWith(myObstacles[i])) { myGameArea.stop(); return; } } myGameArea.clear(); myGameArea.frameNo += 1; if (myGameArea.frameNo == 1 || everyinterval(150)) { x = myGameArea.canvas.width; y = Math.floor(Math.random() * (myGameArea.canvas.height - 100)); if (Math.random() > 0.7) {
type = "moving";
width = 20;
height = 80;
color = "purple";
} else {
type = "static";
width = 15;
height = Math.floor(Math.random() * 100 + 50);
color = "green";
}
myObstacles.push(new component(width, height, color, x, y, type));
}
for (i = myObstacles.length - 1; i >= 0; i -= 1) {
myObstacles[i].speedX = -2;
myObstacles[i].newPos();
if (myObstacles[i].x + myObstacles[i].width < 0) { myObstacles.splice(i, 1); } else { myObstacles[i].update(); } } myGamePiece.speedX = 0; myGamePiece.speedY = 0; if (myGameArea.key && myGameArea.key == 37) {myGamePiece.speedX = -2; } if (myGameArea.key && myGameArea.key == 39) {myGamePiece.speedX = 2; } if (myGameArea.key && myGameArea.key == 38) {myGamePiece.speedY = -2; } if (myGameArea.key && myGameArea.key == 40) {myGamePiece.speedY = 2; } myGamePiece.newPos(); myGamePiece.update(); } function everyinterval(n) { if ((myGameArea.frameNo / n) % 1 == 0) {return true;} return false; } startGame(); </script>[/code]

moving 타입의 장애물은 위아래로 움직인다. direction 속성을 사용하여 경계에 닿으면 방향을 바꾼다.

점수 시스템과 난이도 조절

점수에 따라 난이도가 증가하는 시스템을 추가해보자.

<canvas id="myCanvas8" width="480" height="270" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var myGamePiece;
var myObstacles = [];
var myScore;
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);
window.addEventListener('keydown', function (e) {
myGameArea.key = e.keyCode;
})
window.addEventListener('keyup', function (e) {
myGameArea.key = false;
})
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
},
stop: function() {
clearInterval(this.interval);
}
}
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 {
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
this.newPos = function() {
this.x += this.speedX;
this.y += this.speedY;
}
this.crashWith = function(otherobj) {
var myleft = this.x;
var myright = this.x + (this.width);
var mytop = this.y;
var mybottom = this.y + (this.height);
var otherleft = otherobj.x;
var otherright = otherobj.x + (otherobj.width);
var othertop = otherobj.y;
var otherbottom = otherobj.y + (otherobj.height);
var crash = true;
if ((mybottom < othertop) || (mytop > otherbottom) || (myright < otherleft) || (myleft > otherright)) {
crash = false;
}
return crash;
}
}
function startGame() {
myGamePiece = new component(30, 30, "red", 10, 120);
myScore = new component("30px", "Consolas", "black", 280, 40, "text");
myGameArea.start();
}
function updateGameArea() {
var x, y, width, height, speed, interval;
for (i = 0; i < myObstacles.length; i += 1) { if (myGamePiece.crashWith(myObstacles[i])) { myGameArea.stop(); return; } } myGameArea.clear(); myGameArea.frameNo += 1; speed = Math.floor(myGameArea.frameNo / 500) + 2; interval = Math.max(50, 120 - Math.floor(myGameArea.frameNo / 300)); if (myGameArea.frameNo == 1 || everyinterval(interval)) { x = myGameArea.canvas.width; y = Math.floor(Math.random() * (myGameArea.canvas.height - 100)); width = Math.floor(Math.random() * 20 + 10); height = Math.floor(Math.random() * 80 + 40); myObstacles.push(new component(width, height, "green", x, y)); } for (i = myObstacles.length - 1; i >= 0; i -= 1) {
myObstacles[i].speedX = -speed;
myObstacles[i].newPos();
if (myObstacles[i].x + myObstacles[i].width < 0) { myObstacles.splice(i, 1); } else { myObstacles[i].update(); } } myGamePiece.speedX = 0; myGamePiece.speedY = 0; if (myGameArea.key && myGameArea.key == 37) {myGamePiece.speedX = -2; } if (myGameArea.key && myGameArea.key == 39) {myGamePiece.speedX = 2; } if (myGameArea.key && myGameArea.key == 38) {myGamePiece.speedY = -2; } if (myGameArea.key && myGameArea.key == 40) {myGamePiece.speedY = 2; } myGamePiece.newPos(); myGamePiece.update(); myScore.text = "점수: " + myGameArea.frameNo + " 속도: " + speed; myScore.update(); } function everyinterval(n) { if ((myGameArea.frameNo / n) % 1 == 0) {return true;} return false; } startGame(); </script>[/code]

시간이 지날수록 장애물의 속도가 빨라지고 생성 간격이 짧아진다. 점수와 현재 속도를 화면에 표시하여 플레이어가 진행 상황을 확인할 수 있다.

💡 게임 장애물 설계 팁:
• 장애물 생성 간격과 패턴을 조절하여 적절한 난이도를 유지하자
• 메모리 누수를 방지하기 위해 화면을 벗어난 장애물은 반드시 제거하자
• 다양한 타입의 장애물로 게임에 변화를 주면 더 흥미롭다
• 충돌 감지는 정확해야 하지만 너무 엄격하면 게임이 어려워질 수 있다

보너스 아이템

플레이어가 수집할 수 있는 보너스 아이템을 추가해보자.

<canvas id="myCanvas9" width="480" height="270" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var myGamePiece;
var myObstacles = [];
var myBonuses = [];
var myScore;
var points = 0;
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);
window.addEventListener('keydown', function (e) {
myGameArea.key = e.keyCode;
})
window.addEventListener('keyup', function (e) {
myGameArea.key = false;
})
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
},
stop: function() {
clearInterval(this.interval);
}
}
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 == "bonus") {
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;
}
this.crashWith = function(otherobj) {
var myleft = this.x;
var myright = this.x + (this.width);
var mytop = this.y;
var mybottom = this.y + (this.height);
var otherleft = otherobj.x;
var otherright = otherobj.x + (otherobj.width);
var othertop = otherobj.y;
var otherbottom = otherobj.y + (otherobj.height);
var crash = true;
if ((mybottom < othertop) || (mytop > otherbottom) || (myright < otherleft) || (myleft > otherright)) {
crash = false;
}
return crash;
}
}
function startGame() {
myGamePiece = new component(30, 30, "red", 10, 120);
myScore = new component("20px", "Consolas", "black", 280, 30, "text");
myGameArea.start();
}
function updateGameArea() {
var x, y, width, height;
for (i = 0; i < myObstacles.length; i += 1) { if (myGamePiece.crashWith(myObstacles[i])) { myGameArea.stop(); return; } } for (i = myBonuses.length - 1; i >= 0; i -= 1) {
if (myGamePiece.crashWith(myBonuses[i])) {
points += 10;
myBonuses.splice(i, 1);
}
}
myGameArea.clear();
myGameArea.frameNo += 1;
if (myGameArea.frameNo == 1 || everyinterval(120)) {
x = myGameArea.canvas.width;
y = Math.floor(Math.random() * (myGameArea.canvas.height - 100));
width = Math.floor(Math.random() * 20 + 10);
height = Math.floor(Math.random() * 80 + 40);
myObstacles.push(new component(width, height, "green", x, y));
}
if (everyinterval(300)) {
x = myGameArea.canvas.width;
y = Math.floor(Math.random() * (myGameArea.canvas.height - 30));
myBonuses.push(new component(20, 20, "gold", x, y, "bonus"));
}
for (i = myObstacles.length - 1; i >= 0; i -= 1) {
myObstacles[i].speedX = -2;
myObstacles[i].newPos();
if (myObstacles[i].x + myObstacles[i].width < 0) { myObstacles.splice(i, 1); } else { myObstacles[i].update(); } } for (i = myBonuses.length - 1; i >= 0; i -= 1) {
myBonuses[i].speedX = -2;
myBonuses[i].newPos();
if (myBonuses[i].x + myBonuses[i].width < 0) { myBonuses.splice(i, 1); } else { myBonuses[i].update(); } } myGamePiece.speedX = 0; myGamePiece.speedY = 0; if (myGameArea.key && myGameArea.key == 37) {myGamePiece.speedX = -2; } if (myGameArea.key && myGameArea.key == 39) {myGamePiece.speedX = 2; } if (myGameArea.key && myGameArea.key == 38) {myGamePiece.speedY = -2; } if (myGameArea.key && myGameArea.key == 40) {myGamePiece.speedY = 2; } myGamePiece.newPos(); myGamePiece.update(); myScore.text = "점수: " + (myGameArea.frameNo + points); myScore.update(); } function everyinterval(n) { if ((myGameArea.frameNo / n) % 1 == 0) {return true;} return false; } startGame(); </script>[/code]

황금색 원형 보너스 아이템이 주기적으로 나타난다. 플레이어가 보너스 아이템과 충돌하면 점수가 증가하고 아이템이 사라진다.

게임 장애물 시스템은 게임의 핵심적인 도전 요소를 제공한다. 다양한 타입의 장애물, 적절한 난이도 조절, 보너스 시스템 등을 통해 플레이어가 지속적으로 흥미를 느낄 수 있는 게임을 만들 수 있다.

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