.NET Lab

Godot .NET で
3DのiPhoneゲームを作る

C# NativeAOT・AIエージェント並行開発

高丘 知央

株式会社ジェイテックジャパン CTO / J-Tech Creations, Inc. VPoE / Sekiban Framework メンテナ / Microsoft MVP

▶ 初代(2009)メインテーマ

自己紹介

  • 高丘 知央 — 株式会社ジェイテックジャパン CTO(日本)/ J-Tech Creations, Inc. VPoE(米国)
  • カリフォルニア州・ロサンゼルス近郊在住
  • イベントソーシング / CQRS のコンサル、AI活用開発の支援・セミナー
  • OSS: Sekiban(Event Sourcing)/ intent-cli(意図駆動開発)
  • Microsoft MVP(Developer Technologies, 2024.11〜)
  • そして——学生時代に作り始めたゲームが、30年越しで完成!
高丘 知央

「Zero4」の30年 — 原点と心残り

1996-97高専時代、C++で
テキストレース(たしか
「Zero4」・非公開)
当時のPC(EPSON 486DX)のイラストEPSON 486DX(イラスト)
2009渡米後、Objective-Cで
Zero4Racer リリース
(個人開発)
初代Zero4Racerの起動画面
2011Zero4Racer Pro
J-Tech Creationsで開発
アーティストと制作
収入にはならず
Zero4Racer Proのゲーム画面
2018ストアから取り下げ
——心残りに
2026AI×C#でリメイク
Pro v3(2D)→
Godot .NETで3Dへ
Zero4Racer Pro 復刻版のアプリアイコンPro 復刻版
2026.10.12Zero4Racer Drive
1.0.0 リリース
Zero4Racer Drive のアプリアイコンZero4Racer Drive の App Store 用スクリーンショット

30年越しの「Zero4」を、C#とAIと一緒に完成させた話

2026年の Zero4Racer 両アプリ(Pro復刻版・Drive)は J-Tech Creations, Inc. の社内プロジェクトとしてリリース(企画・開発: 高丘)。製品の所有は同社です

01

CONCEPT

何を・なぜ作るか

Zero4Racer Drive

3Dレーシング × 運転免許練習 × カスタムペイント × 車で鬼ごっこ

  • 原点は直線だけのゼロヨン(1996-97 C++テキスト版 → 2009 iPhone版 Zero4Racer)— 実車データで「クルマらしさ」
  • 3D化で街へ: 公道・教習所・日米の運転免許試験まで遊びにする
  • 運転練習機能は、カリフォルニアで17歳の娘の免許練習に合わせて追加 — 家族が最初のユーザー
  • 自分の車にカスタムペイント — 描いて、審査されて、走る
  • カスタムペイントした車で、平地・アメリカの街並み・日本の街並みを舞台に鬼ごっこ

「運転が上手くなる気がするレースゲーム」を、ひとりで完成させる

App Store のプレビュー動画(実物)

再始動から5か月 — Pro再現と、31日のDrive

Pro(2D)再現 2026.5.16〜9.11 ・ 途中で何度も止まりながら

0作業日(119日中)
0commits
0C# 行(うちテスト 67,773)

Drive(3D)作成 2026.9.12〜10.12 1.0.0公開

0作業日(31日中)
0commits
0C# 行(うちテスト 44,247)

Godot 4.7.2 .NET / .NET SDK 10.0.401(global.jsonで固定)/ App Store id6812499414

  • ローカルWiFi対戦(レース・鬼ごっこ)に加え、Epic Online Servicesでインターネット・マッチング対戦まで実装
  • 少人数の開発なら後回しになりがちな機能も、この5か月に同梱できた

AIエージェント2系統と並行開発した結果の数字

02

GODOT .NET × C#

.NETエンジニアにどう見えるか

構成の要 — Godotに触れない「Core」を純C#で

同じCoreをGodotは毎フレーム呼び、テストは実時間より速く回す — シミュレーションは.NETの中で完結

この分離は旧Proの物理モデル(TTCar)から — 理由は次の「テスタビリティ」

業務アプリの .NET と、Godot の .NET

業務・エンタープライズの .NET
Godot の .NET
① ループを回すのは

自分のコード(Main / Host)。リクエストが来たら動く

エンジン。毎フレーム _Process(delta)、物理は一定間隔の _PhysicsProcess が呼ばれる

② 部品の組み立て

DIコンテナとインターフェース

シーンツリー。Node を継承して子ノードを並べ、GetNode<T>() で参照

③ 設定の渡し方

appsettings.json / Options

[Export] をエディタで設定 → シーン(.tscn)に保存。partial 必須はこのソースジェネレーターのため

④ イベント

event / メディエーター / メッセージング

Signal([Signal] で宣言、エディタからもつなげる)

⑤ 時間の感覚

1リクエスト数十〜数百ms。GCはほぼ気にしない

1フレーム 16.6ms。毎フレームの割り当てはGCのカクつきに
⑥ オブジェクトの寿命

GCに任せる

Node はエンジン側の寿命(QueueFree)で、GCとは別 → 次のページの問題へ

エンジンが主で、C#は呼ばれる側 — だから計算はCoreに出してテストする

Godot × iOS で直面した問題

1GCとノード寿命は別物

QueueFree してもマネージド側の大配列は残る

2ヒープ140MBで OutOfMemory

iPhoneでは「大きな連続配列」が先に壁 → メッシュ頂点は自前リスト(NativeMemory.Realloc)で持つ

3終了時クラッシュ(FATAL !rc_owner)

C#にバインドされたGodotオブジェクト → Quit前にGC

どれも「エンジンのネイティブ世界と.NETの世界が隣り合う」から起きる

テスタビリティが、ゲーム作りで一番大事

旧Proにテストは無かった。復刻も最初はレース進行がGodotのシーンに埋まってuntestable — そこを純C#の Step(input, dt) に切り出した(2026-06)。3D版のCoreはこの形を最初から引き継いでいる

0倍速 — 免許試験コース5,967m(交通・採点込み)
ゲーム内16.5分 → 実時間7.3秒
0倍速 — 400m×4台のフル物理
ゲーム内55秒 → 1.2ms
0テスト全合格 / 3分16秒
(dotnet test・Godot起動なし)
0ステップ — 2D↔3Dの物理一致
(旧Pro→2D→3Dの継承をテストで証明)

実時間より速く「遊び」を再現できる形にしておく — 速さの感覚の調整も、AIの修正の検証も、全部ここに乗る

AIへの依頼: 入力をすべてシミュレーターに渡せる形に

Godotタッチ・ペダル・ハンドルUI
→ 入力データを作るだけ
Core シミュレーター Step(input, dt) 物理・判定・交通 = 純C#
Godotを知らない
Godot 描画車・カメラ・音に反映
テストxUnit・Godot起動なし
入力列をスクリプトで生成
Assert実時間の最大4.7万倍速で検証
  • アクセル・ブレーキ・ステアリングなどGodotで取れる入力を全部、シミュレーターへの入力データとして渡す
  • 境界は入力データだけ → 「入力を作る側」と「計算するCore」が完全に切り離せる。差し替えるだけでテストが運転する

03

NativeAOT → iPhone

実機に載せるまで

iOS では NativeAOT が必須(JIT不可)

なぜ?iOSは実行時に機械語を作って動かすこと(JIT)を許さない → C#は事前に全部ネイティブ化(AOT)しておく

Macでは普通の.NET(JIT)で動くため、問題はiOS向けpublishで初めて現れる

「AOTで動く」は最初から設計の制約として効いてくる

NativeAOT で問題が出やすいもの

① リフレクション前提のコード

例: テスト用コードで匿名型 new { … } を JsonSerializer に渡していた → iOS向けpublishが失敗

error IL3050: … requires dynamic code generationEXPORT FAILED: Export lacks managed AOT payload

対策: source-generated の [JsonSerializable] ×92 / Utf8JsonWriter直書き。dotnet build は通るので publishをゲートに

② ネイティブ連携 — [DllImport("__Internal")] ×73
[DllImport("__Internal", EntryPoint = "z4r_video_append_frame")]
static extern int AppendFrame(ref byte rgba, int length);
extern "C" __attribute__((visibility("default")))
int z4r_video_append_frame(const uint8_t *rgba, int length);

StoreKit課金 / Game Center / EOS / AVAssetWriter / オーディオ / アクセシビリティ — 全部この橋を渡る

z4r_video_beginz4r_video_append_framez4r_video_append_audioz4r_storekit_purchasez4r_gamecenter_authz4r_eos_session_joinz4r_audio_sessionz4r_accessibility_voiceoverz4r_video_share_beginz4r_video_save_begin z4r_video_beginz4r_video_append_framez4r_video_append_audioz4r_storekit_purchasez4r_gamecenter_authz4r_eos_session_joinz4r_audio_sessionz4r_accessibility_voiceoverz4r_video_share_beginz4r_video_save_begin

04

VEHICLE DYNAMICS

物理はGodot、クルマらしさは実車データ

剛体は RigidBody3D、駆動とタイヤは自前の8ステップ

次のキーで1ステップずつ見ていきます

① トルク補間エンジンのトルクを回転数で補間し、アクセル開度を掛けて、エンジン摩擦を引く

② クラッチクラッチのつながり具合と総減速比で、タイヤに届く力に変換する

③ ブレーキブレーキの力と、速度に比例する空気抵抗を差し引く

④ 荷重移動加減速で前後の荷重が移り、速度が上がるとダウンフォースが増える

⑤ 駆動配分FF / FR / 4WD に応じて、駆動力を前輪と後輪に配る

⑥ 摩擦で制限タイヤの静/動摩擦の上限で駆動力を制限する — 超えると滑る

⑦ 積分車速・位置・RPM を dt で積分して、次の状態にする

⑧ エンスト回転が落ちすぎたらエンスト、上がりすぎたらレブリミット

①の実データ2007/2008年のSQLにあるトルク曲線(6車種)を、そのまま補間に使っている

12車種のトルク曲線・ギア比は19年前のSQLから移植 — 3D版は操舵・横力・4輪サスを追加

05

MULTIPLE AI AGENTS × HUMAN

意図駆動開発(intent-cli)と、人が持つ承認

intent-cli — 意図を管理して、意図で開発する

株式会社ジェイテックジャパンが開発する、C#製の意図駆動開発(IDD)ツール。会話で決めた「なぜ・何を」をリポジトリに残す

intent-cli は AI を起動しない — 意図と契約を残し、次の手順を決定論的に案内。人が残すのは意図、プロンプトは生成物

intent-cli 自身も intent-cli で開発 — merge済みPR 916件中、意図ユニット(G番号)659件(2026-04-10〜10-21)・Release 62件(最新 v0.32.1)・Apache-2.0 / dotnet tool install -g JTechJapan.IntentSystem.Cli(2026-10-23時点)

orchestrator mode(複数席)— 「意図を渡して回す」

1席+独立したレビュー用subagentで回す solo-conductor もあり、チームの形は対等(ADR 0015)。Zero4Racer Drive は Claude Code と Codex を実装に使い、各作業は git worktree(スレッド間連携の agmsg は現在非推奨、新しく組むなら herdr-only)

自走させる所と、人が持ち続けた承認

AIに任せた(自走)
  • 実機記録(.z4rrec)の吸い出し・デコード・原因特定
  • テスト再現 → 修正 → PR → マージ
  • 開発ビルドを両端末にインストール
人が承認を持ち続けた
  • Cloudflare Worker のデプロイ(変更ごと)
  • TestFlight / App Store への提出
  • 課金設定・マルチプレイのプロトコル番号
  • リリースに何を入れるかの判断

「まだ live でない間はマージは任せる」— 権限は段階で委譲する

06

SERVER SIDE

Sekiban-dcb-ts on Cloudflare

カスタムペイント審査 — イベントを積んで状態を導く

カスタムペイントを解放した結果、不適切な画像を他のプレイヤーに見せない仕組みが必要になった

審査の状態が変わるたびに、誰に見せてよいか(権限)とゲームでの見え方(動作)が変わる。その変化を状態の上書きではなくイベントとして積むので、いつ・なぜ見えるようになり、なぜ見えなくなったかを後から辿れる — 取り消しも消すのではなく追記

Sekiban-dcb-ts 0.2.0(SekibanのTS版。株式会社ジェイテックジャパンの開発プロジェクト)on Cloudflare — 構成は次のページ

書き込みの一貫性は「タグごとのDurable Object」が持つ

☁ Cloudflare iPhone アプリ (Godot/C#) API Worker ゲームのAPI PaintAuthority Worker ペイント審査 Workers AI 自動チェック RPC Durable Objects(各DOが自分のSQLiteを持つ) TAG DO タグ1つにつき1個 paint:0123…cdef ヘッド照合+追記を 1トランザクションで 正本 TAG_STATE タグ×projector ごとの状態 ALLOCATOR×1 並び順ID払い出し BOOTSTRAP×1 入場チェック PaintImage paintごと 画像・削除可 JOURNAL 診断用 — 書き込み確定の経路には入らない 追記 Queueoutbox イベント D1 dcb_events 派生の全体コピー D1_MV 読み取りモデル unsafe lane 数秒 cron 毎分 safe lane 全件確認

タグ = DCBの整合性境界。DOはキーごとに1インスタンス+トランザクション付きSQLiteなので、分散ロックなしでタグ内の一貫性を書ける

正本はタグDOのSQLite。D1へは派生コピーが数秒で流れ、毎分のcronが全件を確認する

一番の設計論点 — 「不変ログ」と「削除義務」の両立

ゲームでも業務でも同じ — 何を不変にし、何を消せるようにするかが設計

07

SHIP IT

AIが仕上げてくれる今、完成に何が要るか

実機で触って、指摘を返す — 正しさは現実から来る

「後続車が内側から突っ込んできた」→ .z4rrec を解析: 車線中央から2.2mずれ、判定幅の10cm外だった

「問題ないのに FAILURE TO YIELD」→ 横切り終えた対向車を「交差点占有中」と数えていた

航空写真と見比べて「停止線が奥すぎる」→ 道路生成の構造ごと変更

現実がそのままルールになる

本人が実際の路上試験で「右折専用の青矢印で完全停止」して不合格になった

→ その日のうちに「青矢印で止まると不合格」のルールと信号をゲームに追加(2026-10-07)

  • iPhoneでしか出ないOOM — Macでは再現せず、端末内ログに例外とメモリ状況を残す仕組みで特定
  • 合成テストでは再現できないバグが、実機のリプレイ記録で初めて見える
実機と同じ生成コードの街(HARBOR)

AIが仕上げてくれる今、完成に要るもの

AIの速度直すのは速い
×
実機で触り続ける人「何が正しいか」は現実から
×
人が持つ承認デプロイ・提出・リリース判断
  • 気持ちよさ・面白さ・ゲームとしてのバランスを、妥協点を探しながら考え抜き、諦めずに改善要求を出し続ける気合いとこだわり
  • 幸いこれはビジネスアプリを作ってリリースするときのこだわりと同じ方向性 — 30年の開発経験が判断の土台になった

アプリを実際に使い、細部まで確かめて、折れずに指摘を出し続ける
それが完成の原動力

08

AFTER LAUNCH

Godotはゲームを出した後も武器になる

リプレイ → MP4 書き出しも C#(アプリ内蔵)

リプレイ記録.z4rrec
→
SubViewport 再描画番組時計で決定論的に
→
GetImage() 読み戻し7.9MB/フレーム
→
AVAssetWriterDllImport経由でMP4
using var image = _director.Viewport.GetTexture().GetImage();   // GPU→マネージド
if (!_encoder.AppendFrame(image.GetData())) { EncoderStopped("append-frame"); return; }
_audio.Render(_audioCursor, chunk);                              // 効果音もその場でミックス

Mac側は同一インターフェースでハッシュだけ取る別実装 → 書き出し全体をテストで検証できる

このプレゼンも同じ素材から: 章扉の背景はゲームを撮った動画、車や交差点は実GLBをThree.jsでリアルタイム描画

これが書き出された実物 — iPhone実機のアプリがAVAssetWriterで書き出したMP4、無加工。CPU対戦(6台中P2) SHORT CIRCUIT。共有テキストもアプリが生成

リール / ショートで露出を作る

交通クイズを「見られる・語られる」ものにする7つの工夫

1

問いは「あなたならどうする?」の二択

悪役(クラクション・あおり・割り込み)× 誰でも経験する場面 × 意見が割れる答え → 体験談とツッコミが集まる「後ろからクラクション」8,700回

2

日本語版と英語版で狙いを分ける

日本語: 日本人が驚く米国ルール「赤でも右折OK」8時間で2,058回英語: 米国の「あるある違反」「出口直前の割り込み」8時間で1,943回

3

主役を最優先にした画面

文字はスマホの安全枠内 / 主役の車は隠さず大きく / 注釈は画面の25%以内 / 話している方だけ小さく表示

4

場面の作り込み

地域・施設・道路標示を「場所の設定書」で決めてから3Dに。車はゲーム内の車種モデル

5

正確さ

法令は一次資料で確認。画面に「日本では/カリフォルニアでは」と出すときはセリフでも同時に言う

6

品質の自動チェック

全コマ検査(文字位置・主役の見え方・走る向き・衝突・信号)/ 音声認識で読み間違い検出 / AIの画面レビュー ▶ 読み上げを聴く

7

アプリへの導線

最後に実際のゲーム画面で「日本と米国の標識で練習できる」と具体的な価値を伝える。「いいね・コメントして」とは頼まない

方針: 量より質

1回 10本(5テーマ × 日英)を作り、オーナーが見て確認してから公開

O05「後ろからクラクション。右折する?」(公開済み)

3D運転クイズをSNSへ — 約1か月で23万回超の表示・再生

0表示・再生(複数SNS合算 ※)
↓ 約51%
0視聴者(Instagram 30日)
↓ 約0.6%
0プロフィール訪問(Instagram)
↓ 約14%
0リンクタップ(アプリへの関心)

帯の長さは対数目盛

23万回の内訳

Instagram 207,604YouTube 16.8KFacebook 8.2K

Instagram フォロワー 0開設1週間

線は 0 → 103 の推移のイメージ(日ごとの実数ではない)

  • 日本語・英語のショート動画を7日間で100本 — 独自3Dエンジン(Godot+C#)でシーンを量産できるのが展開の強み
  • 交通判断クイズ → 参加 → アプリへの関心(リアクション約1,400。リンクタップはインストール数ではない)

※ Instagram 207,604 + YouTube 16.8K + Facebook 8.2K の参考合算(集計期間・指標定義は媒体ごとに異なる)。2026-10-23 時点のスナップショット

まとめ

  1. 01
    30年越しの「Zero4」を完成3Dレーシング × 運転免許練習 × カスタムペイント × 車で鬼ごっこ — 2026.10.12 Drive 1.0.0
  2. 02
    Godot .NET入力をすべてシミュレーター(純C#)に渡す形にして、Godotの表示と分離して開発・テスト
  3. 03
    NativeAOTiOSはAOTが必須。publishをゲートに、ネイティブとは73本の橋
  4. 04
    複数のAIエージェント × 意図駆動開発直す速度はAI、デプロイ・提出・リリースの承認は人
  5. 05
    出した後も Godot+C# が武器3Dエンジンで交通クイズを作り、量より質で約1か月 23万回超

アプリを実際に使い、細部まで確かめて、折れずに指摘を出し続ける
それが完成の原動力

THANK YOU

Zero4Racer Drive
ぜひ遊んでみてください

日本とアメリカの道で、運転とレースと鬼ごっこ
交通クイズのリールは Instagram で公開中

Zero4Racer Drive: J-Tech Creations, Inc. 社内プロジェクト / Sekiban-dcb-ts: 株式会社ジェイテックジャパン 開発プロジェクト

@tomohisa / jtechs.com / sekiban.dev / intent-driven-development.com

Zero4Racer Drive の App Store ページへのQRコード App Store で入手 apps.apple.com/app/id6812499414 Zero4Racer の Instagram へのQRコード Instagram @zero4racer