불꽃놀이

Starter pack

Sky Adventure에 오신 것을 환영합니다. 은하계에서 최고의 불꽃놀이를 제공하는 미래 지향적인 회사입니다! 🎇

우리는 Three.js, React Three Fiber 및 VFX 엔진을 사용하여 불꽃놀이를 선보이는 3D 웹사이트를 만들 것입니다.

이것이 우리가 함께 만들게 될 것입니다!

스타터 프로젝트

우리의 스타터 프로젝트에는 이미 다음이 포함되어 있습니다:

  • 불꽃놀이가 발사될 사랑스러운 떠다니는 섬이 있는 기본 React Three Fiber 설정.
  • 모델(그리고 나중에 불꽃놀이)에서 빛을 발하게 할 후처리 효과.
  • Tailwind CSS로 제작된 간단한 UI와 나중에 불꽃놀이를 발사할 세 개의 버튼.

떠다니는 섬과 함께한 Sky adventure 미리보기

스타터 프로젝트를 실행할 때 얻을 수 있는 모습입니다.

불꽃놀이

불꽃놀이를 만들기 위해 이전 레슨에서 제작한 VFX 엔진을 사용할 것입니다. 이 엔진은 다양한 동작을 가진 여러 파티클 시스템을 생성하고 관리할 수 있게 해줍니다.

useFireworks

불꽃놀이를 효율적으로 관리하기 위해 useFireworks라는 커스텀 hook을 만들 것입니다. 이 hook은 불꽃놀이의 생성과 관리를 담당합니다.

zustand 라이브러리를 프로젝트에 추가합시다:

yarn add zustand

이제 hooks라는 폴더에 useFireworks.js라는 새 파일을 만듭니다:

import { create } from "zustand";

const useFireworks = create((set, get) => {
  return {
    fireworks: [],
  };
});

export { useFireworks };

불꽃놀이의 빈 배열이 있는 간단한 store입니다.

이제 불꽃놀이를 생성하는 메서드를 추가해봅시다:

// ...
import { randFloat, randInt } from "three/src/math/MathUtils.js";

const useFireworks = create((set) => {
  return {
    fireworks: [],
    addFirework: () => {
      set((state) => {
        return {
          fireworks: [
            ...state.fireworks,
            {
              id: `${Date.now()}-${randInt(0, 100)}-${state.fireworks.length}`,
              position: [0, 0, 0],
              velocity: [randFloat(-8, 8), randFloat(5, 10), randFloat(-8, 8)],
              delay: randFloat(0.8, 2),
              color: ["skyblue", "pink"],
            },
          ],
        };
      });
    },
  };
});

// ...

addFirework는 store에 새 불꽃놀이를 추가합니다:

  • id: React 컴포넌트에서 key로 사용할 고유 식별자.
  • position: 불꽃놀이가 시작할 위치.
  • velocity: 폭발하기 전 불꽃놀이의 방향과 속도.
  • delay: 불꽃놀이가 폭발하기 전까지의 시간.
  • color: 불꽃놀이 폭발 입자에 사용할 색깔 배열.

이제 이 hook을 UI에 연결해 불꽃놀이를 시작할 수 있습니다.

UI.jsx를 열고 addFirework 메서드를 버튼에 연결합시다:

import { useFireworks } from "../hooks/useFireworks";

export const UI = () => {
  const addFirework = useFireworks((state) => state.addFirework);

  return (
    <section className="fixed inset-0 z-10 flex items-center justify-center">
      {/* ... */}
      <div
      // ...
      >
        {/* ... */}
        <div className="flex gap-4">
          <button
            // ..
            onClick={addFirework}
          >
            🎆 Classic
          </button>
          <button
            // ..
            onClick={addFirework}
          >
            💖 Love
          </button>
          <button
            // ..
            onClick={addFirework}
          >
            🌊 Sea
          </button>
        </div>
        {/* ... */}
      </div>
    </section>
  );
};

작동 여부를 확인하기 위해, Fireworks.jsx 컴포넌트를 만들어 보겠습니다. 지금은 콘솔에 불꽃놀이를 단순히 로그로 출력하겠습니다:

import { useFireworks } from "../hooks/useFireworks";

export const Fireworks = () => {
  const fireworks = useFireworks((state) => state.fireworks);

  console.log(fireworks);
};

그리고 Experience 컴포넌트에 추가합시다.

// ...
import { Fireworks } from "./Fireworks";

export const Experience = () => {
  // ...

  return (
    <>
      {/* ... */}

      <Float
        speed={0.6}
        rotationIntensity={2}
        position-x={4}
        floatIntensity={2}
      >
        <Fireworks />
        <Gltf src="/models/SkyIsland.glb" />
      </Float>

      {/* ... */}
    </>
  );
};

Fireworks 컴포넌트를 가져와 <Float /> 컴포넌트 내의 SkyIsland 옆에 추가합니다.

Fireworks in the console

버튼을 누를 때마다, 콘솔에서 불꽃놀이가 store에 올바르게 추가되는 것을 볼 수 있습니다.

장면에서 불꽃놀이를 나타내기 전에 불꽃놀이의 라이프사이클을 관리해야 합니다. 현재로서는 추가되기만 할 뿐 제거되지 않습니다.

useFireworks hook의 addFirework에서 일정 시간이 지나면 불꽃놀이를 제거하도록 setTimeout을 추가할 수 있습니다.

먼저 불꽃놀이가 생성될 때를 알아야 합니다. 불꽃놀이 객체에 time 속성을 추가할 수 있습니다:

{
  id: `${Date.now()}-${randInt(0, 100)}-${state.fireworks.length}`,
  // ...
  time: Date.now(),
},

그런 다음 터지고 사라진 불꽃놀이를 제거하기 위해 setTimeout을 호출합니다:

addFirework: () => {
  set((state) => {
    // ...
  });
  setTimeout(() => {
    set((state) => ({
      fireworks: state.fireworks.filter(
        (firework) => Date.now() - firework.time < 4000 // 최대 지연 2초 + 최대 파티클 생명 주기 2초
      ),
    }));
  }, 4000);
},

4초 이상 추가된 불꽃놀이는 필터링합니다. 이렇게 하면 여전히 활성화된 불꽃놀이만 유지됩니다.

불꽃놀이의 최종 설정에 따라 시간을 조정하십시오.

Fireworks in the console

버튼을 클릭할 때마다, 콘솔에서 불꽃놀이가 일정 시간이 지나면 올바르게 제거되는 것을 볼 수 있습니다.

이제 여러분이 기다리고 있던 부분: 장면에서 불꽃놀이를 만드는 부분으로 들어가봅시다!

VFX 엔진 (Wawa VFX)

이전 강의의 컴포넌트를 복사/붙여넣기 하지 않기 위해, VFX 엔진을 npm 패키지로 게시했습니다: Wawa VFX. 아래 명령어로 설치할 수 있습니다:

yarn add wawa-vfx@^1.0.0

@^1.0.0를 사용하면, 패키지의 메이저 버전 1을 항상 사용하되, 최신 마이너 및 패치 버전을 포함합니다. 이렇게 하면, 최신 기능과 버그 수정 사항을 누리면서도 깨지는 변경 사항 없이 사용할 수 있습니다.

이제 프로젝트에서 <VFXParticles /><VFXEmitter />를 사용할 수 있습니다!

경험적으로, VFXParticles 컴포넌트를 씬에 추가해봅시다:

// ...
import { VFXParticles } from "wawa-vfx";

export const Experience = () => {
  const controls = useRef();

  return (
    <>
      {/* ... */}

      <VFXParticles
        name="firework-particles"
        settings={{
          nbParticles: 100000,
          gravity: [0, -9.8, 0],
          renderMode: "billboard",
          intensity: 3,
        }}
      />

      <EffectComposer>
        <Bloom intensity={1.2} luminanceThreshold={1} mipmapBlur />
      </EffectComposer>
    </>
  );
};

다음 설정으로 VFXParticles 컴포넌트를 추가합니다:

  • 100000 입자. 한계에 도달하면 가장 오래된 입자가 제거되므로, 동시에 여러 폭죽을 터뜨리기에 충분합니다.
  • gravity를 설정하여 입자에 중력을 시뮬레이션합니다. -9.8은 지구의 중력입니다. (하지만 우리는 우주에 있어요! 👀)
  • renderModebillboard로 설정하여 항상 카메라를 바라보게 합니다.
  • intensity3으로 설정하여 밤하늘에서 입자가 빛나게 합니다.

<VFXParticles /> 컴포넌트를 어디에 배치하느냐는 크게 중요하지 않습니다. 그냥 씬의 최상위 레벨에 있으면 됩니다.

그리고 VFXEmitter 컴포넌트를 VFXParticles 옆에 추가하여 debug 모드에서 폭발을 형성하고 시각적 제어에 접근해 봅시다:

// ...
import { VFXEmitter, VFXParticles } from "wawa-vfx";

export const Experience = () => {
  const controls = useRef();

  return (
    <>
      {/* ... */}

      <VFXParticles
        name="firework-particles"
        settings={{
          nbParticles: 100000,
          gravity: [0, -9.8, 0],
          renderMode: "billboard",
          intensity: 3,
        }}
      />
      <VFXEmitter emitter="firework-particles" debug />

      {/* ... */}
    </>
  );
};

emitter 속성을 VFXParticles 컴포넌트의 name과 동일하게 설정하고 debugtrue로 설정하여 제어 기능들을 확인하세요.

*첫 번째 버전의 폭죽 폭발을 초안으로 작성하세요. *💥

설정이 만족스럽다면, VFXEmitter 컴포넌트에서 debug 속성을 제거하고, 내보내기 버튼을 누른 후 설정을 VFXEmitter 컴포넌트에 붙여넣습니다.

<VFXEmitter
  emitter="firework-particles"
  settings={{
    nbParticles: 5000,
    delay: 0,
    spawnMode: "burst",
    colorStart: ["skyblue", "pink"],
    particlesLifetime: [0.1, 2],
    size: [0.01, 0.4],
    startPositionMin: [-0.1, -0.1, -0.1],
    startPositionMax: [0.1, 0.1, 0.1],
    directionMin: [-1, -1, -1],
    directionMax: [1, 1, 1],
    startRotationMin: [degToRad(-90), 0, 0],
    startRotationMax: [degToRad(90), 0, 0],
    rotationSpeedMin: [0, 0, 0],
    rotationSpeedMax: [3, 3, 3],
    speed: [1, 12],
  }}
/>

이제 폭죽VFX 엔진에 연결할 준비가 되었습니다!

불꽃놀이 폭발

Fireworks.jsx 파일 안에서 하나의 불꽃놀이를 나타내는 Firework 컴포넌트를 생성해 봅시다:

// ...
import { useRef } from "react";
import { VFXEmitter } from "wawa-vfx";

export const Fireworks = () => {
  // ...
};

const Firework = ({ velocity, delay, position, color }) => {
  const ref = useRef();
  return (
    <>
      <group ref={ref} position={position}>
        <VFXEmitter
          emitter="firework-particles"
          settings={{
            nbParticles: 5000,
            delay: 0,
            spawnMode: "burst",
            colorStart: ["skyblue", "pink"],
            particlesLifetime: [0.1, 2],
            size: [0.01, 0.4],
            startPositionMin: [-0.1, -0.1, -0.1],
            startPositionMax: [0.1, 0.1, 0.1],
            directionMin: [-1, -1, -1],
            directionMax: [1, 1, 1],
            startRotationMin: [degToRad(-90), 0, 0],
            startRotationMax: [degToRad(90), 0, 0],
            rotationSpeedMin: [0, 0, 0],
            rotationSpeedMax: [3, 3, 3],
            speed: [1, 12],
          }}
        />
      </group>
    </>
  );
};

VFXEmitterExperience 컴포넌트에서 Firework 컴포넌트로 잘라 붙여 넣으세요.

우리는 <group />로 감싸서 positionvelocity에 따라 장면에서 불꽃놀이를 이동할 수 있도록 합니다.

Three.js logoReact logo

React Three Fiber: The Ultimate Guide to 3D Web Development

✨ You have reached the end of the preview ✨

Go to the next level with Three.js and React Three Fiber!

Get full access to this lesson and the complete course when you enroll:

  • 🔓 Full lesson videos with no limits
  • 💻 Access to the final source code
  • 🎓 Course progress tracking & completion
  • 💬 Invite to our private Discord community
Unlock the Full Course – Just $85

One-time payment. Lifetime updates included.