『たのしい 2D ゲームの作り方 第3版』Unity 初心者がつまずいたところ

翔泳社の『たのしい 2D ゲームの作り方 第3版』を読みながら Unity を勉強しております。本に書かれているとおりに進めると、そのままではうまくいかない所がいくつかあったので、まとめておきます。まだ完読していませんが (2026 年 1 月現在)、今後も必要に応じて追記していきます。

[p.59] 最新の Unity (6.1) でゲームの実行時にエラーになる

本書の対象バージョンは 6.0 のようですが、最新の Unity (6.1) で本書のスクリプトを打ち込んで実行すると次のようなエラーになります。

InvalidOperationException: You are trying to read Input using the UnityEngine.Input class, but you have switched active Input handling to Input System package in Player Settings.
UnityEngine.Internal.InputUnsafeUtility.GetAxisRaw (System.String axisName) (at <44a762dafa6b4e0ca8423d645d61ba11>:0)
UnityEngine.Input.GetAxisRaw (System.String axisName) (at <44a762dafa6b4e0ca8423d645d61ba11>:0)
PlayerController.Update () (at Assets/Player/PlayerController.cs:18)

これは、Unity の入力システムのデフォルトが、6.0 までは「Input Manager」であったのに対して、6.1 からは「Input System Package」に変更されたためのようです。情報源:

対策は2通りあります。

対策 1. Input Manager も使えるようにする

Input Manager も使えるようにプロジェクト設定を変更します。手順は次のとおりです。

  1. [File] > [Build Profiles] をクリックする
  2. Build Profiles ウィンドウの右上にある [Player Settings] をクリックする
  3. Player Settings ウィンドウの [Player] タブで [Other Settings] > [Configuration] >[Active Input Handling] の項目を [Both] にする

ただ、Input Manager は廃止される方向のようですので、手間はかかりますが、次のように Input System 対応コードに書き換えるのがよいと思います。

対策 2. Input System 対応コードに書き換える

追記: Input System については、本の中でも p.236 あたりから触れられていました。よって下記は参考程度に読むのがよいと思います。

本書 p.59 のスクリプトを Input System 用に書き換えたコードを示します。InputAction というしくみを使って、物理的な入力 (「A」「D」キーとかゲームパッドの十字ボタン左右とか) を hAction というアクションに抽象化することで、Input.GetAxisRaw("Horizontal") と同様の結果が得られるようにしています。もっとスマートなやり方があるかもしれませんが、これでとりあえず読み進めることはできると思います。

using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerController : MonoBehaviour
{
    Rigidbody2D rbody;        // Rigidbody2D 型の変数
    float axisH = 0.0f;       // 入力

    // 書籍に掲載のコードを Input Manager (旧) から Input System (新) に移行する
    private InputAction hAction;

    // オブジェクトが有効になったときに呼ばれるイベントハンドラ
    void OnEnable()
    {
        // 水平移動の入力設定:
        // キーボードの a/d, ←/→ をゲームパッド左スティック X 軸方向に割り当てる
        hAction = new InputAction(type: InputActionType.Value, binding: "<Gamepad>/leftStick/x");
        hAction.AddCompositeBinding("1DAxis")
            .With("Negative", "<Keyboard>/a")
            .With("Negative", "<Keyboard>/leftArrow")
            .With("Positive", "<Keyboard>/d")
            .With("Positive", "<Keyboard>/rightArrow");
        hAction.Enable();
    }

    // オブジェクトが無効になったときに呼ばれるイベントハンドラ
    void OnDisable()
    {
        hAction.Disable();
    }

    void Start()
    {
        // Rigidbody2D を取ってくる
        rbody = GetComponent<Rigidbody2D>();
    }

    void Update()
    {
        // 水平方向の入力をチェックする
        axisH = hAction.ReadValue<float>();
        Debug.Log("axisH = " + axisH);    // デバッグ用
    }

    void FixedUpdate()
    {
        // 速度を更新する
        rbody.linearVelocity = new Vector2(axisH * 3.0f, rbody.linearVelocity.y);
    }
}

同様にして、ジャンプ動作を追加したコード (p.100) も、次のように書き換えることができます。

using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerController : MonoBehaviour
{
    Rigidbody2D rbody;               // Rigidbody2D 型の変数
    float axisH = 0.0f;              // 入力
    public float speed = 3.0f;       // 移動速度
    public float jump = 9.0f;        // ジャンプ力
    public LayerMask groundLayer;    // 着地できるレイヤー
    bool goJump = false;             // ジャンプ開始フラグ
    bool onGround = false;           // 地面フラグ

    // 書籍に掲載のコードを Input Manager (旧) から Input System (新) に移行する
    private InputAction hAction;
    private InputAction jumpAction;

    // オブジェクトが有効になったときに呼ばれるイベントハンドラ
    void OnEnable()
    {
        // 水平移動の入力設定:
        // キーボードの a/d, ←/→ をゲームパッド左スティック X 軸方向に割り当てる
        hAction = new InputAction(type: InputActionType.Value, binding: "<Gamepad>/leftStick/x");
        hAction.AddCompositeBinding("1DAxis")
            .With("Negative", "<Keyboard>/a")
            .With("Negative", "<Keyboard>/leftArrow")
            .With("Positive", "<Keyboard>/d")
            .With("Positive", "<Keyboard>/rightArrow");

        // ジャンプの入力設定:
        // キーボードの Space をゲームパッド South ボタンに割り当てる
        jumpAction = new InputAction(type: InputActionType.Button);
        jumpAction.AddBinding("<Keyboard>/space");
        jumpAction.AddBinding("<Gamepad>/buttonSouth");

        hAction.Enable();
        jumpAction.Enable();
    }

    // オブジェクトが無効になったときに呼ばれるイベントハンドラ
    void OnDisable()
    {
        hAction.Disable();
        jumpAction.Disable();
    }

    void Start()
    {
        // Rigidbody2D を取ってくる
        rbody = GetComponent<Rigidbody2D>();
    }

    void Update()
    {
        // 地上判定
        onGround = Physics2D.CircleCast(transform.position,    // 発射位置
                                        0.2f,                  // 円の半径
                                        Vector2.down,          // 発射方向
                                        0.0f,                  // 発射距離
                                        groundLayer);          // 検出するレイヤー

        // キャラクターをジャンプさせる
        // Input Manager からの移行:
        // - Input.GetButtonDown() → InputAction.WasPressedThisFrame()
        // - Input.GetButton()     → InputAction.IsPressed()
        // - Input.GetButtonUp()   → InputAction.WasReleasedThisFrame()
        if (jumpAction.WasPressedThisFrame()) {
            goJump = true;    // ジャンプフラグを立てる
        }

        axisH = hAction.ReadValue<float>();               // 水平方向の入力をチェックする
        switch (axisH) {                                  // 向きの調整
        case > 0.0f:
            transform.localScale = new Vector2(1, 1);     // 右移動
            break;
        case < 0.0f:
            transform.localScale = new Vector2(-1, 1);    // 左右反転させる
            break;
        default:
            break;
        }
    }

    void FixedUpdate()
    {
        if (onGround || axisH != 0) {    // 地面の上 or 速度が 0 ではない
            // 速度を更新する
            rbody.linearVelocity = new Vector2(axisH * speed, rbody.linearVelocity.y);
        }

        if (onGround && goJump) {    // 地面の上でジャンプキーが押された
            Vector2 jumpPw = new Vector2(0, jump);          // ジャンプさせるベクトルを作る
            rbody.AddForce(jumpPw, ForceMode2D.Impulse);    // 瞬間的な力を加える
            goJump = false;                                 // ジャンプフラグを下ろす
        }
    }
}

[p.98] ゲーム画面両端の「見えない壁」の設置がムズい

プレイヤーキャラクターが画面の端から出ていかないように、両端に見えない壁 (コライダー) を設定しているところがあります (p.98)。本では、スクリーンショットを提示して「このような形に調整してください」と書かれているので、同じになるようにマウスを使って操作したのですが、どうもマウスではコライダーの位置やサイズを変更することはできないようです。別の書籍には、インスペクタービューの「Offset」と「Size」に直接数値を入力する方法が書かれています。ほんとうにマウスで操作できないなら、一言そう書いておいてほしい……。

追記 1:コライダーはシーンビュー内のツールバーから移動ツール、拡大・縮小ツールを使うことで、位置やサイズを変更できるようです。

追記 2:本書では WallObject に2つの Box Collider 2D をアタッチしていますが、これだと個別に位置やサイズを指定することができないらしく、p.163 でステージ 2 を作るときに苦労します。なので WallObject の下に2つの子オブジェクト (空オブジェクトでよい。名前は Left と Right とか、適当に) を作って、それぞれに Box Collider 2D をアタッチしたほうがよいのではないか?と思いました。

[p.134] 「GAME START」の画像を Canvas にセットすると「GAME」としか表示されない

「GAME START」と書かれた画像を UI (ユーザーインターフェース) として画面の真ん中に設置する作業 (p.134) を本に書かれているとおりにやったところ、実際のゲーム画面には「GAME」としか表示されませんでした (図 1)。

図 1: 「START」はどこへいった…?

プロジェクトビューの当該画像の横に、再生ボタンのようなものがあったのでクリックしてみたところ、画像が「GAME」と「START」の2枚構成になっているようでした (図 2)。

図 2: なんか GameStart_0 と 1 がある

PNG の仕様がよくわかりませんが、なんか関係しそうな項目をインスペクターで探して、次のように設定したら、期待したとおりの表示になりました (図 3)。

  1. 当該画像のインスペクターの、[Sprite Mode] の値を [Multiple] から [Single] に変更する。
  2. 「Unapplied import settings for ‘Assets/Images/GameStart.png’」と表示されるので、[Save] をクリックする。
  3. Canvas の Image の Source Image が「Missing (Sprite)」になっているので、再度プロジェクトビューから当該画像をドラッグ&ドロップする。
図 3: 無事に?解決

ただ、このやり方が正しいのかどうかはわかりません。

[p.136] RestartButton と NextButton は Panel に入れる

p.136 のスクリーンショットでは、RestartButton と NextButton を Canvas オブジェクトの下に直接入れていますが、Panel オブジェクトの中に入れるようにしないと、後工程 (GameController スクリプトや p.144 の作業) で不都合が生じます。

次のようなヒエラルキーになるようにします。

Canvas
    Image
    Panel
        RestartButton
            Text (TMP)
        NextButton
            Text (TMP)

手順は次のとおり:

  1. ヒエラルキービューの Canvas に [+] ボタンで [UI] > [Panel] を追加する。
    • カラー設定で RGBA の「A」の値を 0 にしておく (100% 透過)。
    • パネルのサイズはデフォルトだと Canvas のサイズと同じになるらしい。調整したほうがいいかもしれないが、とりあえずそのままにしておく。
  2. 手順 1 で作ったパネルの下に RestartButton と NextButton をぶら下げる。

[p.138] Google Fonts の設定

p.138 で Google Fonts を導入していますが、詳しい手順は書かれていないので、ここにメモしておきます。

  1. Unity のプロジェクトビューで Assets フォルダの下に Fonts フォルダを作る。
  2. Google Fonts (https://fonts.google.com) から使用したいフォントをダウンロードする。とりあえず Noto Sans Japanese を選択。
  3. 取得した ZIP ファイルを適当なフォルダに解凍する。ここで、NotoSansJP-VariableFont_wght.ttf は避け (※)、static/NotoSansJP-Regular.ttf を選んで手順 1 で作ったフォルダにドラッグ&ドロップする。
    ※ 可変フォントは Text Mesh Pro でうまく扱えないらしく、導入するとフォントの色が薄くなってしまいます。
  4. あとは pp.138-139 にしたがってフォントアセットを作成する。保存する際、ファイル名はデフォルトのまま「NotoSansJP-Regular SDF.asset」とした。

[p.142] 不要な変数が紛れ込んでいる?

(2025 年 10 月 3 日更新)
p.251 まで進んだところで、この変数の必要性がわかりました。「GameManager.gamestate 変数のワナ」でちょっともの申したいと思います。

p.142 のコードの 6、19、31 行目で gamestate という private な変数の定義と代入を行っていますが、たぶん 3 行とも不要です。

public class GameManager : MonoBehaviour
{
    ...
    GameState gamestate = GameState.InGame;  // ゲームの状態  //たぶん不要
    ...
    void Update()
    {
            ...
            // ゲームクリア
            gamestate = GameState.GameClear;  // たぶん不要
            ...
            // ゲームオーバー
            gamestate = GameState.GameOver;  // たぶん不要
            ...
    }
}

[p.157] 背景の配置

細かいことかもしれませんが……「Image のサイズは適当に調整」って書かれていますが、初心者としては「どう適当でいいのか?」がよくわかりません……。p.158 のスクリーンショットをみると、背景画像が Canvas の枠にピッタリ一致していますが、アスペクト比がそれぞれ 4:3、16:9 で異なるので、一致するように画像の縦横比をいじるんでしょうか?それとも、多少画像が枠からはみ出てもいいんでしょうか?とりあえず、後者でやっています (図 4)。

図 4:p.158 のスクリーンショットとなんかちがう…

[p.172] 強制スクロールのゲームオーバーについて

(2025 年 9 月 24 日更新)
正誤表に掲載されました。

p.172 のスクリーンショットは WallObject と DeadObject の位置が左右逆ではないでしょうか?プレイヤーが画面左端に接触したらゲームオーバーになるようにしたいので……。そもそもこの節は全体的に意図がよくつかめません……。

[p.179] コンパイルエラーになる

GameManager.cs の次の 4 行は、p.179 の時点では不要などころかコンパイルエラーになるので、削除します (totalScore が定義されるのは p.190 まで行ってからです)。

        // スコア追加
        // 整数に代入することで小数を切り捨てる
        int time = (int)timeCnt.displayTime;
        totalScore += time * 10; // 残り時移換をスコアに加える

[p.187] コンパイルエラーになる

(2025 年 9 月 24 日更新)
正誤表に掲載されました。

(2025 年 9 月 9 日更新)
PlayerController.cs の下から 6、7 行目は、正しくは次のとおりではないかと思います。

            // スコアアイテム
            ScoreItem si = collision.gameObject.GetComponent<ScoreItem>();
            score = si.itemdata.value;

参考: 更新前のコード (2025 年 9 月 4 日掲載)
コンパイルは通るけど、ゲームを実行してキャラクターとアイテムとの衝突が発生するとエラーになります。

            // スコアアイテム
            ItemData item = collision.gameObject.GetComponent<ItemData>();
            score = item.value;

[p.189] スコアが表示されない?

ScoreText のデフォルトのフォント色が白?らしく、背景色にまぎれてしまいます。ScoreText のインスペクターで、[Main Settings] > [Vertex Color] を白以外の色に変更しましょう。

[p.192] リザルト画面への遷移方法 (やっつけ)

p.192 からリザルト画面を作成していますが、どうやってリザルト画面に遷移するのか?については書かれていないように思います (見逃していたらすみません)。

そこで、著者の意図したとおりではないかもしれませんが、ここでは簡単に、ステージ 2 をクリアしたら単にリザルト画面に切り替える方法を掲載しておきます。

  1. シーンを Stage2 に切り替える。
  2. ヒエラルキービューから Canvas を選択する。
  3. インスペクターの [GameManager (Script)] > [Next Scene Name] プロパティに “Result” と入力し、シーンを保存する。
  4. GameManager.cs (p.189) の Update() メソッドに次の 8 行のコードを加える。
        void Update()
        {
            if (PlayerController.gameState == GameState.GameClear)
            {
                ...
            }
            else if (PlayerController.gameState == GameState.Ingame)
            {
                ...
            }
            else if (PlayerController.gameState == GameState.End)
            {
#if true  // true のとき強制的にタイトルに戻る
                if (nextSceneName == "Result") {
                    SceneManager.LoadScene(nextSceneName);
                }
#end
            }
        }

これで、ステージ 2 をクリアしたら、(余韻に浸る間もなく) リザルト画面に遷移します。強制的にタイトルに戻るのはあんまりだ、と思ったら「#if true」を「#if false」に変更してください。

[p.219] 砲台の画像が書籍に掲載されているものと違う!

サンプルアセットに収録されている砲台の画像と、書籍に掲載されているスクリーンショットの砲台がなぜか別物になっています。書籍のほうの画像は Z 軸で回転させても支障がないように配慮されたデザインになっていますが、サンプルアセットの画像はそうなっていないので、p.226 まで行って Z 軸で 180 度回転させると逆さになってしまいます (笑)。

そのうえ、せっかくスクリプトのほうで斜め上にも発射できるようにしている (p.221 の CannonController.cs のコード) のに、Z 軸で回転できないのでは、それも無駄になってしまいます。

同じサイトからダウンロードできる「Chapter 07 完成データ」には正しい画像が収録されているようなので、次の手順で差し替えましょう。

  1. まちがった砲台の画像が収録されているサンプルアセットは、p.24 の手順でプロジェクトに登録されているものとする。
  2. 「Chapter 07 完成データ」をサポートサイトからダウンロードする。
    https://www.shoeisha.co.jp/book/download/5359/read
  3. ダウンロードしたファイルを解凍し UniSideGame_chap07\Assets\Images フォルダを開く。
  4. Unity エディタのプロジェクトビューで [Assets] > [Images] を開き、手順 2 のフォルダから cannon.png をドラッグアンドドロップする。
  5. あとは p.219 以降に書かれているとおりに作業を進める。

[p.251] GameManager.gamestate 変数のワナ

上のほうで「gamestate 変数は不要ではないか?」と書いたのですが、p.251 まできて GameEnd() メソッドが追加されたことにより、この変数の必要性がわかりました。ただ、「この変数名は紛らわしいだろ! (汗)」と思ったので、一言もの申したいと思います。

ステージを「クリア」したか、または操作をミスって「ゲームオーバー」になったかすると、ゲームの状態が GameState.GameEnd に遷移します。具体的には、GameManager.Update() メソッドの中で PlayerContoller.gameState 変数 (GameManager.gamestate じゃないよ) を書き換えています。

    void Update()
    {
        if (PlayerController.gameState == GameState.GameClear)
        {
            // ゲームクリア
            ...
            PlayerController.gameState = GameState.GameEnd;
            ...
        }
        else if (PlayerController.gameState == GameState.GameOver)
        {
            // ゲームオーバー
            ...
            PlayerController.gameState = GameState.GameEnd;
            ...
        }
        ...
    }

これは問題ありません。こうしないと、Update() が呼ばれるたびに「// ゲームクリア」や「// ゲームオーバー」の部分が実行されて大変なことになってしまいます。そういうわけで、ステージクリアなりゲームオーバーなりして Restart ボタンや Next ボタンが表示されるときには、ゲームの状態は GameState.GameEnd になっています。

この状態でどちらかのボタンが押されて GameManager.GameEnd() まで来ると、ステージクリアなのか?ゲームオーバーなのか?どちらの要因でここに来たのかがわからず、Next() と Restart() のどちらを呼べばよいのか分からなくなってしまいます。そこで、GameState.GameEnd に遷移する前の状態をなんらかの変数に保持しておく必要があり、それが GameManager.gamestate というわけです。

    void Update()
    {
        if (PlayerController.gameState == GameState.GameClear)
        {
            // ゲームクリア
            gamestate = GameState.GameClear;
            ...

            PlayerController.gameState = GameState.GameEnd;
            ...
        }
        else if (PlayerController.gameState == GameState.GameOver)
        {
            // ゲームオーバー
            gamestate = GameState.GameOver;
            ...

            PlayerController.gameState = GameState.GameEnd;
            ...
        }
        ...
    }

つまり GameManager.gamestate はゲームの「現在の」状態を表すためではなく、「ある時点での」状態をスナップショットとして保持するための変数です。コードがまちがっているわけではないのですが、なんかゲームの状態をあちこちで変更しているように見えて混乱を招きやすいと思います。変数名を「gameresult」みたいな名前にして状態遷移には無関係であることを明確にしたほうがよいと思います。

あるいは、デリゲートを使って Next() と Result() のうち呼び出すほうをセットしておくというやり方もあります。これなら GameManager.gamestate 変数は不要です。

public class GameManager : MonoBehaviour
{
    ...

    // GameState gamestate = GameState.InGame;    // 削除
    delegate void SceneLoaderDelegate();          // シーンをロードするメソッド (Restart or Next) を指すデリゲート
    SceneLoaderDelegate sceneLoader = null;
    ...

    // ゲーム終了
    public void GameEnd()
    {
        if (sceneLoader != null) sceneLoader();
    }
    ...

    void Update()
    {
        if (PlayerController.gameState == GameState.GameClear)
        {
            // ゲームクリア
            // gamestate = GameState.GameClear;    // 削除
            sceneLoader = Next;
            ...

            PlayerController.gameState = GameState.GameEnd;
            ...
        }
        else if (PlayerController.gameState == GameState.GameOver)
        {
            // ゲームオーバー
            // gamestate = GameState.GameOver;    // 削除
            sceneLoader = Restart;
            ...

            PlayerController.gameState = GameState.GameEnd;
            ...
        }
        ...
    }
    ...
}

[p.260] TileMap.png が書籍に掲載されているものと違う!

p.219 と似たような問題です。p257 で案内されているリンク先からダウンロードできるアセットの TileMap.png が、書籍の p.260 に掲載されているものと違っています (2025 年 10 月 7 日に取得)。

書籍と同じ画像がほしい場合は (私はほしいです)、「トップビューゲーム完全データ」から取得します。

完全データを解凍してできる DungeonShooter\Assets\Map フォルダの下に、書籍と同じ TileMap.png があります。

なお、ほかの画像も微妙に違いがあるようで、今後読み進めていくうちになにか支障が出るかもしれません。その場合は、同様にして「完全データ」から引っ張ってくる必要があるかもしれません。

完全データの各画像ファイルは次の場所にあるので、必要になったときには参考にしてください。

ファイル名場所 (DungeonShooter\Assets の下)
Boss.pngBoss
bt_continue.pngTitle
bt_gamestart.pngTitle
bullet.pngBoss
button.pngUIManager
Door.pngItem
DoorGold.pngItem
EnemyImage.pngEnemy
GameClear.pngUIManager
GameOver.pngUIManager
GameStart.pngUIManager
Items.pngItem
ItemImage.pngUIManager
Lantern.pngItem
PlayerImage.pngPlayer
TileMap.pngMap
title_back.pngTitle
title_chara.pngTitle
title_logo.pngTitle

[p.303] 敵キャラに接触してもヒットバックしない

p.305 に掲載されているコードの中ほどに、敵キャラに接触したときのヒットバックの処理が掲載されていますが、これだけだとヒットバックしません。FixedUpdate() 関数の先頭に次のコード (#if ture#endif で囲まれた部分) を追加する必要があります。

    void FixedUpdate()
    {
#if true  // この部分を追加しないとヒットバックしません
        // ゲーム中以外とダメージ中は何もしない
        if (gameState != GameState.InGame || inDamage) 
        {
            return;
        }
#endif

        rbody.linearVelocity = moveVec * speed;
    }

これは書籍のサポートサイトのダウンロードデータ (Chapter 08完成データ) に記載されていたコードです。私が見落としてなければ、このコードは書籍には掲載されていません。

[p.305] ライフが 0 になってもゲームオーバーにならない

p.305 に掲載の GetDamage() 関数に誤記があります。else ブロックの位置がまちがっているために、ライフが 0 になってもゲームオーバーになりません。

誤)

    void GetDamage(GameObject target)
    {
        if (gameState == GameState.InGame)
        {
            ...
        }
        else
        {
            GameOver();
        }
    }

正)

    void GetDamage(GameObject target)
    {
        if (gameState == GameState.InGame)
        {
            life -= 0.25f;
            if (life > 0)
            {
                ...
            }
            else
            {
                GameOver();
            }
        }
    }

これも上記の「敵キャラに接触してもヒットバックしない」問題と同じで、サポートサイトのダウンロードデータは修正されています。なんで正誤表は更新しないんですかね……?

[p.333] EnemyDead アニメーション追加方法の補足

p.333 に「Animation ウィンドウを開き、そこから死亡アニメーションを追加してください。」と書かれていますが、やり方がよくわからず p.333 と p.334 の間をウロウロしてしまったので、ようやく理解できたやり方をメモっておきます。

  1. ヒエラルキービューで Enemy オブジェクトを選択する。
  2. [Window] > [Animation] > [Animation] で Animation ウィンドウを開く。
  3. 左上のほうに p.332 で作った EnemyIdle アニメーションのプルダウンがあるので、そこをクリックする。
  4. [Create New Clip] をクリックする。
  5. ファイル作成ダイアログで EnemyDead.anim と入力する。

あとは p.334 にしたがって進めます。

ちなみに、なんで上下左右の移動アニメーションと同じ方法で作らないんだろ?と思ったんですが、たぶん EnemyDead は画像が 1 枚しかないので、シーンビューにドラッグアンドドロップしてもアニメーションにはならないよという Unity の仕様なんでしょうね……。

[p.346] シーンが切り替わるたびに「GAME START」と表示される

p.346 で作成した Canvas は、プレハブ化して他のシーンでも使用するので、シーンが切り替わるたびに「GAME START」と表示されてしまいます。そういう仕様のようですが気になります。直したい場合はどうするのがいいでしょうか。

単純に Canvas の下の Image オブジェクトを削除してしまうと、「GAME OVER」とか「GAME CLEAR」の表示も出来なくなってしまいます。で、いろいろ考えた結果、GameStart.png と同じサイズで透明な画像を作って、それを GameStart.png と差し替えれば、ソースコードを変更しなくても済むので楽チンだろう。ということになりました。

[p.352] ライフのメーターを更新し忘れてた

p.352 アイテムをゲットしたときに UI を更新するコードですが、ライフを更新するところだけ差分がハイライトされていないので、見落としてしまうかもしれません (というか見落としてました)。これを忘れると、ハートをゲットしても見た目の上でライフが回復しません。

                // ライフ追加
                life = Mathf.Clamp(life + item.itemdata.value, 0, 1);
                gm.UpdateLife(life);                // ライフを更新

[p.407] タイルマップにコライダを設定するのを忘れてた

p.407 でラスボスステージを作ったら、p.272 に書かれているようにタイルマップにコライダを設定する必要があるのですが、わりと見落としがちかもしれません (というか見落としてました)。これを忘れると、ステージの壁をすり抜けて、どこまでも自由に駆け巡ることができてしまいます。