今はこれが精一杯-5

前回の続きです。

長々とリファクタリングを続けてきましたが、だいぶ満足したので、これで最後にして先に進めることにします。

せっかくここまでやったので、最後にパーティクルシステムも同じ形式で実装してみたいと思います。

パーティクルの細かいかけら(部品)と、たくさんのかけらを集約管理するクラスに分けます。まずはパーティクルの部品となるクラスを定義してみます。
TextureObjectDefaultImplementから派生させることによって、実装を楽に済ませます。

template <typename T, typename C, typename S> class ParticleSystem;

// パーティクル部品クラス
template <typename T, typename C, typename S>
class Particle : public TextureObjectDefaultImplement<T, C, S>
{
    typedef TextureObjectDefaultImplement<T, C, S> inherited;
    friend class ParticleSystem<T, C, S>;
    
public:
    Particle(int life) : _frame(0), _life(life) {}
    virtual ~Particle() {}
    
protected:
    virtual void Move() = 0;
    
protected:
    int _frame;       // 生成からのフレーム数
    int _life;        // 寿命フレーム数
};

部品と管理クラスにはfriend関係が成り立つと思ったので、friend指定してみました。
templateクラスをfriend指定するには、前方宣言が必要です。
個々のかけらがどう動くのかは演出によって様々なので、かけらを動かすメソッドは純粋仮想関数としました。

次に、パーティクル管理クラスの定義をします。

// パーティクル管理クラス
template <typename T, typename C, typename S>
class ParticleSystem final : public TextureObject<T, C, S>
{
    typedef TextureObject<T, C, S> inherited;
    typedef std::function<Particle<T, C, S>*()> Allocator;

public:
    ParticleSystem(int, Allocator);
    ~ParticleSystem();
    
    void Alloc();                  // メモリ領域を確保する
    void Generate(int = 1);	   // パーティクルを指定数分発生させる
    void Update();                 // パーティクルの状態を更新する
    void Release();                // メモリを解放する
    
    virtual void GetDrawSize(TextureVerticesHeader<S>*) override;
    virtual void PrepareDraw(TextureVerticesPointers<T, C>*) override;
    
private:
    int _count;                       // 最大パーティクル数
    int _memct;                       // メモリ内に確保したパーティクル領域の数
    int _activeCount;                 // 生存中のパーティクルの数
    Particle<T, C, S>** _particles;   // パーティクル
    Allocator _alloc;                 // ファクトリ関数オブジェクト
};

最初は、Add(Particle*) というメソッドでパーティクルを追加するように考えたのですが、外でnewされたパーティクルを内部でdeleteするというのはオブジェクトのオーナーシップに反するので、ファクトリオブジェクトをコンストラクタで受け取る設計にしました。

実装は以下のようになりました。

// 生成
template <typename T, typename C, typename S>
ParticleSystem<T, C, S>::ParticleSystem(int max, Allocator alloc)
{
    _count = max;
    _activeCount = 0;
    _particles = NULL;
    _alloc = alloc;
}

// 破棄
template <typename T, typename C, typename S>
ParticleSystem<T, C, S>::~ParticleSystem()
{
    Release();
}

// メモリ確保
template <typename T, typename C, typename S>
void ParticleSystem<T, C, S>::Alloc()
{
    Release();
    _activeCount = 0;
    _memct = _count;
    _particles = (Particle<T, C, S>**)malloc(sizeof(Particle<T, C, S>*) * _memct);
    memset(_particles, NULL, sizeof(Particle<T, C, S>*) * _memct);
}

// パーティクル発生
template <typename T, typename C, typename S>
void ParticleSystem<T, C, S>::Generate(int count)
{
    Particle<T, C, S>** p = _particles;
    Particle<T, C, S>** end = _particles + _memct;
    
    for (int n = 0; end != p && count > n; ++ p)
    {
        if (!*p)
        {
            *p = _alloc();
            
            if (*p)
            {
                ++ n;
                ++ _activeCount;
            }
            else
            {
                break;
            }
        }
    }
}

// パーティクル状態更新
template <typename T, typename C, typename S>
void ParticleSystem<T, C, S>::Update()
{
    Particle<T, C, S>** p = _particles;
    Particle<T, C, S>** end = _particles + _memct;
    
    while (end != p)
    {
        if (*p)
        {
            (*p)->Move();
            (*p)->_frame ++;
            
            if ((*p)->_life <= (*p)->_frame)
            {
                delete *p;
                *p = NULL;
                -- _activeCount;
            }
        }
        
        ++ p;
    }
}

// メモリサイズ計算
template <typename T, typename C, typename S>
void ParticleSystem<T, C, S>::GetDrawSize(TextureVerticesHeader<S>* header)
{
    header->vertexArraySize = 0;
    header->colorArraySize = 0;
    header->coordArraySize = 0;
    header->vertexNum = 0;
    
    Particle<T, C, S>** p = _particles;
    Particle<T, C, S>** end = _particles + _memct;
    
    while (end != p)
    {
        if (*p)
        {
            TextureVerticesHeader<S> h;
            (*p)->GetDrawSize(&h);
            header->vertexArraySize += h.vertexArraySize;
            header->colorArraySize += h.colorArraySize;
            header->coordArraySize += h.coordArraySize;
            header->vertexNum += h.vertexNum;
        }
        
        ++ p;
    }
}

// 座標計算
template <typename T, typename C, typename S>
void ParticleSystem<T, C, S>::PrepareDraw(TextureVerticesPointers<T, C>* ptrs)
{
    Particle<T, C, S>** p = _particles;
    Particle<T, C, S>** end = _particles + _memct;
    
    while (end != p)
    {
        if (*p)
        {
            (*p)->PrepareDraw(ptrs);
        }
        
        ++ p;
    }
}

// リソース解放
template <typename T, typename C, typename S>
void ParticleSystem<T, C, S>::Release()
{
    if (_particles)
    {
        Particle<T, C, S>** p = _particles;
        Particle<T, C, S>** end = _particles + _memct;
        
        while (end != p)
        {
            delete *p;
            ++ p;
        }
    }
    
    free(_particles);
}


これでパーティクルシステムの基盤は実装完了。
使用例として、車の軌跡を30フレーム表示するパーティクルを実装してみます。

// 車の軌跡を表示するパーティクル部品クラス
class CpCarOrbit : public CpParticle
{
    typedef CpParticle inherited;
    
public:
    // 生成
    CpCarOrbit(float x, float y) : inherited(30)
    {
        // x, y座標は車の座標
        _x = x, _y = y;
        
        // それ以外は自身で決定する
        _width = _height = 0.05f;
        _mx = (RANDF() - 0.5f) * 0.002f;
        _my = (RANDF() - 0.5f) * 0.002f;
    };

    // 1フレームあたりの更新処理
    void Move()
    {
        // 最初に決定したベクトルに従って移動
        _x += _mx;
        _y += _my;
        
        // 残り寿命に応じてフェードイン、アウト
        float lifePos = (float)_frame / (float)_life;
        
        if (0.5f >= lifePos)
        {
            _a = (int)(round(lifePos * 2.0f * 255.0f));
        }
        else
        {
            _a = 255 - (int)(round(lifePos * 255.0f));
        }
    }
    
private:
    float _mx, _my;    // 1フレームあたりの移動量
};



// ... 以下、パーティクルを使うクラスで、属性に_carがあるとする

// パーティクルシステムを初期化
CpParticleSystem particle(30, [&]()->CpParticle*{return new CpCarOrbit(_car.GetPosX(), _car.GetPosY());})
particle.Alloc();


// ... 以下、毎フレーム指示

// 車の現在位置にパーティクルを発生させる
particle.Generate();
	
// パーティクルを更新する
particle.Update();


パーティクルシステムは元々いっぺんに大量のテクスチャを表示するように作られていたので、これによって速度が向上するというわけではありません。ある程度汎用的にして、新しい設計基盤の上に乗っけられたといったところですね。速度面に関しては、GL_TRIANGLESで描画しているのと、テクスチャ座標が毎回同じ、RGB値が変わらないなどの理由から、頂点インデックスを使えばもっと処理は速くなると思います。

とりあえず、これで一旦、一連のリファクタリングはおしまい。

ふぅ・・・長い戦いだった。



GLくんのこと、ちょっとは分かってあげられたかな?