React 수업 과제에서 개인 과제로 "피카츄 놀이터 만들기"를 받았는데 이 과제는 이벤트 핸들러를 연습하는 과제였습니다.
배경이 있고 피카츄가 그 배경에 키다운으로 위치를 이동하는 것이고 맵 크기는 10*10, 점프 기능 구현을 해야 해서 그리드로 배경이미지를 입혀서 컴포넌트 맵을 만드는 방법으로 생각했습니다.
// App.jsx
import "./App.css";
import Map from "./components/Map";
function App() {
const MAP_WIDTH = 10;
const MAP_HEIGHT = 10;
const TILE_SIZE = 50;
return (
<>
<div>
<Map mapWidth={MAP_WIDTH} mapHeight={MAP_HEIGHT} tileSize={TILE_SIZE} />
</div>
</>
);
}
export default App;
// Map.jsx
import PropTypes from "prop-types";
import "./Map.css";
function Map({ mapWidth, mapHeight, tileSize }) {
return (
<div
className="map-container"
style={{
gridTemplateColumns: `repeat(${mapHeight}, ${tileSize}px)`,
gridTemplateRows: `repeat(${mapWidth}, ${tileSize}px)`,
width: mapWidth * tileSize,
height: mapHeight * tileSize,
}}
>
{Array.from({ length: mapWidth * mapHeight }).map((_, i) => (
<div
key={i}
className="map-tile"
style={{
width: tileSize,
height: tileSize,
}}
>
{/* {renderPikachu(i)} */}
</div>
))}
</div>
);
}
Map.propTypes = {
mapWidth: PropTypes.number.isRequired,
mapHeight: PropTypes.number.isRequired,
tileSize: PropTypes.number.isRequired,
};
export default Map;
// Map.css
.map-container {
display: inline-grid;
}
.map-tile {
background-image: url("../../assets/grass.png");
background-size: cover;
position: relative;
}
function App() {
const [position, setPosition] = useState({
x: 0,
y: 0,
direction: "right",
prevDirection: "right",
});
const handleKeyDown = (event) => {
const newPosition = { ...position };
switch (event.key) {
case "ArrowLeft":
newPosition.x = Math.max(newPosition.x - 1, 0);
newPosition.direction = "left";
console.log(newPosition);
break;
case "ArrowRight":
newPosition.x = Math.min(newPosition.x + 1, MAP_WIDTH - 1);
newPosition.direction = "right";
console.log(newPosition);
break;
case "ArrowUp":
newPosition.y = Math.max(newPosition.y - 1, 0);
console.log(newPosition);
break;
case "ArrowDown":
newPosition.y = Math.min(newPosition.y + 1, MAP_HEIGHT - 1);
console.log(newPosition);
break;
case " ":
setPosition(newPosition);
setTimeout(() => {
setPosition({ ...newPosition, y: newPosition.y - 1 });
setTimeout(() => {
setPosition(newPosition);
}, 300);
}, 300);
break;
}
setPosition((prevState) => ({
...newPosition,
prevDirection: prevState.direction,
}));
};
키다운 이벤트 핸들러는 Math.max와 Math.min을 이용해서 맵 밖으로 피카츄가 나가지 못하게 로직을 작성했고 맵에 있는 타일이 배열의 요소여서 1씩 더하거나 빼줘서 이동시켜서 한 칸씩 이동하게 구현했습니다.
const renderPikachu = (index) => {
const x = index % MAP_WIDTH;
const y = Math.floor(index / MAP_WIDTH);
if (x === position.x && y == position.y) {
let flipClass = "";
if (position.direction !== position.prevDirection) {
flipClass = " flip";
}
return (
<img
src={pikachuImage}
alt="Pikachu"
className={`rotate-${position.direction}${flipClass}`}
style={{
width: "50px",
height: "50px",
position: "absolute",
top: 0,
left: 0,
}}
/>
);
}
};
const MAP_WIDTH = 10;
const MAP_HEIGHT = 10;
const TILE_SIZE = 50;
return (
<>
<div>
<Map
mapWidth={MAP_WIDTH}
mapHeight={MAP_HEIGHT}
tileSize={TILE_SIZE}
handleKeyDown={handleKeyDown}
renderPikachu={renderPikachu}
/>
</div>
</>
);
}
export default App;
%연산자로 나머지와 Math.floor를 이용해서 x축과 y축의 위치가 일치하는 곳에 이미지를 렌더링해서 화면에 표시하게 했습니다. 그리고 화면이 전환 될 때 피카츄가 뒤집혀야하는 조건을 충족시키기 위해 prevDirection를 추가해서 이전 방향을 기억해서 prevDirection가 direction랑 다르면 클래스 flip을 추가해서 css 애니메이션을 추가했습니다.
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.rotate-left {
transition: transform 0.3s ease-in-out;
transform: rotateY(180deg);
}
.rotate-right {
transition: transform 0.3s ease-in-out;
transform: rotateY(0deg);
}
.rotate-left.flip {
animation: flipToLeft 0.3s ease-in-out;
}
.rotate-right.flip {
animation: flipToRight 0.3s ease-in-out;
}
@keyframes flipToLeft {
0% {
transform: rotateY(0deg);
}
100% {
transform: rotateY(180deg);
}
}
@keyframes flipToRight {
0% {
transform: rotateY(180deg);
}
100% {
transform: rotateY(0deg);
}
}
구현하면서 느낀 점
이벤트 핸들러로 피카츄를 이동시키는 것과 맵을 만드는 것은 금방 구현 했지만 피카츄를 뒤집는데 시간이 많이 걸린 것 같습니다. 같은 방향으로 이동할 때도 회전하고 위 아래 키를 눌러도 회전했던 문제를 prevDirection로 이전 방향을 기억한 다음 조건문을 통해 해결했습니다.
'TIL' 카테고리의 다른 글
TIL - [React] 리렌더링 동작을 제어하는 역할 useEffect, useRef (0) | 2024.05.20 |
---|---|
TIL - 리액트 컴포넌트 파일 구조 및 상태 업데이트 방식 개선 (0) | 2024.05.17 |
TIL - 리액트로 투두리스트 만들면서 느낀 리액트의 장점 (1) | 2024.05.14 |
TIL - 리액트 주요 개념 정리 (0) | 2024.05.13 |
TIL - React를 배우기 전에 JS 복습하기 (0) | 2024.05.10 |