그냥 심심할 때 작업하는 간단한 장난감이 있는데요
리액트로 만든 에임연습게임입니다
원래 자바스크립트 처음 배우면서 연습삼아 해봤었는데
꽤나 재미있어서 리액트로 다시 작성하고 이거저거 만지고 있습니다
표적이랍시고 띄워놓는 게 그냥 다각형으로 그린 애들인데.. 이렇게 생겼습니다
아무튼 이번시간에는 아래와같은 좀.. 색깔때문에 촛불같아보이는데요 아무튼 저런 모양의 다각형을 그리는 법을 알아보겠습니다?
그림을 그리려면 항상 캔버스가 있어야합니다
다음과 같이 캔버스를 가져와줍시다
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
그리고 캔버스를 슥삭 지웁니다
ctx.clearRect(0, 0, canvas.width, canvas.height);
어려운 몸통부터 그려봅시다. 저같은 경우는 따로 함수를 빼놨는데..
function drawBody(ctx, x, y) {
const roundness = 5;
ctx.beginPath();
ctx.moveTo(x, y - (bodyY) / 2);
ctx.arcTo(
x + (bodyX) / 2,
y - (bodyY) / 2,
x + (bodyX) / 2,
y + (bodyY) / 2,
roundness
);
ctx.lineTo(x + (bodyX) / 2, y + (bodyY * rate) / 2);
ctx.lineTo(x - (bodyX) / 2, y + (bodyY * rate) / 2);
ctx.arcTo(
x - (bodyX) / 2,
y - (bodyY) / 2,
x,
y - (bodyY) / 2,
roundness
);
ctx.closePath();
}
인자를 먼저 보면, 아까 얻어온 캔버스 ctx
, 위치좌표 x,y
를 받아옵니다
이 x,y
좌표는 사실상 타겟 몸통의 명치입니다 직사각형의 중간이라고 보시면 되겠습니다.
그리고 bodyX, bodyY
가 나오는데, 몸통 직사각형의 높이와 너비입니다. bodyWidth, bodyHeight
로 할 걸 그랬네요~
몸통의 목표는 직사각형인데 오른쪽과 왼쪽 위가 조금 둥글게 나와서 어깨like한 무언가를 만드는 것입니다
이 때 arcTo
메소드를 활용하는데
arcTo(x1, y1, x2, y2, radius)
이렇습니다.
현재 위치 와 를 잇는 직선
와 를 잇는 직선을 긋고
교차하는 곳에 radius
만큼의 반지름을 갖는 내접원을 그려줍니다.
이제 준비는 끝났습니다
beginPath()
로 경로를 시작합니다. 나 그림그릴거야~하는거랑 비슷moveTo(x, y - bodyY/2)
: 원래 직사각형의 정중앙에 있다가, 직사각형의 꼭대기 중간으로 위치합니다 (x0, y0)arcTo()
로 오른쪽 위 모서리를 둥글게 하고, lineTo()
로 오른쪽 바닥까지 선을 긋습니다lineTo()
로 다시 왼쪽바닥으로 선을 긋고arcTo
로 왼쪽어깨를 그려주고closePath()
그림 다 그리면 끗그럼 와우~ 몸통 그렸습니다
아 이걸 이제 본함수에서 부를 때는
drawBody(ctx, target.x, target.y);
ctx.fillStyle = "white";
ctx.fill();
ctx.stroke();
drawBody
호출하고, white로 fill
하고, stroke
까지
쉽습니다
ctx.beginPath(); //그리기 시작
ctx.arc(
target.x,
target.y - (bodyY) / 2 - headRadius,
headRadius,
0,
2 * Math.PI
);
ctx.fillStyle = "red";
ctx.fill(); //빨간색 채우고
ctx.stroke(); //그리기 끝
똑같이 x,y
는 명치 위치입니다
그러면 머리 반지름을 headRadius
라고 할 때
머리의 중심은 x, y - bodyY/2 - headRadius
겠죠?
해당 좌표에 대해 arc
메소드를 2pi로 한바꾸 둘러주면 원이 뙹 나옵니다
아까 구한 머리 중심좌표를 기준으로, 적당히 선을 그어줍니다
function drawEyes(ctx, x, y, rate) {
ctx.beginPath();
ctx.moveTo(x - 2, y - (bodyY) / 2 - headRadius);
ctx.lineTo(x - 5, y - (bodyY) / 2 - headRadius - 3);
ctx.moveTo(x + 2, y - (bodyY) / 2 - headRadius);
ctx.lineTo(x + 5, y - (bodyY) / 2 - headRadius - 3);
ctx.strokeStyle = "black";
ctx.stroke();
}
진짜 그냥 적당히입니다
에임연습이라면 자고로 헤어크로스가 있어야겠죠?
헤어크로스란..
출처 나무위키
네 조준점입니다
그냥 십자가로 할건데
마우스 현재 좌표를 기준으로 또 적당히 선을 그어줍니다
function drawCross(ctx, x, y) {
ctx.beginPath();
ctx.moveTo(x + 3, y);
ctx.lineTo(x + 10, y);
ctx.moveTo(x - 3, y);
ctx.lineTo(x - 10, y);
ctx.moveTo(x, y + 3);
ctx.lineTo(x, y + 10);
ctx.moveTo(x, y - 3);
ctx.lineTo(x, y - 10);
ctx.strokeStyle = "black";
ctx.stroke();
}
뭐 이런식..
그러면 짜잔
이런 결과를 얻을 수 있습니다
보면 타겟 둘이 크기가 좀 미묘하게 다른데
실제 게임에서는 표적이 다가오는 것 마냥 조금씩 커지도록 해놨습니다.
이건 그냥 rate
변수를 도입해서 매 순간 조금씩 증가하게 하고, headRadius, bodyX
같은 높이, 너비, 반지름등에 곱해주면 됩니다
이거 히트스캔이라고 하나요? 아무튼
function isInTarget(x, y, targetPositionX, targetPositionY, rate) {
const headDist = Math.sqrt(
(targetPositionX - x) ** 2 +
(targetPositionY - headRadius - (bodyY) / 2 - y) ** 2
);
if (headDist <= headRadius) {
return 2;
} else if (
Math.abs(targetPositionX - x) <= (bodyX) / 2 &&
Math.abs(targetPositionY - y) <= (bodyY) / 2
) {
return 1;
} else {
return 0;
}
}
대충 이렇습니다?
그리고 이거 할 때는 x,y
처럼 모든 좌표들이 해당 컨텍스트 객체 내의 상대좌표라서, onClick
에서 마우스클릭좌표를 가져올 때는
nativeEvent.offsetX, nativeEvent.offsetY
를 기준으로 하도록 합니다
아 그리고 rate
를 타겟에 적용했다면 여기에도 rate
를 써줘야 실제로 커진 만큼 히트스캔이 되겠죠?
라면 불었겠는데요? 이만 마칩니다
정식출시해주세요