私に来る多くのアイデア、誰かがすでに実装しているか、すぐに実現します(インターネットからの引用)
2001年に、リアルタイム戦略のファンである私は、ゲーム「コサック」に衝撃を受けました。 マップをローミングしている巨大な群衆に打たれました。 これらの群衆が当時の低電力コンピューターでかなり活発に走っていたことは印象的でした。 しかし、当時私は救急車に取り組んでいたので、プログラミングには程遠いので、これは賞賛に限定されていました。
すでに私たちは、ほぼ同じ数の移動ユニットでおもちゃを作りたかったのです。そのため、「叙事詩」が屋根を通り抜けただけです(!)。 そして、これらのユニットが単に移動するだけでなく、外向きに(!)移動するように。 そして(最も重要なことですが)、この素晴らしさはすべて弱いモバイルプラットフォームで機能していました。
疑問が生じました-どうやって? グラフィックスに疑問はありません。最新のプラットフォームには、生産性の観点から異なるグラフィックライブラリがあり、群衆の画面への出力を処理します。 主な質問は、プレーヤーが明確に認識する意味のある(または意味のない)何かをプログラムで実現する方法です。これは、単にちらつきする数字のセットではなく、何らかのインセンティブの対象となる群衆です。
私は多くの推奨事項、文献、さらには実装があると確信しています。 しかし、私は「気取らない」ものに興味がありました。それは「携帯電話」の簡単なおもちゃで使用でき、「膝の上」で組み立てられます。 つまり 安くて陽気な、そして最も重要なこと-私には明らかです(!)。
一年前、私はミハイル・ブラゼノフによるKRI 2008からのすばらしい講義(この音声の印刷版は見つけられませんでした)を聞いて、 モバイルゲームのコンベア開発の詳細、計画、移植、テストについて聞きました。 この記事の重要な結論-大量のデータを使用してアプリケーションロジックを作成する場合、データ指向のパラダイムを使用する必要があります。 (Tadaaam!)。
アイデアを聞いて生まれたエッセンスは次のとおりです。
- ゲーム内のすべての(すべての)さまざまな「個人の動機付けの動機」は、ベクトルのシステム(順序、恐怖、憎しみ、怠laz、難聴、慣性...)で記述しなければなりません。 1ゲームサイクル(または10分の1、それは重要ではありません)の間に、これらのベクトルを使用したさまざまな操作が実行されます。 すべての操作の結果はベクトルの合計になり、最も単純なケースでは、キャラクターの動きの方向(数百または数千)を決定します。
- 計算の標準化のために、各「個別」には同じベクトルのセットが必要です。 それから、小さな繰り返しコードの一部、つまりパイプラインが、群衆の中の個々の「個人」の行動を決定できます。 コンベアの美しさは、彼が処理する「だれ」にとっても絶対に重要ではないことです。太った男、転がる車輪、死体...彼にとっての主なものは「動機」のセットです。 このおかげで、出口に群衆ができただけでなく、多様な(!)群衆ができました。
さて、メカニズム自体を記述する必要がありました...しかし、一体何でしょう! 結局のところ、アイデアがあなたを訪問した場合、それはあなたの前に別の1億人がすでに訪問した可能性が非常に高いです。 それとも、この数百万人の誰かが適切なツールを作成したのでしょうか? ライブラリはありますか?
そのようなツールが見つかりました-パーティクルシステム(パーティクルシステム) 。 FLINTを選択しました。
- プロトタイピングに理想的な粒子システム
- 粒子システム内の計算は、古典力学の法則に基づいています-つまり 現在、独自のプログラミングニーズに適応できる無数の多様なアルゴリズムが既にあります。
- サードパーティのライブラリからのレンダリングは、パーティクルシステムにねじ込むことができます。
- パーティクルシステムのこのバージョンは、 Richard LordによるEntity SystemフレームワークAshの作成者によって書かれました(これは別の歌です)。
パーティクルシステムの特定の実装のための基本的なツールを簡単に説明します。
- エミッタ( Emitter2D )-実際にはクラスのインスタンスであり、指定されたパラメータに従って、パーティクルの生成を担当し、さらに「メンテナンス」
- Emitter2D.addInitializer(初期化子) -「プロパティ」をエミッターに追加する方法で、生成時にパーティクルに割り当てられます。
- Emitter2D.addAction(アクション) -このメソッドを使用すると、パーティクルコントロールルールがエミッタに追加されます。これは、パーティクルの寿命中にパーティクルに適用されます。
もちろん、私の夢では、「動機ベクトル」の助けを借りて、ユニット内のキャラクターについてのあなた自身の意見で、「独立した」ものをたくさん使ってRPGを作ることさえできるように思えますが、シンプルなものから始める必要があります。 タワーディフェンスのジャンルを選びました。 ゲーム内でのみ、「タンク」の液体の小さな細流ではなく、ブラジン(!)のホストを確認したいので、爆発、崩壊、平坦化、散布のように散布できます...
Uff ...序文の終わり。 しかし、私にはそれが重要だと思われます。
アイデアの実装
最も簡単なタスクから始めましょう:
- 複雑なルートを生成する
- キャラクターは与えられたルートをたどります
- 互いに「合わない」
- 進行方向を「見る」
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(); }
したがって、タスクに必要なライブラリを検索し、最小限の「仕上げ」を行った結果、時間と費用対効果の比率が非常に良好な結果が得られました(この言葉は恐ろしくもありません!)。 そして、これはデバッグプレーヤーで起動されたプロトタイプにすぎません。
リリース中に、コードを最適化することができます(必要な場合もあります)。
- いくつかのアクション ( DeathZoneとFollowWaylines 、 MoveとRotateToDirectionとActionResistanceなど )を組み合わせます。 つまり -1つのアクションを最適化することにより、少なくともエミッター内のパーティクルの数だけ反復回数を減らします。
- ルートの直線部分でルートの中間点を削除します
コードはgoogle codeで入手できます 。
PS:次の部分では、タスクを複雑にします。 追加:
- 爆発(体の散乱あり)
- 遅いキャラクター(これらは大きな矢印になります)
- 遅い矢印の周りに速く曲がる
PPS:他の記事では、コードをjavascriptに移植しています(少し時間をかけずに済むように、少し経験を積んでいきます)