前回の記事「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
をつくっている点です。
で、実際にSKNode
をuserdata
に紐付けするのですが、
パーティクルはボディと違って、パーティクルがデータフィールドを持っておらず、代わりにワールドがすべてのパーティクルの情報をバッファ (メモリ的に連続した配列) として持っています。
例えば、ユーザデータのバッファは_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()
で破棄されるようになっています。
フラグを変更したときの挙動変化
b2ParticleDef
のflags
の値を変えることで、パーティクルの挙動が大きく変化します。
そこで、値をかえたときの挙動をgifアニメにしてみました。
Water
ただの水です。
Tensile
物体の表面の流れる水。
Viscous
油みたいなねばっこいものらしいです。
Powder
砂など。
Elastic
やわらかいゴムみたいなもの。
Spring
ばねっぽいもの?
ちなみに、これらの値はビットフィールドになっているので、値を組み合わせることもできます (ただし、b2_waterParticle
は0
なので別)。
それぞれの挙動の詳細などはLiquidFun Programmer’s GuideのParticle Moduleを参照ください。
0 件のコメント:
コメントを投稿
注: コメントを投稿できるのは、このブログのメンバーだけです。