카테고리 없음

[Frontend Game] 리액션 기반 게임 (like 두더지 잡기) - "Catch the Wombat!

MoveForward 2025. 5. 26. 14:13

1. 프로젝트 소개  

  • 프로젝트 설명 : 귀여운 동물인 "웜뱃"을 컨셉으로 한 "리액션 게임" 입니다. (두더지 잡기)
  • 프로젝트 모티브 : 가정의 달을 맞아 토스뱅크에서 진행한 이벤트 게임 "카네이션 잡기"를 플레이 해보고 모티브를 받았다.
  • 사용 기술 : HTML, CSS, JavaScript (프론트엔드만을 사용)
  • 사용 에셋 출처 : Chat GPT 이미지 생성 이미지 활용
  • 개발 이유 : 
    • 1. 프론트엔드만을 사용한 이유 : 깃허브 정적 디렉토리 배포 기능을 사용해서 배포하기 위함입니다.
    • 2. 프로젝트 키워드인 "웜뱃", "두더지 잡기류 게임" 선정 이유 : 
      • 1. 최근 관심을 갖게된 "웜뱃"이란 동물이 귀엽고 확실한 캐릭터성을 갖고있기 때문입니다.
      • 2. 단순하며, 중독적인 게임이 "두더지 잡기류 게임"이기 때문입니다.
      • 3. "두더지 잡기류 게임"은 로직 구현과 UI 상호작용이 명확하게 드러나서, 프론트엔드 공부에 좋은 주제이기 때문입니다.

2. 게임 규칙 설명

플레이 규칙

  • 3 x 5 크기의 게임 필드에서 웜뱃이 무작위 위치0.1초 마다 등장합니다.
  • 웜은 두 가지 형태(앉아 있는 웜뱃 / 공격하는 웜뱃)로 나타납니다.
  • 플레이어는 등장한 웜뱃을 보고 마우스로 클릭하여 반응합니다.
  • 제한 시간 동안 최대한 많은 웜뱃을 클릭해 점수를 획득합니다.

점수 산정 방식

  • 앉아 있는 웜뱃 클릭 시: +1점

  • 공격하는 웜뱃 클릭 시: -1점

 

제한 시간

  • 전체 게임 시간은 20초입니다.
  • 시간이 종료되면 점수 집계 후 결과가 표시됩니다.

■ 효과 (애니메이션)

  • 클릭 시 웜뱃이 사라지고 +1 / -1 텍스트 애니메이션이 표시되어 시각적인 피드백을 줍니다.

 

3. 기능 구성 설명

■ Intro / Game / Result Section 분리하여 구성

<!-- Intro Page -->
<div class="game-screen">
...
</div>

<!-- Game Page -->
<section id="game" style="display:none">
...
</div>

<!-- Result Page -->
<section id="result" style="display:none">
...
</div>
/**id 기반 section 변환 메서드*/
function showSection(id) {
  document.querySelectorAll("section").forEach(sec => sec.style.display = "none");
  document.getElementById(id).style.display = "block";
}

function startIntro() {
  showSection("intro");
  ...
}

function startGame() {
  showSection("game");
  ...
}

function endGame() {
  showSection("result");
  ...
}

"wombat-game.html" 은 세개의 섹션으로 구분되어 있다.

"wombat-game.js"는 게임의 진행 상황에 따라 한 가지 섹션만을 보여준다.

단건의 HTML 파일로 3개의 페이지 효과를 구현하길 원해 이와 같은 구성방식을 채택하였다.

 

■ 3x5 게임필드 구성

웜이 등장하는 게임 필드를 3x5 사이즈로 구성하였다.

<!-- game area -->
<div id="wombat-area">
  <div class="cell"></div>
  <div class="cell"></div>
  <div class="cell"></div>

  <div class="cell"></div>
  <div class="cell"></div>
  <div class="cell"></div>

  <div class="cell"></div>
  <div class="cell"></div>
  <div class="cell"></div>

  <div class="cell"></div>
  <div class="cell"></div>
  <div class="cell"></div>

  <div class="cell"></div>
  <div class="cell"></div>
  <div class="cell"></div>
</div>

 

 

■ 웜뱃 등장

등장 위치 : 3x5 게임 필드 무작위 위치

등장 간격 : 0.1s 마다 등장

function startGame() {
  score = 0;
  document.getElementById("score").textContent = score;
  showSection("game");

  startGauge();

  // 웜뱃 생성 반복 시작
  spawnInterval = setInterval(spawnWombats, 100); // 0.1초마다 시도

  // 20초 후 게임 종료 및 웜뱃 생성 중단
  setTimeout(() => {
    clearInterval(spawnInterval);
    endGame();
  }, limitTime);
}

 

■ 클릭 감지

//웜뱃 생성 메서드
function spawnWombats() {
  //cell 15개 가져오기
  const wombatArea = document.getElementById("wombat-area");
  const cells = wombatArea.querySelectorAll(".cell");

  // 랜덤 셀 선택
  const randomIndex = Math.floor(Math.random() * cells.length);
  const targetCell = cells[randomIndex];

  // 이미 웜뱃이 있다면 생성하지 않음
  if (targetCell.querySelector("img")) return;

  // 이미지 요소 생성
  const wombatImg = document.createElement("img");
  // 랜덤으로 이미지 선택 및 data-type 지정
  const isGood = Math.random() < 0.5;
  wombatImg.src = isGood ? imgSrc2 : imgSrc1;
  wombatImg.dataset.type = isGood ? "good" : "bad";
  wombatImg.classList.add("wombat-img");

  // 클릭 이벤트
  wombatImg.addEventListener("click", () => {
    if (wombatImg.dataset.clicked === "true") return; // 이미 클릭된 이미지면 무시
    wombatImg.dataset.clicked = "true"; // 클릭 처리 마크

    //1. score 반영
    if (wombatImg.dataset.type === "good") {
      score += 1;
    } else {
      score -= 1;
      if (score < 0) score = 0;
    }
    document.getElementById("score").textContent = score;

    //2. score 애니메이션
    const floatText = document.createElement("div");
    floatText.className = "score-float " + (wombatImg.dataset.type === "good" ? "plus" : "minus");
    floatText.textContent = wombatImg.dataset.type === "good" ? "+1" : "-1";

    targetCell.appendChild(floatText);

    // 제거 타이머
    setTimeout(() => floatText.remove(), 800);


    //3. wombat 삭제 애니메이션
    wombatImg.classList.add("wombat-exit");
    setTimeout(() => wombatImg.remove(), 300);
  });

  // 셀에 이미지 추가
  targetCell.appendChild(wombatImg);

  // 1초 후 이미지 제거
  setTimeout(() => {
    if (wombatImg.parentElement) {
      wombatImg.classList.add("wombat-exit");
      setTimeout(() => wombatImg.remove(), 300); // exit 애니메이션 끝나고 제거
    }
  }, 1000);
}

<img> 태그 -  data-clicked : 이미지가 클릭되었는지 처리

<img> 태그 -  data-type : 앉아있는 이미지 / 공격하는 이미지 구분

 이를 통해, 웜뱃 클릭을 통한 score를 계산하고, HTML에 업데이트 한다.

score는 0보다 작을 수 없다.

 

■ 제한 시간 설정

const limitTime = 20000; // limit time = 20seconds

function startGame() {
  score = 0;
  document.getElementById("score").textContent = score;
  showSection("game");

  startGauge();

  // 웜뱃 생성 반복 시작
  spawnInterval = setInterval(spawnWombats, 100); // 0.1초마다 시도

  // 20초 후 게임 종료 및 웜뱃 생성 중단
  setTimeout(() => {
    clearInterval(spawnInterval);
    endGame();
  }, limitTime);
}

제한 시간 20초 동안 웜뱃 생성 메서드를 반복한 후 endGame() 메서드를 실행하여, Result Section으로 변환합니다.

 

■ 애니메이션 처리

//2. score 애니메이션
const floatText = document.createElement("div");
floatText.className = "score-float " + (wombatImg.dataset.type === "good" ? "plus" : "minus");
floatText.textContent = wombatImg.dataset.type === "good" ? "+1" : "-1";

targetCell.appendChild(floatText);

// 제거 타이머
setTimeout(() => floatText.remove(), 800);
/*점수 애니메이션*/
.score-float {
  position: absolute;
  font-size: 80px;
  font-weight: bold;
  animation: floatUp 0.8s ease-out forwards;
  pointer-events: none;
  user-select: none;
  left: 30px;
}

.score-float.plus {
  color: blue;
}

.score-float.minus {
  color: red;
}

@keyframes floatUp {
  0% {
    opacity: 1;
    transform: translateY(0);
  }
  100% {
    opacity: 0;
    transform: translateY(-40px);
  }
}

클릭한 웜뱃의 종류에 따라서, +1 / -1 텍스트가 등장 애니메이션과 함께 등장하며, 0.8초 후 사라집니다.

 

4. 문제 해결 과정

1. 웜뱃 등장 간격 구현하기

카네이션 게임의 원본 코드를 알고 있지 않기 때문에, 등장 간격을 계속 변경해가며 가장 비슷한 것을 찾았다.

 

2. "시간 종료!" & "내 점수" 애니메이션 구현하기

function showTimeUpMessage(callback) {
  const message = document.getElementById("time-up-message");

  message.style.display = "inline-block";
  message.classList.add("appear-anim");

  // start wave animation
  startWaveAnimation();

  // 2초 동안 보여주고
  setTimeout(() => {
    message.style.display = "none";
    message.classList.remove("appear-anim");
    if (typeof callback === "function") {
      callback();
    }
  }, 2500);
}

function startWaveAnimation() {
  const spans = document.querySelectorAll("#time-up-message span");

  spans.forEach((span, index) => {
    span.classList.add("wave", `delay-${index + 1}`);
  });
}

등장 애니메이션을 적용하는 메서드 , 텍스트에 웨이브를 주는 메서드를 각각 구현하여 적용하였다.

 

5. 결과물  

 

front-games/wombat-game at main · yashin20/front-games

프런트엔드 게임 모음. Contribute to yashin20/front-games development by creating an account on GitHub.

github.com

 

Wombat Game

20초 동안 웜뱃을 잡아보세요 3

yashin20.github.io

 

6. 배운점 & 업데이트 계획

배운 점

  • data-* 속성을 활용해 게임 캐릭터(웜벳)의 상태 정보를 HTML 요소에 효과적으로 저장하고 제어할 수 있음
  • setInterval()과 setTimeout()을 조합하여 웜벳 등장 타이밍을 제어하는 방법
  • 클릭 이벤트 처리와 애니메이션 효과를 함께 적용해 사용자 반응에 따른 시각적 피드백을 구현하는 경험
  • 제한 시간 설정 및 점수 계산 로직을 통해 간단한 게임 흐름을 설계하는 전반적인 과정

업데이트 계획

  • 윈도우 로컬 스토리지를 이용하여 브라우저에 최고 점수를 저장하는 기능 추가 예정