Fogos de Artifício

Starter pack

Bem-vindo à Sky Adventure, uma empresa futurista que oferece os melhores fogos de artifício da galáxia! 🎇

Vamos criar um site 3D para exibir nossos fogos de artifício usando Three.js, React Three Fiber e nosso motor VFX.

Esse é o projeto que vamos construir juntos!

Projeto inicial

Nosso projeto inicial já inclui o seguinte:

  • Uma configuração básica de React Three Fiber com uma adorável ilha flutuante de onde lançaremos nossos fogos de artifício.
  • Efeitos de pós-processamento para fazer brilhar as luzes do modelo (e posteriormente dos fogos de artifício).
  • Uma interface simples feita com Tailwind CSS com três botões para posteriormente lançar os fogos de artifício.

Pré-visualização da Sky Adventure com ilha flutuante

Aqui está o que obtemos quando executamos o projeto inicial.

Fogos de Artifício

Para criar os fogos de artifício, usaremos o motor VFX que construímos na lição anterior. Este motor nos permite criar e gerenciar múltiplos sistemas de partículas com comportamentos diferentes.

useFireworks

Para gerenciar os fogos de artifício de maneira eficiente, vamos criar um hook customizado chamado useFireworks. Este hook lidará com a criação e gestão dos fogos de artifício.

Vamos adicionar a biblioteca zustand ao nosso projeto:

yarn add zustand

Agora, na pasta chamada hooks, crie um novo arquivo chamado useFireworks.js:

import { create } from "zustand";

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

export { useFireworks };

Uma store simples com um array vazio de fogos de artifício.

Vamos adicionar um método para criar um fogo de artifício:

// ...
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 adicionará um novo fogo de artifício à store com:

  • id: um identificador único para usar como chave no componente React.
  • position: onde o fogo de artifício começará.
  • velocity: a direção e a velocidade do fogo de artifício antes de explodir.
  • delay: o tempo antes de o fogo de artifício explodir.
  • color: um array de cores para usar nas partículas da explosão do fogo de artifício.

Agora podemos conectar esse hook à nossa UI para lançar fogos de artifício.

Abra UI.jsx e vamos conectar o método addFirework aos botões:

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>
  );
};

Para verificar se funciona, vamos criar um componente Fireworks.jsx. Vamos simplesmente logar os fogos de artifício no console por enquanto:

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

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

  console.log(fireworks);
};

E vamos adicioná-lo ao componente 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>

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

Nós importamos o componente Fireworks e o adicionamos ao lado do SkyIsland em nosso componente <Float />.

Fogos de artifício no console

Ao pressionar os botões, podemos ver no console que os fogos de artifício são devidamente adicionados à store.

Antes de representá-los na cena, precisamos gerenciar o ciclo de vida dos fogos de artifício. Atualmente, eles são adicionados, mas nunca removidos.

No método addFirework do nosso hook useFireworks, podemos adicionar um setTimeout para remover o fogo de artifício após um certo tempo.

Primeiro, precisamos saber quando o fogo de artifício é criado. Podemos adicionar uma propriedade time ao objeto do fogo de artifício:

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

Em seguida, chamamos setTimeout para remover os fogos de artifício que explodiram e desapareceram:

addFirework: () => {
  set((state) => {
    // ...
  });
  setTimeout(() => {
    set((state) => ({
      fireworks: state.fireworks.filter(
        (firework) => Date.now() - firework.time < 4000 // Max delay de 2 segundos + tempo de vida máximo das partículas de 2 segundos
      ),
    }));
  }, 4000);
},

Filtramos os fogos de artifício que foram adicionados há mais de 4 segundos. Desta forma, mantemos os fogos de artifício que ainda estão ativos.

Ajuste o tempo de acordo com as configurações finais que você usará para os fogos de artifício.

Fogos de artifício no console

Ao pressionar os botões, podemos ver no console que os fogos de artifício são devidamente removidos após um certo tempo.

Agora podemos mergulhar na parte que você estava esperando: criando os fogos de artifício na cena!

Motor de VFX (Wawa VFX)

Para não copiar/colar o componente da lição anterior, publiquei o motor de VFX como um pacote no npm: Wawa VFX. Você pode instalá-lo executando:

yarn add wawa-vfx@^1.0.0

Ao usar @^1.0.0, garantimos que sempre utilizamos a versão principal 1 do pacote, mas incluindo as versões mais recentes menores e de correções. Dessa forma, podemos nos beneficiar dos recursos mais recentes e correções de bugs sem alterações que quebram a compatibilidade.

Agora podemos usar <VFXParticles /> e <VFXEmitter /> em nosso projeto!

Na experiência, vamos adicionar o componente VFXParticles à cena:

// ...
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>
    </>
  );
};

Adicionamos o componente VFXParticles com as seguintes configurações:

  • 100000 partículas. Como, ao atingirmos o limite, as partículas mais antigas serão removidas, isso deve ser mais do que suficiente para muitos fogos de artifício ao mesmo tempo.
  • gravity para simular a gravidade nas partículas. -9.8 é a gravidade na Terra. (Mas espere, estamos no espaço! 👀)
  • renderMode para billboard para sempre enfrentar a câmera.
  • intensity para 3 para fazer as partículas brilharem no céu noturno.

Onde você coloca o componente <VFXParticles /> não é muito importante. Apenas certifique-se de que ele esteja no nível superior da cena.

E vamos adicionar um componente VFXEmitter ao lado do VFXParticles para moldar a explosão no modo debug e ter acesso aos controles visuais:

// ...
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 />

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

Certifique-se de definir a prop emitter com o mesmo name que o componente VFXParticles e definir debug como true para ver os controles.

*Elabore uma primeira versão da explosão de fogos de artifício. *💥

Quando estiver satisfeito com as configurações, você pode remover a prop debug do componente VFXEmitter, pressionar o botão de exportação e colar as configurações no componente 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],
  }}
/>

Temos tudo pronto para conectar os fogos de artifício ao motor de VFX!

Explosão de Fogos de Artifício

Dentro do arquivo Fireworks.jsx, vamos criar um componente Firework que representará um fogo de artifício:

// ...
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>
    </>
  );
};

Simplesmente corte e cole o VFXEmitter do componente Experience para o componente Firework.

Nós o envolvemos em um <group /> para poder mover o fogo de artifício na cena com base na position e velocity.

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.