이번 네 번째 미니 프로젝트 스터디의 주제는 리액트로 미니 게임 만들기!
뭔가 만들어보고 싶은 것은 많은데, 아무래도 게임은 처음이라 웹상에 다양한 튜토리얼이 있는 것을 골라 만들어 보기로 했다. 그러다 발견한 것이 행맨 게임. 어릴 적 영어 학원에서 많이 했던 단어 맞히기 게임이다.
아직 리액트 개발이 익숙하지 않아 컴포넌트의 구조나 관리할 상태들에 대해 미리 생각을 좀 해보고 코드를 짜기 시작했다. 먼저, 컴포넌트 구조는 App 컴포넌트 안에 게임판이 될 Hangman 컴포넌트와 자판이 될 Alphabet 컴포넌트가 자식으로 들어가 있는 간단한 구조이다. 그리고 App 컴포넌트에 아래 세 가지 상태를 만들었다.
const [answer, setAnswer] = useState(""); // 맞혀야 할 단어
const [guessed, setGuessed] = useState(new Set()); // 유저가 추측한 알파벳 집합
const [remainingGuesses, setRemainingGuesses] = useState(6); // 남은 기회
Set 메서드는 처음 사용해 보는 것 같은데, 중복된 값을 제거하고 싶을 때 많이 사용한다고 한다.
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Set
Set - JavaScript | MDN
Set 객체는 자료형에 관계 없이 원시 값과 객체 참조 모두 유일한 값을 저장할 수 있습니다.
developer.mozilla.org
영어 단어 데이터는 무료 API를 통해 가져오려고 했지만 대부분 API가 제공하는 단어들이 인명이나 지명도 포함되어 있어 게임용으로 쓰기엔 적절하지 않은 것 같아 직접 데이터 파일을 넣어 주었다. 부산시 교육청 자료를 보고 중학 영단어 200개를 뽑아 배열 형태로 만들었는데, 이 과정에서 chatGPT를 이용했고, 적당히 자료를 뽑아 가공하는 데 꽤 쓸만해서 시간을 단축할 수 있었다.
import Words from "./wordsData";
const restartGame = () => {
const randomIndex = Math.floor(Math.random() * Words.length);
const randomWord = Words[randomIndex];
setAnswer(randomWord.toUpperCase()); //랜덤으로 단어 뽑기
console.log(randomWord);
setGuessed(new Set());
setRemainingGuesses(6);
}; //초기화 함수
<button className="startBtn" onClick={restartGame}>Start Game</button>
그리고 게임을 (재)시작하는 버튼을 위와 같이 만들었다. Math.random() 메서드를 이용해서 배열의 단어들을 랜덤으로 뽑고, 게임 화면에 대문자가 나올 수 있도록 소문자인 배열 자료를 toUpperCase() 해주었다.
아래는 Hangman 컴포넌트 파일의 코드이다.
import img1 from "./img/1.jpg";
import img2 from "./img/2.jpg";
import img3 from "./img/3.jpg";
import img4 from "./img/4.jpg";
import img5 from "./img/5.jpg";
import img6 from "./img/6.jpg";
import loseImg from "./img/lose.jpg";
import winImage from "./img/win.jpg";
const Hangman = (props) => {
const answerArray = props.answer.split("");
const letters = answerArray.map((letter, index) => (
<span className="letter" key={index}>
{props.guessed.has(letter) ? letter : " _ "}
</span>
));
let image;
const isWin = !letters.some((letter) => letter.props.children === " _ ");
if (isWin && letters.length !== 0) {
image = winImage;
} else if (props.remainingGuesses === 0) {
image = loseImg;
} else if (props.remainingGuesses === 1) {
image = img1;
} else if (props.remainingGuesses === 2) {
image = img2;
} else if (props.remainingGuesses === 3) {
image = img3;
} else if (props.remainingGuesses === 4) {
image = img4;
} else if (props.remainingGuesses === 5) {
image = img5;
} else {
image = img6;
}
return (
<>
<div className="Hangman">
<img
src={image}
alt={`Hangman: ${props.remainingGuesses} guesses left`}
/>
</div>
<div className="Word">{letters}</div>
</>
);
};
export default Hangman;
위 코드에서 answerArray를 map으로 돌릴 때, 처음엔 키를 {letter}로 했었는데, 재시작 시 기존 상태들이 제대로 초기화되지 않는 문제가 발생했다. 에러 코드를 읽어보니 고유한 값을 키로 설정해 달라고 해서 index 인자를 이용해 키를 작성해 주었고, 문제가 해결되었다.
그리고 각 조건문을 통해 나타나는 이미지는 아래와 같다.
다음으로 Alphabet 컴포넌트.
const Alphabet = (props) => {
const handleClick = (event) => {
const guessedLetter = event.target.value;
props.updateGuessedLetters(guessedLetter);
}
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const alphabetButtons = alphabet.split('').map((letter) => (
<button className='alphabetBtn' key={letter} value={letter} onClick={handleClick}>{letter}</button>
))
return (
<div className="alphabet">
{alphabetButtons}
</div>
)
}
export default Alphabet;
const updateGuessedLetters = (guessedLetter) => {
const updatedGuessed = new Set(guessed);
updatedGuessed.add(guessedLetter);
setGuessed(updatedGuessed);
if (!answer.includes(guessedLetter)) {
setRemainingGuesses(remainingGuesses - 1);
}
};
updateGuessedLetters 함수는 부모 컴포넌트인 App.js 에서 props로 가져왔는데, 지금 보니 굳이 그러지 않고 Alphabet.js파일에 직접 함수를 작성했어도 됐을 것 같다.
페이지 배포는 그동안 해왔던 것처럼 깃허브 페이지를 이용했다. 그런데 리액트 프로젝트를 빌드한 후 페이지를 만들고 삼십분이 지나도 빈 화면만 보였다. 구글링을 통해 경로 문제라는 것을 알 수 있었고 (빌드 폴더 안 index.html 파일에 적힌 경로들이 "./static/js/main" 이런 식이어야 하는데 "/static/js/main" 이렇게 .이 없으면 경로를 찾을 수 없게 된다.), package.json 파일에 "homepage": "." 를 추가해서 경로를 수정해 주었다.
그런데 이렇게 해도 다른 건 다 잘 불려오는데, 이미지 파일이 불려오질 않았다. 역시 경로 문제인 것 같아 수정을 해보려고 하다가 배포된 페이지 주소 뒤에 직접 경로를 덧붙이는 방식으로 문제를 해결했다.
(https://(깃허브 아이디).github.io/(레포지토리 이름)/index.html 이런 식으로 index.html을 붙여줌.)
→ 다음 날 다시 확인해 보니 문제가 없었다. 깃허브 페이지 적용에 시간이 더 필요했었던 것 같다.
행맨 게임 보러가기 >> https://dd-stack.github.io/mini-game-hangman/
● 아쉬운 점
게임이다 보니 사운드도 좀 들어가고 난이도 조절을 할 수 있게 하거나 유저의 게임 기록을 저장해두는 등의 부가 기능이 있으면 더 좋을 것 같은데, 기본 기능 구현에도 어려움이 많아 이러한 추가 기능 구현을 시도해 보지 못한 게 좀 아쉬웠다.
● 느낀 점
확실히 직접 리액트로 뭘 만들어 보니 그냥 이론만 공부하는 거랑 다른 것 같다. 이번 미니 프로젝트는 하면서 계속 막혀서 튜토리얼도 이것저것 보고 구글링도 많이 하며 다양한 자료를 찾아봤다. 부족한 부분을 그때그때 공부해서 만들어 가는 느낌이었다. 결과적으로 많은 공부가 되었고, 어느 정도 의도한 대로 게임이 완성되어 꽤 기분이 좋았다. 앞으로도 부지런히 공부해서 내가 만들고 싶은 창작 게임도 만들어낼 수 있는 실력을 쌓아야겠다.
'[ 프로젝트 ] > 미니 프로젝트 스터디' 카테고리의 다른 글
< 날씨별 추천 옷차림 > (0) | 2023.02.23 |
---|---|
🌈 투 두 리스트가 포함된 시작 페이지 (0) | 2023.02.04 |
✨ 귀여운 계산기 ✨ (0) | 2023.01.29 |
댓글