[Frontend Game] 리액션 기반 게임 (like 두더지 잡기) - "Catch the Wombat!
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. 결과물
- GitHub 저장소 주소 : https://github.com/yashin20/front-games/tree/main/wombat-game
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()을 조합하여 웜벳 등장 타이밍을 제어하는 방법
- 클릭 이벤트 처리와 애니메이션 효과를 함께 적용해 사용자 반응에 따른 시각적 피드백을 구현하는 경험
- 제한 시간 설정 및 점수 계산 로직을 통해 간단한 게임 흐름을 설계하는 전반적인 과정
업데이트 계획
- 윈도우 로컬 스토리지를 이용하여 브라우저에 최고 점수를 저장하는 기능 추가 예정