
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.speedX = 2;
this.speedY = 1;
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.checkBounds();
}
this.checkBounds = function() {
if (this.x <= 0 || this.x >= myGameArea.canvas.width - this.width) {
this.speedX = -this.speedX;
}
if (this.y <= 0 || this.y >= myGameArea.canvas.height - this.height) {
this.speedY = -this.speedY;
}
}
}
function startGame() {
myGamePiece = new component(30, 30, "red", 10, 120);
myGameArea.start();
}
function updateGameArea() {
myGameArea.clear();
myGamePiece.newPos();
myGamePiece.update();
}
startGame();
</script>
checkBounds() 메서드에서 오브젝트가 캔버스 경계에 닿으면 해당 방향의 속도를 반전시켜 바운싱 효과를 만든다.
원형 오브젝트 바운싱
원형 오브젝트의 바운싱을 구현해보자.
<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(radius, color, x, y) {
this.radius = radius;
this.speedX = 3;
this.speedY = 2;
this.x = x;
this.y = y;
this.update = function() {
ctx = myGameArea.context;
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
ctx.fill();
}
this.newPos = function() {
this.x += this.speedX;
this.y += this.speedY;
this.checkBounds();
}
this.checkBounds = function() {
if (this.x - this.radius <= 0 || this.x + this.radius >= myGameArea.canvas.width) {
this.speedX = -this.speedX;
this.x = Math.max(this.radius, Math.min(this.x, myGameArea.canvas.width - this.radius));
}
if (this.y - this.radius <= 0 || this.y + this.radius >= myGameArea.canvas.height) {
this.speedY = -this.speedY;
this.y = Math.max(this.radius, Math.min(this.y, myGameArea.canvas.height - this.radius));
}
}
}
function startGame() {
myGamePiece = new component(20, "blue", 50, 100);
myGameArea.start();
}
function updateGameArea() {
myGameArea.clear();
myGamePiece.newPos();
myGamePiece.update();
}
startGame();
</script>
원형 오브젝트는 중심점에서 반지름만큼 떨어진 거리를 경계로 계산한다. 경계를 벗어나지 않도록 위치를 조정하는 것도 중요하다.
탄성 계수가 있는 바운싱
탄성 계수를 적용하여 바운싱할 때마다 속도가 감소하는 효과를 추가해보자.
<canvas id="myCanvas3" width="480" height="270" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var myGamePiece;
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(radius, color, x, y) {
this.radius = radius;
this.speedX = 4;
this.speedY = 3;
this.x = x;
this.y = y;
this.bounce = 0.8;
this.friction = 0.99;
this.gravity = 0.1;
this.gravitySpeed = 0;
this.update = function() {
ctx = myGameArea.context;
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
ctx.fill();
}
this.newPos = function() {
this.gravitySpeed += this.gravity;
this.speedX *= this.friction;
this.speedY *= this.friction;
this.gravitySpeed *= this.friction;
this.x += this.speedX;
this.y += this.speedY + this.gravitySpeed;
this.checkBounds();
}
this.checkBounds = function() {
if (this.x - this.radius <= 0) {
this.x = this.radius;
this.speedX = -this.speedX * this.bounce;
}
if (this.x + this.radius >= myGameArea.canvas.width) {
this.x = myGameArea.canvas.width - this.radius;
this.speedX = -this.speedX * this.bounce;
}
if (this.y - this.radius <= 0) {
this.y = this.radius;
this.speedY = -this.speedY * this.bounce;
this.gravitySpeed = -this.gravitySpeed * this.bounce;
}
if (this.y + this.radius >= myGameArea.canvas.height) {
this.y = myGameArea.canvas.height - this.radius;
this.speedY = -this.speedY * this.bounce;
this.gravitySpeed = -this.gravitySpeed * this.bounce;
}
}
}
function startGame() {
myGamePiece = new component(15, "green", 100, 50);
myGameArea.start();
}
function updateGameArea() {
myGameArea.clear();
myGamePiece.newPos();
myGamePiece.update();
}
startGame();
</script>
bounce 속성은 탄성 계수를, friction은 마찰력을, gravity는 중력을 나타낸다. 시간이 지날수록 공이 천천히 멈추는 현실적인 물리 효과를 구현했다.
다중 공 바운싱
여러 개의 공이 동시에 바운싱하는 시스템을 만들어보자.
<canvas id="myCanvas4" width="480" height="270" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var myBalls = [];
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(radius, color, x, y) {
this.radius = radius;
this.speedX = (Math.random() - 0.5) * 6;
this.speedY = (Math.random() - 0.5) * 6;
this.x = x;
this.y = y;
this.bounce = 0.9;
this.update = function() {
ctx = myGameArea.context;
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
ctx.fill();
ctx.strokeStyle = "black";
ctx.lineWidth = 1;
ctx.stroke();
}
this.newPos = function() {
this.x += this.speedX;
this.y += this.speedY;
this.checkBounds();
}
this.checkBounds = function() {
if (this.x - this.radius <= 0) {
this.x = this.radius;
this.speedX = -this.speedX * this.bounce;
}
if (this.x + this.radius >= myGameArea.canvas.width) {
this.x = myGameArea.canvas.width - this.radius;
this.speedX = -this.speedX * this.bounce;
}
if (this.y - this.radius <= 0) {
this.y = this.radius;
this.speedY = -this.speedY * this.bounce;
}
if (this.y + this.radius >= myGameArea.canvas.height) {
this.y = myGameArea.canvas.height - this.radius;
this.speedY = -this.speedY * this.bounce;
}
}
}
function getRandomColor() {
var colors = ["red", "blue", "green", "purple", "orange", "pink", "yellow", "cyan"];
return colors[Math.floor(Math.random() * colors.length)];
}
function startGame() {
for (var i = 0; i < 8; i++) {
var radius = Math.random() * 15 + 10;
var x = Math.random() * (myGameArea.canvas.width - 2 * radius) + radius;
var y = Math.random() * (myGameArea.canvas.height - 2 * radius) + radius;
var color = getRandomColor();
myBalls.push(new component(radius, color, x, y));
}
myGameArea.start();
}
function updateGameArea() {
myGameArea.clear();
for (var i = 0; i < myBalls.length; i++) {
myBalls[i].newPos();
myBalls[i].update();
}
}
startGame();
</script>[/code]
무작위 크기, 색상, 초기 속도를 가진 여러 개의 공이 각각 독립적으로 바운싱한다.
공끼리 충돌
공들이 서로 충돌했을 때 바운싱하는 효과를 추가해보자.
[code lang="markup"]<canvas id="myCanvas5" width="480" height="270" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var myBalls = [];
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(radius, color, x, y) {
this.radius = radius;
this.speedX = (Math.random() - 0.5) * 4;
this.speedY = (Math.random() - 0.5) * 4;
this.x = x;
this.y = y;
this.mass = radius;
this.bounce = 0.8;
this.update = function() {
ctx = myGameArea.context;
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
ctx.fill();
ctx.strokeStyle = "black";
ctx.lineWidth = 2;
ctx.stroke();
}
this.newPos = function() {
this.x += this.speedX;
this.y += this.speedY;
this.checkWallBounds();
}
this.checkWallBounds = function() {
if (this.x - this.radius <= 0) {
this.x = this.radius;
this.speedX = -this.speedX * this.bounce;
}
if (this.x + this.radius >= myGameArea.canvas.width) {
this.x = myGameArea.canvas.width - this.radius;
this.speedX = -this.speedX * this.bounce;
}
if (this.y - this.radius <= 0) {
this.y = this.radius;
this.speedY = -this.speedY * this.bounce;
}
if (this.y + this.radius >= myGameArea.canvas.height) {
this.y = myGameArea.canvas.height - this.radius;
this.speedY = -this.speedY * this.bounce;
}
}
this.checkCollision = function(other) {
var dx = this.x - other.x;
var dy = this.y - other.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < this.radius + other.radius) {
var angle = Math.atan2(dy, dx);
var sin = Math.sin(angle);
var cos = Math.cos(angle);
var vx1 = this.speedX * cos + this.speedY * sin;
var vy1 = this.speedY * cos - this.speedX * sin;
var vx2 = other.speedX * cos + other.speedY * sin;
var vy2 = other.speedY * cos - other.speedX * sin;
var finalVx1 = ((this.mass - other.mass) * vx1 + 2 * other.mass * vx2) / (this.mass + other.mass);
var finalVx2 = ((other.mass - this.mass) * vx2 + 2 * this.mass * vx1) / (this.mass + other.mass);
this.speedX = finalVx1 * cos - vy1 * sin;
this.speedY = vy1 * cos + finalVx1 * sin;
other.speedX = finalVx2 * cos - vy2 * sin;
other.speedY = vy2 * cos + finalVx2 * sin;
var overlap = this.radius + other.radius - distance;
var separateX = overlap * cos * 0.5;
var separateY = overlap * sin * 0.5;
this.x += separateX;
this.y += separateY;
other.x -= separateX;
other.y -= separateY;
}
}
}
function startGame() {
var colors = ["red", "blue", "green", "purple", "orange"];
for (var i = 0; i < 5; i++) {
var radius = Math.random() * 20 + 15;
var x = Math.random() * (myGameArea.canvas.width - 2 * radius) + radius;
var y = Math.random() * (myGameArea.canvas.height - 2 * radius) + radius;
myBalls.push(new component(radius, colors[i], x, y));
}
myGameArea.start();
}
function updateGameArea() {
myGameArea.clear();
for (var i = 0; i < myBalls.length; i++) {
for (var j = i + 1; j < myBalls.length; j++) {
myBalls[i].checkCollision(myBalls[j]);
}
myBalls[i].newPos();
myBalls[i].update();
}
}
startGame();
</script>[/code]
두 공이 충돌했을 때 운동량 보존 법칙을 적용하여 현실적인 충돌 효과를 구현했다. 질량과 속도를 고려한 탄성 충돌 공식을 사용한다.
패들과 공 바운싱
플레이어가 조작하는 패들과 공이 상호작용하는 바운싱 게임을 만들어보자.
[code lang="markup"]<canvas id="myCanvas6" width="480" height="270" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var myBall;
var myPaddle;
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);
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);
}
}
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 == "ball") {
this.radius = width / 2;
this.speedX = 3;
this.speedY = -2;
}
this.update = function() {
ctx = myGameArea.context;
if (this.type == "ball") {
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(this.x + this.radius, this.y + this.radius, this.radius, 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.checkBounds = function() {
if (this.type == "ball") {
if (this.x <= 0 || this.x >= myGameArea.canvas.width - this.width) {
this.speedX = -this.speedX;
}
if (this.y <= 0) {
this.speedY = -this.speedY;
}
if (this.y >= myGameArea.canvas.height - this.height) {
this.x = myGameArea.canvas.width / 2 - this.radius;
this.y = myGameArea.canvas.height / 2 - this.radius;
this.speedX = 3;
this.speedY = -2;
}
}
}
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;
}
this.bounceOff = function(otherobj) {
if (this.type == "ball") {
var ballCenterX = this.x + this.radius;
var paddleCenterX = otherobj.x + otherobj.width / 2;
var hitPos = (ballCenterX - paddleCenterX) / (otherobj.width / 2);
this.speedX = hitPos * 3;
this.speedY = -Math.abs(this.speedY);
}
}
}
function startGame() {
myBall = new component(20, 20, "red", 240, 135, "ball");
myPaddle = new component(80, 10, "blue", 200, 250);
myGameArea.start();
}
function updateGameArea() {
myGameArea.clear();
myPaddle.speedX = 0;
if (myGameArea.key && myGameArea.key == 37) {myPaddle.speedX = -3; }
if (myGameArea.key && myGameArea.key == 39) {myPaddle.speedX = 3; }
myPaddle.newPos();
myBall.newPos();
myBall.checkBounds();
if (myBall.crashWith(myPaddle)) {
myBall.bounceOff(myPaddle);
}
myPaddle.update();
myBall.update();
}
startGame();
</script>
패들과 공이 충돌했을 때 공이 패들의 어느 부분에 맞았는지에 따라 반사 각도가 달라진다. 패들의 중앙에 맞으면 수직으로, 가장자리에 맞으면 각도가 크게 바뀐다.
브릭 브레이커 바운싱
벽돌을 부수는 브릭 브레이커 스타일의 바운싱 게임을 구현해보자.
<canvas id="myCanvas7" width="480" height="270" style="border:1px solid #d3d3d3;">브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var myBall;
var myPaddle;
var myBricks = [];
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);
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);
}
}
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 = true;
if (type == "ball") {
this.radius = width / 2;
this.speedX = 3;
this.speedY = -3;
}
this.update = function() {
if (!this.active) return;
ctx = myGameArea.context;
if (this.type == "ball") {
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(this.x + this.radius, this.y + this.radius, this.radius, 0, 2 * Math.PI);
ctx.fill();
} else {
ctx.fillStyle = color;
ctx.fillRect(this.x, this.y, this.width, this.height);
if (this.type == "brick") {
ctx.strokeStyle = "white";
ctx.lineWidth = 2;
ctx.strokeRect(this.x, this.y, this.width, this.height);
}
}
}
this.newPos = function() {
this.x += this.speedX;
this.y += this.speedY;
}
this.checkBounds = function() {
if (this.type == "ball") {
if (this.x <= 0 || this.x >= myGameArea.canvas.width - this.width) {
this.speedX = -this.speedX;
}
if (this.y <= 0) { this.speedY = -this.speedY; } if (this.y >= myGameArea.canvas.height - this.height) {
this.x = myGameArea.canvas.width / 2 - this.radius;
this.y = myGameArea.canvas.height / 2;
this.speedX = 3;
this.speedY = -3;
}
}
}
this.crashWith = function(otherobj) {
if (!otherobj.active) return false;
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;
}
this.bounceOff = function(otherobj) {
if (this.type == "ball") {
var ballCenterX = this.x + this.radius;
var ballCenterY = this.y + this.radius;
var objCenterX = otherobj.x + otherobj.width / 2;
var objCenterY = otherobj.y + otherobj.height / 2;
var dx = Math.abs(ballCenterX - objCenterX);
var dy = Math.abs(ballCenterY - objCenterY);
if (dx > dy) {
this.speedX = -this.speedX;
} else {
this.speedY = -this.speedY;
}
}
}
}
function startGame() {
myBall = new component(16, 16, "white", 240, 200, "ball");
myPaddle = new component(80, 10, "blue", 200, 250);
for (var row = 0; row < 4; row++) { for (var col = 0; col < 8; col++) { var colors = ["red", "orange", "yellow", "green"]; var brick = new component(55, 20, colors[row], col * 60 + 5, row * 25 + 30, "brick"); myBricks.push(brick); } } myGameArea.start(); } function updateGameArea() { myGameArea.clear(); myPaddle.speedX = 0; if (myGameArea.key && myGameArea.key == 37) {myPaddle.speedX = -4; } if (myGameArea.key && myGameArea.key == 39) {myPaddle.speedX = 4; } myPaddle.newPos(); myBall.newPos(); myBall.checkBounds(); if (myBall.crashWith(myPaddle)) { var ballCenterX = myBall.x + myBall.radius; var paddleCenterX = myPaddle.x + myPaddle.width / 2; var hitPos = (ballCenterX - paddleCenterX) / (myPaddle.width / 2); myBall.speedX = hitPos * 4; myBall.speedY = -Math.abs(myBall.speedY); } for (var i = 0; i < myBricks.length; i++) { if (myBricks[i].active && myBall.crashWith(myBricks[i])) { myBricks[i].active = false; myBall.bounceOff(myBricks[i]); break; } } myPaddle.update(); myBall.update(); for (var i = 0; i < myBricks.length; i++) { myBricks[i].update(); } } startGame(); </script>[/code]
벽돌과 충돌했을 때 충돌 방향을 계산하여 적절한 반사 각도를 만든다. 벽돌은 충돌 후 비활성화되어 사라진다.
중력이 있는 바운싱
중력과 바운싱을 함께 적용한 현실적인 물리 시뮬레이션을 구현해보자.
<canvas id="myCanvas8" width="480" height="270" style="border:1px solid #d3d3d3;">브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<button onclick="addBall()">공 추가</button>
<script>
var myBalls = [];
var myGameArea = {
canvas: document.getElementById("myCanvas8"),
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(radius, color, x, y) {
this.radius = radius;
this.speedX = (Math.random() - 0.5) * 8;
this.speedY = Math.random() * -5;
this.x = x;
this.y = y;
this.gravity = 0.2;
this.gravitySpeed = 0;
this.bounce = 0.7;
this.friction = 0.98;
this.update = function() {
ctx = myGameArea.context;
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
ctx.fill();
ctx.strokeStyle = "black";
ctx.lineWidth = 1;
ctx.stroke();
}
this.newPos = function() {
this.gravitySpeed += this.gravity;
this.speedX *= this.friction;
this.gravitySpeed *= this.friction;
this.x += this.speedX;
this.y += this.speedY + this.gravitySpeed;
this.checkBounds();
}
this.checkBounds = function() {
if (this.x - this.radius <= 0) { this.x = this.radius; this.speedX = -this.speedX * this.bounce; } if (this.x + this.radius >= myGameArea.canvas.width) {
this.x = myGameArea.canvas.width - this.radius;
this.speedX = -this.speedX * this.bounce;
}
if (this.y - this.radius <= 0) { this.y = this.radius; this.speedY = -this.speedY * this.bounce; this.gravitySpeed = -this.gravitySpeed * this.bounce; } if (this.y + this.radius >= myGameArea.canvas.height) {
this.y = myGameArea.canvas.height - this.radius;
if (Math.abs(this.gravitySpeed) > 0.5) {
this.gravitySpeed = -this.gravitySpeed * this.bounce;
} else {
this.gravitySpeed = 0;
}
this.speedY = -this.speedY * this.bounce;
}
}
}
function getRandomColor() {
var colors = ["red", "blue", "green", "purple", "orange", "pink", "yellow", "cyan"];
return colors[Math.floor(Math.random() * colors.length)];
}
function addBall() {
var radius = Math.random() * 15 + 10;
var x = Math.random() * (myGameArea.canvas.width - 2 * radius) + radius;
var y = radius;
var color = getRandomColor();
myBalls.push(new component(radius, color, x, y));
}
function startGame() {
addBall();
addBall();
addBall();
myGameArea.start();
}
function updateGameArea() {
myGameArea.clear();
for (var i = 0; i < myBalls.length; i++) { myBalls[i].newPos(); myBalls[i].update(); } } startGame(); </script>[/code]
중력, 마찰력, 탄성 계수를 모두 적용하여 현실적인 바운싱 볼 시뮬레이션을 만들었다. 공들이 점차 에너지를 잃으며 바닥에서 정지한다.
💡 게임 바운싱 시스템 설계 팁:
• 탄성 계수는 0과 1 사이의 값을 사용한다. 1에 가까울수록 완전 탄성, 0에 가까울수록 비탄성 충돌이다
• 충돌 감지 시 오브젝트가 경계를 벗어나지 않도록 위치를 조정하는 것이 중요하다
• 복잡한 충돌 계산은 성능에 영향을 줄 수 있으므로 필요에 따라 최적화해야 한다
• 마찰력과 중력을 함께 사용하면 더 현실적인 물리 효과를 얻을 수 있다
HTML5 Canvas 게임에서 바운싱 효과는 게임에 역동성과 재미를 더하는 중요한 물리 시뮬레이션이다. 기본적인 벽면 반사부터 복잡한 오브젝트 간 충돌까지 다양한 바운싱 기법을 통해 매력적인 게임을 만들 수 있다.