์ ์ ํฌ์ผ๋ชฌ ํ๋ง๋ฆฌ๋ง ๋ฉ๊ทธ๋ฌ๋ ๋์์ ธ ์์๋๋ฐ, ํ์ ๋ถ๋ค๊ณผ์ ํ์ ํ์ ์ข์ ์์ด๋์ด๋ฅผ ์ป์๋ค.
์์๋ ์ฌ๋ฌ ํฌ์ผ๋ชฌ์ ๋ฐฐ์นํด ๋๊ณ ์๋์๋ ์ง์ ์กฐ์์ด ๊ฐ๋ฅํ ํฌ์ผ๋ชฌ ํ ๋ง๋ฆฌ๋ฅผ ๋ ํ์, ์์ ์๋ ํฌ์ผ๋ชฌ์ ํด๋ฆญํ๋ฉด ์กฐ์ ๊ฐ๋ฅํ ํฌ์ผ๋ชฌ์ด ๋ณ๊ฒฝ๋๊ฒ๋ ํ๋ฉด ์ข๊ฒ ๋ค๋ ์๊ฒฌ์ ๋ฃ๊ณ ์ค! ์ด๊ฑฐ๋ค ์ถ์ด์ ์ ๊ทน์ ์ผ๋ก ์๊ฒฌ์ ๋ฐ์๋ค์ธ ํ ์คํ์ ์ฎ๊ฒผ๋ค.
๐ป src/app/(providers)/(root)/page.tsx
"use client";
import React, { useEffect, useRef, useState } from 'react';
import api from '@/lib/axios';
import Image from 'next/image';
import backgroundImage from '@/assets/background.png';
interface Pokemon {
id: number;
name: string;
korean_name: string;
height: number;
weight: number;
sprites: {
front_default: string,
other: {
showdown: {
front_default: string;
}
}
};
types: { type: { name: string; korean_name: string } }[];
abilities: { ability: { name: string; korean_name: string } }[];
moves: { move: { name: string; korean_name: string } }[];
x?: number;
y?: number;
};
const MOVEMENT_AREA = { width: 600, height: 1100 };
export default function Home() {
const canvasRef = useRef<HTMLCanvasElement>(null);
const [pokemonData, setPokemonData] = useState<Pokemon[]>([]);
const [selectedPokemon, setSelectedPokemon] = useState<Pokemon | null>(null);
const [selectedPokemonPos, setSelectedPokemonPos] = useState({ x: 0, y: 0 });
const moveSelectedPokemon = (dx: number, dy: number) => {
if (!selectedPokemon) return;
setSelectedPokemonPos(prevPos => ({
x: prevPos.x + dx,
y: prevPos.y + dy
}));
};
const handleKeyDown = (event: KeyboardEvent) => {
switch (event.key) {
case 'a':
moveSelectedPokemon(-20, 0);
break;
case 'd':
moveSelectedPokemon(20, 0);
break;
case ' ':
moveSelectedPokemon(0, -50);
setTimeout(() => moveSelectedPokemon(0, 50), 500);
break;
}
};
const handlePokemonClick = (index: number) => {
if (!selectedPokemon) return;
const newPokemonData = [...pokemonData];
const clickedPokemon = newPokemonData[index];
// ์ด๋ฏธ์ง ๊ต์ฒด
const tempSprite = clickedPokemon.sprites.front_default;
newPokemonData[index].sprites.front_default = selectedPokemon.sprites.front_default;
setSelectedPokemon({ ...selectedPokemon, sprites: { ...selectedPokemon.sprites, front_default: tempSprite } });
setPokemonData(newPokemonData);
};
useEffect(() => {
const fetchPokemon = async () => {
try {
const response = await api.get<Pokemon[]>('/api/pokemons');
const data = response.data.slice(0, 6); // ์๋จ์ 5๋ง๋ฆฌ, ์๋ 1๋ง๋ฆฌ๋ง ํ์
if (data.length > 0) {
const canvasWidth = canvasRef.current?.width || 800;
const canvasHeight = canvasRef.current?.height || 600;
const spacing = canvasWidth / (5 + 1);
const updatedData = data.map((pokemon, index) => ({
...pokemon,
x: index < 5 ? spacing * (index + 1) - 150 : canvasWidth / 2 - 200,
y: index < 5 ? 50 : canvasHeight + 280,
}));
setPokemonData(updatedData);
setSelectedPokemon(updatedData[5]); // 6๋ฒ์งธ ํฌ์ผ๋ชฌ์ ์ ํ
setSelectedPokemonPos({ x: updatedData[5].x!, y: updatedData[5].y! });
}
} catch (error) {
console.log('๋ฐ์ดํฐ ๋ถ๋ฌ์ค๋ ๋์ค ์ค๋ฅ:', error);
}
};
fetchPokemon();
}, []);
useEffect(() => {
window.addEventListener('keydown', handleKeyDown);
return () => {
window.removeEventListener('keydown', handleKeyDown);
};
}, [selectedPokemon]);
return (
<div
className="relative h-full w-full"
style={{
width: MOVEMENT_AREA.width,
height: MOVEMENT_AREA.height,
backgroundImage: `url(${backgroundImage.src})`,
backgroundSize: 'cover',
backgroundPosition: 'center'
}}
>
{pokemonData.slice(0, 5).map((pokemon, idx) => (
<div
key={idx}
id={`pokemon-${idx}`}
className="absolute z-1 transition-transform duration-1000 cursor-pointer"
style={{ transform: `translate(${pokemon.x}px, ${pokemon.y}px)` }}
onClick={() => handlePokemonClick(idx)}
>
<Image src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-v/black-white/animated/${pokemon.id}.gif`} alt="ํฌ์ผ๋ชฌ ์ด๋ฏธ์ง" width={100} height={100} />
</div>
))}
{selectedPokemon && (
<div
id="selected-pokemon"
className="absolute z-1 transition-transform duration-1000"
style={{ transform: `translate(${selectedPokemonPos.x}px, ${selectedPokemonPos.y}px)` }}
>
<Image src={selectedPokemon.sprites.front_default} alt="์ ํ๋ ํฌ์ผ๋ชฌ ์ด๋ฏธ์ง" width={200} height={200} />
</div>
)}
</div>
);
}
์ด ์ค ์๋กญ๊ฒ ์ถ๊ฐ๋ ์ฝ๋๋ฅผ ์์ฝํด ๋ณด์๋ฉด
๐ป ํฌ์ผ๋ชฌ์ ์์น ์ํ ์ ๋ฐ์ดํธ ์ฝ๋
const [selectedPokemon, setSelectedPokemon] = useState<Pokemon | null>(null);
const [selectedPokemonPos, setSelectedPokemonPos] = useState({ x: 0, y: 0 });
// ์ ํ๋ ํฌ์ผ๋ชฌ์ ์์น๋ฅผ ์ด๋์ํค๋ ํจ์
const moveSelectedPokemon = (dx: number, dy: number) => {
// ์ ํ๋ ํฌ์ผ๋ชฌ์ด ์๋ค๋ฉด return
if (!selectedPokemon) return;
// ์ ํ๋ ํฌ์ผ๋ชฌ์ ์์น ์ํ ์
๋ฐ์ดํธ
setSelectedPokemonPos(prevPos => ({
// ํ์ฌ x ์์น์ dx ๊ฐ์ ๋ํด์ ์๋ก์ด x ์์น ์ค์
x: prevPos.x + dx,
// ํ์ฌ y ์์น์ dy ๊ฐ์ ๋ํด์ ์๋ก์ด y ์์น๋ฅผ ์ค์
y: prevPos.y + dy
}));
};
๐ป ํฌ์ผ๋ชฌ ํด๋ฆญ ์ ์ด๋ฏธ์ง ๋ณ๊ฒฝ ์ฒ๋ฆฌ ํจ์
// ํฌ์ผ๋ชฌ์ ํด๋ฆญํ์ ๋ ์ฒ๋ฆฌํ๋ ํจ์
const handlePokemonClick = (index: number) => {
// ์ ํ๋ ํฌ์ผ๋ชฌ์ด ์๋ ๊ฒฝ์ฐ ํจ์๋ฅผ ์ข
๋ฃ
if (!selectedPokemon) return;
// ๊ธฐ์กด ํฌ์ผ๋ชฌ ๋ฐ์ดํฐ๋ฅผ ๋ณต์ฌํ์ฌ ์๋ก์ด ๋ฐฐ์ด ์์ฑ
const newPokemonData = [...pokemonData];
// ํด๋ฆญํ ํฌ์ผ๋ชฌ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ด
const clickedPokemon = newPokemonData[index];
// ์ด๋ฏธ์ง ๊ต์ฒด
// ํด๋ฆญํ ํฌ์ผ๋ชฌ์ ์ด๋ฏธ์ง ์ฃผ์๋ฅผ ์์๋ก ์ ์ฅ
const tempSprite = clickedPokemon.sprites.front_default;
// ํด๋ฆญํ ํฌ์ผ๋ชฌ์ ์ด๋ฏธ์ง๋ฅผ ์ ํ๋ ํฌ์ผ๋ชฌ์ ์ด๋ฏธ์ง๋ก ๋ณ๊ฒฝ
newPokemonData[index].sprites.front_default = selectedPokemon.sprites.front_default;
// ์ ํ๋ ํฌ์ผ๋ชฌ์ ์ด๋ฏธ์ง๋ฅผ ํด๋ฆญํ ํฌ์ผ๋ชฌ์ ์ด๋ฏธ์ง๋ก ๋ณ๊ฒฝ
setSelectedPokemon({ ...selectedPokemon, sprites: { ...selectedPokemon.sprites, front_default: tempSprite } });
// ํฌ์ผ๋ชฌ ๋ฐ์ดํฐ๋ฅผ ์
๋ฐ์ดํธํ์ฌ ๋ฆฌ๋ ๋๋ง
setPokemonData(newPokemonData);
};
๐ป ํฌ์ผ๋ชฌ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๋ ๋น๋๊ธฐ ํจ์
useEffect(() => {
// ํฌ์ผ๋ชฌ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๋น๋๊ธฐ ํจ์
const fetchPokemon = async () => {
try {
// API๋ก๋ถํฐ ํฌ์ผ๋ชฌ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ด
const response = await api.get<Pokemon[]>('/api/pokemons');
// ์๋จ์ 5๋ง๋ฆฌ, ์๋ 1๋ง๋ฆฌ๋ง ํ์ํ๋ฏ๋ก ์ฒ์ 6๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉ
const data = response.data.slice(0, 6);
if (data.length > 0) {
// ์บ๋ฒ์ค์ ๋๋น์ ๋์ด๋ฅผ ์ค์ , ๊ธฐ๋ณธ๊ฐ์ 800๊ณผ 600
const canvasWidth = canvasRef.current?.width || 800;
const canvasHeight = canvasRef.current?.height || 600;
// ํฌ์ผ๋ชฌ์ ๊ท ๋ฑํ๊ฒ ๋ฐฐ์นํ๊ธฐ ์ํ ๊ฐ๊ฒฉ์ ๊ณ์ฐ
const spacing = canvasWidth / (5 + 1);
// ๊ฐ ํฌ์ผ๋ชฌ์ ์์น๋ฅผ ์ค์ ํ์ฌ ์
๋ฐ์ดํธ๋ ๋ฐ์ดํฐ๋ฅผ ์์ฑ
const updatedData = data.map((pokemon, index) => ({
...pokemon,
// ์๋จ 5๋ง๋ฆฌ๋ ๊ฐ๋ก๋ก ๋์ดํ๊ณ , ๋ง์ง๋ง ํ ๋ง๋ฆฌ๋ ์ค์ ์๋์ ๋ฐฐ์น
x: index < 5 ? spacing * (index + 1) - 150 : canvasWidth / 2 - 200,
y: index < 5 ? 50 : canvasHeight + 280,
}));
// ์ํ๋ฅผ ์
๋ฐ์ดํธํ์ฌ ํฌ์ผ๋ชฌ ๋ฐ์ดํฐ๋ฅผ ์ค์
setPokemonData(updatedData);
// 6๋ฒ์งธ ํฌ์ผ๋ชฌ์ ์ ํํ์ฌ ์ํ์ ์ ์ฅ
setSelectedPokemon(updatedData[5]);
// ์ ํ๋ ํฌ์ผ๋ชฌ์ ์ด๊ธฐ ์์น๋ฅผ ์ํ์ ์ ์ฅ
setSelectedPokemonPos({ x: updatedData[5].x!, y: updatedData[5].y! });
}
} catch (error) {
console.log('๋ฐ์ดํฐ ๋ถ๋ฌ์ค๋ ๋์ค ์ค๋ฅ:', error);
}
};
// fetchPokemon ํจ์๋ฅผ ํธ์ถํ์ฌ ํฌ์ผ๋ชฌ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ด
fetchPokemon();
}, []);
