Godot Modding 入門
はじめに
私のGodot歴は1日です。
説明に間違いがある可能性があります。
間違いを見つけたらやさしく教えてください。
対象者
- Godotに興味のある方
- 今回対象にするゲームは構造がきれいなので、ModdingしながらGodotの勉強もできそうです
- Moddingに興味がある方
- 興味があれば大体解決できる!
- ある程度プログラミングができる方
- 開発に当たり、まずゲームのプログラムを読む必要があります
あるといい知識
- Godot, Unityに関する理解
- Unityに似ているのでUnityの知識は流用できそうでした
- Modding経験
- UnityのModdingフレームワークBepInExを使ったことがあると理解が早いと思います
対象のゲーム
ゲーム配信プラットフォームのSteamで配信されているデスクトップ小動物牧場 - Tiny Pastureを対象とします。
デスクトップ下に常駐して牧場で動物を育てる基本放置のんびりゲームです。
かわいい、安いので興味があれば遊んでみてください。
ゲームが提供する機能がすくなく、デコンパイルするとゲーム構造やソースコードがとても丁寧に書かれていることがわかります。
難読化もされていないのでコメントがなくても理解しやすいModdingの入門としても非常によいゲームだと思います。
MOD配布の注意
今回対象とするゲームのMOD作成には成功しましたが、Steamワークショップにはアップロードできませんでした。
公式から提供されているSteamワークショップにMODをアップロードするツールがクラッシュして正常動作しないためです。
この問題は解決しました。
詳細はTiny PastureのSteam workshop uploaderがクラッシュする問題の解決にまとめました。
ただし、やはり配布できるかどうかは先に確認しておいた方が無難かと思いますのでこの項目は残します。
もしMODを配布する予定がある場合はアップロード先と通信できるのか事前に確認しておいた方が無難です。
MODのアップロード先はSteamワークショップ以外にもいくつかあるので他のサービスを検討してもよいと思います。
MOD配布プラットフォーム
成果物
この解説を通しての成果物を先に示します。
Tiny Pasture 桁区切りMODを作成しました。
ゲームを進めるとある程度大きな数値が登場しますが、桁区切りがされていないので少し見づらいということで、これを改善するMODです。
シンプルなMODなので初めてのModdingにはよい課題だと思います。
Modding前の注意
Modding End User License Agreement(EULA)が示されていますので、こちらに目を通してから作業を始めてください。
Modding
ゲームの開発環境
- ゲームエンジン
- Godot 4.3
- 言語
- GDScript
関連フォルダパス
ゲームファイルフォルダ
Moddingにあたりゲームの配置先を参照する場合は以下の方法で参照できます。
- Steamクライアントのゲーム一覧を表示
- 対象のゲームを右クリック
-
管理
を表示 -
ローカルファイルを閲覧
を選択
このパスはユーザにより異なります。以下は例になります。
X:\SteamLibrary\steamapps\common\TinyPasture
Steamワークショップフォルダ
ワークショップフォルダはゲームのインストール先と紐づいています。
インストール先が以下の場合
X:\SteamLibrary\steamapps\common\TinyPasture
ワークショップフォルダは以下になります。
X:\SteamLibrary\steamapps\workshop\content\3167550
3167550
はゲームのIDです。
ゲームIDが不明な場合はストアページのアドレスから確認できます。
Tiny Pasture
の場合のストアアドレスは以下の通りです。
https://store.steampowered.com/app/3167550/_/
ログデータフォルダ
ログデータは以下に保存されます。
AppData
フォルダは隠しフォルダです。
ファイルエクスプローラのアドレス欄にコピー&ペーストでアクセスする方法が簡単です。
C:\Users\%username%\AppData\Roaming\TinyPasture\logs
セーブデータフォルダ
セーブデータは以下に保存されます。
AppData
フォルダは隠しフォルダです。
ファイルエクスプローラのアドレス欄にコピー&ペーストでアクセスする方法が簡単です。
C:\Users\%username%\AppData\Roaming\TinyPasture\save
セーブデータのバックアップ
Moddingの過程などでセーブデータが破損する可能性があります。
事前にバックアップを取ることを強く推奨します。
Steamクラウドによるセーブデータのバックアップ
通常Steamクラウドによるバックアップが有効化されています。
この影響で、意図せずセーブデータが巻き戻されたりします。
必要に応じてSteamクラウドの利用設定を変更します。
Steamクラウドの設定
- Steamクライアントのゲーム一覧を表示
- 対象のゲームを右クリック
-
プロパティ
を表示 -
一般タブ
を表示(最初に表示されるタブ) -
Steamクラウドに[ゲーム名]のセーブデータを保存
のトグルを変更
環境構築
作業環境にはマルチバイト文字が含まれないパスが安全だと思います。
(マルチバイトパスの検証はしていません)
ゲームのデコンパイル
デコンパイルツールの導入
Godotのデコンパイルツールをダウンロードします。
先人の知恵でv0.9.0-beta.4を推奨していますが、v0.9.1でも問題なくデコンパイルできました。
私はv0.9.1を利用しました。
ダウンロードしたzipファイルを任意の位置に展開します。
デコンパイル
gdre_tools.exe
を実行します。
ゲームインストール先のTinyPasture.pck
をGodot RE Tools
にドラッグ&ドロップします。
すべての項目にチェックされていることを確認し、出力先のパスを指定してExtract
します。
ツールを閉じます。
Godot Steamプラグインの導入
プラグイン入手
GodotSteamをダウンロードします。
ゲームのバージョンに合う物をダウンロードしてください。
Godotのバージョンがわからない場合はgdre_tools
でデコンパイル画面で確認できます。
Tiny Pasture
の場合はGodot4.3を利用しているので、Godot 4.1-4.4 - Steamworks 1.61 - GodotSteam GDExtension 4.13のgodotsteam-4.14-gdextension-plugin-4.1-4.3.zip
をダウンロードします。
プラグインの導入
ダウンロードしたzipファイルを任意の位置に展開します。
zipファイル内のaddon
フォルダに含まれるgodotsteam
フォルダをデコンパイル済みのゲームフォルダaddon
にコピーし上書きします。
プラグインの設定
デコンパイルしたゲームファイル群のルートフォルダにsteam_data.json
を作成します。
IDはゲーム合わせて変更します。
このファイルを作成しないとGodotからデバック実行できません。
{ "app_id": "3167550"}
Godotによるゲームの読み込み
Godotの導入
Godot4.3を利用して作られているので、Godot公式サイトから4.3-stableを入手します。
私はWindows - Standardを入手しました。
ダウンロードしたzipファイルを任意の位置に展開します。
ゲームの読み込み
Godot_v4.3-stable_win64.exe
を実行し、Godotを起動します。
インポート
ボタンからデコンパイルしたゲームフォルダを選択、インポートして編集から読み込みを開始します。
読み込み時に警告ポップアップが表示されることがありますが無視します。
Godot起動後にファイルの読み込みが入るのでしばらく待ちます。
ModLoaderの有効化
- 上部メニューから
プロジェクト
メニューを表示 -
プロジェクト設定
を選択 -
プラグイン
タブ表示 -
mod loader
を有効化 - 設定画面を閉じる
MODの基本構造の作成
新規作成
画面上部のMod Tool
を選択し、Create new Mod
を選択します。
Namespace
とMod Name
を記入し作成します。
項目名 | 必須か | 説明 | 備考 |
---|---|---|---|
NameSpace | 〇 | 団体名や作者名 | 半角英数が無難、アンダースコアは使用できる |
Mod Name | 〇 | MOD名 | 半角英数が無難、アンダースコアは使用できる |
Namespace
とMod Name
はMODのフォルダ名に使用されます。
後でも変更できますが、ややこしいので最初に決めておく方が良いです。
長すぎる名前をつけるとMODが読み込まれない場合があるのである程度の長さにとどめておくとよいです。
50文字くらいの長さのMODを作ったところ、読み込めませんでした。
※試行錯誤していたので、MODの問題ではなかった可能性もあります。
MODの保存先
開発中のMODはデコンパイルしたゲームと同じフォルダに格納されます。
MODのファイルは全てmods-unpacked
フォルダにNameSpace-ModName
の書式のフォルダに格納されます。
このフォルダ名の書式はとても重要です。
この書式を守らないとModLoaderに認識されず、正常にMODが読み込まれません。
デコンパイルされたゲームフォルダ
├─mods-unpacked // 開発中のMODが格納されるフォルダ
│ │
│ ├─rin_jugatla-separate_digits // NameSpace-ModNameの書式
│ │ │ manifest.json // MODの基本情報 先ほどのGUIで設定した項目が保存される
│ │ │ mod_main.gd // MODのエントリーポイント
雛形の修正
MODの新規作成を行うとmod_main.gd
が雛形から作成されます。
作成されたmod_main.gd
を開き、以下の項目を変更します。
この変更を行うとこでゲーム実行時のログファイルでMODの動作を確認しやすくなります。
状態 | NameSpace | ModName |
---|---|---|
雛形 | AuthorName | ModName |
変更例 | rin_jugatla | separate_digits |
- const MOD_DIR: = "AuthorName-ModName"
- const LOG_NAME: = "AuthorName-ModName:Main"
+ const MOD_DIR: = "rin_jugatla-separate_digits"
+ const LOG_NAME: = "rin_jugatla-separate_digits:Main"
既存MODの編集
画面上部のMod Tool
を選択し、Connect existing Mod
から編集対象のMODを選択します。
MODの基本情報を編集できます。ここでは最低限の解説とします。
項目名 | 必須か | 説明 | 備考 |
---|---|---|---|
Mod Name | 〇 | MOD名 | 半角英数が無難、アンダースコアは使用できる |
NameSpace | 〇 | 団体名や作者名 | 半角英数が無難、アンダースコアは使用できる |
Compatible Game Version | 対応するゲームバージョン | 今回は1.0.10
|
|
Compatible Mod Loader Version | 対応するMod Loaderバージョン | 今回は7.0.1
|
設定を変更した場合は必ず画面右上のSave to manifest.json
を実行します。
Ctrl + S
では保存されません。
MODの作成
いよいよ本題のMODの中身の作成に入ります。
今回取り組むのは数字を3桁区切りして表示する
という内容です。
具体的には以下のように桁区切りされていない箇所が、,
で桁区切りされて表示されれば成功となります。
変更箇所の洗い出し
ゲームをプレイしながら、どの画面のどの部分に変更すべき箇所があるのか洗い出します。
今回は例として動物の購入画面
の動物の価格
を対象とします。
Godotで対象のUIシーンを探す
今回はstore
やanimal
をキーワードに探すと早そうです。
UIを確認する場合は画面上部の2D
を選択し、.tscn
ファイルを開いて目当てのファイルか確認します。
今回はres://Scene/UI/PopupUI/UIShop/ui_shop_pack_item.tscn
が目当てのファイルになります。
関連のUIから対象のUIを探す
上記のようにピンポイントに対象のUIを見つけられれば良いですが、実際には少し難しいです。
そこで関連のUIから探す手段も示します。
例えば、今回は店
に関する画面なのでstore
はファイル名に入っていそうだな、というところからres://Scene/UI/PopupUI/UIShop/ui_shop.tscn
を見つけられたとします。
これは店の画面なので、動物のUIにも関連していそうです。
画面左上のシーン
を見るとAnimals
が探しているUIに関連していそうです。
目が閉じているアイコン
なのでUIが非表示になっています。
アイコンを切り替えてどのようなUIなのか確認しましょう。
探しているUIであることがわかるので、次にビデオのアイコン
を押してみましょう。
ビデオのアイコンを押すと、ノードの元となったシーンに飛びます。
UnityでいうところのPrefabのような物と理解するとわかりやすいと思います。
このように関連のUIをたどっていくことで目的のUIにたどり着ける場合があります。
※動的に作成されるUIの場合はこの方法ではたどれないのでスクリプトも参照して頑張ります。
なおスクロールのアイコン
を押すと紐づくスクリプトを開けます。
対象のUIアイテムを探す
対象のUIの中からさらに、どのアイテムを参照すればよいのか探します。
今回は金額部分なので、金額部分の2000
ラベルUIをクリックします。
ラベルUIをクリックするとシーンのTXT_Price
アイテムが選択状態に変わります。
これで画面内の2000
のラベルがTXT_Price
という名前で定義されていることがわかります。
UIアイテムの紐づく変数を探す
対象のUIアイテムがわかったので、スクリプトでいつ、どのように変更しているか探すためスクリプトを参照します。
スクロールのアイコン
を押してシーンに紐づくスクリプトを開きます。
ここではres://Scene/UI/PopupUI/UIShop/ui_shop_pack_item.gd
が開きます。
コードを見るとTXT_Price
ラベルはtxt_price
変数に格納されていることがわかります。
変数がいつどのように変更されているのか探す
txt_price
でファイル内を検索するとrefresh
関数でpack_data.price
をもとにラベルのテキストを変更している事がわかります。
既存機能を拡張するスクリプトを作成
ここまでの作業でres://Scene/UI/PopupUI/UIShop/ui_shop_pack_item.gd
ファイルのrefresh
関数に変更を加えれば桁区切りを実現できる事がわかりました。
既存のスクリプトを拡張するスクリプトを作成するため、対象のスクリプトの右クリックメニューからModTool: Create Script Extension
を選択します。
編集中のMODの中にファイル構造を維持してMOD用のファイルが作成されます。
MODの機能は以下のスクリプトに記述します。
これにより、MODにゲーム内のオリジナルのファイルを含まず配布、導入することができます。
res://mods-unpacked/rin_jugatla-separate_digits/extensions/Scene/UI/PopupUI/UIShop/ui_shop_pack_item.gd
拡張スクリプトの読み込み
※ModTool: Create Script Extension
から拡張スクリプトを作成した場合はこの作業は不要です。
ModTool
の機能で拡張スクリプトをロードする記述が自動追記されます。
知っておいた方が理解が深まるので記載します。
拡張スクリプトui_shop_pack_item.gd
などを作成しただけではMODでは読み込まれません。
MOD起動時に拡張スクリプトを読み込むためmod_main.gd
にスクリプトをロードするための記述を行います。
func _init() -> void :
# MODのスクリプトが読み込まれた時のエントリポイント
# _readyよりも先に実行される
install_script_extensions()
func install_script_extensions() -> void :
# この記述で`ui_shop_pack_item.dg`を拡張したスクリプトをロードする
# 絶対パスで指定する
ModLoaderMod.install_script_extension("res://mods-unpacked/rin_jugatla-separate_digits/extensions/Scene/UI/PopupUI/UIShop/ui_shop_page_facility_one.gd")
# こちらでもよい(extensions_dir_path.path_joinを展開すると同じパスの指定になる)
const MOD_DIR: = "rin_jugatla-separate_digits"
var mod_dir_path = ModLoaderMod.get_unpacked_dir().path_join(MOD_DIR)
var extensions_dir_path = mod_dir_path.path_join("extensions")
ModLoaderMod.install_script_extension(extensions_dir_path.path_join("Scene/UI/PopupUI/UIShop/ui_shop_page_facility_one.gd"))
桁区切り機能の実装
該当シーンの桁区切りを実装するスクリプトは以下の通りです。
extends "res://Scene/UI/PopupUI/UIShop/ui_shop_pack_item.gd"
## 桁区切りされていない数値を桁区切り
static func format_number_text_with_commas(number_text: String) -> String:
# ゲームアップデートで桁区切りに対応した場合に二重で桁区切りしないようチェック
var has_comma = number_text.find(",") > -1
if has_comma:
return number_text
var formated = str(number_text)
var length = formated.length()
while length > 3:
formated = formated.insert(length - 3, ",")
length -= 3
return formated
func refresh():
super()
txt_price.text = format_number_text_with_commas(txt_price.text)
実装の説明
ModLoaderでは既存スクリプトを継承して機能を実装します。
このため、既存スクリプトに記述済みのフィールド変数にアクセスしたり、関数を上書きまたは実行前後に機能を割り込ませることができます。
このため、ファイルの最初にextends
で元のスクリプトを指定します。
extends "res://Scene/UI/PopupUI/UIShop/ui_shop_pack_item.gd"
さらに、桁区切りを行う関数を実装します。
## 桁区切りされていない数値を桁区切り
static func format_number_text_with_commas(number_text: String) -> String:
# ゲームアップデートで桁区切りに対応した場合に二重で桁区切りしないようチェック
var has_comma = number_text.find(",") > -1
if has_comma:
return number_text
var formated = str(number_text)
var length = formated.length()
while length > 3:
formated = formated.insert(length - 3, ",")
length -= 3
return formated
最後にrefresh
関数にフックします。
既存の機能は書き換えず、価格のラベルだけ変更すればよいので、super()
で元の関数を実行させた後に、桁区切りされていない価格の文字列が格納されたtxt_price.text
から桁区切りした結果をラベルに反映します。
func refresh():
super()
txt_price.text = format_number_text_with_commas(txt_price.text)
MODのデバッグ
実装がうまくいっているかMODをデバッグします。
F5キー
または画面右上のプロジェクトを実行
からゲームを実行します。
必要に応じてブレークポイントを貼ります。
Tiny Pastureでは起動直後にエラーが出るのでF12キー
または〇->アイコン
から処理を続行させます。
MODのテスト
Godotからデバッグできない場合や本番環境でテストする場合はゲームに直接MODを読み込ませます。
MODのパッキング
MODは基本的にzipで配布します。
また、GodotのMODエクスポート機能もzipでパッキングするので特に理由がなければzip形式でテストします。
Tiny Pasture
のワークショップフォルダを開きます。
ワークショップフォルダパスはユーザにより異なりますが、関連フォルダパスのSteamワークショップフォルダ
の項からパスを特定します。
例としてワークショップフォルダは以下に配置されていたとします。
X:\SteamLibrary\steamapps\workshop\content\3167550
テスト用にquicktest
フォルダを作成します。
X:\SteamLibrary\steamapps\workshop\content\3167550\quicktest
エスクポート先のパスに上記のパスを指定し、Export Mod
を押します。
エクスポートに成功するとファイルエクスプローラが立ち上がり、エクスポート先のフォルダにzipファイルが生成されたことが確認できます。
すでにzipファイルが存在する場合でもExport Mod
を押すと新しいzipファイルで上書きし、エクスプローラが立ち上がります。
エクスプローラが立ち上がらない場合はエクスポートに失敗している可能性が高いです。
ゲームが起動しているとエクスポートできないので、起動していないか確認しましょう。
MODが読み込まれているか確認
MODをエクスポートし、ゲームを起動します。
ゲームの起動時にMODが読み込まれているか確認します。
ログフォルダのgodot.log
をメモ帳等で開きます。
C:\Users\%username%\AppData\Roaming\TinyPasture\logs
このエラーはMODを導入していない場合でも表示されるので無関係です。
ERROR ModLoader:Path: Encountered an error (Invalid parameter) when attempting to open a directory, with the path: res://mods-unpacked/
USER ERROR: Encountered an error (Invalid parameter) when attempting to open a directory, with the path: res://mods-unpacked/
at: push_error (core/variant/variant_utility.cpp:1092)
見つかったMOD数とMOD名が列挙されます。
ここでMODが見つからない場合はフォルダ構造やファイル名の書式が間違っている可能性があります。
DEBUG ModLoader: Found 1 mods at the following paths:
読み込むMODには順序があります。
順序は以下のログで確認できます。
INFO ModLoader: mod_load_order -> 1) rin_jugatla-separate_digits
MODの関係でERRORが表示されているとゲームが起動した直後にクラッシュしゲームが終了します。
添付のログではここでエラーが出てゲームをクラッシュさせています。
USER SCRIPT ERROR: Parse Error: Identifier "DigitsUtility" not declared in the current scope.
at: GDScript::reload (res://mods-unpacked/rin_jugatla-separate_digits/extensions/Scene/UI/PopupUI/UIShop/ui_shop_opened_pack.gd:6)
MODの読み込みに成功した場合のログファイル
Godot Engine v4.3.stable.official.77dcf97d8 - https://godotengine.org
OpenGL API 3.3.0 NVIDIA 576.02 - Compatibility - Using Device: NVIDIA - NVIDIA GeForce RTX 3080
INFO ModLoader:Store: Options override feature tag "editor". does not apply, skipping.
INFO ModLoader:Godot: override.cfg setup detected, ModLoader will be the last autoload loaded.
DEBUG ModLoader: Autoload order
[
"ModLoaderStore",
"ModLoader",
"GState",
"FarmDB",
"GSave",
"GSteam",
"GSound"
]
INFO ModLoader: game_install_directory: X:/SteamLibrary/steamapps/common/TinyPasture/
ERROR ModLoader:Path: Encountered an error (Invalid parameter) when attempting to open a directory, with the path: res://mods-unpacked/
USER ERROR: Encountered an error (Invalid parameter) when attempting to open a directory, with the path: res://mods-unpacked/
at: push_error (core/variant/variant_utility.cpp:1092)
INFO ModLoader:Path: The directory for mods at path "X:/SteamLibrary/steamapps/common/TinyPasture/mods" does not exist.
INFO ModLoader:ThirdParty:Steam: Checking workshop items, with path: "X:/SteamLibrary/steamapps/workshop/content/3167550"
INFO ModLoader:ThirdParty:Steam: Checking workshop item path: "X:/SteamLibrary/steamapps/workshop/content/3167550/quicktest"
DEBUG ModLoader: Found 1 mods at the following paths:
- X:/SteamLibrary/steamapps/workshop/content/3167550/quicktest/rin_jugatla-separate_digits.zip
DEBUG ModLoader:File: Loading mod_manifest from -> X:/SteamLibrary/steamapps/workshop/content/3167550/quicktest/rin_jugatla-separate_digits.zip
SUCCESS ModLoader: X:/SteamLibrary/steamapps/workshop/content/3167550/quicktest/rin_jugatla-separate_digits.zip loaded.
SUCCESS ModLoader: DONE: Loaded 1 mod files into the virtual filesystem
DEBUG ModLoader:Config: No config for mod id "rin_jugatla-separate_digits"
DEBUG ModLoader:UserProfile: Updated the mod lists of all user profiles
DEBUG ModLoader:UserProfile: Updated the active state of all mods, based on the current user profile "default"
SUCCESS ModLoader: DONE: Loaded all mod configs
DEBUG ModLoader:Dependency: Checking dependencies - mod_id: rin_jugatla-separate_digits optional dependencies: []
DEBUG ModLoader:Dependency: Checking dependencies - mod_id: rin_jugatla-separate_digits required dependencies: []
INFO ModLoader: mod_load_order -> 1) rin_jugatla-separate_digits
INFO ModLoader: Initializing -> rin_jugatla-separate_digits
DEBUG ModLoader: Loading script from -> res://mods-unpacked/rin_jugatla-separate_digits/mod_main.gd
DEBUG ModLoader: Loaded script -> <GDScript#-9223371977463429776>
INFO rin_jugatla-separate_digits:Main: Init
DEBUG ModLoader: Adding mod main instance to ModLoader -> rin_jugatla-separate_digits:<Node#59995325809>
DEBUG ModLoader: mod data
{
"rin_jugatla-separate_digits": "<Resource#-9223371978419731128>"
}
SUCCESS ModLoader: DONE: Completely finished loading mods
INFO ModLoader:ScriptExtension: Installing script extension: res://Scene/UI/PopupUI/UICollection/hover_tip/wd_collection_tip.gd <- res://mods-unpacked/rin_jugatla-separate_digits/extensions/Scene/UI/PopupUI/UICollection/hover_tip/wd_collection_tip.gd
INFO ModLoader:ScriptExtension: Installing script extension: res://Scene/UI/PopupUI/UIFarm/animal_box/ui_upgrade_tip.gd <- res://mods-unpacked/rin_jugatla-separate_digits/extensions/Scene/UI/PopupUI/UIFarm/animal_box/ui_upgrade_tip.gd
INFO ModLoader:ScriptExtension: Installing script extension: res://Scene/UI/PopupUI/UIFarm/ui_farm.gd <- res://mods-unpacked/rin_jugatla-separate_digits/extensions/Scene/UI/PopupUI/UIFarm/ui_farm.gd
INFO ModLoader:ScriptExtension: Installing script extension: res://Scene/UI/PopupUI/UIFarm/ui_farm_animal_detail.gd <- res://mods-unpacked/rin_jugatla-separate_digits/extensions/Scene/UI/PopupUI/UIFarm/ui_farm_animal_detail.gd
INFO ModLoader:ScriptExtension: Installing script extension: res://Scene/UI/PopupUI/UIShop/decorations/ui_shop_deco_bg.gd <- res://mods-unpacked/rin_jugatla-separate_digits/extensions/Scene/UI/PopupUI/UIShop/decorations/ui_shop_deco_bg.gd
INFO ModLoader:ScriptExtension: Installing script extension: res://Scene/UI/PopupUI/UIShop/decorations/ui_shop_deco_normal.gd <- res://mods-unpacked/rin_jugatla-separate_digits/extensions/Scene/UI/PopupUI/UIShop/decorations/ui_shop_deco_normal.gd
INFO ModLoader:ScriptExtension: Installing script extension: res://Scene/UI/PopupUI/UIShop/ui_shop_opened_pack.gd <- res://mods-unpacked/rin_jugatla-separate_digits/extensions/Scene/UI/PopupUI/UIShop/ui_shop_opened_pack.gd
INFO ModLoader:ScriptExtension: Installing script extension: res://Scene/UI/PopupUI/UIShop/ui_shop_pack_item.gd <- res://mods-unpacked/rin_jugatla-separate_digits/extensions/Scene/UI/PopupUI/UIShop/ui_shop_pack_item.gd
INFO ModLoader:ScriptExtension: Installing script extension: res://Scene/UI/PopupUI/UIShop/ui_shop_page_facility_one.gd <- res://mods-unpacked/rin_jugatla-separate_digits/extensions/Scene/UI/PopupUI/UIShop/ui_shop_page_facility_one.gd
INFO ModLoader:ScriptExtension: Installing script extension: res://Scene/UI/PopupUI/UIShop/baby_creater.gd <- res://mods-unpacked/rin_jugatla-separate_digits/extensions/Scene/UI/PopupUI/UIShop/baby_creater.gd
SUCCESS ModLoader: DONE: Installed all script extensions
SUCCESS ModLoader: DONE: Applied all scene extensions
INFO rin_jugatla-separate_digits:Main: Ready
Did Steam initialize?: { "status": 0, "verbal": "" }
USER ERROR: Parameter "fd" is null.
at: _font_get_ascent (modules/text_server_adv/text_server_adv.cpp:2608)
USER ERROR: Parameter "fd" is null.
at:_font_get_spacing (modules/text_server_adv/text_server_adv.cpp:2464)
USER ERROR: Parameter "fd" is null.
at: _font_get_descent (modules/text_server_adv/text_server_adv.cpp:2640)
USER ERROR: Parameter "fd" is null.
at:_font_get_spacing (modules/text_server_adv/text_server_adv.cpp:2464)
USER ERROR: Parameter "fd" is null.
at: _font_get_ascent (modules/text_server_adv/text_server_adv.cpp:2608)
USER ERROR: Parameter "fd" is null.
at:_font_get_spacing (modules/text_server_adv/text_server_adv.cpp:2464)
USER ERROR: Parameter "fd" is null.
at: _font_get_descent (modules/text_server_adv/text_server_adv.cpp:2640)
USER ERROR: Parameter "fd" is null.
at:_font_get_spacing (modules/text_server_adv/text_server_adv.cpp:2464)
USER ERROR: Parameter "fd" is null.
at: _font_get_ascent (modules/text_server_adv/text_server_adv.cpp:2608)
USER ERROR: Parameter "fd" is null.
at:_font_get_spacing (modules/text_server_adv/text_server_adv.cpp:2464)
USER ERROR: Parameter "fd" is null.
at: _font_get_descent (modules/text_server_adv/text_server_adv.cpp:2640)
USER ERROR: Parameter "fd" is null.
at:_font_get_spacing (modules/text_server_adv/text_server_adv.cpp:2464)
USER ERROR: Parameter "fd" is null.
at: _font_get_ascent (modules/text_server_adv/text_server_adv.cpp:2608)
USER ERROR: Parameter "fd" is null.
at:_font_get_spacing (modules/text_server_adv/text_server_adv.cpp:2464)
USER ERROR: Parameter "fd" is null.
at: _font_get_descent (modules/text_server_adv/text_server_adv.cpp:2640)
USER ERROR: Parameter "fd" is null.
at:_font_get_spacing (modules/text_server_adv/text_server_adv.cpp:2464)
backup saved at: 0019
Game Saved!
ERROR: 1 RID allocations of type 'N5GLES37TextureE' were leaked at exit.
USER ERROR: Texture with GL ID of 14: leaked 3248 bytes.
at: ~Utilities (drivers/gles3/storage/utilities.cpp:79)
USER ERROR: Parameter "RenderingServer::get_singleton()" is null.
at: ~CompressedTexture2D (scene/resources/compressed_texture.cpp:464)
USER WARNING: ObjectDB instances leaked at exit (run with --verbose for details).
at: cleanup (core/object/object.cpp:2284)
USER ERROR: 1 resources still in use at exit (run with --verbose for details).
at: clear (core/io/resource.cpp:604)
MODの読み込みに失敗した場合のログファイル
Godot Engine v4.3.stable.official.77dcf97d8 - https://godotengine.org
OpenGL API 3.3.0 NVIDIA 576.02 - Compatibility - Using Device: NVIDIA - NVIDIA GeForce RTX 3080
INFO ModLoader:Store: Options override feature tag "editor". does not apply, skipping.
INFO ModLoader:Godot: override.cfg setup detected, ModLoader will be the last autoload loaded.
DEBUG ModLoader: Autoload order
[
"ModLoaderStore",
"ModLoader",
"GState",
"FarmDB",
"GSave",
"GSteam",
"GSound"
]
INFO ModLoader: game_install_directory: X:/SteamLibrary/steamapps/common/TinyPasture/
INFO ModLoader:UserProfile: No mod_ids inside "mod_list" for user profile "default"
ERROR ModLoader:Path: Encountered an error (Invalid parameter) when attempting to open a directory, with the path: res://mods-unpacked/
USER ERROR: Encountered an error (Invalid parameter) when attempting to open a directory, with the path: res://mods-unpacked/
at: push_error (core/variant/variant_utility.cpp:1092)
INFO ModLoader:Path: The directory for mods at path "X:/SteamLibrary/steamapps/common/TinyPasture/mods" does not exist.
INFO ModLoader:ThirdParty:Steam: Checking workshop items, with path: "X:/SteamLibrary/steamapps/workshop/content/3167550"
INFO ModLoader:ThirdParty:Steam: Checking workshop item path: "X:/SteamLibrary/steamapps/workshop/content/3167550/quicktest"
DEBUG ModLoader: Found 1 mods at the following paths:
- X:/SteamLibrary/steamapps/workshop/content/3167550/quicktest/rin_jugatla-separate_digits.zip
DEBUG ModLoader:File: Loading mod_manifest from -> X:/SteamLibrary/steamapps/workshop/content/3167550/quicktest/rin_jugatla-separate_digits.zip
SUCCESS ModLoader: X:/SteamLibrary/steamapps/workshop/content/3167550/quicktest/rin_jugatla-separate_digits.zip loaded.
SUCCESS ModLoader: DONE: Loaded 1 mod files into the virtual filesystem
DEBUG ModLoader:Config: No config for mod id "rin_jugatla-separate_digits"
DEBUG ModLoader:UserProfile: Updated the mod lists of all user profiles
DEBUG ModLoader:UserProfile: Updated the active state of all mods, based on the current user profile "default"
SUCCESS ModLoader: DONE: Loaded all mod configs
DEBUG ModLoader:Dependency: Checking dependencies - mod_id: rin_jugatla-separate_digits optional dependencies: []
DEBUG ModLoader:Dependency: Checking dependencies - mod_id: rin_jugatla-separate_digits required dependencies: []
INFO ModLoader: mod_load_order -> 1) rin_jugatla-separate_digits
INFO ModLoader: Initializing -> rin_jugatla-separate_digits
DEBUG ModLoader: Loading script from -> res://mods-unpacked/rin_jugatla-separate_digits/mod_main.gd
DEBUG ModLoader: Loaded script -> <GDScript#-9223371977429875343>
INFO rin_jugatla-separate_digits:Main: Init
DEBUG ModLoader: Adding mod main instance to ModLoader -> rin_jugatla-separate_digits:<Node#60028880242>
DEBUG ModLoader: mod data
{
"rin_jugatla-separate_digits": "<Resource#-9223371978386176689>"
}
SUCCESS ModLoader: DONE: Completely finished loading mods
USER SCRIPT ERROR: Parse Error: Identifier "DigitsUtility" not declared in the current scope.
at: GDScript::reload (res://mods-unpacked/rin_jugatla-separate_digits/extensions/Scene/UI/PopupUI/UIShop/ui_shop_opened_pack.gd:6)
ERROR: Failed to load script "res://mods-unpacked/rin_jugatla-separate_digits/extensions/Scene/UI/PopupUI/UIShop/ui_shop_opened_pack.gd" with error "Parse error".
at: load (modules/gdscript/gdscript.cpp:2936)
INFO ModLoader:ScriptExtension: Installing script extension: res://Scene/UI/PopupUI/UICollection/hover_tip/wd_collection_tip.gd <- res://mods-unpacked/rin_jugatla-separate_digits/extensions/Scene/UI/PopupUI/UICollection/hover_tip/wd_collection_tip.gd
INFO ModLoader:ScriptExtension: Installing script extension: res://Scene/UI/PopupUI/UIFarm/animal_box/ui_upgrade_tip.gd <- res://mods-unpacked/rin_jugatla-separate_digits/extensions/Scene/UI/PopupUI/UIFarm/animal_box/ui_upgrade_tip.gd
INFO ModLoader:ScriptExtension: Installing script extension: res://Scene/UI/PopupUI/UIFarm/ui_farm.gd <- res://mods-unpacked/rin_jugatla-separate_digits/extensions/Scene/UI/PopupUI/UIFarm/ui_farm.gd
INFO ModLoader:ScriptExtension: Installing script extension: res://Scene/UI/PopupUI/UIFarm/ui_farm_animal_detail.gd <- res://mods-unpacked/rin_jugatla-separate_digits/extensions/Scene/UI/PopupUI/UIFarm/ui_farm_animal_detail.gd
INFO ModLoader:ScriptExtension: Installing script extension: res://Scene/UI/PopupUI/UIShop/decorations/ui_shop_deco_bg.gd <- res://mods-unpacked/rin_jugatla-separate_digits/extensions/Scene/UI/PopupUI/UIShop/decorations/ui_shop_deco_bg.gd
INFO ModLoader:ScriptExtension: Installing script extension: res://Scene/UI/PopupUI/UIShop/decorations/ui_shop_deco_normal.gd <- res://mods-unpacked/rin_jugatla-separate_digits/extensions/Scene/UI/PopupUI/UIShop/decorations/ui_shop_deco_normal.gd
INFO ModLoader:ScriptExtension: Installing script extension: res://Scene/UI/PopupUI/UIShop/ui_shop_pack_item.gd <- res://mods-unpacked/rin_jugatla-separate_digits/extensions/Scene/UI/PopupUI/UIShop/ui_shop_pack_item.gd
USER SCRIPT ERROR: Parse Error: Identifier "DigitsUtility" not declared in the current scope.
at: GDScript::reload (res://mods-unpacked/rin_jugatla-separate_digits/extensions/Scene/UI/PopupUI/UIShop/ui_shop_opened_pack.gd:6)
MODの動作を確認
変更した部分が正常に動作しているか確認します。
無事動物の購入金額が3桁区切りされたことが確認できます。
MODの公開
ここではSteamワークショップへの公開方法を解説します。
自分で使うだけ、友達に使ってもらうだけならパッキングしたzipで事足りるかと思います。
Steam Workshop Uploaderの入手
Tiny PastureのSteam Workshop Uploaderはゲームエディタ
という名前で提供されています。
ゲームエディタは提供される機能から乖離があるのでタイトルはSteam workshop uploader
としました。
このアプリはSteamのブランチ機能でbetaブランチとして提供されることが多いです。
- Steamクライアントのゲーム一覧から
ゲームのプロパティ
を開く -
ベータ
タブを表示 - ベータへの参加から
mod uploader
ブランチに切り替える
ブランチを切り替えると、自動的にブランチの内容がダウンロードされます。
ダウンロードされない場合はファイルの整合性確認やSteamクライアント、PCの再起動を試します。
Steam Workshop Uploaderの起動
ゲームのプレイから、ゲームエディター
を選択して起動
MODのアップロード
-
Select mod file
にMODのzipファイルを指定 -
Select preview image
に画像を指定
これはSteamワークショップのMOD一覧のサムネイルに使用されます
新規の場合は新しい画像を指定
既存MODの更新の場合は未記入または更新するサムネイルの画像を指定 -
workshop ID
にIDを記入
新規MODの場合は空欄(自動採番されます)
既存MODの更新の場合は公開済みのWorkshopIDを調べて記入
Workshop URLが以下の場合は3484179926
がIDになります
https://steamcommunity.com/sharedfiles/filedetails/?id=3484179926
- Uploadを押す
-
Select preview image
に指定したサムネイルの表示例
SteamのワークショップページでMOD情報を記入
新規にアップロードしたMODは非公開の状態でSteamに保存されます。
アップロードしたMODは以下のように探します。
- Steamクライアントの
コミュニティ
からワークショップ
を押す -
あなたのファイル
を押す
MODの情報を記入して、公開状態を非公開から公開に変更します。
うまく公開できているか不安な場合はシークレットブラウザなどでワークショップページを確認します。
ページが参照できてサブスクライブ
が表示されていればMODが公開できています。
言語別の表示を確認したい場合は画面右上の言語
を切り替えます。
さいごに
基礎の基礎はおさえられたと思います。
たくさん面白いMODを作ってみてください!
困りごと
MODを作ってみて困ったことです。
解決方法を知っていればコメントなどで教えてください。
TinyPastureのSteam Workshop Uploaderが起動できない
解決しました。
詳細はTiny PastureのSteam workshop uploaderがクラッシュする問題の解決にまとめました。
register_global_classes_from_arrayが機能しない
Godot Mod Loader
にはクラスをグローバルに使用するための機能があります。
ユーティリティクラスなどは毎回定義するのが手間なのでグローバルに使えるようにできると便利です。
公式のドキュメントを見るとregister_global_classes_from_arrayで実現できそうですが、以下のコードはエラーで起動できませんでした。
なお、GitHubのIssueでこの機能が利用できないとの記述があります。
func _init() -> void :
install_global_script()
func install_global_script() -> void :
var classes = [
{
"base": "Object",
"class": "DigitsUtility",
"language": "GDScript",
"path": "res://mods-unpacked/rin_jugatla-separate_digits/digits_utility.gd"
}
]
ModLoaderMod.register_global_classes_from_array(classes)
class_name DigitsUtility
extends Object
## 桁区切りされていない数値を桁区切り
static func format_number_text_with_commas(number_text: String) -> String:
# ゲームアップデートで桁区切りに対応した場合に二重で桁区切りしないようチェック
var has_comma = number_text.find(",") > -1
if has_comma:
return number_text
var formated = str(number_text)
var length = formated.length()
while length > 3:
formated = formated.insert(length - 3, ",")
length -= 3
return formated
Godotの便利な機能
定義に飛ぶ
Ctrlを押したときにアンダーバーが出た項目について、マウス左クリックで定義に飛べます。
Godot4.4からはF12で定義に飛べます。
開いているファイルのツリー位置に飛ぶ
開いているファイルがファイルツリーのどこにあるのかわからなくなったときは、右クリックメニューからファイルシステム上で表示
を選択します。
参考資料
-
MOD制作及上传指南-MOD Creation and Upload Guide
- おそらく公式または公式に近い方からのガイド
- 内容はModding環境の構築から新しい動物の追加方法、MODのSteamワークショップへの公開方法
- feishuに公開されているガイドはブラウザのウェブページ丸ごとの翻訳が機能しなかったので読むのが少し大変
-
Godot Mod Loader
- 使用するModLoaderの公式ドキュメント
-
Brotato Modding Guide
- 別のゲームですか、網羅的に書かれています
-
Brotato Modding Guide 【環境構築】
- 別のゲームですが、Godot環境でのModdingを扱った記事
関連ツール
-
Godot
- ゲームエンジン公式サイト
-
godot-mod-loader
- Godot向けModLoader
-
GodotSteam
- Godot EngineとSteam向けツールのエコシステム
-
gdsdecomp
- Godotのデコンパイルツール
Discussion