الألعاب النارية

Starter pack

مرحبًا بك في Sky Adventure، الشركة المستقبلية التي تقدم أفضل الألعاب النارية في المجرة! 🎇

سنقوم بإنشاء موقع ويب ثلاثي الأبعاد لعرض ألعابنا النارية باستخدام Three.js وReact Three Fiber ومحرك VFX الخاص بنا.

هذا ما سنقوم ببنائه معًا!

مشروع البداية

مشروع البداية لدينا يشمل بالفعل ما يلي:

  • إعداد أساسي لـ React Three Fiber مع جزيرة عائمة جميلة نطلق منها الألعاب النارية.
  • تأثيرات ما بعد المعالجة لجعل الأضواء من النموذج تتوهج (ولاحقًا الألعاب النارية).
  • واجهة مستخدم بسيطة مصنوعة بـ Tailwind CSS مع ثلاثة أزرار لتشغيل الألعاب النارية لاحقًا.

معاينة مغامرة السماء مع الجزيرة العائمة

هذا ما نحصل عليه عندما نشغل مشروع البداية.

الألعاب النارية

لإنشاء الألعاب النارية، سنستخدم محرك VFX الذي قمنا ببنائه في الدرس السابق. هذا المحرك يسمح لنا بإنشاء وإدارة أنظمة جسيمات متعددة ذات سلوكيات مختلفة.

useFireworks

لإدارة الألعاب النارية بكفاءة، سنقوم بإنشاء hook مخصص يسمى useFireworks. هذا الـ hook سيتولى إنشاء وإدارة الألعاب النارية.

لنقم بإضافة مكتبة zustand إلى مشروعنا:

yarn add zustand

الآن في مجلد يسمى hooks، قم بإنشاء ملف جديد يسمى useFireworks.js:

import { create } from "zustand";

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

export { useFireworks };

متجر بسيط يحتوي على مصفوفة فارغة للألعاب النارية.

لنقم بإضافة طريقة لإنشاء لعبة نارية:

// ...
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 بإضافة لعبة نارية جديدة إلى المتجر تضم:

  • id: معرّف فريد لاستخدامه كمفتاح في مكون React.
  • position: موقع البداية للعبة النارية.
  • velocity: الاتجاه والسرعة للعبة النارية قبل أن تنفجر.
  • delay: الزمن قبل أن تنفجر اللعبة النارية.
  • color: مصفوفة من الألوان لاستخدامها لجسيمات انفجار اللعبة النارية.

يمكننا الآن ربط هذا الـ hook مع واجهة المستخدم لإطلاق الألعاب النارية.

افتح 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 ونضيفه بجانب SkyIsland في مكون <Float />.

الألعاب النارية في وحدة التحكم

عند الضغط على الأزرار، يمكننا رؤية الألعاب النارية تُضاف بشكل صحيح إلى المتجر في وحدة التحكم.

قبل تمثيلها في المشهد، نحتاج إلى إدارة دورة حياة الألعاب النارية. حاليًا، يتم إضافتها ولكن لا يمكن إزالتها.

في الميثود addFirework في الـ hook useFireworks، يمكننا إضافة 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 ثواني مضت. بهذه الطريقة، نستبقي الألعاب النارية الفعّالة.

قم بتعديل الوقت بناءً على الإعدادات النهائية التي ستستخدمها للألعاب النارية.

الألعاب النارية في وحدة التحكم

عند الضغط على الأزرار، يمكننا رؤية الألعاب النارية تُزال بشكل صحيح بعد وقت معين في وحدة التحكم.

يمكننا الآن الخوض في الجزء الذي كنت تنتظره: إنشاء الألعاب النارية في المشهد!

محرك الـ 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 هو الجاذبية على الأرض. (لكن انتظر نحن في الفضاء! 👀)
  • renderMode إلى billboard ليواجه دائماً الكاميرا.
  • intensity إلى 3 لجعل الجسيمات تتوهج في سماء الليل.

مكان وضع المكون <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 إلى نفس name مثل المكون VFXParticles وتعيين debug إلى true لرؤية التحكمات.

*اصنع نسخة أولية من انفجار الألعاب النارية. *💥

بمجرد أن تكون راضياً عن الإعدادات، يمكنك إزالة الخاصية debug من المكون VFXEmitter، اضغط على زر التصدير وألصق الإعدادات في المكون 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>
    </>
  );
};

ببساطة قطع/لصق VFXEmitter من مكون Experience إلى مكون Firework.

نقوم بتغليفه في <group /> لنتمكن من تحريك الألعاب النارية في المشهد بناءً على position و 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.