ステラリス 開発者日記 第240回 Ver3.3でのスクリプト改善

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

2022/2/5 フォーラム内のやり取りを少しだけ更新


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

今回はゲームバージョン3.3でのスクリプト改善に関するお話です。
また今後のMod製作に関わってくる箇所の改修についても触れられています。

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

ステラリス 開発者日記 第240回 Ver3.3でのスクリプト改造

今回の担当はステラリス開発チーム・テクニカルスクリプターのCaligula Caesarさんです。

冒頭のあいさつ

こんにちは、最近のリリース前数週間のタイミングにおける伝統となった、改造をベースとした新たな開発日記にようこそ。

私はそろそろスクリプトシステムに革命を起こす方法はもうなくなるのではないかと心配していますが、今のところはVer3.3でデビューするであろうとてもクールな改良をお見せすることができます(オープンベータ版でいち早く試すこともできます)。

Script Values(スクリプト値)

今回のお話は重さの分野から始まります。これは次のようなものを意味しています。

weight = {
    base = 1
    modifier = {
        factor = 2
        some_trigger = yes
    }
}

私たちは、このスクリプト構造の基礎となるコードが一貫していないことに気づきました。
エンド・ユーザーであるスクリプト作成者には容易にはわからない方法で、異なるコード実装が多数存在していました。
たとえば、factorやaddを入力できるものもあれば、factorやweightを入力できるものもあります。
さらに明らかに問題のあるケースもありました。
たとえば基数を0に設定しているのにゲーム側は1と読み取る場合があり、1つのケース(ai personalities)では、factorが実際にはaddとしてカウントされてしまいます!

これについての解決策は、すべてのバリエーションを削除しそれらを1つのコード実装に統合して、すべてをルール化することでした。
既存のバージョンの独自性(前述の問題/バグを除く)を組み込む必要があります(つまり、膨大な量のスクリプトを分割しません)が、一方で1つのシステムとするとでゲームのあらゆる場所で使用できる改善を展開することができます。

当初、いくつかの問題がありましたが(あるときには誤ってすべてのアノマリーをすべての惑星で発生させることができたかもしれません)、この改修はかなり達成可能であることが証明されたので、今ではこれらの分野が異なる場所で異なる動作をすることを心配する必要はありません。
そして基本的にはデフォルト値が1か0かの違いだけが残ります。

これでシステムにいくつかのものを追加できます。
例えばなぜ factor、add、weightさだけなのでしょうか?
他にも多くの数学的演算はあります。
そこで、subtraction、division、modulo、min、max、abs、rounding(round、floor、ceiling、round_to)の演算を追加しました。
またトリガーされるのではなく常に適用されるように意図されている場合は、これらをmodifier={}で囲む必要がなくなりました。

しかしこれは始まりにすぎませんでした。
Ver3.1ではこのような場所で数値の代わりに”“trigger:<trigger>”を使う機能を追加して、もっと複雑な計算ができるようにしました(例えばnum_popsは絶対数ではなく32 popsを返します)。
しかし、このコードはあまり理想的ではありませんでした。
基本的にゲームが”trigger:num_pops”の意味を計算したいときは、string(文字列)”trigger:num_pops”を読み取り、それが”trigger:” で始まっているかどうかを確認し、Yesの場合はそれを省いて残りのコードからトリガーを作成しようとします (失敗した場合はエラーを記録します)。
残念なことにこれは起動時には発生しませんが、ゲームがスクリプトでこれを検出したときにはいつでも発生します。
たとえばツールチップを計算する必要がある場合はフレームごとにこれを実行します。
そのためデバッグが面倒になり必要以上にコストがかかる可能性がありました。

このような事に関してはもっとうまくできるかもしれません。
そこでVer3.3では”CVariableValue”というコンテナを作成しました。
これにはいくつかのオブジェクトを含めることができます。

  • 整数または固定点(基本的には通常数)
  • スコープ/イベントターゲット owner.trigger:num_popsを参照できます。
  • トリガー
  • modifier定義
  • string型
  • スクリプト値*

*これについては後で触れます。

基本的にゲームがスクリプト値を読むときはいつでも、それが何であるかを起動時に判断します。
つまり実際の値が必要なときはいつでも、文字列を切り刻んで何が必要かを調べる必要がなく、単に値を与えるか、指定されたトリガーを呼び出すだけでいいのです。
偶然にもこのシステムによって、様々な場所で “trigger:”を使用する機能を展開することが非常に簡単になりました。
したがって、もし望ましい場所が更にあるのだとすれば、そこにこの機能を提供をしないという言い訳はそれほどないのです(あーあ言ってしまった…)。

Mod製作が好きな方ならお気づきでしょうが、この過程でいくつか追加で実現したことがあります。
まず手っ取り早いのは”trigger: “を呼ぶのと同じように “modifier: “を呼べるようにしたことです。
基本的にあるポップに対して+20%の市民幸福度を与える改造が適用されていて、modifier: pop_citizen_happiness(スペースなし😛)を使った場合、0.2を得ることになります。
その他にはスクリプト値も追加しました。

このアイデアはParadoxスタジオ製の新しいゲームから得たもので、そのゲームのコンテンツデザイナーたちは、彼らのゲームの優れたスクリプト計算能力で私たちの事をあざ笑っているでしょう。
基本的にスクリプトが強力な理由は、キーに値を代入することで必要に応じて一連の計算を実行できることでした。
したがってmy_script_valueは 57+(24*num_pops)/num_colonies とかそんな感じになります。
すでに述べたような変更を加えたところで私たちはほぼ作業を完成したので、新たな機能も追加しました(私たちの新しいゲームにおいて可能な多くのことを可能にしていますが、実際にはほとんどコードを共有していないため、実際の動作は多少異なる可能性があります)。

これらのscript valuesは基本的には、このセクションの最初で説明したような重みフィールドであり、script_valuesディレクトリ内のキーによって定義されます。

leader_cost = {
    base = 2
    modifier = {
        subtract = 6
        num_owned_leaders > 5
    }
    modifier = {
        add = trigger:num_owned_leaders
        num_owned_leaders > 5
    }
    mult = 50
}

次にvalue:leader_costを使用してゲーム内の任意の場所を参照し、オンデマンドで値を計算します。
この機能はゲームのスクリプトを改善するのに非常に便利です。
正しい値を取得するのが簡単なだけでなく、weightフィールドにコピーペーストされるスクリプトを大幅に削減できます(職業のweights、今行きます!)。
便利なことにスクリプトの値はスクリプトの値やトリガと同様に読み取られるため、パラメータを入力することができます。
たとえば、value:my_value|PARAMETER|50|は、ゲームにスクリプトの値my_valueを使用させ、”$PARAMETER$”はすべて 50 で置き換えられます。

これだけの変更を加えてもスクリプト言語にはまだいくつかの工夫が必要でした。
ひとつはscript_valuesとweight フィールドにcomplex_trigger_modifiersを追加したことです。
基本的にこれらは、trigger:<trigger>で使用するには複雑すぎるトリガーの値を使用できるようにするものです。
例を挙げると、次のようになります。

complex_trigger_modifier = {            #fewer worlds => more menace from destroying one
    trigger = check_galaxy_setup_value
    parameters = { setting = habitable_worlds_scale }
    mode = divide
}

これは、export_trigger_value_to_variableで動作するのと同じトリガーで動作します。
またこれらにいくつかのトリガーを追加しました。
特に注目はすべてをcount_xするスクリプトリストトリガー(例:count_owned_planet)とdistanceトリガーです。

スクリプト値でできるすべてのことに関する包括的なガイドは、この投稿に添付されています(およびcommon/script_valuesにあります)。
正直なところ、この新しいシステム系が可能にしてくれることの多さは計り知れないものがあります。
例えば、上記の例ではリーダーコストは所有しているリーダーの数に基づいてスケーリングされました。
またこの方法でアンロックしたアセンションパークの数に応じてAutochthonモニュメントによる統合力ブーストをスケーリングしています。
このようにアップデートが行われるたびにこのリストは増えていきます。

Modの上書き

今日お話しできるのはスクリプトの値だけではありません。
Mod製作者は長い間、ゲームのさまざまな要素が上書きの処理をする方法に少々困惑してきたことでしょう。
具体的にはバラついているのです。
残念ながらまだしばらくはこの状況が続くと思いますがここで少し進展がありましたので、なぜこのような問題が発生するのかを知ってもらう事は面白いのではないかと考えました。

基本的にMod製作者がバニラファイルを上書きする場合、ファイル全体を上書きするか(これは常に機能します)、またはminer(採掘師)ジョブなど、ファイル内の個々のエントリを上書きできます。
さて、ゲームが既存のキーと一致する2つ目のエントリに遭遇すると、さまざまなことが起こります。

  • 既存のものを完全に置き換える(最後に読み込んだものが使われる)
  • 既存のものを置き換えるが不完全となる。
    例えばジョブを個別に上書きした場合、改造で参照できなくなります(これは理想的な事ではありません)。
  • 2番目のエントリは無視される(最初に読み込んだものが使用される)
  • 最初のエントリと2番目のエントリの両方が保持される(重複 – 理想的ではありません)
  • 内部情報を既存のエントリに追加する(on_actionsの特殊なケース)

ではなぜこのような多様な行動があるのでしょうか?
基本的にはデータベースがC++コードでどのように読み取られるかの問題になっています。

ゲームがスクリプトファイルに遭遇すると原則としてオブジェクト(例:miner={})が読み込まれ、それがマッチングデータベース(ジョブタイプデータベースなど)に保存されます。
このデータベースはゲームがそのマッチングオブジェクトタイプを処理する必要があるときにさまざまな方法で使用されます。
ゲーム内で最も古いオブジェクトの多く(テクノロジーや倫理などリリース前から現在の形で存在していたもの)の場合、カスタムメイドのデータベース内オブジェクトとしてコード化されます。
このデータベースを読み取るコードは、新しいオブジェクトが定義されるたびに新しく書き込まれる(またはコピーされる)ため、ファイルを読み取る順序(AからZまたはZからA)と、重複の処理方法の両方が異なる可能性があり理想的ではありません。
いくつかのケースではこの仕組みは理にかなっていました:例えば、on_actionsでは、特別な処理のための正当な理由がありますー基本的に、Mod製作者がバニラ版の内容を心配することなくon_actionsを使用できるという意図があります。
これは外交活動のようにコード依存度の高いデータベースの場合にも当てはまります。
このようなデータベースでは単にエントリーを追加するだけでは、コードがそのエントリーを処理する方法を知っているとは期待できません。

しかしほとんどの場合これは単に技術的負債の問題であり、最近ではデータベースのコーディング方法が改善されています。
新しいオブジェクトを追加するときは、(数年前から) TSingleObjectGameDatabaseのTGameDatabaseObjectとして追加しています。
TSingleObjectGameDatabaseの標準コードはオブジェクトの読み取りを処理し、コピー&ペーストする必要がありません。
Mod製作者にとって最も重要なことは既存のオブジェクトを削除して新しいオブジェクトで置き換えることによって上書きを処理することです。
これは通常、Mod製作にはうまくいきますが、うまくいかないケースがいくつかありました。
ジョブ、地区、惑星クラス、その他のいくつかのケースでは、Modが壊れてしまいます。
つまり惑星に採掘ジョブを追加するModは何もしなくなってしまいます。
基本的にはジョブによってModが作成されると、a)Modデータベースに追加され、b) ゲームのジョブ定義(スクリプトファイルではなく、プログラムがスクリプトファイルを読んで得たもの)に保存されます。
これによりゲームはそのModに効果を追加できます(つまりこのジョブについて x値を許可します)。
その後ジョブは削除され、新しいジョブが作成されます。同様に修正も作成されます。
しかし修正リストには同じキーを持つ2つのエントリがあります。
次にゲームがスクリプトファイルで使用されているときにこのような修正に遭遇すると、リストを調べて一致する最初のものを見つけ、それが意図したものであると想定します。
残念ながらジョブ自体は2番目の修正値が適用されると考えています。
その結果そのModは-Mod製作者の意図と目的を鑑みると-使えなくなります。

しかし私はこの面で良いニュースを報告することができます-私たちはその問題を修正しました。
これらのオブジェクトは安全に上書きできるようになりました。
少なくともこの特定の原因は上書きが解除される理由ではありません。
Modは正しく機能するようになります。
(なぜわざわざ注意するのか?基本的にあるデータベースにエントリを追加することで別のデータベースが変更された場合、上書きすると慎重に扱わない限り問題が発生する可能性があります。幸いなことに、これは改造の例を除けばかなりまれです)。
これはMod製作者にとって非常に有用であることが期待されます。
というのも、製作者がおそらく変更したいと思われるオブジェクトの一部はジョブと地区であろうからです。

TGameDatabaseObjectsの最後の注意点として、すべての読み込み方法で同じコード行が使用されるため、Mod製作用の小さな機能が追加されました。
上書きに関するエラーログメッセージの警告では、どちらが使用されているかが正確に指定されるようになり、上書きのあいまいさがある程度解消されました。
このエラーメッセージが表示された場合はゲームがどのように動作しているのか、かなり確信が持てる事でしょう。

game_singleobjectdatabase.h:147]:オブジェクトキーを持つminerは既に存在します。
ファイル:common / pop_jobs / 03_worker_jobs.txt行:319にあるオブジェクトを使用します。

管理人注:⬆この箇所はおそらく日記公開後に追記された内容かと思います。

ちなみに、Ver3.3 Unity Open Betaのフィードバック期間は2月7日の月曜日まで延長されました。
Open Betaブランチは3.3リリースまで利用できますので、現在Open Betaでプレイしている方は3.3リリースまでゲームを続けることができます。
3.3 Unity Open Betaへのフィードバックはこちらからどうぞ!

2月7日(月)のDev Clash 2022の次のエピソードは、http://twitch.tv/paradoxinteractive にて15:00 CETに始まります。

今週は以上です!
来週はEladrinがオープンベータについて彼の考えを皆さんと共有するために戻ってくるでしょう。Dev Clashもあります!


以上

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

フォーラム内のやり取りで気になったものを紹介。

今回は技術的な話なので、それに関するQ&Aが出ても私には理解できないと思われます。ですので紹介はなしの予定ですm(_ _)m

2022/2/5 追記
技術的じゃない内容で気になったところがあったので少しだけ抜粋してみます。

Q:3.3のリリースには(新)DLCは伴わないのでしょうか?

A:すでにリリースされているDLCのアップデートは別として、この(3.3の)リリースでは(新規)DLCはありません。


Q:支店(企業のブランチオフィスの事と思われます)の拡張計画を追加してほしい。

A:支店の拡張計画というのはよいアイデアだと思います。


以上です。
その他は主にMod製作に関するテクニカルな内容だったので紹介は控えますm(_ _)m

感想・まとめ

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

正直、何言ってるかちんぷんかんぷんです( ´Д`)

訳しててさっぱり意味不明だったのでおそらく色々と間違ってる箇所もありそうです(きっとある)。

特に英語で技術的な事が書いてあると、どこまでが変数を意味しているのかがわかりにくいですねー😥

これが変数でこれが型で…というところまで注釈で触れようかとも思ったのですが、正直Modを作らない人には関係ない話ですし、Mod作る人はそんなのわざわざ書かれなくても理解してる事であり、結局誰の得にもなりそうも無いのでやめときました。

一応気をつけて訳してみましたが、全体的に変な箇所はあると思うのでご了承くださいm(_ _)m