ステラリス 開発者日記 第182回 スクリプトの危険とそれを回避する方法

スポンサーリンク
更新情報

パラドックス社の公式フォーラムに
ステラリスの開発者日記 第182回が掲載されています。

Stellaris Dev Diary #182 : The Perils of Scripting and How to Avoid Them
Hi everyone! I am Caligula, one of Stellaris’ Content Designers, which means that I do a variety of tasks based around n...

今回はスクリプトに関するお話で、技術的な内容になります。

私には技術的な事はわかりませんので
以下の訳文は正確さに欠ける点が(いつもに増して)大いにあると思います。
参考程度にお読みくださいm(_ _)m

以下パラドックスフォーラムの内容を意訳したものとなります。
※画像等はフォーラムより引用。
スポンサーリンク

ステラリス 開発者日記 第182回 スクリプトの危険とそれを回避する方法

今回の担当はステラリス・コンテンツデザイナーのCaligula Caesarさんです。

冒頭のあいさつ

皆さんこんにちは!
私はStellarisのコンテンツデザイナーの1人、Caligulaです。
これはつまり、私がナラティブな文章を書くこととスクリプトを書くことに基づいて、様々なタスクを行っていることを意味しています。
「スクリプティング」はプログラミングに多少似ていることを行うための用語ですが、ソースコードを変更する必要はありません。
言い換えれば、私はMod製作者が行っていることをやっています(とはいえ、わたしにはソースコードを覗いてみて、必要なときに変更できるという大きな利点もあります)。
すべてのコンテンツデザイナーにはそれぞれの得意分野がありますが、私の場合、特に複雑なシステムをスクリプト化する必要があるときに(あるいは、もっとよくある例としてある種のトラブルを引き起こしています-「天上の戦い」はいまでも私に悪夢を思い起こさせます…)、その領域へと足を踏み入れることとなります。

さて、今後数週間から数ヶ月の間に、私たちはたくさんのエキサイティングな事柄を披露していきますが、今日は前回の開発者日記の後に尋ねられたいくつかの質問に触発され、私はMod製作者および野心的なMod製作者のためにスクリプト作成の技術的側面について、特に何がパフォーマンスの問題を引き起こす可能性があるか、どうすれば悪いスクリプトを作成するのを避けることができるかについて書きたいと思います。

Stellarisのスクリプト言語は非常に強力なツールであり、多くのことを実行できますがまず注意点があります。
つまり、何かが可能だからといって実行する必要があるわけではないということです。
なぜならば(私はこれまでの経験から)この態度こそが、パフォーマンスの問題と読めないスクリプトの両方を引き起こすことがほぼ確実であり6ヶ月後それらスクリプトの一部が壊れていることに気づいた後で混乱を解消することができなくなるためです。

コードで何かを行う方が、スクリプトよりも高速であることに注意してください。
つまり、コード内では、単一の関数をチェックしてそれを処理することができますが、スクリプトを介してそれにアクセスできるようにしたい場合、関数をチェックする前に通過しなければならないかなりの数の必要な関数があります(スクリプトの行をコード・コマンドに変換し、正しいスコープで使用されているかどうかをチェックするなどです)。
したがって、これがいくつかのものがハードコードされている理由であり、また発生した問題の解決法がでたらめなものになってしまうのはなぜかという事に繋がります。
ですから、最初に検討すべき問題は本当にこれを実行する必要があるのか?​​ということです。

とはいえ、私はここでMod製作者の方に向けて話しているので、もちろん皆さんはそれを実行するのでしょう。そしてかなり苦労することになるでしょう…。

パフォーマンスの問題の原因はなにか?

チェックを実行したりエフェクトを実行するたびに、コンピュータの処理能力のうち非常に小さくな量を消費します。
控えめに使用すべきいくつかの例外(私は、後でそれらに触れます)を除き、これは全く例外なく何をするにも必要です。
問題が発生するのは、多くのオブジェクトに対してチェックが頻繁に繰り返される場合です。
実際には、これは通常ポップが原因であることを意味しますが、銀河内のすべての惑星にわたって何かを実行することもかなり悪いアイデアだと言えるでしょう。

最初のステップとして、可能であればスクリプトの実行時期を制御することをお勧めします。
そのための最善の方法はイベントが発生する場所を設定し、可能な限りon_actions (あるいはデシジョンなどからイベントを発生させたり)を使用することです。
ある程度のランダム性が必要な場合は、例えば年1回のパルスを使って隠しイベントを発生させてから、実際に必要なイベントをランダムな遅延で発生させることもできます(たとえば、イベントアクションを.220でチェックアウトします)。

もちろん、すべてがイベントであるわけではありません。
しかし、残念なことにStellarisでは多くのことがポップにより行われています!
特に、ジョブ・ファイル内のジョブの重みおよびその他のトリガーは、過去に問題を引き起こすことが示されています。
大雑把に言えば、たとえあなたが超クールなことをすることができるとしても、ジョブに対してあまりにも複雑なスクリプトの焚き付けを行うことは悪い考えなのです。

たとえば、惑星上に失業しているポップが他に存在しないことに起因してジョブの重みを作成する場合(planet = { any_owned_pop = { is_unemployed = yes } }を使います)、惑星上のすべてのポップを定期的にチェックし、次に惑星の他のすべてのポップをチェックすることになります。つまりポップの2乗です。
ゲームの後半に達すると、問題が発生することはほぼ確実でしょう。

管理人感想:でもわざわざそんな事しなくてもゲーム後半ですでに激重ですやんとか言ったらダメでしょうか…(´・_・`)

じゃあ何ができるのか?

入れ子にされたループ構造を避け、イベントが適切に起動されるようにし、可能であればポップを回避することで、何らかの方法を得ることができますが、それ以上のこともできます。
スクリプトを最適化するためのアドバイスを以下に示します。

常に最適なスコープを使用する

以下ソースコードが多くなりますが、サイト内にコードを上手く貼れないので、インデントによる整形等までは行いません。
正確なところは元ページにてご確認ください。

例えば、現在の帝国の連邦におけるリーダーについて何か確認したいとします。
理論的には、次のようにすることができます。

any_country = {
is_in_federation_with = root
is_federation_leader = yes
<my_triggers_here> = yes
}

これは、ゲーム内のすべての国を通過し、それらがスペースアメーバの国と下劣なPasharti(および誰が連邦に入れるでしょうか?)を含み、あなたと同じ連邦に属しているかどうかを確認します。それらは明らかに無関係な存在です。
したがって、より適切なチェックは、次のように行います。

any_federation_ally = {
is_federation_leader = yes
<my_triggers_here> = yes
}

コード上では、ゲームが帝国単位から連邦へと移行し、現在の帝国を除く連邦メンバーのリストを取得し、それらに対してトリガーをチェックします。
したがって、これは明らかにチェックの数が少なくなります。
しかし、更に最良のバージョンは次のようになります。

federation.leader = {
<my_triggers_here> = yes
}

このバージョンでは連邦に対して直接(コマンドが)送られ、そこからリーダーの要素に直接いきます。
必要なスクリプトからコードへの変換が可能な限り少なく、そこに到達するためにトリガーを国に対してチェックする必要はありません。
また、最も読みやすいです(読みやすさとパフォーマンスの向上には相互関係があることがよくあります…)。

この場合、ゲーム内において最初のバージョンのコードだと約50カ国をチェックし、2番目のバージョンが5カ国、3番目が1カ国となります。
同様のロジックを使用して、銀河内のすべてのオブジェクト(特に、すべてのポップまたはすべての惑星)をチェックするのではなく、フィルタリングされたリストを使用することをお勧めします。
たとえば、
any_planet = { solar_system.owner = { is_same_value = prevprev } } の代わりにany_planet_within_borderを使用します(あなたは笑うかもしれませんが実際に私はそれを見たことがあります)。
また実際は常にany_owned_shipの代わりにany_owned_fleetでチェックします。

管理人補足:最後の箇所は艦隊の保有者を探るために船(ship)の保有者でチェックすると科学船とか建設船まで含んでしまって検索対象に無駄がでるので、最初からfleetで検索しましょうねってことだと思います(・_・)

ステラリス Ver2.6で追加したもう1つの重要な改善点は、any_owned_speciesです。これは、多くのany_owned_popチェック(特に、ポップの特性などをチェックするチェック)を置き換えることができ、チェックする必要のあるオブジェクトの数が少なくなります(異星人嫌いの帝国では、 any_owned_speciesの場合は1桁、any_owned_popの場合は数千桁になります。)

場合によっては、スコープを完全に回避できることも

同様の注意として、スコープを使用せずに何かをチェックできる場合は、常に改善できます。
例えば惑星に鉱山労働者として働いているポップが2人以上あるかどうかを確認したい場合は、次の2つの方法があります。

count_owned_pop = {
count > 2
limit = {
has_job = miner
}
}
num_assigned_jobs = {
job = miner
value >= 2
}

前者は惑星上のそれぞれのポップをチェックして、それがマイナーなジョブを持っているかどうかを確認し、その数が2より大きいかどうかを確認します。
後者はゲームが既に計算した、キャッシュされた数をチェックして、それが2より大きいかどうかを確認します。こちらの方が、はるかに速く実行できます。

まさに価値のあるもの

すべてのチェックまたは効果が等しいわけではありません。
通常、フラグや値のチェックは非常に簡単で変更もそれほど複雑ではありません。
ただし、ゲームがデータを再計算しなければならない場合は、すでにわかっている数値を検索するだけではないため時間がかかります。
何か新しいものをしたり、少々複雑なこと(create_speciesの効果は、冗談抜きで600行を超えるC++コードとなります…)をすると、おそらくすべての種類の再計算が必要になるため、よりコストがかかります。
どのトリガーやエフェクトが悪いのかに気づく事は少し難しいかもしれませんが、原則として以下のようなケースに注意してください。

  • create_country、create_species、modify_speciesなど、新しいスコープを作成するすべてのもの
  • パス検索を計算または再計算する必要があるもの(例:can_access_systemトリガ、新しいハイパーレーンの作成、特に新しい星系の作成)
  • popを計算するもの(例えば惑星上のポップのジョブなど)

それをやらなければならない場合…

時には悪いこともしなければならないでしょう。
このような場合でも、それほど優れていないものでも正確に使用するのが最善です。
ゲームがイベントなどのトリガをチェックしている時、何かが偽(これは「短絡評価」と呼ばれているそうです。)を返した時点でトリガのチェックを停止するのが一般的なので次のようにします。

trigger = {
has_country_flag = flag_that_narrows_things_down_loads
<something really horrible here>
}

私は最近、難民のポップの効果のためにこのようなことをしました。
以前は少々異常だった(完全な恐怖については01_scripted_triggers_refugees.txtを参照)。
合計で、以下の変化を最大8回チェックします。

any_relation = {
is_country_type = default
has_communications = prev #relations include countries that have made first contact but not established comms
NOT = { has_policy_flag = refugees_not_allowed }
prevprev = { #this ensures Pop scope, as root will not always be pop scope
OR = {
has_citizenship_type = { type = citizenship_full country = prev }
has_citizenship_type = { type = citizenship_caste_system country = prev }
AND = {
has_citizenship_type = { type = citizenship_limited country = prev }
has_citizenship_type = { type = citizenship_caste_system_limited country = prev }
prev = { has_policy_flag = refugees_allowed }
}
}
}
any_owned_planet = {
is_under_colonization = no
is_controlled_by = owner
has_orbital_bombardment = no
}
}

これが変化するのは、ただ最後のany_owned_planetだけでした。
つまりそれは、ポップが住むのに本当に良い惑星との関係を見つけようとし、それからかなり良い、そしてかなりまともな、そして最後に古い惑星へと至るでしょう。
これは明らかに非効率的です。難民を受け入れる国のリストは8回チェックしても変わりません。
これを回避し、スクリプトをずっと読みやすくするために、次のようにチェックの前にフラグを設定しました。

every_relation = {
limit = {
has_any_habitability = yes #bare minimum for being a refugee destination
}
set_country_flag = valid_refugee_destination_for_@event_target:refugee_pop
}

次の段階として、チェックは単に「その国にはフラグがありますか、もしあるなら、十分に良い惑星がありますか?」でなければなりません。

has_good_habitability_and_housing = {
has_country_flag = valid_refugee_destination_for_@event_target:refugee_pop
any_owned_planet = {
habitability = { who = event_target:refugee_pop value >= 0.7 }
free_housing >= 1
is_under_colonization = no
is_controlled_by = owner
has_orbital_bombardment = no
}
}

同様に、if-limitsとelse(また、可能であればswitch構文を使用することで、パフォーマンスが向上します。)をトリガに使用して、チェックを絞り込んで、作業中にずっと読みやすくすることもできます。
私は最近、種の権利のファイルを調べ、sanityのためにトリガーの許可を再編集しました。

以前のもの

custom_tooltip = {
fail_text = MACHINE_SPECIES_NOT_MACHINE
OR = {
has_trait = trait_mechanical
has_trait = trait_machine_unit
from = { has_valid_civic = civic_machine_assimilator }
}
}
custom_tooltip = {
fail_text = ASSIMILATOR_SPECIES_NOT_CYBORG
OR = {
NOT = { from = { has_valid_civic = civic_machine_assimilator } }
AND = {
OR = {
has_trait = trait_cybernetic
has_trait = trait_machine_unit
has_trait = trait_mechanical
}
from = { has_valid_civic = civic_machine_assimilator }
}
}
}

修正後のもの

if = {
limit = {
from = { NOT = { has_valid_civic = civic_machine_assimilator } }
}
custom_tooltip = {
fail_text = MACHINE_SPECIES_NOT_MACHINE
OR = {
has_trait = trait_mechanical
has_trait = trait_machine_unit
}
}
}
else = {
custom_tooltip = {
fail_text = ASSIMILATOR_SPECIES_NOT_CYBORG
OR = {
has_trait = trait_cybernetic
has_trait = trait_machine_unit
has_trait = trait_mechanical
}
}
}

第2のバージョンでは、種に機械的特性があるかどうかや、その国に同化者の市民権があるかどうかを2回ではなく1回チェックするだけであり、2番目のカスタムツールチップのトリガーが不快で変にならないので、より効率的です(またNANDsについてはすべて削除しました。私の脳を壊してしまうからです。)。

管理人補足:文中のNANDsとは1番目のコードに大量にあったNot AND構文のことかと。

Happy New Year

「ハッピーニューイヤー」のバグについて皆さんに告げずにこの開発日記を書くことはできません。
基本的に私たちは適度に大きな銀河で開発マルチプレイをプレイしており、ゲームのかなり終盤の時期に到達しました。
また、私たち全員が非常に変化するコンピューターとインターネット接続速度でリモートで作業していたため、パフォーマンスは少し遅くなっていましたが、ほとんどの場合は許容できるものでした。
ところが突然、1月1日に-20秒以上ものラグが発生することに気づきました。
これらのスパイクがあまりにも目立っていたので、私たちはゲームがフリーズするたびにお互いに新年おめでとうと言い始めました!

この遅延の始まりは偶然にも、いくつかの大帝国が統合を決意し、帝国を同化し始めた時期と一致していた。
さて、同化はスクリプトで行われるもののカテゴリーに分類されます。
後から考えれば、おそらくそのように行われるべきではなかったはずです…。
そして、毎年1月1日に同化している国ごとにイベントを発生させることによって機能します。
次に、このイベントは惑星上のポップの束を選択しそれぞれの惑星でmodify_speciesを少なくとも1回、時には4回使用し、各惑星のイベントを起動します。
これはかなり重要なパフォーマンスの独占になります!

さまざまな解決策を試した結果、これを修正する最善の方法はまず国の範囲からevery_owned_speciesを調べ、この種が同化されるべきかどうかをチェックし、そうであればmodify_speciesを使って同化先の種を作成し、同化元の種を示す種フラグを設定することでした。
その後、同化されたポップごとに新しい種を作成するのではなく、スクリプトを書き直して、そのポップがどのような種になるべきかを見つけ、単純にchange_speciesを使用します。
その結果はまだ読めないスクリプトですが(私は皆さんの目を守るため、ここには投稿しません)、私のテストでは複雑なエフェクト(modify_species)をできるだけ実行しないようにしたおかげで、年ごとのラグを50%以上減らしました。

今日はこれで終わりです!
たいていの人にとって、今日はいつもよりも退屈な開発日記だったと思いますが、それでも興味深いものだったことを望みます😊

最後のメモとして、これらのガイドラインに完全に準拠していないベースゲームのスクリプトの一部について、より勇気のある読者の方が私を呼び出してくれるのではないかと期待します。
この仕事というものは、本当に恐ろしいものを取り出してきて、それをより恐ろしさが少ないものに作り変える事以上に満足できることはほとんどありません。
ですのでどうぞ遠慮なく私を呼び出してください!!


以上

フォーラム内のやり取り(Q&A)

フォーラム内のやり取りで気になったものを紹介。
回答側はステラリス コンテンツデザイナーのRaygunGothさんです。

Q:より読みやすいものを使用した方がいいのではないでしょうか?
例えばC++、C#、VB、あるいは他の一般的なプログラミング言語です。
膨大な量の既存のコードを再処理しなければならないために、そういった事は行わないのでしょうか?

A:多くのゲームがコード化されたコンテンツにそれほど依存せず代わりにスクリプトを支持する主な理由は、読みやすさです。
XMLやその他のマークアップ言語は、多くの物語を伴うゲームや改造のために公開することを目的としたタイトルでは非常に一般的となっています。

PDS(パラドックススタジオ)のタイトルには多くのトリガーや修飾子が含まれているため、私たちが使用しているスクリプトはもう少し複雑ですが、コードを人間が読みやすいスクリプトから分離することで、よりナラティブなデザインとそれに合わせた記述に焦点を当てることができます。
これはチームのコアコンピタンスを少し広げてくれるだけでなく、通常はソースファイルとやりとりする必要がないので、開発ラインをスムーズにしてくれます。
同じことは、アート・アセットとスクリプトにも当てはまります。


他にもいくつかQ&Aがありましたが、技術的な事、Mod製作者以外はあまり関係なさそうだったので割愛します。

今回は以上ですm(_ _)m

感想・まとめ

以上、Stellaris 開発者日記 第182回の紹介でした。

日記再開2回目にしてとんでもないボリューム&理解しづらい内容で訳すのが大変でした…( -_-)

今回の感想で特に書けることもないので別のトピックでも。

ステラリスの日本語版がPS4でリリースされています。

私はそれ自体はとても良いことだと思ってて
日本人のステラリスファンが増えてほしいなぁと思うのですが、
どうもこのPS4版、文字があまりにも小さいとか色々と問題があるっぽいですね…。
個人的にはPS4で本当にあの激重の最終盤が動くのか?ってのも気になりますが…。

テキストフォントのサイズについてはUIにも絡んでくるので
アップデートで改善できる範囲なのか微妙な気もしますが、
少しでも良いものになってくれることを願うばかりです。

ていうか、やっぱりStellarisはPCで遊んだほうが良いとは思うんですが
今の御時世、PCを持ってない人、特に若者世代も増えているらしいので、
PC版を闇雲にも進められない背景もあり難しいところです(´・_・`)