中級者向け講座



中級者を目指すための基礎講座

  • はじめに
既にある程度OKEの開発に慣れた設計者の方々にはご存知のように、本ゲームには明確な到達目標など存在しません。
どこまでやり込むかも、どんな方向性で楽しむかも自由であり、この目標設定が既にゲームの内容の一部とも言えます。
つまり慣れれば慣れるほど、攻略の方向も、楽しみ方も、人それぞれ少しずつ変わってくるのです。

例えば、地雷をいかに上手に回避するかを毎日追い求める人がいます。しかしその横で、地雷を全部破壊して
突撃することに全力を注ぐ人もいるわけです。両者を同時に満足させる説明は、困難を極めます。
慣れるほど、細かい内容になるほど、その道を歩いた人だけの攻略法が作られていくのです。

こうした理由から、中級者向けに何を解説するべきか、あるいは自分も含めそもそもどこから中級者なのか、
判断が難しいところです。そこでまず中級者を目指す方向けに、大規模なプログラムを作成する際の必須の知識である、
プログラムの実行速度とタイミングの解説から入ろうかと思います。よろしくお願いします。

  • プログラムの規模とOKEの反応速度について
ハードウェアで選択するCPUには「記憶容量」と「実行速度」(と、重量)というパラメータがあります。
この「実行速度」とはプログラムの実行速度を表した数値で、単位の C / S は正確には Chips per Second であり、
「1秒間に実行するチップの数」という意味です。最高速度のCPUで240C/S、秒間240の判断や行動を実行することができます。
一見とんでもなく大きな数値に見えますが、実際はそれほどではありません。詳しく見ていきましょう。

このゲームでは、戦闘時の各種判定はフレーム単位で行われます。フレームとは動画を構成する1枚1枚の絵のことです。
戦闘中のシステムメニュー「戦闘更新速度」で「コマ送り」を選んだ際には、1フレームずつ状態を確認することができます。
ゲームは30fps、1秒間に30枚の絵が表示される仕組みとなっています。したがって最高速度CPUの240C/Sの場合、
1秒間=30フレームで240チップを実行するので、1フレーム(=1/30秒)あたり8チップを実行することになるのです。
実際に戦闘中にポーズをかけて、実行中の明るく表示されたチップの数を確認してみましょう。

この数値が「OKEの反応速度」に大きく影響します。OKEはあらゆる行動をその時点で読み込んだチップの指示通りに実行します。
どれほど優秀なプログラムを組んだとしても、それが必要なタイミングで実行されなければ宝の持ち腐れになるばかりか、
不要なタイミングで実行されれば却って足を引っ張る結果に繋がることもあります。


回避判定で考えてみましょう。回避判定の最初に「自機周辺に弾があるか」という分岐があり、
これがYESだったら回避動作を実行、NOだったら次の判定に進む場合、重要なのは弾が自機の周辺にあるタイミングで
最初の分岐が実行されることです。例えば自機周辺半径100mと範囲を設定した場合、できればその範囲に弾が入った瞬間、
100mという距離の余裕を持って回避動作に分岐するのが理想です。

しかしもしプログラムが長くなり、例えば最短でも40チップほど実行してようやくRETURNするプログラムだとどうでしょうか。
最速のCPUを用いても、1周まわるのに5フレーム以上かかります。回避判定が1周に1回しかない場合、
最悪なタイミングで弾が飛んでくると、回避動作は5フレーム以上遅れて実行されることになるわけです。

5フレーム=5/30秒、およそ0.17秒です。この間に、鉄鋼弾なら50m前後、レールガンやビーム系なら80m前後飛びます。
つまりその距離から撃たれた場合は無反応で直撃を受けることになりますし、仮に反応できてもその時点で30mも離れてなければ、
動作が間に合わず手遅れです。ビームの1発程度ならまだしも、レールガンだとその1撃が敗北に直結することすらあります。


回避判定を例に出しましたが、勿論全ての判定に同じことが言えます。
大規模なプログラムになるほど、あらゆる状況に対応した複雑な状況判断と行動をOKEに実行させられるのですが、
代わりに反応速度は確実に低下します。この微妙な反応の低下が積み重なることで、戦闘結果に少なからず影響が出るのです。
反応速度について考えずに大規模なプログラムを作ってしまうと、大抵は中小プログラムよりも総合的に弱体化します。

大規模なプログラムを構築しつつ反応速度を維持するノウハウは、主に2つ挙げられます。
サブルーチンを活用する方法と、ループ処理を活用する方法です。


  • サブルーチンを活用する
最大の記憶容量を持つCPUならば、右側に7×7サイズのサブルーチンを組むことが可能な領域が2つ存在します。
サブルーチンは、左側のメインプログラムの中に配置した「SUB:1」「SUB:2」のチップから実行することが可能です。
実行したサブルーチンが終了した時点で、メインに配置した「SUB」チップの先に処理が進む仕組みとなっています。

この「SUB」チップをメインの中で定期的に配置することで、サブルーチンの中に構築したプログラムを
メインプログラムが1周回る間に数回実行することが可能になります。例えば回避判定部分をサブルーチンに構築し、
メインプログラムが10チップほど進む度にサブルーチンを呼び出すようにすると、およそ2フレームに1回は
回避が必要かどうかを判定するようになり、反応速度は大幅に向上します。

注意点として、サブルーチン実行中は当然メインプログラムは「SUB」チップの場所で停止しています。
サブルーチンの中に常に実行される長いプログラムがあると、呼び出し回数に応じてメインプログラムの回転は鈍くなります。
サブルーチンに組んだ行動の反応は上がっても、他の行動は全てわずかに遅れるようになるのです。

したがって、必要な場合のみサブルーチンを実行するようにしておく必要があります。
回避の例ならば、自機が既にジャンプ中であったり急速移動中ならば、サブルーチンは実行する必要がありません。
不要な処理を減らして少しでもメインループの回転速度を上げ、各動作の反応を向上させましょう。


  • ループ処理を活用する
まず最初にことわっておくこととして、ループ処理は慣れないと高確率で致命的なバグを発生させる原因になり得ます。
ループ処理とは何か、ループ処理の有用性とその危険性について、まとめて簡単な例を挙げて解説いたします。
下の画像をご覧下さい。


これは「初心者向け講座」で扱ったプログラムの回避判定に、ミサイル対策を導入したものです。
高速飛翔体の判定前にミサイル判定を加えて「100m範囲にミサイルを検知したら伏せ続け、20mまで接近したらジャンプ回避」
という動作を想定して組んでいます。20m以内にミサイルがこない限り、通過モードの伏せる動作を繰り返し実行し続けます。

この繰り返し実行する部分、これがループ処理になります。ここで一時的にループさせることで、
ミサイルが20m範囲に入った瞬間、即座に次の行動を起こすことが可能になります。
ある特定の状況のみで判断させたい処理を素早く実行させたり、状況が変化した瞬間に
次の行動をタイミング良く実行させるために、ループ処理はとても有用なのです。

さてサンプル画像のプログラムを動作テストし、1対1でミサイルを相手に撃たせ、回避できることが確認できました。
意気揚々と3機チームを編成し、ミサイルを持つチームと対戦させてみると、なんということでしょう。
伏せたまま一向に動かない機体が現れたではありませんか。そんな死んだフリがOKEに通用するわけもなく、
数的不利な状況のまま全滅してしまうのでした。一体、どこに問題があったのでしょうか。

このサンプルは「向かってくるミサイルを100m距離で検知」→「20m範囲にミサイルが検知されるまでループ処理」
という形です。問題は、そもそも「向かってくるミサイル」が確実に自機の場所まで届く前提でループを組んでいる点です。
飛んでくるミサイルは、自機を狙ったものでしょうか。あるいは、自機に届く前に壁などがないでしょうか。
そうした様々な理由で、ミサイルが20m範囲に入ってこなかった場合、ループを抜け出すことができなくなります。

このケースでは、下の画像のように修正することで問題を解消できます。


矢印の先を一箇所ずらすだけで、ミサイルが20m範囲に来なくてもループを離脱するようになります。
また双方向ループを維持する場合も、今作から追加された機能を用いて下の画像のようにして問題を解決可能です。



今作からは、範囲指定時にRボタンを押しながら上下キー入力で、自機周辺を範囲外にすることが可能です。
これを利用して「ミサイルが20m - 100mの間に1発以上あれば伏せ続け、なくなったら回避判定」というループにするのです。
こうすると、20m以内にミサイルが入ってきたら回避判定でジャンプできます。また、例えミサイルが途中で消えたり
別方向に向かったとしても、ループは必ず解除されます。(この例でもまだ問題や無駄がありますが、とりあえず置いておきます)


長くなりましたが、要するにループ処理の利点と危険性をしっかりと理解しておいて欲しいということです。
ループを導入する際はこの手のバグは頻繁に発生しますので、十分な検証と動作テストを重ねましょう。
うまく使いこなせれば、限定的な状況下で素早い反応が可能になり、そのわずかな差が大きな結果に結びつくことでしょう。

[ポイントまとめ]
大規模プログラムを構築するときは、素早い反応が求められる行動の実行タイミングに要注意。
複雑で丁寧なプログラムほど、反応速度は低下する。多くの場合で不要な処理の扱いに気をつけよう。
キャンセルできない動作中に長い思考処理をさせるなど、各判定の必要性と実行するタイミングを考えてみよう。
サブルーチン・ループ処理は有用だが、同時に欠点もしっかりと把握して使いこなそう。
呼び出されるサブルーチンが毎回長ければ、メインプログラムが回らなくなる。
ループの脱出条件を一歩間違える=敗北に繋がる可能性が非常に高い。

[補足解説:ループ処理例]
参考としてミサイル対策ループをあげましたが、簡単に作れる比較的安全なループ処理の一例を追記解説しますと、
機体の各状態に合わせてループの出入りを行うのが一番手っ取り早く、安全なループ処理になります。
例えば、自機が被弾状態のときはほとんどの動作が実行できないので、専用のループに入って状況をチェックし、
オプション発動などを判断、状態回復直後に追撃を避けるための回避行動をとれるようにする方法があります。
逆にターゲットが被弾状態であれば、追撃用のループに入って細かい判断をさせてみるのもおもしろいかもしれません。
機体状態は自動的に回復するので、無限ループバグを発生させることなく、一定の効果が見込めるループ処理になります。


  • 処理を絞り込む
高速飛来物検出や、オプションの起動、被弾時の処理など、状況変化を常時監視したい処理は多くあります。
この様な処理は、頻繁に通過する場所(メイン先頭や頻繁に使うサブの先頭)に置くことで、簡単に常時監視することができます。
しかし、逆を言えば「必要不要問わずに頻繁に同じ処理を繰り返し行っている」ということになり、処理能力を無駄にしています。
これは応答速度を低下させる原因となります。

例えばオプションの冷却装置ですが、多くの場合で自機の熱量を常時監視する必要はありません。
冷却を起動する目的は、大抵被弾時の熱ダメージを防ぐか、攻撃の手数を増やすかのいずれかです。
熱ダメージを防ぐ目的であれば回避中、もしくは被弾中の処理に入る(もしくは出る)時に一回だけ行えば足ります。
攻撃の手数を増やすのであれば、熱が原因で攻撃を控えた時に判定すれば足ります。
このように常時監視している処理を、常時監視しないようにすることで処理のスループットを向上させることができます。

しかし常時監視を止めることで、監視できないケースが生じる可能性も出てきます。
冷却装置の例では、自機の熱量を監視する処理を回避中、もしくは被弾中の処理に入る(もしくは出る)時にした場合は、
「至近距離からのビーム(飛来物検出できないし、怯まない)」「ナパームによる炎上による発熱(飛来物検出はしないし、怯まない)」で
蒸し焼きにされる可能性があります。これを回避するには別の箇所にチェックを入れればいいのですが、
今度は実装面積が増えてしまいますし、あまりにチェックを入れすぎると結局処理能力を無駄にしてしまいます。

必要なタイミングを絞り込みピンポイントで判定すること、絞り込めないのであれば常時監視を止めた場合のリスクとその効果、
実装面積のトレードオフを分析した上で判断する必要があります。

(講座へのリンクと重要データへのリンクは外部講座・データ集リンクへ移転しました。)