個人開発でアプリを長く運用してきた延長で、空き時間に Unity の小さなゲームを作り直しています。普段は Dolice Labs の技術サイトを書く道具として AI を使っていますが、ゲーム開発となると勝手が違いました。そこへ Antigravity を持ち込んでまず感じたのは、「全部やってくれる」でも「何も使えない」でもなく、工程ごとに得手不得手がはっきり分かれる、ということでした。
ここでは「Unity で AI が便利」という抽象論ではなく、私が実際に投げたプロンプトと返ってきたコード、そして手で直した箇所をそのまま並べます。任せて伸びる工程と、人が書いたほうが速い工程の線引きが、この記事の主題です。
プレイヤーコントローラは「8割」で返ってくる
最初の一歩として定番のプレイヤー移動を頼みました。プロンプトは「WASD で移動し、スペースでジャンプ。可変ジャンプ(押している時間でジャンプ高さが変わる)と、足元の接地判定を入れた CharacterController 用のスクリプト」。返ってきたのがこれです。
using UnityEngine;
[RequireComponent(typeof(CharacterController))]
public class PlayerController : MonoBehaviour
{
[SerializeField] float moveSpeed = 6f;
[SerializeField] float jumpForce = 7f;
[SerializeField] float gravity = -20f;
[SerializeField] float jumpCutMultiplier = 0.5f; // 早離しで上昇を減衰
CharacterController cc;
Vector3 velocity;
void Awake() => cc = GetComponent<CharacterController>();
void Update()
{
bool grounded = cc.isGrounded;
if (grounded && velocity.y < 0) velocity.y = -2f; // 接地時は床に押し付ける
float x = Input.GetAxisRaw("Horizontal");
float z = Input.GetAxisRaw("Vertical");
Vector3 move = transform.right * x + transform.forward * z;
cc.Move(move.normalized * moveSpeed * Time.deltaTime);
if (grounded && Input.GetButtonDown("Jump"))
velocity.y = jumpForce;
// ジャンプボタンを早く離したら上昇を切る = 可変ジャンプ
if (Input.GetButtonUp("Jump") && velocity.y > 0)
velocity.y *= jumpCutMultiplier;
velocity.y += gravity * Time.deltaTime;
cc.Move(velocity * Time.deltaTime);
}
}骨格は問題ありません。可変ジャンプを「ボタンを離した瞬間に上昇速度を減衰させる」方式で実装している点も、素直で読みやすい書き方です。
ただし、そのままだと斜め移動が move.normalized で正規化されているのに transform.right と transform.forward を足しているため、カメラを傾けると意図とずれます。私の用途ではカメラ相対の移動が欲しかったので、入力ベクトルを「カメラの向きに投影してから正規化」する一手間を足しました。AI は「CharacterController で動く形」までは出しますが、そのゲーム固有のカメラ設計までは読めません。ここが「8割で返ってくる」の中身です。
敵 AI は「状態機械の骨組み」を任せると速い
敵の挙動は、ゼロから書くより骨組みを出させてから埋めるのが効率的でした。「パトロール→プレイヤー検出→追跡→見失ったら最後の地点へ→一定時間でパトロールに戻る、を enum の状態機械で」と頼むと、分岐の取りこぼしが少ない雛形が出ます。
public enum EnemyState { Patrol, Chase, Investigate }
public class EnemyBrain : MonoBehaviour
{
[SerializeField] float sightRange = 12f;
[SerializeField] float loseSightAfter = 3f;
EnemyState state = EnemyState.Patrol;
float lostTimer;
Vector3 lastSeen;
void Update()
{
bool canSee = CanSeePlayer();
switch (state)
{
case EnemyState.Patrol:
Patrol();
if (canSee) state = EnemyState.Chase;
break;
case EnemyState.Chase:
ChasePlayer();
if (canSee) { lastSeen = PlayerPos(); lostTimer = 0; }
else { lostTimer += Time.deltaTime;
if (lostTimer > loseSightAfter) state = EnemyState.Investigate; }
break;
case EnemyState.Investigate:
MoveTo(lastSeen);
if (canSee) state = EnemyState.Chase;
else if (ReachedLastSeen()) state = EnemyState.Patrol;
break;
}
}
}ここで効くのは、人間が忘れがちな「見失った後の振る舞い」を AI が先に枠として用意してくれる点です。CanSeePlayer() や Patrol() の中身は、レイキャストの層設定やナビメッシュの有無といったプロジェクト固有の事情に依存するので、結局は自分で書きます。骨組みは AI、中身は人、という分担が一番噛み合いました。
シェーダーは「叩き台まで」が現実的な期待値
被ダメージ時の赤フラッシュのような、よくあるエフェクトは Antigravity に下書きを出させると時短になります。「URP の Shader Graph ではなく、HLSL で被ダメージ時に色を白〜赤へ寄せる簡単なフラグメント」を頼んだ結果がこちらです。
// 簡易ヒットフラッシュ(_FlashAmount を 0→1 で制御)
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
fixed3 flash = lerp(col.rgb, fixed3(1, 0.2, 0.2), _FlashAmount);
return fixed4(flash, col.a);
}考え方としては十分です。ただ、シェーダーは Unity のレンダーパイプライン(Built-in / URP / HDRP)によって書き方も変数名も変わります。AI は指定したパイプラインを取り違えることがあり、URP と書いても Built-in 前提のコードが混ざることがありました。私は「叩き台をもらって、自分のパイプラインに合わせて直す」前提で使っています。シェーダーを丸ごと信頼するのは、まだ早いというのが正直な感触です。
パフォーマンス調査こそ、AI が本領を出す
意外だったのは、コード生成よりもボトルネックの当たりをつける用途のほうが頼りになったことです。「Update が重い。フレームレートが落ちる」という曖昧な相談でも、Antigravity は典型的な容疑者から順に挙げてくれます。
たとえば私のコードでは、Update の中で毎フレーム GetComponent を呼んでいた箇所と、new でリストを作り直していた箇所を指摘されました。前者はキャッシュ、後者は使い回しに直すだけでアロケーションが消えます。
// Before: 毎フレーム GetComponent + 新規リスト = GC を生む
void Update() {
var rb = GetComponent<Rigidbody>();
var hits = new List<Collider>(Physics.OverlapSphere(transform.position, 3f));
// ...
}
// After: 参照はキャッシュ、バッファは使い回す
Rigidbody rb;
readonly Collider[] buffer = new Collider[16];
void Awake() => rb = GetComponent<Rigidbody>();
void Update() {
int n = Physics.OverlapSphereNonAlloc(transform.position, 3f, buffer);
// ...
}OverlapSphereNonAlloc のような「アロケーションを生まない API」へ寄せる提案は、公式リファレンスを読み慣れていないと出てこない発想です。私はプロファイラで赤くなった原因が分からないとき、まず Antigravity に状況を説明して当たりをつけ、それから Unity Profiler で裏を取る、という順番にしてから調査が速くなりました。
逆に、AI に任せて伸びなかった工程
正直に書くと、効かない工程もはっきりあります。
ひとつは「ゲームの手触り(ゲームフィール)」です。ジャンプの気持ちよさ、ヒットの重み、カメラの追従の柔らかさといった数値は、実際に動かして自分の指で確かめるしかありません。AI に「気持ちよくして」と頼んでも、それは自分が遊んで決める領域でした。
もうひとつはシーン構成とプレハブ設計です。どのオブジェクトを親子にし、どこをプレハブに切り出すかは、そのゲームの拡張方針に強く依存します。ここを AI 任せにすると、後から手を入れにくい構造になりがちでした。設計の意思決定は人が握り、その実装の手数を AI に渡す、という線引きに落ち着いています。
次の一歩
もしこれから Unity に Antigravity を入れるなら、いきなり大きな機能を頼まず、プレイヤーコントローラのような「正解が見えている小さな課題」で一度試すことをおすすめします。返ってきたコードのどこを直したかをメモしておくと、自分のプロジェクトで AI が読み取れない前提(カメラ設計・パイプライン・命名規約)が見えてきます。その境界が分かれば、任せる工程と手で書く工程の判断が一気に速くなります。