はじめに
AI(Claude Code)を使ってUnityで2D格闘ゲームを個人開発しました。「格闘ゲームなんてフレーム単位の制御が必要だし、個人では無理だろう」と思っていましたが、意外と形になりました。
ただ、一番苦労したのはコードではなく スプライトシートの生成と分割 でした。
本記事では、AI画像生成でスプライトシートを作る際にぶつかった問題と、それを解決するために作ったUnityエディタツールについて書きます。
環境
- Unity 6(6000.3.x)
- 画像生成AI(スプライトシート生成用)
- Claude Code(コーディング支援)
問題1:AIが勝手に気を利かせる
格闘ゲームのスプライトシートは、Idle・Walk・Attack・Jump・Damageなど、決められたポーズを決められた順番で並べる必要があります。
ところが、AIに「正面の構えポーズを描いて」と指示しても、頼んでいないカッコいいアングルの回し蹴りが出てきたりします。AIは「こっちの方が見栄えがいいだろう」と気を利かせてくれるのですが、スプライトシートには決まったポーズが必要なので、これは困ります。
対処法
プロンプトでフレームの配置を厳密に指定しました。最終的に落ち着いたのが 6列×3行の18フレーム構成です。
Row 0: Idle, Walk1, Walk2, Attack1, Attack2, Damage
Row 1: Jump, JumpAtk1, JumpAtk2, JumpAtk3, JumpAtk4, JumpAtk5
Row 2: Crouch, CrouchAtk, Victory, KO, Sweep, Guard
これをプロンプトに明示した上で、「これ以外のポーズは不要」と念押しすることで、安定して意図通りのシートが得られるようになりました。
問題2:切れ目問題
これが一番厄介でした。
AIはスプライトシート上にキャラクターを 等間隔に配置してくれない ことがあります。キャラ同士が微妙に寄って配置されるため:
- 髪の毛が隣のコマにはみ出す
- 足が切れる
- 拳が次のフレームに突き抜ける
均等なグリッドで切り出すと、こうした「はみ出し」がそのままゲーム画面に反映されてしまいます。
解決策:スプライトシート分割エディタツールを作った
手動で切り出し位置を調整するのが現実的だったので、Unityのエディタ拡張として 「スプライトシート分割&中央配置」 ツールを作りました。
[MenuItem("Tools/スプライトシート分割&中央配置")]
static void Open()
{
var w = GetWindow("シート分割");
w.minSize = new Vector2(500, 450);
}
主な機能:
- プレビュー上で分割線をドラッグ操作で調整できる
- 均等分割ボタンで2×1〜8×4まで一発配置
- 各コマの不透明領域を自動検出して、出力画像の中央に再配置
- 右クリックで分割線を個別削除
ポイントは GetContentBounds() メソッドです。各コマのアルファ値を走査して、実際にピクセルが存在する矩形を検出します:
Rect GetContentBounds(Texture2D tex)
{
int w = tex.width, h = tex.height;
Color[] px = tex.GetPixels();
int minX = w, minY = h, maxX = 0, maxY = 0;
for (int y = 0; y < h; y++)
for (int x = 0; x < w; x++)
if (px[y * w + x].a > 0.01f)
{
if (x < minX) minX = x;
if (x > maxX) maxX = x;
if (y < minY) minY = y;
if (y > maxY) maxY = y;
}
return new Rect(minX, minY, maxX - minX + 1, maxY - minY + 1);
}
検出した領域を指定サイズ(512×512等)のキャンバスに中央配置して出力することで、どんなにAIが配置をズラしてきても、統一されたフレーム画像が得られます。
問題3:ピボット位置のズレ
スプライトシートのフレームを切り出せても、各フレームのキャラクターの足元位置がバラバラだと、アニメーション再生時にキャラが上下にガタガタ揺れます。
解決策:アルファ値ベースの自動ピボット検出
ロード時にIdleフレームを走査して、最下部の不透明ピクセルをピボットY座標として採用しています:
// アイドルフレームの最下部の不透明ピクセルを検出
for (int y = idleY; y < idleY + cellH; y++)
{
int solidCount = 0;
for (int x = 0; x < cellW; x++)
if (tex.GetPixel(x, y).a > 0.25f) solidCount++;
if (solidCount >= 3)
{
pivotY = (float)(y - idleY) / cellH;
break;
}
}
X方向はアルファ値の加重平均で重心を求め、全フレームに共通のピボットとして適用します。これにより、足元が安定したアニメーションが実現できました。
フレーム管理:定数で整理する
18フレームの管理は、定数で明確にマッピングしています:
public const int F_IDLE = 0;
public const int F_WALK1 = 1;
public const int F_WALK2 = 2;
public const int F_ATTACK1 = 3;
public const int F_ATTACK2 = 4;
public const int F_DAMAGE = 5;
public const int F_JUMP = 6;
// ... 以下略
public const int F_GUARD = 17;
18フレームは格闘ゲームとしては最小限ですが、Idle・歩行2コマ・攻撃2段・ジャンプ攻撃5段・しゃがみ攻撃・ガード・勝利・KOまでカバーできます。AIに新キャラを追加してもらう際も、「この18フレーム構成で」と指定すれば同じコードで動きます。
ベルトスクロールへの応用
この知見はそのまま別のゲーム(ベルトスクロールアクション)にも応用できました。そちらでは同じSpriteSheetSplitterをプロジェクトにコピーして、コンボ・ジャンプ攻撃・掴み投げまで実装しています。一度仕組みを作ってしまえば、キャラクターを追加するコストが大幅に下がります。
まとめ
┌──────────────────┬──────────────────────────┬────────────────────────────────────────┐
│ 問題 │ 原因 │ 解決策 │
├──────────────────┼──────────────────────────┼────────────────────────────────────────┤
│ 意図しないポーズ │ AIの「気の利かせ」 │ フレーム構成を厳密にプロンプト指定 │
├──────────────────┼──────────────────────────┼────────────────────────────────────────┤
│ フレームの切れ目 │ 不均等な配置 │ エディタツールで手動分割+自動中央配置 │
├──────────────────┼──────────────────────────┼────────────────────────────────────────┤
│ ピボットのズレ │ フレームごとの位置不一致 │ アルファ値走査で自動ピボット検出 │
└──────────────────┴──────────────────────────┴────────────────────────────────────────┘
AI画像生成×Unityで2Dアクションゲームを作ること自体は十分に可能です。ただし、AIが生成した画像をそのまま使えることはほぼなく、間に「整形するパイプライン」を挟むのが現実的です。
個人的には、この整形パイプラインを一度作ってしまえば、キャラ追加のたびに同じ苦労をしなくて済むようになったのが一番の成果でした。