이전까지 도형을 그리고, deltaTime을 활용해 중력과 가속도를 고려한 애니메이션을 그리는 법에 대해서 공부했다. 이번에는 키보드와 마우스 입력을 통해 화면이나 오브젝트에 변화를 주는 방법에 대해 정리할 것이다.
키보드 입력에 따라 사각형이 움직이도록 해볼 것이다.
let keys = {};
window.addEventListener('keydown', (e) => {
keys[e.code] = true;
});
window.addEventListener('keyup', (e) => {
keys[e.code] = false;
});
keys 객체를 두고 value를 boolean으로 어떤 값이 입력되었는지 알 수 있도록 한다. 여러 키 동시에 입력할 수 있기 때문에 객체로 두는 것이 구현하기 편리하다.
if (keys['ArrowRight']) {
player.x += player.speed;
}
if (keys['ArrowLeft']) {
player.x -= player.speed;
}
다음과 같이 해당 방향이 true일 때 방향에 맞춰 speed를 + 또는 -를 해준다.
canvas.addEventListener('click', (e) => {
const rect = canvas.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
player.targetX = mouseX - player.size / 2;
player.targetY = mouseY - player.size / 2;
});
const rect = canvas.getBoundingClientRect()는 브라우저 화면 기준으로 캔버스의 위치와 크기 정보를 가져오는 코드이다. offsetX, offsetY을 이용해 마우스의 위치를 가져올 수 있지만, 일부 경우 잘못된 값을 가져올 수 있다고 한다. 일반적으로는 getBoundingClientRect을 활용해 마우스의 위치를 찾는다고 한다. 즉, 현재 clientX/Y와 rect을 이용하면 마우스의 위치를 알 수 있다. 브라우저 전체 좌표에서 캔버스가 시작하는 위치를 빼줘야 정확한 캔버스 내부 좌표를 알 수 있기 때문에.
그리고 fillRect을 하면 일반적으로 x, y 위치가 왼쪽 위를 기준으로 한다. 하지만, 사용자 입장에서 클릭했을 때 사각형의 중심이 해당 위치로 오는 것이 더 익숙할 것이기 때문에 targetX/Y는 mouse에서 사각형의 크기/2를 뺀 값으로 해주어 클릭한 위치가 사각형의 중심이 되도록 한다.
function update(deltaTime) {
const dx = player.targetX - player.x;
const dy = player.targetY - player.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 1) {
const moveX = (dx / distance) * player.speed * deltaTime;
const moveY = (dy / distance) * player.speed * deltaTime;
if (Math.abs(moveX) > Math.abs(dx)) {
player.x = player.targetX;
player.y = player.targetY;
} else {
player.x += moveX;
player.y += moveY;
}
}
}
target 위치로 이동하기 위해서는 먼저 두 점 사이의 거리를 알아야 한다. 현재 player의 위치와 target 위치를 이용해서 distance를 구해준다.
길이가 1인 벡터, 즉 방향만 있고 크기는 1인 벡터를 단위 벡터라고 한다. 방향은 유지하면서 크기만 원하는 값(속도 등)으로 조절하고 싶을 때, 단위 벡터를 기반으로 계산하면 편하다.
예를 들어, 지금 사각형이 (100, 100)에 있고 목표 지점이 (400, 300)이라면,
dx = 400 - 100 = 300
dy = 300 - 100 = 200
그냥 player.x += dx 0.01, player.y += dy 0.01 으로 한다면, 속도가 방향에 따라 달라져버리는 문제가 발생한다. 어떤 방향은 빠르게, 어떤 방향은 느리게 움직이고 심지어 멀리 있는 목표로 갈 땐 빨라지고, 가까우면 느려진다. 하지만, 방향만 분리해서 단위 벡터 만들고 거기다 내가 정한 speed를 곱하면 항상 같은 속도로 이동할 수 있다. 즉, 무슨 방향이든 항상 {speed}px 거리만큼 이동하게 된다.
따라서 dx, dy를 각각 distance로 나눠주면 단위 벡터로 만들 수 있게 된다.
if (Math.abs(moveX) > Math.abs(dx)) {
player.x = player.targetX;
player.y = player.targetY;
}
player는 target 지점을 향해서 일정한 속도로 계속 움직이기 때문에 프레임 단위로 이동하다 보면 정확히 target 위치에 안 멈추고 overshoot 할 수 있기 때문에 정확한 위치로 보정해주어야 한다.