AIゲーム制作ラボ
Unity×AI5分で読めます

アンドゥを無制限にする(状態を積む方式と、手を逆にたどる方式)

パズルゲームに「何手でも戻せるアンドゥ」を入れました。手を逆にたどる方式ではなく、動かすたびに状態を丸ごと積む方式にしています。重くないのかを実測(100手で4,096バイト)し、戻し忘れやすいもの、テストで見張っている点まで書きます。

Libra

公開 2026年9月19日

この記事の要点4 points

  1. アンドゥの作り方は2つ 手を逆にたどるか、状態を丸ごと積むか。後者にした。
  2. 重さを心配したが、いちばん大きい面で100手ぶん 4,096バイト(1手あたり40バイト)だった。
  3. 戻すときに忘れやすいのは、数えていた値(押した回数)。 状態と一緒に戻す必要がある。
  4. 遊びの中身(盤・移動・アンドゥ)をUnity に依存しない場所に置いたので、エディターを開かずに36件のテストが 3.86 秒で回る。
目次6
検証した環境最終確認 2026年9月19日
  • Windows 11
  • Unity 6.6(6000.6.0f1)
  • Claude Code(Opus 5 / 思考レベル 高)

書いてあるのは、この環境で実際に試した結果です。ツールの仕様や料金は変わるので、重要な判断の前には公式の情報も確認してください。

箱を押すパズルに、何手でも戻せるアンドゥを入れました。詰みやすいゲームなので、戻せることは遊びの前提です。

やり方は2つありました。

手を逆にたどる(選ばなかった)

  • 「上に動いた」なら「下に動かす」と、操作ごとに逆を書く
  • 箱を押していたら、箱も一緒に戻す必要がある
  • 持つのは操作だけなので軽い
  • 逆の条件を1つ間違えると、盤が静かに壊れる

状態を丸ごと積む(選んだ)

  • 動かす前の状態を、そのまま積んでおく
  • 戻すときは、積んだものを取り出して差し替えるだけ
  • 盤の形が変わっても、書き足すことがない
  • そのぶんメモリを使う(あとで実測した)

戻したときに「元と同じ」であることを、考えずに保証したかった。

中身はこれだけ

// PuzzleSession.cs(抜粋)
readonly List<LevelState> _history = new List<LevelState>();
readonly List<bool> _pushes = new List<bool>();   // その手で箱を押したか

public bool Move(Move move)
{
    if (Cleared) return false;
    if (!Rules.TryMove(_board, State, move, out var next, out bool pushed)) return false;

    _history.Add(State);        // ← 動かす前を積む
    _pushes.Add(pushed);
    State = next;
    if (pushed) Pushes++;
    return true;
}

public bool Undo()
{
    if (_history.Count == 0) return false;
    int last = _history.Count - 1;
    State = _history[last];
    if (_pushes[last]) Pushes--;   // ← 数えていた値も一緒に戻す
    _history.RemoveAt(last);
    _pushes.RemoveAt(last);
    return true;
}

「最初からやり直す」も同じ考え方で済みます。最初の状態を持っておいて、履歴を空にするだけです。

戻し忘れやすいのは「数えていた値」

上のコードで _pushes を持っているのは、押した回数を数えながら進めているからです。この面の難しさは押した回数で測っているので、画面にも出しています。

状態だけ戻して押した回数を戻さないと、アンドゥを繰り返すほど数字だけが増えていきます。しかも盤は正しいので、見ていても気づきにくい。

戻す必要があるのは「盤から計算し直していない値」

盤を見れば分かる値(クリアしたか・箱が目印に載っているか)は、戻したあとに計算し直せば正しくなります。戻す必要があるのは、進めながら足していた値だけです。この区別をつけると、積むものが減ります。

本当に重くないのかを測った

「状態を丸ごと積む」で気になるのはメモリです。いちばん大きい面(8×8・箱3個)で100手動かして、増えたぶんを測りました。

Unity のコンソール(エディター上で実行)OK
盤=8x8 箱=3 最短押し=8 積んだ手=100 増えたメモリ=4096バイト 1手あたり=40バイト

100手で4,096バイト。1面をどれだけ長く遊んでも、まず問題になりません。

理由は、積んでいる状態が小さいからです。盤(壁と目印)は面の中で変わらないので積んでいません。積むのは「プレイヤーの位置」と「箱の位置の一覧」だけ。箱3個なら、数字が4つです。

01

盤ごと積むと、いきなり重くなる

症状
1手ごとに盤の大きさぶんの配列が増える
原因
壁や目印は面の中で変わらないのに、状態と一緒に持ってしまう
対処
変わらないものは1つだけ持つ。積むのは変わるものだけ
02

積んだ状態の中身を、あとから書き換える

この形にしてから、履歴が壊れる不具合は出ていない
症状
戻したはずなのに、戻った先も同じ盤になっている
原因
箱の位置の配列を使い回して、その中身を直接書き換えていると、積んだ過去も一緒に変わる
対処
動かすときは新しい配列を作って差し替える。過去は読むだけのものとして扱う

画面はこうなった

ハコオシのプレイ画面。上に LEVEL 1 MOVES 3 PUSHES 3、下に UNDO・RETRY・LEVELS のボタン
3手動かしたところ。UNDO は回数制限なし、RETRY はこの面の最初へ戻る

Unity に依存しない場所に置いた

盤・移動・押し・アンドゥは、Unity の型を1つも使わずに書いています(Core という場所にまとめている)。おかげでアンドゥのテストは、シーンもゲームオブジェクトも作らずに動きます。

Git BashOK
run_tests	true	36/36 件成功 (EditMode, 3.86s)

テストで見張っているのは、たとえばこういうことです。

アンドゥで見張っていること

戻したら、動かす前とまったく同じ盤になる
押した回数も一緒に戻る
最初の状態では戻せない(戻す先が無い)
RETRY のあとは、履歴も手数も空になる

まとめ

  • アンドゥは状態を丸ごと積む方式にした。戻したときに元と一致することを、考えずに保証できる
  • 積むのは変わるものだけ(プレイヤーと箱)。盤は積まない
  • 100手で 4,096バイト心配するほど重くなかった
  • 戻し忘れやすいのは、進めながら数えていた値
  • 遊びの中身をエンジンから切り離すと、アンドゥのテストが軽く回る

FAQ

よくある質問

アンドゥは「手を逆にたどる」のと「状態を積む」の、どちらがいいですか?

戻したときに元と一致することを確実にしたいなら、状態を積むほうが安全です。手を逆にたどる方式は、逆の操作を1つずつ正しく書く必要があり、押した・押していないの条件を間違えると盤が壊れます。状態を積む方式は「積んで、戻すだけ」で済みます。

状態を全部積んだら、メモリを使いすぎませんか?

このゲームでは使いすぎませんでした。8×8・箱3個の面で100手動かして、増えたのは 4,096 バイト(1手あたり40バイト)です。盤が大きい・履歴が何千手も続く種類のゲームなら、手を逆にたどる方式や、間引いて積む方式を検討することになります。

アンドゥで戻し忘れやすいものは?

盤の状態から計算し直していない値です。このゲームでは「箱を押した回数」を数えながら進めていたので、1手ごとに押したかどうかも一緒に積んで、戻すときに減らしています。

自動生成の「巻き戻し」と同じ仕組みですか?

考え方は似ていますが、別物です。面の自動生成はクリア状態から箱を引いて作るので、作り手が逆の操作を選んでいます。アンドゥは遊ぶ人が何を選ぶか分からないので、状態を積むほうにしました。

この記事は役に立ちましたか?

NEXT