Factorio MOD自作入門|3ファイルで動く最小構成
Factorio のMOD自作は、info.json・data.lua・control.lua の3ファイルから始まる。実際には info.json だけでも動くが、最初に触る範囲をこの3つに絞るだけで、巨大なAPIドキュメントを最初から読み切る必要はないと見えてきます。
Factorio MOD自作入門|3ファイルで動く最小構成
Factorio のMOD自作は、info.json・data.lua・control.lua の3ファイルから始まる。
実際には info.json だけでも動くが、最初に触る範囲をこの3つに絞るだけで、巨大なAPIドキュメントを最初から読み切る必要はないと見えてきます。
初めて作ったMODが起動せず30分ほど溶かしたときも、原因はフォルダ名を my-first-mod にしたのに info.json の name を myfirstmod と書いていたという単純な不一致で、ログの1行目にそのまま出ていました。
Lua自体は他言語の経験があれば読みやすく、つまずくのは文法よりも Factorio 固有の作法だとわかると、modding の入口はずっと軽くなるでしょう。
MOD自作のゴール:3ファイルで動く最小構成
Factorio のMODは、最小なら info.json、data.lua、control.lua の3ファイルで動きます。
もっとも、厳密に必須なのは info.json だけで、作りたいものがアイテム追加中心なら control.lua を置かずに済む構成も成立します。
まずは完成形を先に見ておくと、学ぶ範囲が驚くほど絞られるはずです。
完成イメージ:自作アイテムとレシピを追加する
自分が最初に触ったMODは、既存レシピの必要素材を1つ減らすだけの十数行でした。data.lua の中身も実質3行で、それでもゲーム内のレシピ表示が変わった瞬間に手応えがあり、その小さな成功体験が次の実装へ進む動機になりました。
MOD開発は大掛かりな仕組みから始める必要はなく、まず「ゲームに反映される1変更」を作れれば十分です。
フォルダ構成も複雑ではありません。mods 直下に my-first-mod_0.1.0 のような name_version 形式で置けば読み込まれるので、開発中はzip化せず展開したフォルダのまま作業したほうが速いです。
実際に小規模MODを1つ展開して中身を読んだときも、想像していたような巨大な構造ではなく info.json と data.lua だけで完結していて拍子抜けしました。
大型MODでも、基本の骨格はこの延長線上にあります。
3ファイルの責務を1行で押さえる
info.json はMODの身分証明で、名前・バージョン・依存関係を示します。data.lua はアイテムやレシピの設計図を定義する場所で、ゲーム開始前のデータを組み立てる役目です。control.lua はゲームが動いている間の挙動を担当し、イベント反応やセーブをまたぐ処理を受け持ちます。
この3層が分かれていると、公式ドキュメントのどこを読むべきかが一気に整理されます。
静的な追加なら data.lua、プレイ中の反応なら control.lua、まずMODとして認識させる土台は info.json という切り分けです。info.json だけで厳密には成立するのも、この土台がMODの存在自体を決めているからだと考えると理解しやすいでしょう。
| ファイル | 役割 | 何を書くか |
|---|---|---|
info.json | MODの基本情報 | 名前、バージョン、作者、依存 |
data.lua | プロトタイプ定義 | アイテム、レシピ、必要なら既存定義の調整 |
control.lua | 実行中の処理 | イベント反応、セーブ保持、状態管理 |
Lua未経験でもここまでは書ける
Lua未経験でも到達できる範囲は広いです。
必要なのは高度なアルゴリズムではなく、既存MODのコードを読んで似た形を真似する力で、プログラミング経験の有無より Factorio 固有の作法を知っているかのほうが効いてきます。
自分も最初はコードの見た目に身構えましたが、実際に触ると「どこに何を書くか」が分かれば前に進めました。
この段階のゴールは、自作アイテムとレシピを追加し、さらにゲーム中のイベントに反応させるところまでです。
そこまで来れば、あとは作りたい機能ごとに該当するプロトタイプやイベントを公式APIドキュメントで引いていく作業の繰り返しになります。
最初の壁は高く見えても、踏み台は3つしかありません。
準備するもの:modsフォルダとテキストエディタ
FactorioのMOD作成は、まず作業場所と編集環境を固めるだけで、つまずきの半分を避けられます。
modsフォルダの位置、構文チェックが効くエディタ、そしてFactorioのバージョン確認を先に揃えると、後でログを追う時間がぐっと減ります。
フォルダ名とinfo.jsonの一致も最初に確認しておくと、MOD一覧に出ない原因を早い段階で切り分けられるでしょう。
OS別のmodsフォルダの場所
modsフォルダはOSごとに場所が違い、Windowsなら%APPDATA%\Factorio\mods、macOSなら~/Library/Application Support/factorio/mods、Linuxなら~/.factorio/modsです。
既存MODをすでに入れているなら、zipが並んでいるそのフォルダが該当します。
迷ったときは、導入済みMODの置き場所を見れば早い。
ここを先に押さえておくと、サンプルをどこへ置くかで立ち止まらずに済みます。
エディタはJSONとLuaの構文チェックが効くものを選ぶ
エディタは、JSONとLuaの構文チェックが効くものを選びます。
MOD開発の初期トラブルは、JSONの末尾カンマや引用符の閉じ忘れが驚くほど多く、色分けと警告だけで数分で片が付く場面が多いからです。
実際、JSON対応のエディタに変えた途端、それまでログとにらめっこしていた末尾カンマが赤く表示され、すぐ直せたことがありました。
専用のデバッグ拡張もあり、ブレークポイントを置いて実行を追える環境まで作れます。
フォルダ名とinfo.jsonの一致が最初の関門
フォルダ名はname_version形式にし、info.jsonのnameとversionと一致させます。
たとえばmy-first-mod_0.1.0なら、info.json側のnameがmy-first-mod、versionが0.1.0でなければ読み込まれません。
ここがずれるとMOD一覧に出ないため、最初の詰まりどころとして何度も出てきます。
フォルダを作ってからinfo.jsonを書く順番にすると、不一致に気づきやすい。
まずこの形を固定してしまいましょう。
Factorioの対応バージョンも先に合わせます。
2.0世代と1.1世代ではAPIに非互換があり、1.1時代のサンプルコードを2.0環境でそのまま動かそうとすると、storage周りで止まって原因究明に時間を取られます。
実際、バージョン確認を怠って1時間ほど溶かしたことがあり、最初に自分の環境とinfo.jsonのfactorio_versionを揃えるだけで避けられる手間だと痛感しました。
Space Age DLCを前提にするなら、dependenciesの書き方も変わるので、そこまで含めて環境を確定させておくと検証が速くなります。
Step1: info.jsonでMODを認識させる
info.json は、Factorio にこの MOD を「別のものではなく自作MODとして認識させる」ための入口です。
ここで name と title を取り違えると、一覧に出したい名前と内部識別子がずれて読み込みに失敗します。
まずは必須4項目を正しく埋め、続いて factorio_version と dependencies を整える流れで進めましょう。
必須フィールドと推奨フィールド
必須なのは name、version、title、author の4項目です。
name は MOD の内部識別子で、英数字とハイフンで付ける前提のため、日本語タイトルをそのまま入れる場所ではありません。
title は一覧に表示される名前なので、見せたい文言はそちらに置きます。
この役割分担を崩すと、開発中は気づきにくくても、読み込み段階でつまずきやすくなるのが厄介です。
version は major.minor.patch の3数字区切り、たとえば 0.1.0 の形にします。
2桁や4桁の書き方は通らず、公開後は同じバージョンを重ねて出せないため、最初から小刻みに上げる習慣が後で効いてきます。
factorio_version と dependencies は仕様上は任意ですが、実運用ではほぼ必須です。
factorio_version を書かないと古い扱いになりやすく、dependencies も基本は ["base >= 2.0"] を入れておくと、意図しない版で読まれて原因不明のエラーに寄るのを避けやすくなります。
description、homepage、contact は公開時の見え方に直結する補助項目です。
自分用の試作なら省けますが、Mod Portal に載せる段階では description を用意しておくと、一覧から内容を判断しやすくなります。
ここは機能のためというより、見つけてもらうための整え方です。
dependenciesの記法とプレフィックスの意味
dependencies は「プレフィックス+MOD内部名+演算子+バージョン」の形で書きます。
書く対象は表示名ではなく、相手 MOD の内部名です。
自分は以前、Mod Portal の表示名をそのまま書いて読み込まれず、対象 MOD の zip を展開して info.json の name を確認してようやく原因に気づきました。
見た目が似ていても内部名が一致しなければ別物なので、ここは名前の確認を先に済ませてしまいましょう。
プレフィックスの意味も押さえておくと整理しやすいです。! は非互換、? は任意、(?) は隠し任意、~ は読み込み順に影響しない依存、無印は必須です。
とくに無印の必須依存は、相手が見つからないと自分の MOD ごと無効化されるため、他MOD連携では慎重さが要ります。
実際、連携先を無印にしたことで、その MOD を入れていない環境では自作 MOD まで無効化される報告を受け、? の任意依存に変えて解決しました。
連携機能を残したいなら、相手がいなくても本体が動く設計にしておく方が扱いやすいです。
認識されないときに疑うJSONの記法ミス
info.json が認識されないときは、内容より先に JSON の書式を疑うと早いです。
カンマの抜け、引用符の閉じ忘れ、配列やオブジェクトの括弧の不整合は、見た目では分かりにくくても読み込み時に止まります。
とくに dependencies の配列は文字列を1つ足したつもりで末尾のカンマを残しやすく、そこで崩れることが多いです。
もう1つ多いのが、値の型を混ぜてしまうミスです。
version は文字列、dependencies も文字列の配列として書く必要があり、数字をそのまま置いたり、配列の中に別形式を混ぜたりすると意図通りに読まれません。
MOD が見つからないのか、JSON が壊れているのかを切り分けるには、まず最小構成に戻して name、version、title、author、factorio_version、dependencies だけで通る形を作るのがおすすめです。
そこから description などを足していく方が、原因の特定がずっと楽になります。
Step2: data.luaでアイテム・レシピを追加する
dataステージは、全MODの data.lua が走り切ってから、全MODの data-updates.lua、最後に全MODの data-final-fixes.lua へ進みます。
MODごとに3ファイルが連続するわけではなく、ステージ単位で全MODを横断するため、どの段階で何を置くかが競合整理の分かれ目になります。
ここを押さえると、他MODの定義を待つ処理と、自分で先に作る定義をきれいに分けられます。
dataステージの3段階と実行順序
dataステージでは全MODが単一の共有Lua状態で動きます。
ゲーム本体はまだ起動していないので、標準LuaのioのようなAPIは使えず、代わりに data というグローバルテーブルへプロトタイプを積み上げていく形です。
つまり、この段階は「ゲーム中の処理」ではなく「辞書を確定させる準備作業」であり、後から動的に差し替える前提は通りません。
実際、レシピの素材をゲーム中に変える発想はここで止まるため、設計時点で決め切る意識が要ります。
data:extendでアイテムとレシピを追加する
新しいアイテムやレシピを足す基本形は、data:extend に配列を渡す書き方です。
アイテムなら type と name を軸に、icon、icon_size、stack_size など必須プロパティを埋めていきます。
レシピなら ingredients に type・name・amount の組を並べるのが要点です。
自作アイテムでアイコンが出ず数十分悩んだことがありますが、原因は icon_size と実ファイルの解像度の不一致でした。
ログに派手なエラーは出ず、表示だけ崩れるので見落としやすい。
だからこそ、必須プロパティを機械的に埋めるだけでなく、画像サイズまで含めて確認しておく流れが有効です。
ファイルが増えてきたら prototypes フォルダを切って require で分割し、data.lua を肥大化させない構成にしておくと追いやすくなります。
既存レシピを書き換えるならdata-updates.lua
既存プロトタイプの書き換えは data-updates.lua 以降で行うのが定石です。
data.lua の時点では他MODの定義がまだ存在しない可能性があり、そこで書き換えようとすると「そんなレシピは存在しない」エラーになります。
自分も他MODのレシピを data.lua で触って同じ壁に当たり、data-updates.lua に移しただけで通りました。
新規定義は data.lua、他MODや既存定義への上書きは data-updates.lua 以降、と分けて考えるだけで事故はかなり減ります。
data-final-fixes.lua は最後の微調整に回す位置づけなので、競合の残りを整える場として覚えておくと扱いやすいでしょう。
Step3: control.luaでゲーム中の挙動を書く
control.lua は、ゲームが始まってから終わるまでの挙動を扱う場所です。
data ステージで定義を固めたあと、プレイ中に何が起きたかを拾って反応させるのが役目になります。
ここではイベント駆動で処理を書き、セーブをまたぐ状態は storage に寄せる、という2本柱を押さえるとでしょう。
controlステージはいつ始まるか
controlステージは新規ゲーム開始かセーブ読み込みの時点で始まり、そのセーブが動いている限り持続します。
dataステージが起動時に1回だけ走って定義を確定させるのに対し、controlステージはプレイ中ずっと生きているため、建設や生産、設定変更のような「進行中の出来事」に向いています。
しかも control.lua はMODごとに独立したLuaインスタンスで動くので、他MODの変数をそのまま触ることはできません。
連携したいなら、リモートインターフェースのような専用の仕組みを使う前提になります。
script.on_eventでイベントを拾う
control.lua の基本はイベント駆動です。script.on_event にイベント種別とハンドラ関数を登録しておけば、エンティティが建設された、プレイヤーがアイテムを作ったといった契機で必要な処理だけが呼ばれます。
毎tick処理も書けますが、何も起きていない時間まで回し続けると負荷に直結するため、まずはイベントで済むかを考えるのが安全です。
自分も最初は更新監視を雑にtickへ載せていて、あとからイベントに寄せたほうがずっと扱いやすいと痛感しました。
セーブをまたぐデータはstorageに置く
セーブをまたいで保持したいデータは storage テーブルに置きます。
ローカル変数はセーブに含まれず、ロードした瞬間に消えるので、カウンタやフラグをそこに置いたままだと再開時にゼロへ戻ってしまいます。
実際にローカル変数で管理していたカウンタが、連続プレイでは問題なく見えていたのに、セーブ・ロードのあとで初めて壊れたことがありました。
2.0 ではこの保存先が global から storage に改名されているため、1.1時代のコードをそのまま持ってくると動かない点も見落とせません。
構造を変えるときは script.on_configuration_changed で移行処理を書き、既存セーブの storage を作り直す流れまでセットで用意しておくと安心です。
プロトタイプは data ステージ確定後に変更できないので、control.lua からアイテム定義そのものを上書きすることはできません。
control.lua で扱うのは「その世界に存在する実体」であって「設計図」ではない、という線引きを持っておくと、どこまでを data で作り、どこからを control で制御するかがはっきりします。
起動しないときのトラブルシュート
factorio-current.log はまず最初に開くべきファイルで、MOD の読み込み失敗は MOD 名と行番号付きで残ります。
推測で直す前にログを読むだけで切り分けが速くなり、エラーが出ていなくても MOD 一覧に現れないなら、そもそも読み込み対象に入っていません。
まずfactorio-current.logを開く
ログを先に見る習慣があるかどうかで、初動の時間はかなり変わります。
Factorio では読み込み失敗の手がかりが MOD 名と行番号まで含めて出るので、コードを疑う前に「どこで止まったか」を確認できるからです。
自分も、コードは合っているのにアイテム名がゲーム内で Unknown key のままになって悩み、原因は locale の cfg ファイルを en フォルダの外に置いていたことでした。
表示が崩れるときほど、まずログと配置を見てみてください。
原因の大半はJSON記法とフォルダ名の不一致
初期トラブルの多くは、フォルダ名と info.json の name/version の不一致、JSON の末尾カンマや引用符の閉じ忘れ、dependencies に書いた MOD 内部名の誤り、プロトタイプの必須プロパティ漏れで説明がつきます。
順番に疑うならこの4つで足りますし、むやみに広く探すよりずっと早いです。
なお、「Value must be a string」エラーはローカライズ周りの典型で、localised string に渡す引数はすべて文字列でなければなりません。
数値や boolean をそのまま渡すと落ちるので、埋め込み時は文字列化してから渡しましょう。
1.1時代の解説をそのまま真似すると動かない箇所
古いサンプルをそのまま流用すると、1.1 時代には通った書き方が 2.0 では崩れます。
自分も storage 周りで動かず、1時間近く悩んだ末に公式の移行情報を見て、global から storage への改名を一括置換して解決しました。
名前が変わっただけの箇所なら修正は単純ですが、版の違いを意識しないと、10分で済む確認に1時間かけることになります。
さらに 2.0 では defines.direction に8方向が追加され、既存の4方角の値も変わりました。
1.1 時代の MOD で方角を storage に保存していたなら、移行時に値を2倍する処理が必要になりますし、建設イベントの引数も stack や item ではなく consumed_items に変わっています。
イベントハンドラをコピーしてきて nil が出るなら、この種の非互換をまず疑うのが近道です。
locale・changelogとMod Portalへの公開
locale は locale/{言語コード}/任意名.cfg の形で置き、英語なら locale/en/、日本語なら locale/ja/ に分けます。
ファイル名は自由ですが、フォルダ名の言語コードを外すと読み込まれず、ゲーム内では Unknown key が出ます。
cfg の中身は "[mod-name]" や "[item-name]" のようなセクション単位で書き、MOD名ならバージョンを含まない内部名、アイテム名ならプロトタイプの name をキーにします。
公開するなら en を先に用意し、ja は追加で重ねる形にすると、海外ユーザーの画面が崩れません。
localeフォルダで表示名を日本語化する
locale/ja/your-file.cfg を置けば日本語表示は作れますが、公開MODではそれだけでは足りません。
自分用のつもりで日本語 locale だけを書いて出したMODが、海外ユーザーの環境ではキー名そのまま表示されたことがありました。
それ以来、最初に en を書き、必要に応じて ja を足す順番にしています。
ローカライズは単なる飾りではなく、名前や説明が自然に見えるだけで完成度の印象が変わります。[mod-name] には MOD の内部名、[item-name] にはアイテムごとの name を対応させておけば、翻訳漏れの切り分けもしやすくなります。
changelog.txtは書式が厳密
changelog.txt は MOD ルート直下に UTF-8 で置きます。
ここで見落としやすいのが区切り線で、ハイフンはちょうど99本という厳密な書式が求められます。
エディタの補完に任せて本数がずれたまま出したところ、Mod Portal で弾かれ、ハイフンを数え直して99本ちょうどに直したら通りました。
書式の厳密さは想像以上で、内容が正しくても体裁が崩れていると受付で止まります。
ログにも書式エラーが出るので、更新履歴は文章より先にレイアウトを確認しておくと安全です。
Mod Portalへアップロードする
公開用の zip は mod-name_version.zip 形式にし、展開したときにその名前のフォルダが1つだけ入っている構造にします。
たとえば fire-armor_0.1.0.zip の中に、直接ファイルをばらまくのではなく fire-armor_0.1.0/ が1階層だけある形です。
アップロード時は Mod Portal にサインインし、zip を送ります。
ポータルは zip 内の info.json を読んでタイトル、バージョン、依存関係を拾うため、ここが正確ならフォーム入力はかなり減らせます。
最後に見た目だけ整えるのではなく、info.json、zip 構造、changelog.txt の三点をそろえてから出すと、公開後の手戻りを避けやすいです。
Haruto
Factorio 1,500時間超。MOD開発・日本語翻訳の貢献経験を持ち、大型MOD踏破と Space Age DLC 全惑星クリア済み。海外コミュニティの最新情報もカバーします。