基本的な考え方
スクリプトを最適化するポイントは、以下の通りです。
- スクリプトの処理時間を減らすこと
- スクリプトのコード量を減らすこと
- ゲーム全体の処理時間を減らすこと
同じ仕事量をしなければならないのであれば、できるだけゲームエンジンにやらせるようにして、スクリプトの仕事量を減らします。
コードが長ければそれだけ仕事量が多くて無駄も多いと言えます。スクリプトの読み込み時間も考えると、小さければ小さいほどよいです。とはいえ、いくら小さくしてもループで大量に回して長時間仕事をしていたのでは意味がありません。
ゲームエンジンだけでやらせるとどうしても100の仕事量になるが、スクリプトにやらせると10で済むというのであれば、スクリプトにやらせます。スクリプトの仕事量を減らしたいが為にかえってゲーム全体の仕事量が増えてしまうのでは本末転倒です。
RemoveItemはキーワードを受け付ける
武器をすべて表示させるModを作るとします。武器を見せるには武器に見える防具を用意して装備します。これをイミテーションと呼びます。こういったModはNexusにいくつかあります。
あるタイミングでイミテーションをすべて脱がせるという処理を考えます。
まずは最悪の実装です。
イミテーションをすべて脱がせる (papyrus)
Form[] MyImitationArmorList ; イミテーション防具を格納した配列
Function RemoveAllImitationArmor(Actor akTarget)
Int i = MyImitationArmorList.Length
while i > 0
i -= 1
if akTarget.IsEquipped(MyImitationArmorList[i])
akTarget.RemoveItem(MyImitationArmorList[i], abSilent = true)
endif
endwhile
EndFunction
この例の何がだめなのかというと、スクリプトでループをしているからです。
1ステップにつき1フレームかかるとして、イミテーションが10個なら10フレーム、100個なら100フレームかかります。つまり、イミテーションの数に比例して遅くなっていきます。
説明を単純にするために配列にしました。配列は128個の壁があるため、FormListにすればもっと多くのイミテーションを扱えます。当然ながら処理時間はさらに遅くなります。
これを1ステップで完了させる実装が以下になります。
イミテーションをすべて脱がせる(改善版) (papyrus)
Keyword ArmorTypeImitation ; イミテーションに付けてあるキーワード
Function RemoveAllImitationArmor(Actor akTarget)
akTarget.RemoveItem(ArmorTypeImitation, -1, abSilent = true)
EndFunction
Fallout 4のRemoveItemはキーワードを受け付けるようになりました。よって、キーワードを指定すれば1発ですべてのイミテーションを脱がせられます。
あらかじめキーワードを用意しておき、すべてのイミテーションに付与しておく必要があります。これは事前準備なので、ゲーム内では一切処理時間がかかりません。
また、Actorが同時に装備できる防具の数は32個です。つまり、どんなに多くても最大で32個の防具を脱ぐという処理になります。一方でイミテーションを片っ端から確認していく実装ではイミテーションの数が増えるほど確認にかかる時間も増えていきます。
確認を省く
スクリプトが1つの作業に1フレームかかるものとして、確認してから更新するのと、確認せずにいきなり更新するのとで、どちらが効率が良いのかという話です。
スクリプトからHUDを操作することを考えます。状況に応じてクロスヘアを表示したり非表示にします。
HUDの操作はUI.Get関数とUI.Set関数で行います。
まず、確認してから更新する場合です。
; 現状の状態を取得する
bool visible = UI.Get("hogehoge.fugafuga.sample_mc.visible") as bool
; 現状の状態を確認する
if visible
; 更新する
UI.Set("hogehoge.fugafuga.sample_mc.visible", false)
endif
取得、判断、更新の3ステップになります。これで3フレームです。本当に3フレームなのかはわかりません。if文はものすごく速いのかもしれませんし、UI関数はとてもコストのかかる操作なのかもしれません。ですので、あくまでも概算です。
一方で、更新だけの場合はこうなります。
; 更新する
UI.Set("hogehoge.fugafuga.sample_mc.visible", false)
更新だけの1ステップなので1フレームです。66%のコストカットに成功です。
ポイントは、スクリプトは遅いのでスクリプトを小さくするということです。UI関数の内部ではゲームエンジンやフラッシュのミドルウェアが動いて処理をするのだと思いますが、スクリプトに比べれば遥かに高速に動作しています。しなくてもよい無駄な更新をしたとしても、スクリプトが小さくなるのであればトータルでお得なわけです。