라면 끓는 동안 리액트로 도형 그리기

조성훈·2023년 11월 20일
2

그냥 심심할 때 작업하는 간단한 장난감이 있는데요
리액트로 만든 에임연습게임입니다
원래 자바스크립트 처음 배우면서 연습삼아 해봤었는데
꽤나 재미있어서 리액트로 다시 작성하고 이거저거 만지고 있습니다
표적이랍시고 띄워놓는 게 그냥 다각형으로 그린 애들인데.. 이렇게 생겼습니다

아무튼 이번시간에는 아래와같은 좀.. 색깔때문에 촛불같아보이는데요 아무튼 저런 모양의 다각형을 그리는 법을 알아보겠습니다?

사전 준비

그림을 그리려면 항상 캔버스가 있어야합니다
다음과 같이 캔버스를 가져와줍시다

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)
이렇습니다.

현재 위치 x0,y0x_0, y_0x1,y1x_1, y_1를 잇는 직선
x1,y1x_1, y_1x2,y2x_2, y_2를 잇는 직선을 긋고
교차하는 곳에 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를 써줘야 실제로 커진 만큼 히트스캔이 되겠죠?

라면 불었겠는데요? 이만 마칩니다

3개의 댓글

comment-user-thumbnail
2023년 11월 21일

정식출시해주세요

2개의 답글