7/11 TIL

Hwiยท2024๋…„ 7์›” 11์ผ

TIL

๋ชฉ๋ก ๋ณด๊ธฐ
68/96

๐Ÿ“š ์ง„ํ–‰ํ•œ ๊ณต๋ถ€

  • ํฌ์ผ“๋ชฌ์„ ์„ ํƒํ•œ ํฌ์ผ“๋ชฌ์œผ๋กœ ๋ณ€๊ฒฝ

๐Ÿ“– ํฌ์ผ“๋ชฌ์„ ์„ ํƒํ•œ ํฌ์ผ“๋ชฌ์œผ๋กœ ๋ณ€๊ฒฝ

์ „์—” ํฌ์ผ“๋ชฌ ํ•œ๋งˆ๋ฆฌ๋งŒ ๋ฉ๊ทธ๋Ÿฌ๋‹ˆ ๋†“์•„์ ธ ์žˆ์—ˆ๋Š”๋ฐ, ํŒ€์› ๋ถ„๋“ค๊ณผ์˜ ํšŒ์˜ ํ›„์— ์ข‹์€ ์•„์ด๋””์–ด๋ฅผ ์–ป์—ˆ๋‹ค.

์œ„์—๋Š” ์—ฌ๋Ÿฌ ํฌ์ผ“๋ชฌ์„ ๋ฐฐ์น˜ํ•ด ๋‘๊ณ  ์•„๋ž˜์—๋Š” ์ง์ ‘ ์กฐ์ž‘์ด ๊ฐ€๋Šฅํ•œ ํฌ์ผ“๋ชฌ ํ•œ ๋งˆ๋ฆฌ๋ฅผ ๋‘” ํ›„์—, ์œ„์— ์žˆ๋Š” ํฌ์ผ“๋ชฌ์„ ํด๋ฆญํ•˜๋ฉด ์กฐ์ž‘ ๊ฐ€๋Šฅํ•œ ํฌ์ผ“๋ชฌ์ด ๋ณ€๊ฒฝ๋˜๊ฒŒ๋” ํ•˜๋ฉด ์ข‹๊ฒ ๋‹ค๋Š” ์˜๊ฒฌ์„ ๋“ฃ๊ณ  ์˜ค! ์ด๊ฑฐ๋‹ค ์‹ถ์–ด์„œ ์ ๊ทน์ ์œผ๋กœ ์˜๊ฒฌ์„ ๋ฐ›์•„๋“ค์ธ ํ›„ ์‹คํ–‰์— ์˜ฎ๊ฒผ๋‹ค.

๐Ÿ’ป 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();
  }, []);

๐Ÿ“– ์˜ค๋Š˜์˜ ๊ฒฐ๊ณผ๋ฌผ

profile
๊ฐœ๋ฐœ์ž๊ฐ€ ๋˜๊ณ  ์‹ถ์–ด~~~

0๊ฐœ์˜ ๋Œ“๊ธ€