2014/01/23

liquidFunとSpriteKitで流体を使う

前回の記事「liquidFunとSpriteKitでボールを使う」の続きです。

今回はようやくliquidFunらしく流体を扱ってみます。

パーティクルの生成

まず、ワールドを生成した直後に、次のコードで単一パーティクルの大きさを決定します。

    _world->SetParticleRadius(1.0 / 8);

続いて、水などの流体を表現するためにパーティクル生成のコードを-touchesBegan:withEvent:に書いていきます。

パーティクルを作成するコードは、前回のボールを使うと基本的には同じです。

    static UIBezierPath* ovalPath2 = [UIBezierPath bezierPathWithOvalInRect: CGRectMake(-4, -4, 8, 8)];
    auto addWater = [self](const CGPoint& pos) {
        b2CircleShape ballShape;
        ballShape.m_radius = 32 / DISPLAY_SCALE / 2;

        b2ParticleGroupDef groupDef;
        groupDef.shape = &ballShape;
        groupDef.flags = b2_tensileParticle;
        groupDef.position.Set(pos.x / DISPLAY_SCALE, pos.y / DISPLAY_SCALE);
        b2ParticleGroup* group = _world->CreateParticleGroup(groupDef);

        for (size_t i = 0; i < group->GetParticleCount(); ++i) {
            SKShapeNode* node = SKShapeNode.alloc.init;
            node.path = ovalPath2.CGPath;
            node.fillColor = UIColor.blueColor;
            node.lineWidth = 0;
            node.position = pos;
            [self addChild:node];

            // TODO: SKNodeをuserdataに紐付ける
        }
    };

違う点は、b2BodyDefの代わりに、b2ParticleGroupDefを使っている点と、 生成したパーティクルグループ内のパーティクルのためにひとつずつSKNodeをつくっている点です。

で、実際にSKNodeuserdataに紐付けするのですが、 パーティクルはボディと違って、パーティクルがデータフィールドを持っておらず、代わりにワールドがすべてのパーティクルの情報をバッファ (メモリ的に連続した配列) として持っています。

例えば、ユーザデータのバッファは_world->GetParticleUserDataBuffer()で得ることができます。

さらに、パーティクルグループはそのバッファ内の特定の区画を占めており、その先頭インデックスはgroup->GetBufferIndex()で得ることができます。

以上をまとめて、ユーザデータを登録するコードを書くと次のようになります。

        int32 offset = group->GetBufferIndex();
        void** userdata = _world->GetParticleUserDataBuffer() + offset;
        for (size_t i = 0; i < group->GetParticleCount(); ++i) {
                  :
            *userdata = (__bridge void*) node;
            ++userdata;
        }

パーティクルの位置更新と削除

ワールドがすべてのパーティクルの情報をバッファとして管理しているというのを先ほど説明しましたが、これは位置などについても同じことが言えます。

それらを考慮して、いつものように_world->step()後に、位置を更新しつつ、地面より下になったら削除するようにしたコードを-update:に書きます。

b2Vec2* v = _world->GetParticlePositionBuffer();
void** userdata = _world->GetParticleUserDataBuffer();
uint32* flags = _world->GetParticleFlagsBuffer();
for (int i = 0; i < _world->GetParticleCount(); ++i, ++v, ++flags, ++userdata) {
    const bool is_remove = v->y < 0;
    SKNode* node = (__bridge SKNode*) *userdata;
    if (is_remove) {
        *flags |= b2_zombieParticle;
        [node removeFromParent];
    } else {
        node.position = CGPointMake(v->x * DISPLAY_SCALE, v->y * DISPLAY_SCALE);
    }
}

ゾンビパーティクルを使う

LiquidFun Programmer’s GuideのParticle Moduleによると、

        _world->DestroyParticle(i);

で直接パーティクルを破棄するよりも、

        _world->GetParticleFlagsBuffer()[i] |= b2_zombieParticle;

で、一旦ゾンビを作成するほうが効率がよいみたいです。ゾンビは次のstep()で破棄されるようになっています。

フラグを変更したときの挙動変化

b2ParticleDefflagsの値を変えることで、パーティクルの挙動が大きく変化します。

そこで、値をかえたときの挙動をgifアニメにしてみました。

Water

ただの水です。

Tensile

物体の表面の流れる水。

Viscous

油みたいなねばっこいものらしいです。

Powder

砂など。

Elastic

やわらかいゴムみたいなもの。

Spring

ばねっぽいもの?

ちなみに、これらの値はビットフィールドになっているので、値を組み合わせることもできます (ただし、b2_waterParticle0なので別)。

それぞれの挙動の詳細などはLiquidFun Programmer’s GuideのParticle Moduleを参照ください。

関連リンク

0 件のコメント:

コメントを投稿

注: コメントを投稿できるのは、このブログのメンバーだけです。