
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);
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) {
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() {
myGameArea.clear();
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();
}
startGame();
</script>
keydown과 keyup 이벤트 리스너를 사용하여 키보드 입력을 감지한다. 화살표 키(37, 38, 39, 40)를 사용하여 오브젝트를 움직인다.
다중 키 입력 처리
여러 키를 동시에 누를 수 있도록 개선해보자.
<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.keys = [];
this.interval = setInterval(updateGameArea, 20);
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) {
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() {
myGameArea.clear();
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; }
myGamePiece.newPos();
myGamePiece.update();
}
startGame();
</script>
keys 배열을 사용하여 여러 키의 상태를 동시에 추적한다. 이제 대각선 이동이 가능하다.
WASD 키 컨트롤
화살표 키 외에 WASD 키로도 조작할 수 있도록 추가해보자.
<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.keys = [];
this.interval = setInterval(updateGameArea, 20);
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) {
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;
if (this.x > myGameArea.canvas.width - this.width) this.x = myGameArea.canvas.width - this.width;
if (this.y < 0) this.y = 0;
if (this.y > myGameArea.canvas.height - this.height) this.y = myGameArea.canvas.height - this.height;
}
}
function startGame() {
myGamePiece = new component(30, 30, "red", 225, 135);
myGameArea.start();
}
function updateGameArea() {
myGameArea.clear();
myGamePiece.speedX = 0;
myGamePiece.speedY = 0;
if (myGameArea.keys && (myGameArea.keys[37] || myGameArea.keys[65])) {myGamePiece.speedX = -3; }
if (myGameArea.keys && (myGameArea.keys[39] || myGameArea.keys[68])) {myGamePiece.speedX = 3; }
if (myGameArea.keys && (myGameArea.keys[38] || myGameArea.keys[87])) {myGamePiece.speedY = -3; }
if (myGameArea.keys && (myGameArea.keys[40] || myGameArea.keys[83])) {myGamePiece.speedY = 3; }
myGamePiece.newPos();
myGamePiece.update();
}
startGame();
</script>
WASD 키(W:87, A:65, S:83, D:68)를 화살표 키와 함께 사용할 수 있도록 했다. 또한 경계 검사를 추가하여 오브젝트가 캔버스를 벗어나지 않도록 했다.
마우스 컨트롤
마우스 클릭으로 오브젝트를 이동시켜보자.
<canvas id="myCanvas4" width="480" height="270" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var myGamePiece;
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);
this.canvas.addEventListener('mousedown', function (e) {
myGameArea.x = e.offsetX;
myGameArea.y = e.offsetY;
})
this.canvas.addEventListener('mouseup', function (e) {
myGameArea.x = false;
myGameArea.y = false;
})
},
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() {
myGameArea.clear();
if (myGameArea.x && myGameArea.y) {
myGamePiece.x = myGameArea.x - myGamePiece.width / 2;
myGamePiece.y = myGameArea.y - myGamePiece.height / 2;
}
myGamePiece.update();
}
startGame();
</script>
mousedown 이벤트로 마우스 클릭 위치를 감지하고, offsetX와 offsetY를 사용하여 캔버스 내의 상대 좌표를 얻는다. 클릭한 위치로 오브젝트가 즉시 이동한다.
마우스 드래그
마우스를 드래그하여 오브젝트를 부드럽게 이동시켜보자.
<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);
this.canvas.addEventListener('mousedown', function (e) {
myGameArea.x = e.offsetX;
myGameArea.y = e.offsetY;
})
this.canvas.addEventListener('mouseup', function (e) {
myGameArea.x = false;
myGameArea.y = false;
})
this.canvas.addEventListener('mousemove', function (e) {
myGameArea.x = e.offsetX;
myGameArea.y = e.offsetY;
})
},
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() {
myGameArea.clear();
if (myGameArea.x && myGameArea.y) {
myGamePiece.x = myGameArea.x - myGamePiece.width / 2;
myGamePiece.y = myGameArea.y - myGamePiece.height / 2;
}
myGamePiece.update();
}
startGame();
</script>
mousemove 이벤트를 추가하여 마우스를 드래그할 때도 오브젝트가 따라오도록 했다.
터치 컨트롤
모바일 기기의 터치 입력을 지원해보자.
<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);
this.canvas.addEventListener('mousedown', function (e) {
myGameArea.x = e.offsetX;
myGameArea.y = e.offsetY;
})
this.canvas.addEventListener('mouseup', function (e) {
myGameArea.x = false;
myGameArea.y = false;
})
this.canvas.addEventListener('mousemove', function (e) {
myGameArea.x = e.offsetX;
myGameArea.y = e.offsetY;
})
this.canvas.addEventListener('touchstart', function (e) {
myGameArea.x = e.touches[0].clientX - e.target.offsetLeft;
myGameArea.y = e.touches[0].clientY - e.target.offsetTop;
})
this.canvas.addEventListener('touchend', function (e) {
myGameArea.x = false;
myGameArea.y = false;
})
this.canvas.addEventListener('touchmove', function (e) {
myGameArea.x = e.touches[0].clientX - e.target.offsetLeft;
myGameArea.y = e.touches[0].clientY - e.target.offsetTop;
})
},
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() {
myGameArea.clear();
if (myGameArea.x && myGameArea.y) {
myGamePiece.x = myGameArea.x - myGamePiece.width / 2;
myGamePiece.y = myGameArea.y - myGamePiece.height / 2;
}
myGamePiece.update();
}
startGame();
</script>
touchstart, touchend, touchmove 이벤트를 추가하여 터치 입력을 지원한다. clientX, clientY와 offsetLeft, offsetTop을 사용하여 정확한 터치 좌표를 계산한다.
가상 조이스틱
모바일 게임에서 자주 사용되는 가상 조이스틱을 구현해보자.
<canvas id="myCanvas7" width="480" height="270" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var myGamePiece;
var joystick = {
centerX: 60,
centerY: 210,
radius: 40,
knobX: 60,
knobY: 210,
knobRadius: 15,
active: false
};
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);
this.canvas.addEventListener('mousedown', function (e) {
var x = e.offsetX;
var y = e.offsetY;
var distance = Math.sqrt((x - joystick.centerX) * (x - joystick.centerX) + (y - joystick.centerY) * (y - joystick.centerY));
if (distance <= joystick.radius) {
joystick.active = true;
joystick.knobX = x;
joystick.knobY = y;
}
})
this.canvas.addEventListener('mouseup', function (e) {
joystick.active = false;
joystick.knobX = joystick.centerX;
joystick.knobY = joystick.centerY;
})
this.canvas.addEventListener('mousemove', function (e) {
if (joystick.active) {
var x = e.offsetX;
var y = e.offsetY;
var distance = Math.sqrt((x - joystick.centerX) * (x - joystick.centerX) + (y - joystick.centerY) * (y - joystick.centerY));
if (distance <= joystick.radius) {
joystick.knobX = x;
joystick.knobY = y;
} else {
var angle = Math.atan2(y - joystick.centerY, x - joystick.centerX);
joystick.knobX = joystick.centerX + Math.cos(angle) * joystick.radius;
joystick.knobY = joystick.centerY + Math.sin(angle) * joystick.radius;
}
}
})
},
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;
if (this.x < 0) this.x = 0;
if (this.x > myGameArea.canvas.width - this.width) this.x = myGameArea.canvas.width - this.width;
if (this.y < 0) this.y = 0;
if (this.y > myGameArea.canvas.height - this.height) this.y = myGameArea.canvas.height - this.height;
}
}
function drawJoystick() {
ctx = myGameArea.context;
ctx.strokeStyle = "black";
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(joystick.centerX, joystick.centerY, joystick.radius, 0, 2 * Math.PI);
ctx.stroke();
ctx.fillStyle = joystick.active ? "lightblue" : "lightgray";
ctx.beginPath();
ctx.arc(joystick.knobX, joystick.knobY, joystick.knobRadius, 0, 2 * Math.PI);
ctx.fill();
ctx.stroke();
}
function startGame() {
myGamePiece = new component(30, 30, "red", 225, 135);
myGameArea.start();
}
function updateGameArea() {
myGameArea.clear();
var deltaX = joystick.knobX - joystick.centerX;
var deltaY = joystick.knobY - joystick.centerY;
myGamePiece.speedX = deltaX / 10;
myGamePiece.speedY = deltaY / 10;
myGamePiece.newPos();
myGamePiece.update();
drawJoystick();
}
startGame();
</script>
가상 조이스틱은 원형 영역과 조작 노브로 구성된다. 노브의 위치에 따라 이동 방향과 속도가 결정된다.
키보드와 마우스 조합
키보드와 마우스를 함께 사용하는 게임 컨트롤을 구현해보자.
<canvas id="myCanvas8" width="480" height="270" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var myGamePiece;
var bullets = [];
var myGameArea = {
canvas: document.getElementById("myCanvas8"),
start: function() {
this.canvas.width = 480;
this.canvas.height = 270;
this.context = this.canvas.getContext("2d");
this.keys = [];
this.interval = setInterval(updateGameArea, 20);
window.addEventListener('keydown', function (e) {
myGameArea.keys[e.keyCode] = true;
})
window.addEventListener('keyup', function (e) {
myGameArea.keys[e.keyCode] = false;
})
this.canvas.addEventListener('click', function (e) {
var mouseX = e.offsetX;
var mouseY = e.offsetY;
var playerCenterX = myGamePiece.x + myGamePiece.width / 2;
var playerCenterY = myGamePiece.y + myGamePiece.height / 2;
var angle = Math.atan2(mouseY - playerCenterY, mouseX - playerCenterX);
var speed = 5;
bullets.push({
x: playerCenterX,
y: playerCenterY,
speedX: Math.cos(angle) * speed,
speedY: Math.sin(angle) * speed,
width: 5,
height: 5
});
})
},
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;
if (this.x < 0) this.x = 0;
if (this.x > myGameArea.canvas.width - this.width) this.x = myGameArea.canvas.width - this.width;
if (this.y < 0) this.y = 0;
if (this.y > myGameArea.canvas.height - this.height) this.y = myGameArea.canvas.height - this.height;
}
}
function startGame() {
myGamePiece = new component(30, 30, "red", 225, 135);
myGameArea.start();
}
function updateGameArea() {
myGameArea.clear();
myGamePiece.speedX = 0;
myGamePiece.speedY = 0;
if (myGameArea.keys && myGameArea.keys[37]) {myGamePiece.speedX = -3; }
if (myGameArea.keys && myGameArea.keys[39]) {myGamePiece.speedX = 3; }
if (myGameArea.keys && myGameArea.keys[38]) {myGamePiece.speedY = -3; }
if (myGameArea.keys && myGameArea.keys[40]) {myGamePiece.speedY = 3; }
myGamePiece.newPos();
myGamePiece.update();
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
bullet.x += bullet.speedX;
bullet.y += bullet.speedY;
if (bullet.x < 0 || bullet.x > myGameArea.canvas.width || bullet.y < 0 || bullet.y > myGameArea.canvas.height) {
bullets.splice(i, 1);
} else {
myGameArea.context.fillStyle = "yellow";
myGameArea.context.fillRect(bullet.x, bullet.y, bullet.width, bullet.height);
}
}
}
startGame();
</script>
키보드로 플레이어를 이동시키고, 마우스 클릭으로 클릭한 방향으로 총알을 발사한다. Math.atan2()를 사용하여 발사 각도를 계산한다.
💡 게임 컨트롤러 개발 팁:
• 키보드 입력은 배열을 사용하여 여러 키의 동시 입력을 처리하자
• 마우스와 터치 이벤트를 모두 지원하면 다양한 기기에서 사용할 수 있다
• 가상 조이스틱은 모바일 게임에서 직관적인 조작감을 제공한다
• 입력 처리 시 경계 검사와 유효성 검증을 잊지 말자
게임패드 API 지원
웹 게임패드 API를 사용하여 컨트롤러 입력을 처리해보자.
<canvas id="myCanvas9" width="480" height="270" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var myGamePiece;
var gamepadIndex = null;
var myGameArea = {
canvas: document.getElementById("myCanvas9"),
start: function() {
this.canvas.width = 480;
this.canvas.height = 270;
this.context = this.canvas.getContext("2d");
this.interval = setInterval(updateGameArea, 20);
window.addEventListener("gamepadconnected", function(e) {
gamepadIndex = e.gamepad.index;
console.log("게임패드 연결됨:", e.gamepad.id);
});
window.addEventListener("gamepaddisconnected", function(e) {
gamepadIndex = null;
console.log("게임패드 연결 해제됨");
});
},
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;
if (this.x < 0) this.x = 0;
if (this.x > myGameArea.canvas.width - this.width) this.x = myGameArea.canvas.width - this.width;
if (this.y < 0) this.y = 0;
if (this.y > myGameArea.canvas.height - this.height) this.y = myGameArea.canvas.height - this.height;
}
}
function handleGamepad() {
if (gamepadIndex !== null) {
var gamepad = navigator.getGamepads()[gamepadIndex];
if (gamepad) {
var leftStickX = gamepad.axes[0];
var leftStickY = gamepad.axes[1];
if (Math.abs(leftStickX) > 0.1) {
myGamePiece.speedX = leftStickX * 3;
} else {
myGamePiece.speedX = 0;
}
if (Math.abs(leftStickY) > 0.1) {
myGamePiece.speedY = leftStickY * 3;
} else {
myGamePiece.speedY = 0;
}
}
}
}
function startGame() {
myGamePiece = new component(30, 30, "red", 225, 135);
myGameArea.start();
}
function updateGameArea() {
myGameArea.clear();
handleGamepad();
myGamePiece.newPos();
myGamePiece.update();
}
startGame();
</script>
웹 게임패드 API를 사용하면 Xbox, PlayStation 등의 컨트롤러를 웹 게임에서 사용할 수 있다. axes 배열은 아날로그 스틱 값을, buttons 배열은 버튼 상태를 제공한다.
다양한 입력 방식을 지원하는 게임 컨트롤러 시스템을 구축하면 더 많은 사용자가 편리하게 게임을 즐길 수 있다. 각 입력 방식의 특성을 이해하고 적절히 조합하여 최적의 게임 경험을 제공하는 것이 중요하다.