群衆モデリングの粒子システム

私に来る多くのアイデア、誰かがすでに実装しているか、すぐに実現します(インターネットからの引用)




2001年に、リアルタイム戦略のファンである私は、ゲーム「コサック」に衝撃を受けました。 マップをローミングしている巨大な群衆に打たれました。 これらの群衆が当時の低電力コンピューターでかなり活発に走っていたことは印象的でした。 しかし、当時私は救急車に取り組んでいたので、プログラミングには程遠いので、これは賞賛に限定されていました。



すでに私たちは、ほぼ同じ数の移動ユニットでおもちゃを作りたかったのです。そのため、「叙事詩」が屋根を通り抜けただけです(!)。 そして、これらのユニットが単に移動するだけでなく、外向きに(!)移動するように。 そして(最も重要なことですが)、この素晴らしさはすべて弱いモバイルプラットフォームで機能していました。



疑問が生じました-どうやって? グラフィックスに疑問はありません。最新のプラットフォームには、生産性の観点から異なるグラフィックライブラリがあり、群衆の画面への出力を処理します。 主な質問は、プレーヤーが明確に認識する意味のある(または意味のない)何かをプログラムで実現する方法です。これは、単にちらつきする数字のセットではなく、何らかのインセンティブの対象となる群衆です。



私は多くの推奨事項、文献、さらには実装があると確信しています。 しかし、私は「気取らない」ものに興味がありました。それは「携帯電話」の簡単なおもちゃで使用でき、「膝の上」で組み立てられます。 つまり 安くて陽気な、そして最も重要なこと-私には明らかです(!)。





一年前、私はミハイル・ブラゼノフによるKRI 2008からのすばらしい講義(この音声の印刷版は見つけられませんでした)を聞いて、 モバイルゲームのコンベア開発の詳細、計画、移植、テストについて聞きました。 この記事の重要な結論-大量のデータを使用してアプリケーションロジックを作成する場合、データ指向のパラダイムを使用する必要があります。 (Tadaaam!)。



アイデアを聞いて生まれたエッセンスは次のとおりです。


  1. ゲーム内のすべての(すべての)さまざまな「個人の動機付けの動機」は、ベクトルのシステム(順序、恐怖、憎しみ、怠laz、難聴、慣性...)で記述しなければなりません。 1ゲームサイクル(または10分の1、それは重要ではありません)の間に、これらのベクトルを使用したさまざまな操作が実行されます。 すべての操作の結果はベクトルの合計になり、最も単純なケースでは、キャラクターの動きの方向(数百または数千)を決定します。
  2. 計算の標準化のために、各「個別」には同じベクトルのセットが必要です。 それから、小さな繰り返しコードの一部、つまりパイプラインが、群衆の中の個々の「個人」の行動を決定できます。 コンベアの美しさは、彼が処理する「だれ」にとっても絶対に重要ではないことです。太った男、転がる車輪、死体...彼にとっての主なものは「動機」のセットです。 このおかげで、出口に群衆ができただけでなく、多様な(!)群衆ができました。




さて、メカニズム自体を記述する必要がありました...しかし、一体何でしょう! 結局のところ、アイデアがあなたを訪問した場合、それはあなたの前に別の1億人がすでに訪問した可能性が非常に高いです。 それとも、この数百万人の誰かが適切なツールを作成したのでしょうか? ライブラリはありますか?



そのようなツールが見つかりました-パーティクルシステム(パーティクルシステム)FLINTを選択しました。

  1. プロトタイピングに理想的な粒子システム
  2. 粒子システム内の計算は、古典力学の法則に基づいています-つまり 現在、独自のプログラミングニーズに適応できる無数の多様なアルゴリズムが既にあります。
  3. サードパーティのライブラリからのレンダリングは、パーティクルシステムにねじ込むことができます。
  4. パーティクルシステムのこのバージョンは、 Richard LordによるEntity SystemフレームワークAshの作成者によって書かれました(これは別の歌です)。




パーティクルシステムの特定の実装のための基本的なツールを簡単に説明します。

  1. エミッタ( Emitter2D )-実際にはクラスのインスタンスであり、指定されたパラメータに従って、パーティクルの生成を担当し、さらに「メンテナンス」
  2. Emitter2D.addInitializer(初期化子) -「プロパティ」をエミッターに追加する方法で、生成時にパーティクルに割り当てられます。
  3. Emitter2D.addAction(アクション) -このメソッドを使用すると、パーティクルコントロールルールがエミッタに追加されます。これは、パーティクルの寿命中にパーティクルに適用されます。




もちろん、私の夢では、「動機ベクトル」の助けを借りて、ユニット内のキャラクターについてのあなた自身の意見で、「独立した」ものをたくさん使ってRPGを作ることさえできるように思えますが、シンプルなものから始める必要があります。 タワーディフェンスのジャンルを選びました。 ゲーム内でのみ、「タンク」の液体の小さな細流ではなく、ブラジン(!)のホストを確認したいので、爆発、崩壊、平坦化、散布のように散布できます...



Uff ...序文の終わり。 しかし、私にはそれが重要だと思われます。





アイデアの実装



最も簡単なタスクから始めましょう:

  1. 複雑なルートを生成する
  2. キャラクターは与えられたルートをたどります

    • 互いに「合わない」
    • 進行方向を「見る」






1.複雑なルートを生成する


つまり -パスはポイント( ウェイポイント )で構成されます。 パーティクルは、ポイントからポイントに順番に移動する必要があります。 残念ながら、対応するアクションはライブラリにありませんでした。 しかし、私は別のライブラリ(ちなみに、私が選んだものに基づいて作成されたライブラリ) -stardust-particle-engine実装を調査しました。







唯一のことは、見つかった実装では、パスのポイントでパーティクルがヒープ内に積み重なり、ルート全体に沿って「広い前線」をたどらないことです。 そのため、ニーズに合わせてクラスをわずかに変更しました



ウェイライン -クラス名、元のウェイポイントからの不器用な「継承」

クラスの本質は、接線パスに対する垂線を格納することです。 アクションFollowWaylinesを使用してパーティクルが生成されると、垂線上の相対位置に関するデータが保存されます。 そして、移動中に、彼らはこの位置に固執するように「試行」します-このように、パスのノードに積み重なることはありません-それらはこの点で接線に垂直に分布しますベジエ )。



package waylines.waypoints { import flash.geom.Point; import flash.geom.Line; public class Wayline extends Waypoint { public var rotation:Number; public var line:Line; public function Wayline(x : Number = 0, y : Number = 0, segmentLength : Number = 40, rotation:Number=0, strength : Number = 1, attenuationPower : Number = 0, epsilon : Number = 1) { super(x, y, segmentLength/2, strength, attenuationPower, epsilon); this.rotation = rotation; this.line = new Line(new Point(x - (radius * Math.cos(rotation)), y - (radius * Math.sin(rotation))), new Point(x + (radius * Math.cos(rotation)), y+(radius * Math.sin(rotation)))); } } }
      
      







次に、節点で構成されるパスを生成します



 protected function setupWaylines():void { _waylines = []; var w:Number = stage.stageWidth; var h:Number = stage.stageHeight; /* * 1.      ,         * 2.        ,    -  ,     */ //var points:Array = [new Point(-9,h*.6), new Point(w*.3,h*.3), new Point(w*.5,h*.25), new Point(w*.6,h*.45), new Point(w*.7,h*.7), new Point(w*.8, h*.75), new Point(w*.9, h*.6), new Point(w*1.3, h*.5)]; var points:Array = [new Point(-9,h*.4), new Point(w*.3,h*.4), new Point(w*.5,h*.1), new Point(w*.8,h*.1), new Point(w*.8,h*.9), new Point(w*.5, h*.9), new Point(w*.3, h*.8), new Point(-40, h*.8)]; /* * : * 1.  Wayline        * 2.         * 3.       * : *       http://silin.su/#AS3,  * 1. FitLine - ,  ""   * 2. Path - ,    ,   ,   .  -    . * 3. ""        */ var fitline:FitLine = new FitLine(points); var path:Path = new Path(fitline.fitPoints); /* * ! -          -    . * ,     ""    ,  "" ,      . *   -   ,     ,   ...   */ var step:Number = path.length / 40; /* *      - ,    "  "? * ..   ,        -     */ var strength:Number = 100; //     for(var i:int=0; i<path.length; i+=step) { //         var segmentLength:int = 60;//*Math.random()+10; var pathpoint:PathPoint = path.getPathPoint(i); var wayline:Wayline = new Wayline(pathpoint.x, pathpoint.y, segmentLength, pathpoint.rotation-Math.PI/2, strength); _waylines.push(wayline); } }
      
      







2.キャラクターは所定のルートをたどりますが、

  • 互いに「合わない」
  • 進行方向を「見る」




このアイテムは、パーティクルシステム自体の設定を使用して実装されます。 つまり -エミッターを構成する



 protected function setupEmitter():void { // ---      ,          ------------- var emitter:Emitter2D = new Emitter2D(); //   -       emitter.counter = new Steady(60); //    var wayline:Wayline = _waylines[0]; //     LineZone  ,      "" Wayline emitter.addInitializer( new Position( new LineZone( new Point(wayline.x - wayline.radius*Math.cos(wayline.rotation), wayline.y - wayline.radius*Math.sin(wayline.rotation)), new Point(wayline.x + wayline.radius*Math.cos(wayline.rotation), wayline.y + wayline.radius*Math.sin(wayline.rotation)) ) ) ); // ,        //emitter.addInitializer( new ImageClass( ArrowBitmap, [4] ) ); emitter.addInitializer( new ImageClass( Arrow, [4] ) ); // ---  actions,        --------------------------------------------- //       (!) . ..       // 1.  ( ) -   -      // 2.  -    (   ,    " ") // 3.   -   -     ,   -  //     action   ,         "" action FollowWaylines emitter.addAction( new DeathZone( new RectangleZone( -30, -30, stage.stageWidth+60, stage.stageHeight + 60 ), true ) ); // new Move() -       . .. -   ,      action emitter.addAction( new Move() ); // ,         emitter.addAction( new RotateToDirection() ); //      emitter.addAction( new MinimumDistance( 7, 600 ) ); //    action    (  "" SpeedLimit,      -      ) emitter.addAction( new ActionResistance(.4)); //  "" action,          emitter.addAction( new FollowWaylines(_waylines) ); //   //var renderer:BitmapRenderer = new BitmapRenderer(new Rectangle(0, 0, stage.stageWidth, stage.stageHeight)); var renderer:DisplayObjectRenderer = new DisplayObjectRenderer(); //     addChild( renderer ); //       renderer.addEmitter( emitter ); //   emitterWaylines = emitter; emitterWaylines.start(); }
      
      











したがって、タスクに必要なライブラリを検索し、最小限の「仕上げ」を行った結果、時間と費用対効果の比率が非常に良好な結果が得られました(この言葉は恐ろしくもありません!)。 そして、これはデバッグプレーヤーで起動されたプロトタイプにすぎません。

リリース中に、コードを最適化することができます(必要な場合もあります)。

  1. いくつかのアクションDeathZoneFollowWaylinesMoveRotateToDirectionActionResistanceなど )を組み合わせます。 つまり -1つのアクションを最適化することにより、少なくともエミッター内のパーティクルの数だけ反復回数を減らします。
  2. ルートの直線部分でルートの中間点を削除します




コードはgoogle codeで入手できます



PS:次の部分では、タスクを複雑にします。 追加:

  1. 爆発(体の散乱あり)
  2. 遅いキャラクター(これらは大きな矢印になります)
  3. 遅い矢印の周りに速く曲がる




PPS:他の記事では、コードをjavascriptに移植しています(少し時間をかけずに済むように、少し経験を積んでいきます)



All Articles