ThorVG로 간단한 슈팅 게임 만들기
ThorVG와 SDL을 사용해 만든 간단한 슈팅 게임입니다. 화면에 무작위로 나타나는 표적을 마우스로 클릭하면 표적이 사라지고 점수가 증가합니다.
표적과 조준점은 별도의 이미지 파일 없이 ThorVG의 네이티브 그래픽 API만으로 그렸습니다.
브랜치: https://github.com/autlyr21/thorvg.example/tree/feat/custom-example
- 최대 3개의 표적 표시
- 표적이 약 1초 후 자동으로 사라짐
- 350ms 간격으로 새로운 표적 생성
- 마우스를 따라 움직이는 조준점
- 표적 클릭 시 점수 증가
- 표적 객체를 재사용해 불필요한 객체 생성을 방지
표적 상태 관리
각 표적은 다음과 같은 상태를 가집니다.
struct Target {
tvg::Scene *paint = nullptr;
float x = 0.0f;
float y = 0.0f;
uint32_t spawnedAt = 0;
bool active = false;
};
tvg::Scene은 머리와 몸처럼 여러 도형을 하나의 객체로 묶는 역할을 합니다.
paint를 이동하거나 숨기면 Scene에 포함된 모든 도형에 동일하게 적용됩니다.
target.paint->translate(x, y);
target.paint->visible(false);
게임에서는 고정된 크기의 배열을 사용해 3개의 표적을 미리 생성합니다.
array<Target, 3> targets;
새로운 표적을 계속 생성하고 삭제하는 대신, 미리 만들어 놓은 표적의 위치와 표시 상태만 변경합니다.
ThorVG API로 표적 그리기
표적의 머리는 두 개의 원으로 만듭니다.
auto head = tvg::Shape::gen();
head->appendCircle(0.0f, -20.0f, 28.0f, 28.0f);
head->appendCircle(0.0f, -20.0f, 20.0f, 20.0f, false);
head->fillRule(tvg::FillRule::EvenOdd);
head->fill(255, 93, 115);
EvenOdd 채우기 규칙을 사용하면 안쪽 원이 비어 있는 고리 형태를 만들 수 있습니다.
몸 부분은 베지어 곡선으로 구성합니다.
auto shoulders = tvg::Shape::gen();
shoulders->moveTo(...);
shoulders->cubicTo(...);
shoulders->lineTo(...);
shoulders->close();
shoulders->fill(255, 93, 115);
완성된 머리와 몸을 하나의 Scene에 추가합니다.
auto target = tvg::Scene::gen();
target->add(head);
target->add(shoulders);
따라서 SVG나 PNG 같은 외부 이미지 파일이 필요하지 않습니다.
무작위 위치 선택
표적 위치는 std::mt19937 난수 생성기를 이용해 결정합니다.
mt19937 random{random_device{}()};
표적이 화면 밖이나 HUD 영역에 나타나지 않도록 허용 범위를 계산합니다.
uniform_real_distribution<float> xPosition(minX, maxX);
uniform_real_distribution<float> yPosition(minY, maxY);
float x = xPosition(random);
float y = yPosition(random);
다른 표적과 겹치는 경우 최대 16번까지 새로운 위치를 선택합니다.
표적 생성과 제거
spawn()은 비활성 상태의 표적을 찾아 새로운 위치에 표시합니다.
place(target);
target.spawnedAt = elapsed;
target.active = true;
target.paint->visible(true);
update()에서는 표적이 표시된 지 1초가 지났는지 검사합니다.
if (elapsed - target.spawnedAt >= TARGET_LIFETIME_MS) {
target.active = false;
target.paint->visible(false);
}
표적은 실제로 삭제되지 않고 숨겨집니다. 이후 다시 위치를 변경해 재사용됩니다.
클릭 판정과 점수
마우스를 클릭하면 클릭 좌표가 표적의 96×96 영역 안에 있는지 확인합니다.
if (x >= target.x - TARGET_HALF_SIZE &&
x <= target.x + TARGET_HALF_SIZE &&
y >= target.y - TARGET_HALF_SIZE &&
y <= target.y + TARGET_HALF_SIZE) {
target.active = false;
target.paint->visible(false);
++score;
updateHud();
}
표적에 명중하면 해당 표적을 숨기고 점수를 1 증가시킵니다.
점수 문자열은 snprintf()로 갱신합니다.
char text[32];
snprintf(text, sizeof(text), "SCORE: %d", score);
scoreText->text(text);
조준점 구현
조준점 역시 ThorVG 도형으로 구성합니다.
- 네 방향의 선
- 가운데 원
- 중앙 점
각 도형을 하나의 tvg::Scene에 추가한 뒤 마우스 위치로 이동합니다.
bool motion(tvg::Canvas *canvas, int32_t x, int32_t y) override {
crosshair->translate(float(x), float(y));
tvgexam::verify(canvas->update());
return true;
}
SDL의 기본 마우스 커서는 숨깁니다.
SDL_ShowCursor(SDL_DISABLE);
프로그램이 종료될 때는 다시 표시합니다.
전체 실행 흐름
main()
→ content()에서 화면 구성
→ 표적 3개 생성
→ HUD와 조준점 생성
→ SDL 이벤트 루프 실행
→ motion(): 조준점 이동
→ clickdown(): 명중 판정
→ update(): 표적 생성 및 만료 처리
→ ThorVG Canvas 다시 그리기
빌드 및 실행
프로젝트 루트에서 다음 명령어로 빌드합니다.
meson setup builddir
meson compile -C builddir HitTestGame
소프트웨어 렌더러로 실행하려면 다음 명령을 사용합니다.
./builddir/src/HitTestGame
OpenGL과 WebGPU도 선택할 수 있습니다.
./builddir/src/HitTestGame gl
./builddir/src/HitTestGame wg
댓글
Discussion 원문