うどんてっくメモ

技術的なメモをまったりと

【Godot】 Godot + Claude Code開発におけるコード検証の仕組み

はじめに

Godotで開発を進める中で、Claude CodeなどのAIコーディングエージェントと連携する場合、コードの品質をどう担保するかは重要な課題です。

  • パス間違いや参照エラー
  • コードスタイルの不統一
  • AIが生成したコードがコンパイルエラー

こうした問題を解決するため、いくつかのツールを使ってコードの品質チェックを自動化する仕組みを紹介します。

本記事で紹介および検証を行なっているツール、ライブラリのバージョンは次の通りです。

バージョンによっては挙動に差異がある場合もありますので、ご了承ください。

使用するツール

1. gdtoolkit (GDScript Toolkit)

github.com

gdtoolkitは、GDScriptの静的解析やフォーマットを行うためのPython製ツールスイートです。以下のコマンドでインストールします。

pip install gdtoolkit

gdtoolkitの以下の2つのツールを使用します。

  • gdformat: GDScriptのフォーマッター。コードのスタイル(インデント、スペース等)を補正します。
  • gdlint: GDScript用の静的解析ツール。未使用変数や命名規則違反など、潜在的なバグや規約違反を検出します。

2. Godot コマンドラインツール

docs.godotengine.org

godot --headless --editor --quit-after 1コマンドを使用します。プロジェクト全体のスクリプトコンパイルし、preloadのパス間違いや型エラーといった、GDScriptの文法エラーを検出します。--quit-after 1自体は起動時の初期化を終えて1フレーム後に落としてるだけです。これでコンパイルなどは実行されるので、その結果を利用します。

スクリプト単体でならgodot --headless --check-only --script hogehoge.gd --quitでもいいんですが、複数の依存関係込みでのテストだと--editor --quit-after 1が良いっぽいです。(違ったら指摘してください)

Makefileの作成

これらのツールを毎回手で実行するのは手間なので、Makefileを使って一連の処理をコマンド一つにまとめます。

プロジェクトのルートに、以下の内容でMakefileファイルを作成します。

# Makefile

# 1. フォーマットをチェックする
format-check:
    gdformat --check .

# 2. フォーマットを自動で整形する
format:
    gdformat .

# 3. 静的解析を実行する
lint:
    gdlint .

# 4. Godotエンジンでコンパイルチェックする
compile-check:
    godot --headless --editor --quit-after 1

# --- 統合コマンド ---

# フォーマットと静的解析をまとめて実行
validate: format-check lint

# 全てのチェックをまとめて実行
full-validate: format-check lint compile-check

.PHONY: format-check format lint compile-check validate full-validate

これにより、ターミナルで make full-validate と呼び出し、すべてのチェックが行われるようになります。

Claude Codeのカスタムコマンドとして定義

.claude/commandsなどにmd形式でコードチェックや、エラーが出た時に適切に対応することを指示し、カスタムコマンドひとつ実行するだけでClaude Codeに実行させるようにします。 サンプルはこんな感じです。

# コード検証プロトコル

## 概要
gdtoolkitとGodot headlessモードを使用したGDScriptファイルの完全なコード品質・コンパイル検証

## 実行手順

### 1. フォーマットチェック
make format-check

フォーマットエラーがある場合:
make format

### 2. 静的解析
make lint

### 3. コンパイル検証
make compile-check

### 4. 統合検証
make validate      # フォーマット + 静的解析のみ
make full-validate # フォーマット + 静的解析 + コンパイル検証

## エラー解決プロトコル

### フォーマットエラー
- `make format`で自動修正
- 意図しない変更の確認
- フォーマット後のコードを個別コミット

### Linterエラー
表面的な修正ではなく、適切な設計改善による解決が必須

#### 命名規則エラー
- セマンティックな明確性の見直し
- ドメイン言語に従った命名の確保
- 関連ドキュメントの更新

#### スタイル違反
- 違反が設計問題を示唆していないか検討
- より良いコード構造への リファクタリング
- 既存コードベースとの一貫性維持

#### 複雑度警告
- 複雑な関数の分割
- 単一責任原則に従ったメソッド抽出
- 設計パターンの改善検討

### コンパイルエラー
実際のGodotエンジンによるコンパイル検証で検出されるエラー

#### 参照エラー
- enum値のリネーム時の参照更新漏れ
- クラス名変更時の参照整合性
- メソッド名変更時の呼び出し箇所の更新

#### 型エラー
- 型の不整合
- 未定義変数の参照
- メソッドシグネチャの不一致

#### リソースエラー
- 存在しないリソースファイルの参照
- バージョン不整合によるリソース読み込み失敗
- シーンファイルの依存関係エラー

## 実行義務
- コード変更後の必須実行
- コミット前の必須通過
- コードレビュー前の完全パス必須

## 失敗時の対応
検証失敗時:
1. 根本原因の分析
2. 適切な設計解決の適用
3. 検証の再実行(`make full-validate`4. 全チェック通過まで反復

適切な設計原則を迂回するクイックフィックスの適用禁止

実行するとこんな感じで実行されます。

Claude Codeに使わせたい

手動で使うのもいいですが、どうせならコードを更新したらClaude Codeに使ってほしいです。 プロジェクトに .claude/commands/code-validate.md のようなAIへの指示ファイルを用意し、「コードを変更したら、必ずこのコマンドで検証すること」というルールを定義しておきます。

## コード検証プロトコル

コードを変更した後は、必ず @.claude/commands/code-validate.md のコマンドに従い、品質チェックを厳守してください。
エラーが出た場合は、その場しのぎの修正ではなく、設計を見直して根本的な解決を図ってください。

こうすることで、ルールを忘れない限りは実装を終えた後にコードの検証を行なってくれます。 Gitのprecommitに仕込んでおくのもありだと思います。

#!/bin/sh
# Pre-commit hook for merchant project

echo "=== Pre-commit Code Validation ==="

if make validate; then
    echo "Code validation passed"
    exit 0
else
    echo "Code validation failed"
    echo "Please fix the issues and try again"
    echo "Run 'make validate' to see detailed errors"
    exit 1
fi

まとめ

GDScriptのlinterやコマンドラインツールを使ったコード検証を行うことで、品質のベースラインを保つための強力なガードレールとなります。

この仕組みにさらにgdUnit4などの単体テストによるテスト検証を組み合わせるのも良さそうです。

github.com

【Unity】BuildProfileのCreateOrLoadでエラーが出て正常に操作できない時の対処

はじめに

本記事の検証環境は以下になります。バージョンが違う場合には動作しない場合がありますので、ご留意ください。

  • Unity 6000.0.41f1

BuildProfileのCreateOrLoadにおけるエラー解決

BuildProfileの設定を色々いじってたら、エラーが出てBuildProfileがインスペクター上で上手く読み込めなくなってしまいました。

Library上のBuildProfileの編集情報におそらく齟齬が発生してそうなので、Library/BuildProfileContext.assetを一度消して再起動したら無事に治りました。

BuildProfileで謎にエラーが出て困った時は一回Libraryのキャッシュを整理すると良さそうです。

【Godot】Beehaveを使ったBehaviour Treeの構築

はじめに

本記事で紹介および検証を行なっているツール、ライブラリのバージョンは次の通りです。

  • Godot Engine v4.4.1.stable.official
  • Beehave v2.9.0

バージョンによっては挙動に差異がある場合もありますので、ご了承ください。

Beehaveとは

Beehaveは、Godotエンジン向けのBehaviour Treeプラグインです。

github.com

ドキュメントもしっかり完備されており、それぞれのパーツの扱い方が記載されています。本記事より詳細な説明を見たい方は英語にはなりますがこちらを参照してください。

bitbra.in

Behaviour Treeとは

端的に説明すると、AIのアルゴリズム手法のひとつで、ゲーム分野でのAIの行動決定に用いられる仕組みです。ツリー構造で制御する仕組みであり、それらをゲームエディタ上でGUIベースで構築する仕組みなどを用意することで、シンプルに動作を管理しやすく、柔軟に拡張できます。

本記事では詳細には説明しません。解説されている方の記事はあるので、そちらを参照してください。 engineering.linecorp.com

Beehaveの説明をする際にもBehaviour Treeの概念に則った説明となるため、都度確認することを推奨します。

Beehaveの導入

Beehaveはプラグインとして公開されており、一般的なプラグインの導入手順で使うことが可能です。

  1. プラグインの取得:GitHubからリリースされているBeehaveをダウンロード。
  2. プロジェクトへの追加:ダウンロードしたファイルを解凍し、addons/beehaveをそのままプロジェクトのaddonsディレクトリに配置。
  3. プラグインの有効化:Godotエディタのプロジェクト設定プラグイン タブから、Beehaveプラグインを有効化。
  4. スクリプトテンプレートの追加 : script_templatesをそのままプロジェクトのscript_templatesディレクトリに配置。

基本的な構成

Beehaveを活用したBehaviourTreeでは、ノードの階層自体がBehaviour Treeのツリーとなり、上から深さ優先探索で実行処理が行われます。 ノードには以下のような種類が存在しています。


1. Composite Node

数の子ノードを順番や条件で評価するノードになります。ツリー構造としてはそれぞれの行動や条件群の上につけるものです。
AIの流れや分岐を構成する役割となります。

主なノード 説明
Sequence 子ノードを順に実行。すべてのノードの実行を完了し完了。
Selector 子ノードを順に実行。いずれかのノードの実行を完了したら完了。(つまりひとつを選ぶ形)
RandomSelector 子ノードをランダムに選んで実行し完了。

2. Leaf Node

実際の行動や条件チェックを担当するノードです。ツリー構造の末端に配置されます。

主なノード 説明
ActionLeaf 特定の処理を実行するノード(移動、攻撃、エモートなど)
ConditionLeaf 条件を返すノード(HPが少ないか?敵が近いか?など)

主なAIの思考やそれに伴う行動の定義をここのAction LeafやCondition Leafとして実装するイメージです。実装する際には継承して実装します。


3. Decorator Node

子ノードの実行結果に変化や条件付けを与えるノードです。
たとえば子ノードの処理を繰り返したり、結果を反転して伝搬するといったロジックが可能です。

主なノード 説明
Inverter 子ノードの結果を逆転(成功 → 失敗、失敗 → 成功)
Repeater 子ノードを複数回繰り返す(無限ループや指定回数)
AlwaysSucceeder 子ノードの結果を常に成功にする(リセット用途など)

以上のノードの説明をもとに、サンプルの構成を示します。

このツリーが上から順に実行され、AIの行動となる形です。このツリーの実行結果は以下の通りになります。

  1. 準備行動を実行
  2. 攻撃条件を満たしていたら攻撃行動を実行
  3. 攻撃条件を満たしておらず、防御条件をみたしていたら防御行動を実行
  4. 1に戻ってループ

ちなみに、各所で「成功」「失敗」という書き方がありますが、これは具体的なツリーの処理に基づくものです。Beehave Treeのノード群は具体的な処理として、特定の時間経過とともにtickという関数を処理して成功と失敗を返すようになっています。

## Executes this node and returns a status code.
## This method must be overwritten.
func tick(actor: Node, blackboard: Blackboard) -> int:
    return SUCCESS

これを上から順に実行して、一連の処理となります。

  1. 一定間隔で BeehaveTree の tick() が呼ばれる。
  2. ルートノードが tick() を呼ぶ。
  3. 子ノードの tick() を再帰的に呼び出す。
  4. 各ノードは SUCCESS / FAILURE / RUNNING を返す。
  5. 返された結果に応じて上位ノードが継続・中断・失敗を判断する。

Action LeafやCondition Leafを実装する際はこの関数をoverrideしていく形です。

Blackboard

Behaviour Treeでは、各ノードが直接お互いに情報をやり取りしない設計になっています。
その代わりに使われるのがBlackboardという存在です。

これは、ノード間で値を読み書きするための共通のメモ領域のようなもので、AIの「状態」や「変数」の保管場所として機能します。

  • Condition ノードで「敵が見つかったか?」を判定したい。
  • Action ノードで「移動先の座標」や「ターゲット」を取得して行動したい。
  • 複数のノード間で「フラグ」や「ステータス」を共有したい。

Beehaveでは、BlackboardはBeehaveTreeが管理しており、前述したtick関数の引数として取り扱うことができます。

func tick(actor: Node, blackboard: Blackboard) -> int:
  # 値の取得
  var target_position = blackboard.get_value("target_pos")

  # 値の設定
  blackboard.set_value("alert", true)

Beehave Debugger

実行中のBehaviour Treeの挙動を確認するために、専用のデバッガーが用意されています。

Behaviour Tree実行時の評価フローの流れがリアルタイムで確認できるため、かなり便利です。 GUIでのサポートでこれがしっかり存在していたのが筆者としては大きな魅力でした。

まとめ

Beehaveプラグインを使用することで、GodotエンジンでのBehaviour TreeによるAI実装が大幅に効率化されます。 筆者は自作しようかなと思っていたのですが、かなり完成度の高いこちらのBeehaveを見つけて助けられました。
ぜひプロジェクトに取り入れて、何かしらのゲームAIの実装に役立ててみてください。

【Unity】Build Profileのコンパイルテスト

はじめに

検証環境は以下になります。バージョンが違う場合には動作しない場合がありますので、ご留意ください。

  • Unity6000.0.34f1

Build Profileのコンパイルテスト

この記事で紹介されているプロジェクトのコンパイルテストについて、Build Profileでのテストのサンプルを作りました。

qiita.com

  • 現在のBuild Profileでのテスト
  • すべてのBuild Profileでのテスト

の簡単な実装を紹介します。

まずはPlayerBuildInterface.CompilePlayerScriptsにBuild Profileの情報を流し込みます。

#if UNITY_EDITOR
using NUnit.Framework;
using UnityEditor;
using UnityEditor.Build.Player;
using UnityEditor.Build.Profile;

public static class BuildProfileCompileTestUtility
{
    /// <summary>
    /// コンパイルテスト
    /// </summary>
    public static ScriptCompilationResult Compile(BuildProfile profile, string output)
    {
        var option = new ScriptCompilationSettings
        {
            target = GetBuildTarget(profile),
            extraScriptingDefines = profile.scriptingDefines
        };

        return PlayerBuildInterface.CompilePlayerScripts(option, output);
    }

    /// <summary>
    /// リフレクション経由でのBuildTarget参照
    /// </summary>
    private static BuildTarget GetBuildTarget(BuildProfile profile)
    {
        var buildProfileType = typeof(BuildProfile);
        var buildProfileBuildTargetObject = buildProfileType
            .GetProperty("buildTarget", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
            ?.GetValue(profile);
        var buildTarget = (BuildTarget)buildProfileBuildTargetObject;
        
        Assert.IsNotNull(buildTarget);
        return buildTarget;
    }
}
#endif

Build Profileが本記事のUnityのバージョンではおおむねinternalでプロパティやメソッドが実装されているため、今回はリフレクションのパワーでBuildTargetを取得しています。

続いて、この処理を用いてテストを実装します。

#if UNITY_EDITOR
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using UnityEditor;
using UnityEditor.Build.Profile;

public static class BuildProfileCompileTest
{
    private const string OutputPath = "Temp/BuildProfileCompileTest";
    
    /// <summary>
    /// 現在のBuildProfileでテスト
    /// </summary>
    [Test]
    public static void 現在のBuildProfileコンパイルテスト()
    {
        BuildProfileCompileTestUtility.Compile(BuildProfile.GetActiveBuildProfile(), OutputPath);
    }
    
    /// <summary>
    /// 管理しているすべてのBuildProfileでテスト
    /// </summary>
    [TestCaseSource(nameof(GetAllBuildProfiles))]
    public static void 全部のBuildProfileコンパイルテスト(BuildProfile profile)
    {
        BuildProfileCompileTestUtility.Compile(profile, OutputPath);
    }

    /// <summary>
    /// 管理しているBuildProfileの取得、今回はテンプレのディレクトリにまとまっている想定
    /// </summary>
    private static IEnumerable<BuildProfile> GetAllBuildProfiles()
    {
        var path = "Assets/Settings/Build Profiles";
        return AssetDatabase
            .FindAssets("t: BuildProfile", new[] { path })
            .Select(AssetDatabase.GUIDToAssetPath)
            .Select(AssetDatabase.LoadAssetAtPath<BuildProfile>);
    }
}

#endif

網羅的にBuild Profileのコンパイルを行う処理については、Build Profileがデフォルトだと「Assets/Settings/Build Profiles」に保存されるため、それらを取得してテストする方式を取りました。

テストするためのサンプルのBuild Profileを作ります。今回はPlatform2種類とScripting Defineを設定した1種類を用意しました。テストが通る前提のBuild ProfileとなるAndroidの何も設定していないBuild ProfileにSwitchします。

スクリプトは以下のサンプルを用意しました。

namespace Temp
{
    public class Sample
    {
#if UNITY_IOS || TEST_DEFINE
        // iOSプラットフォームかTEST_DEFINEが有効の際にエラーになる
        error;
#endif
    }
}

これを実行すると、狙い通りiOSのBuild ProfileとTEST_DEFINEをScripting Defineに設定したBuild Profileでテストがコケます。

Unity6からはこのようなテストを常設していくと、捗りそうです。

2024年の振り返り

2024年やったこと

個人ブログ投稿 8本

この技術ブログである「うどんてっくメモ」、今年は8個の記事を投稿しました。 去年に引き続き毎月1本のペースが乱れてしまいました。ちょっとニッチな部分の記事が増えつつあります。

myudon.hatenablog.com

会社ブログ投稿 2本

technote.qualiarts.jp

technote.qualiarts.jp

会社のブログは2本寄稿しました。最近CIの話ばかりする人になりつつあります。

技術書典

技術書典で「UniTips」というUnityの技術書を定期的に会社で出しているのですが、2024年も執筆に参加しました。

creator.game.cyberagent.co.jp

執筆者があまり揃わず1回休み、その次では3人で出す形となりました。 Unity6のBuild Profileについての記事を執筆しましたが、個人的にBuild Profileにはかなりの期待を抱いています。

CA.unity

CA.unityを2021年にはじめ、2024年はその継続回となるCA.unity #8を開催いたしました。

cyberagent.connpass.com

今年は株式会社アカツキゲームス様にご協力いただき、開催いたしました。おかげさまで大盛況となりました。ありがとうございます。 来年の頭には初のオフライン開催となるCA.unity #9が開催されます。ぜひぜひご参加ください。

cyberagent.connpass.com

アドベントカレンダー

毎年恒例アドベントカレンダー、2024年も会社のものとUnityのものに参加させていただきました。 会社のものは前述した記事で、UnityのものではGame CIについてのマニアックなビルド話を書きました。

myudon.hatenablog.com

今年も今年とて、会社のアドベントカレンダーの運用と広報をしました。毎年同じことを言っていますが、モバイルゲーム開発や運用で培ったニッチな知見が並んでいます。 こちらもぜひぜひご覧ください。

qiita.com

最後に

2024年は何かと会社のことが忙しく、2023年よりもバタバタしてしまいました。 プライベートでなかなか力が出ず、なんとか記事をぽちぽちと書いたりイベントの開催を維持しました。 (CEDECも公募落ちしてしまいました。リベンジしたい。)

来年からも色々と挑戦したいことがあり、忙しい年になりそうです。 2025年も、どうかよろしくお願いいたします。

【Unity】GameCIでタグに存在しないバージョンのUnityのDockerイメージをビルドする

はじめに

本記事は、Unity Advent Calendar 2024 21日目の記事になります。 検証環境は以下になります。バージョンが違う場合には動作しない場合がありますので、ご留意ください。

  • game-ci/docker 3.11

GameCI

game.ci

GameCIはUnityのクラウド上での実行環境を提供してくれるライブラリです。 Docker環境で動くUnityの実行環境を公開し、それらがGitHub ActionsなどのCI環境で手軽に使えるような機能も提供してくれています。

GameCIの存在によって、Unityのビルド職人たちはクラウドビルドにおいて任意のUnityのビルド環境を手軽に使えます。とても便利なライブラリです。

存在しないバージョンのUnityのDocker イメージをビルドする

Docker イメージは各種プラットフォームやUnityのバージョンごとにtagが公開されています。公式サイトやDocker Hubで一覧表示と検索ができます。

game.ci

Docker Hub

しかし、完全に網羅されているわけではなく、特定のパッチバージョンやプラットフォームの組み合わせではDocker イメージが存在しないこともあります。 そういったケースでは自前のビルドが必要です。GitHub上でDockerfileが公開されているため、これを利用します。

バージョンやChangeset、必要なモジュールを引数に指定し、Unity Hubでのインストールを行っています。

github.com

# Install editor
ARG version
ARG changeSet
RUN unity-hub install --version "$version" --changeset "$changeSet" | tee /var/log/install-editor.log && grep 'Failed to install\|Error while installing an editor\|Completed with errors' /var/log/install-editor.log | exit $(wc -l)

# Install modules for that editor
ARG module="non-existent-module"
RUN for mod in $module; do \
      if [ "$mod" = "base" ] ; then \
        echo "running default modules for this baseOs"; \
      else \
        unity-hub install-modules --version "$version" --module "$mod" --childModules | tee /var/log/install-module-${mod}.log && grep 'Missing module\|Completed with errors' /var/log/install-module-${mod}.log | exit $(wc -l); \
      fi \
    done \
    # Set execute permissions for modules
    && chmod -R 755 /opt/unity/editors/$version/Editor/Data/PlaybackEngines

Dockerfileはそれぞれプラットフォームごとに分かれ、images配下にそれぞれ置かれています。

今回は例として、windows/editorかつIL2CPPビルドを行うためのDockerfileをビルドするスクリプトを示します。

# バージョンとチェンジセット
UNITY_VERSION="2022.X.XXX"
UNITY_CHANGESET="hogehogehoge"
UNITY_MODULE="windows-il2cpp"

docker build \
  --build-arg version=${UNITY_VERSION} \
  --build-arg changeSet=${UNITY_CHANGESET} \
  --build-arg module=${UNITY_MODULE} \
  -t custom-windows-il2cpp \
  https://github.com/game-ci/docker.git#main:images/windows/editor

紹介したケースは存在しないUnityバージョンのDocker イメージを生成することでしたが、すでに存在するDocker イメージをベースとしてカスタムビルドすることももちろん可能です。これについては公式ドキュメントでも紹介されていますので、参考にしてください。

game.ci

本記事が少しでもみなさまのUnity CI生活に役立つことを祈っています。

おまけ: UnityのChangesetがわからない場合

UnityのChangesetは公式のリリースノートの下の方に書いてあったりします。

mob-sakaiさんが自動で検索するツールを作っていたりするので、これを利用すると便利です。 github.com

(公式でもっと取得しやすいようにしてほしい)

【Unity】GitHub ActionsのWindows Serverでビルドが「Failed to get ipc connection from UnityShaderCompiler shader compiler」で失敗する

GitHub ActionsのWindows ServerのSelf-hosted RunnerにおけるUnityのビルドを検証していたところ、起動はできるものの、ビルドの途中でエラーが出るようになりました。

Failed to get ipc connection from UnityShaderCompiler shader compiler

調べたところ、Jenkins環境で類似のエラーに当たった人の記事に辿り着き、Windowsマシンのデスクトップ ヒープ領域の問題を知りました。

shinji-blog.com

Jenkins automated deployment reported Failed to get socket connection from - Programmer Sought

同様の手順で設定を変更し、マシンの再起動をかけたところ正常にビルドができるようになりました。 記事より手順を引用します。

1. regedit.exeを起動

2. HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SubSystems\Windows を探す

3. 値のSharedSectionの値を変更する
%SystemRoot%\system32\csrss.exe ...(割愛)... SharedSection=1024,20480,768 ... 
-> %SystemRoot%\system32\csrss.exe ...(割愛)... SharedSection=1024,20480,2048 ... 

4. マシンの再起動

非対話式のWindows Serverはデフォルトのデスクトップ ヒープ領域が小さめに設定してあり、これが起因でアプリケーションが問題を起こすことがあるっぽいです。

learn.microsoft.com

Windows ServerをビルドマシンのRunnerとして選定するときには注意が必要そうです。