iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 26
0
自我挑戰組

寫遊戲初體驗系列 第 26

Day 26 Particle System 2

Particle

我們知道,particle 是效果的基本單位,為了能夠達到我們所要的效果,我們必須調整每個 particle 的位置、顏色、大小等等屬性,所以在這裡我們需要定義一個 particle 會有甚麼屬性。

而我的 Particle 有以下屬性

struct Particle {

    p2 pos;
    p2 startVel;
    p2 endVel;
    p2 currentVel;
    float currentSize, startSize, endSize;
    float angle;
    float startRotSpeed;
    float currentRotSpeed;
    p4 startColor;
    p4 currentColor;
    p4 endColor;
    float t; // Timestep
    uint32_t startLife;
    uint32_t life;

    bool draw = false;
    Particle() = default;
};
  • pos
    • particle 目前的位置
  • startVel
    • particle 產生時的速度
  • currentVel
    • particle 目前的速度
  • endVel
    • particle 死亡時的速度
  • startSize
    • particle 產生時的大小
  • currentSize
    • particle 目前的大小
  • endSize
    • particle 死亡時的大小
  • angle
    • Particle Rotate的角度
  • startRotSpeed
    • Particle 起始旋轉速度
  • currentRotSpeed
    • Particle 目前旋轉速度
  • startColor
    • Particle 產生時的顏色
  • currentColor
    • Particle 目前的顏色
  • endColor
    • Particle 死亡的顏色
  • t
    • 插值所用的 timestep
  • startLife
    • Particle 的生命週期
  • life
    • 目前生下的生命
  • draw
    • 是否繪製此 Particle

我們有很多的 startXXXendXXX,上面也解釋說是產生時到死亡時的變化,例如我的 startColor 設定成白色,endColor設定成黑色,那我的 Particle 在生命週期開始的時候會是白色,並且利用差值的方式,漸漸地由白色轉呈黑色,最後在生命週期到的時候變成黑色然後消失。大小、速度也是一樣的道理。

Emitter

如果不是用ECS架構的話,我的 Particle 會是一個 class,Emitter 也會是一個 class,不過應和架構,我的 Emitter 也只會有 Data。而主要激活 Particle 的就由 Particle System 來做。

那我們知道 Emitter 是定義 Particle 行為的地方,意思是說,如果我希望我的效果會讓粒子從紫色變黃色,我就要告訴 Emitter 我希望我的起始顏色是紫色,結束顏色是黃色。接著當遊戲循環開始時,Emitter就會根據你給的數據,將他給予到每個 Particle,讓他們模擬。
如果今天我希望有另一個效果可以從紅色變藍色,我只需要在產生一個新的 Emitter 去存取新的數據,這樣就可以有新的效果了。

聽起來 Emitter 的數據跟 Particle 很像。

答對了~~

struct ParticleComponent {

    // Emitter排放角度,0~360度就會是圓形排放
    p2 angleRange = {0.f, 360.f};

    // Particle 產生時的速度
    float startSpeed = 0.f;

    // Particle 死亡時的速度
    float endSpeed = 0.f;

    // Particle 產生時的size
    float startSize = 0.f;

    // Particle 死亡時的size
    float endSize = 0.f;

    // Particle Texture Rotate的速度
    float rotateSpeed = 0.f;

    // 每個frame產生Particle的數量,由emitNumber跟emitVariance算出
    int emissionRate = 0; // new Particles

    // 每個frame保底產生Particle的數量
    uint32_t emitNumber = 0;

    // 讓每個frame產生的數量不固定。
    uint32_t emitVariance = 0;

    // 每個Particle的life
    uint32_t maxParticleLife = 0;

    // 用來計算pool size
    uint32_t maxParticlesPerFrame = 0;
    int poolSize;

    // Emitter 是死是活
    bool active = false;

    // Emitter 的 Life
    float life = -1;

    // 每 sleepTime 產生一次Particle
    float sleepTime;

    Timer lifeTimer;
    Timer sleepTimer;

    // Particle 產生時的顏色
    p4 startColor = {0, 0, 0, 0};

    // Particle 死亡時的顏色
    p4 endColor = {0, 0, 0, 0};
    // Random control
    // XXRand 代表 XX 的random範圍,讓Particle看起來不會那麼整齊
    p2 rotSpeedRand = {0.f, 0.f};

    p2 startSpeedRand = { 0.f, 0.f};
    p2 endSpeedRand = {0.f, 0.f};
    p2 emitVarianceRand = {0.f, 0.f};
    p2 startSizeRand = {0.f, 0.f};
    p2 endSizeRand = {0.f, 0.f};
    p2 lifeRand = {0.f, 0.f};

    // disX, disY 用來決定paritcle產生的位置範圍,
    // disX = 100 代表距離emitter pos center的 +- 100內都會產生particle
    float disX = 0.f;
    float disY = 0.f;

    // 產生每個particle的時候要找pool中尚未被激活 或者死亡的particle激活
    int lastUnusedParticle = 0;

    std::shared_ptr<Texture2D> texture;

    // Particle pool
    std::vector<Particle> particles;

    EmitterComponent(std::shared_ptr<Texture2D> texture) : texture(texture){}

};

這裡我提一下 Particle沒有的數據

  • angleRange
    • Particle 會在哪個角度反為裡排放
  • emissionRate
    • 每個frame產生Particle的數量,由emitNumber跟emitVariance算出
  • emitNumber
    • 每個frame保底產生Particle的數量
  • emitVariance
    • 讓每個frame產生的數量不固定。此數會乘上一個介於(0, 1)範圍的值再加上 emitNumber來讓每個 frame 產生的數量不固定。
  • maxParticleLife
    • particle 的生命週期
  • maxParticlesPerFrame
    • 用來計算 pool size
  • poolSize
    • particle pool 的大小
  • active
    • 這個 Emitter 是否活著
  • life
    • Emitter 的生命週期。-1代表永久活著
  • sleepTime
    • 每 sleepTime 產生一次 Particle
  • XXXRand
    • 代表 XX 的random範圍,讓Particle看起來不會那麼整齊
    • 例如 startSpeedRand,如果是(0,1),而 startSpeed 設定成 100,那我實際在激活粒子時不會讓每個粒子的速度都是 100,而是設定成 100 * rand(0, 1),這樣才不會讓效果太過單調。
  • disX/Y
    • disX, disY 用來決定paritcle產生的位置範圍
    • disX = 100 代表距離emitter pos center的 +- 100內都會產生particle
  • std::vector<Particle> particles;
    • particle pool,這個 Emitter 所模擬的 particle

EmitData

我希望我所有的資料是存在 Json 檔裡的,像這樣

{
    "value0": {
        "angleRange": {
            "x": 250.0,
            "y": 280.0
        },
        "startSpeed": 200.0,
        "endSpeed": 200.0,
        "startSize": 0.0,
        "endSize": 80.0,
        "rotateSpeed": 0.0,
        "emitNumber": 3,
        "emitVariance": 2,
        "maxParticleLife": 80,
        "life": -1.0,
        "sleepTime": -1.0,
        "startColor": {
            "x": 1.0,
            "y": 0.3921568691730499,
            "z": 0.0,
            "w": 1.0
        },
        "endColor": {
            "x": 0.8235294222831726,
            "y": 0.8235294222831726,
            "z": 0.8235294222831726,
            "w": 0.0
        },
          
        ...
    }
}

從檔案讀取之後,將資料輸入至 Emitter,再利用 ParticleSystem 將所有的 Emitter 進行模擬。

而我們的 EmitData 所要放的東西就是我們能夠更改的一些 Data,像 emissionRate 這種資料是利用其他資料算出來的,就不用在 EmitData 裡。

struct EmitterData {

    p2 angleRange = {0.f, 0.f};

    // Particle Speed
    float startSpeed = 0.f;
    float endSpeed = 0.f;

    float startSize = 0.f;
    float endSize = 0.f;

    // Particle Texture rotate speed #unused now
    float rotateSpeed = 0.f;

    // Controll emit rate
    uint32_t emitNumber = 0; // Generate per frame
    uint32_t emitVariance = 0; // Offset of random
    uint32_t maxParticleLife = 0; // particle life

    // Control emitter life time
    float life = -1;
    float sleepTime = -1;

    // Particle Color
    p4 startColor = {1.0, 0.0, 0.0, 1};
    p4 endColor = {0.0, 0.0, 1., 0};

    // Random control
    p2 rotSpeedRand = {-1.f, 1.f};
    p2 startSpeedRand = { 0.f, 1.f};
    p2 endSpeedRand = {0.f, 1.f};
    p2 emitVarianceRand = {0.f, 1.f};
    p2 startSizeRand = {0.f, 1.f};
    p2 endSizeRand = {1.f, 1.f};
    p2 lifeRand = {0.5f, 1.f};

    // Different pos init
    float disX = 0.f;
    float disY = 0.f;
};

而將資料存成 Json file 我是用 cereal 這個 library。

而將 System 寫好後的 code 會大概長這樣

// 將資料讀進
std::ifstream is("Fire.json");
std::string str((std::istreambuf_iterator<char>(is)), std::istreambuf_iterator<char>());
std::stringstream oos(str);

cereal::JSONInputArchive ar(oos);

EmitterData data;
ar(data);
////////////////////

// 產生一個 Entity
auto emit = registry.create();
// 添加 Transform,也就是讓他有 pos,並且設定位置在目前劃屬位置
registry.emplace<TransformComponent>(emit, glm::vec2(sf::Mouse::getPosition(window_).x, sf::Mouse::getPosition(window_).y), glm::vec2(100.f, 100.f));
// 添加 Particle效果
registry.emplace<ParticleComponent>(emit, ResourceManager::getTexture("5"));
registry.emplace<TagComponent>(emit);

// 將資料輸入至 Emitter
particleSystem.initEmitter(registry, emit, data);

上一篇
Day 25 Particle System 01
下一篇
Day 27 Particle System 3
系列文
寫遊戲初體驗30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言