【Unity】InputSystemを使って「溜め攻撃」を超シンプルに実装する方法

この記事では、Unityの新しい入力システム「Input System」を活用して、ゼルダの伝説の回転斬りのような「溜め攻撃」アクションを実装する方法を解説します。

Input Systemには長押しを検知する「Hold Interaction」もありますが、今回はあえてそれを使わず、ボタン入力の基本的なイベントである「押した瞬間 (started)」と「離した瞬間 (canceled)」の時刻差を利用するアプローチを紹介します。

従来のUpdate関数内で毎フレーム入力を監視する方法と比較して、Input Systemのイベント駆動型アプローチは、コードがシンプルになり、特に機能追加や変更に対する保守性が向上するメリットが期待できます。


この記事の内容

  1. 溜め攻撃の実装:Input System (Press) vs Update vs Hold Interaction
  2. Input SystemのインストールとInput Actionsアセットの作成
  3. Input Actions:左クリック(攻撃ボタン)アクションの定義
  4. スクリプト連携:PlayerInputとイベント処理スクリプト
  5. 溜め時間の計算ロジック:startedとcanceledの活用
  6. 中間演出の実装:コルーチンでInput Systemの弱点を補う
  7. まとめ:Pressイベント方式のメリット・デメリットと使い分け

溜め攻撃の実装:Input System (Press) vs Update vs Hold Interaction

溜め攻撃を実装する方法はいくつか考えられます。それぞれの特徴を見てみましょう。

  • 従来のUpdate関数による実装:
    • Update()内で毎フレーム入力をチェックし、ボタンが押されている時間を計測します。
    • 仕組みは直感的ですが、入力の種類や条件分岐が増えるとコードが複雑化しがちです。
  • Input System の Hold Interaction を使う実装:
    • Input ActionsアセットでInteractionを「Hold」に設定し、指定時間(Hold Time)長押しされるとperformedイベントが発生します。
    • 設定は簡単ですが、後述するように「短押し(通常攻撃)」と「長押し(溜め攻撃)」を同じボタンで使い分けたい場合に、実装がやや複雑になることがあります。
  • Input System の Press イベント (started/canceled) を使う実装(本記事のアプローチ):
    • ボタンが押された瞬間(started)と離された瞬間(canceled)のイベントを利用し、その間の時間を計測します。
    • イベント駆動でコードが整理されやすく、短押し・長押しの判定や溜め中の処理を柔軟に実装できます。

Q. なぜHold InteractionではなくPressイベント (started/canceled) を使うのか?

Hold Interactionは指定時間長押しされたことを検知するのに便利ですが、「同じボタンで短押し(通常攻撃)と長押し(溜め攻撃)を使い分けたい」場合や、「溜めている最中に演出を入れたい」場合には、少し扱いにくい側面があります。

  • 短押しと長押しの両立: Hold Interactionでは、長押しが成立してperformedが呼ばれる前に、短押し時の処理(通常攻撃など)をボタンを押した直後に実行させたい場合、工夫が必要です。例えば、「押した瞬間(started)に通常攻撃を出し、Holdが成立したらキャンセルする」といった制御や、別途短押し用のアクションを用意するなど、実装が複雑になりがちです。
  • 押下開始タイミングの活用: ボタンを押した瞬間にキャラクターが構えモーションに入ったり、溜めエフェクトを開始したりといった処理を即座に行いたい場合、Hold Interactionだけではタイミングを計りにくいことがあります。
  • 溜め中の制御: 溜めゲージを表示したり、一定時間ごとに溜めレベルが上がる演出を入れたりする場合、最終的にperformedイベントが発生するまで待つ必要があるHold Interactionでは、溜めている途中の細かな制御がしづらいです。

一方、本記事で紹介するPressイベント (started/canceled) を利用する方法では、

  • ボタンを押した瞬間のstartedイベントと、離した瞬間のcanceledイベントの時刻差を計測することで、押下時間を正確に把握できます。
  • これにより、「一定時間未満なら通常攻撃、一定時間以上なら溜め攻撃」といった判定をcanceledイベント内で簡単に行えます。
  • startedイベント発生時に溜め動作やエフェクトを開始し、canceledイベントで攻撃を発動、という流れを自然に実装できます。
  • (後述するコルーチンなどを組み合わせれば)溜めている最中の演出や状態変化も柔軟に組み込めます。

このように、短押し/長押しの判定や、溜め中の演出・状態管理を柔軟に行いたい場合には、started/canceledイベントを利用するアプローチが適していると言えます。


Input SystemのインストールとInput Actionsアセットの作成

まず、プロジェクトにInput Systemパッケージを導入し、入力を定義するInput Actionsアセットを作成します。

Input Systemパッケージのインストール

Unityエディタのメニューから操作します。

Package ManagerからInput Systemをインストールする手順

  1. Window > Package Manager を開きます。
  2. 左上のドロップダウンメニューで「Packages: Unity Registry」を選択します。
  3. リストから「Input System」を探し、「Install」ボタンをクリックします。
  4. インストール中にプロジェクト設定の変更を促すダイアログが出たら、「Yes」を選択してエディタを再起動します。

Input Actionsアセットファイルの作成

次に、入力アクションを定義するためのアセットファイルを作成します。

ProjectウィンドウでInput Actionsアセットを作成

  1. Projectウィンドウで右クリックし、Create > Input Actions を選択します。
  2. 作成されたアセットファイル(例: `PlayerInputActions.inputactions`)に分かりやすい名前を付けます。
  3. 作成したアセットファイルを選択し、Inspectorウィンドウで「Generate C# Class」にチェックを入れ、「Apply」ボタンを押します。

Generate C# Classにチェックを入れるとC#スクリプトが生成される

「Generate C# Class」にチェックを入れることで、このInput Actionsアセットに対応するC#クラスが自動生成され、スクリプトから入力イベントを扱いやすくなります。


Input Actions:左クリック(攻撃ボタン)アクションの定義

作成したInput Actionsアセットファイル(例: `PlayerInputActions.inputactions`)をダブルクリックして編集ウィンドウを開き、溜め攻撃に使用するアクションを定義します。

Input Actionsエディタで攻撃アクションを設定する

  1. Action Maps 列の「+」ボタンをクリックし、新しいAction Mapを作成します(例: `Gameplay`)。Action Mapは関連するアクションをまとめるグループです。
  2. Actions 列の「+」ボタンをクリックし、新しいActionを作成します(例: `AttackLeft`)。これが具体的な入力操作に対応します。
  3. 作成した`AttackLeft` Actionを選択し、右側のPropertiesパネルで以下を設定します。
    • Action Type:Button」を選択します。これは、押す/離すの単純な入力に適しています。
  4. `AttackLeft` Actionの下にある``を選択し、Propertiesパネルで以下を設定します。これが具体的な入力デバイスのボタンとの紐付け(Binding)です。
    • Path: プルダウンメニューから「Mouse」>「Left Button」を選択します。(ゲームパッドのボタンなどもここで設定可能)
  5. 編集が終わったら、ウィンドウ上部の「Save Asset」ボタンをクリックして変更を保存します。

これで、「マウスの左クリック」が`AttackLeft`という名前のアクションとして、スクリプトからイベントとして受け取れるようになりました。


スクリプト連携:PlayerInputとイベント処理スクリプト

定義したInput Actionsを実際にゲーム内で機能させるために、スクリプトとの連携を設定します。

1. シーン内に空のGameObjectを作成し、分かりやすい名前(例: `InputManager`)を付けます。

2. 作成した`InputManager` GameObjectに「Player Input」コンポーネントを追加します。

3. Player Inputコンポーネントの「Actions」フィールドに、先ほど作成したInput Actionsアセット(例: `PlayerInputActions.inputactions`)をドラッグ&ドロップで設定します。

4. 以下のC#スクリプト(例: `InputManager.cs`)を作成し、`InputManager` GameObjectにアタッチします。

using UnityEngine;
using UnityEngine.InputSystem;

// PlayerInputコンポーネントが必須であることを示す
[RequireComponent(typeof(PlayerInput))]
public class InputManager : MonoBehaviour
{
    // ボタンが押され始めた時刻を記録する変数
    private float buttonPressStartTime;
    // 溜め攻撃と判定する時間のしきい値(例: 1秒)
    private const float specialAttackThreshold = 1.0f;

    // PlayerInputコンポーネントから呼び出されるメソッド
    // メソッド名はInput Actionsで定義したアクション名(例: AttackLeft)に
    // "On" を付けたものにするか、後述のようにInspectorで手動設定する
    public void OnAttackLeft(InputAction.CallbackContext context)
    {
        // ボタンが押された瞬間 (started) の処理
        if (context.started)
        {
            Debug.Log("Attack button pressed (started)");
            // 押下開始時刻を記録
            buttonPressStartTime = Time.time;
            // ここで構えモーションや溜めエフェクト開始などの処理を入れることも可能
        }
        // ボタンが離された瞬間 (canceled) の処理
        else if (context.canceled)
        {
            Debug.Log("Attack button released (canceled)");
            // 押されていた時間を計算
            float pressDuration = Time.time - buttonPressStartTime;
            Debug.Log($"Press duration: {pressDuration} seconds");

            // 押下時間がしきい値を超えていたら溜め攻撃
            if (pressDuration > specialAttackThreshold)
            {
                PerformSpecialAttack(); // 溜め攻撃実行メソッド呼び出し
            }
            // しきい値未満なら通常攻撃
            else
            {
                PerformNormalAttack(); // 通常攻撃実行メソッド呼び出し
            }
        }
        // context.performed は Button タイプでは started とほぼ同じタイミングで呼ばれることが多い
        // Hold Interaction を使わない場合、主に started と canceled を使う
    }

    // 通常攻撃を実行する処理(中身は仮)
    private void PerformNormalAttack()
    {
        Debug.Log("Perform Normal Attack!");
        // ここに実際の通常攻撃ロジックを記述
    }

    // 溜め攻撃を実行する処理(中身は仮)
    private void PerformSpecialAttack()
    {
        Debug.Log("Perform Special Attack!");
        // ここに実際の溜め攻撃ロジックを記述
    }
}

5. `InputManager` GameObjectを選択し、InspectorウィンドウでPlayer Inputコンポーネントの「Behavior」を「Invoke Unity Events」に設定します。

6. 「Events」セクションが展開されるので、設定したAction Map名(例: `Gameplay`)を開き、その中のアクション名(例: `Attack Left`)に対応するイベント欄の「+」ボタンをクリックします。

7. イベント欄に`InputManager` GameObject自体をドラッグ&ドロップし、右側のドロップダウンメニューから「InputManager」>「OnAttackLeft (InputAction.CallbackContext)」を選択します。(スクリプトのメソッド名が `On[アクション名]` であれば自動で認識されることもあります)

これで、マウスの左ボタンがクリックされる(押される、または離される)たびに、`InputManager.cs`スクリプト内の`OnAttackLeft`メソッドが呼び出されるようになります。このイベント駆動の仕組みにより、`Update`関数を使うことなく入力処理を実現できます。


溜め時間の計算ロジック:startedとcanceledの活用

前述のスクリプト (`InputManager.cs`) 内の `OnAttackLeft` メソッドで行っている溜め時間の計算ロジックを詳しく見てみましょう。

  1. 押下開始 (context.started):
    • マウスの左ボタンが押された瞬間にこのブロックが実行されます。
    • 現在の時刻 (`Time.time`) を `buttonPressStartTime` 変数に記録します。これが溜め時間の計測開始点となります。
  2. 押下終了 (context.canceled):
    • 押されていた左ボタンが離された瞬間にこのブロックが実行されます。
    • 現在の時刻 (`Time.time`) から、記録しておいた押下開始時刻 (`buttonPressStartTime`) を引くことで、ボタンが押されていた時間 (`pressDuration`) を計算します。
    • 計算した `pressDuration` と、あらかじめ定義しておいた溜め攻撃のしきい値 (`specialAttackThreshold`) を比較します。
    • pressDuration がしきい値より長ければ `PerformSpecialAttack()` メソッドを、短ければ `PerformNormalAttack()` メソッドを呼び出します。

このように、Input Systemの `started` と `canceled` イベントを利用することで、ボタンが押されていた時間を正確に計測し、それに基づいて通常攻撃と溜め攻撃を振り分けることができます。`Update()` 関数内で毎フレーム時間を加算していく必要がないため、コードがシンプルになり、処理負荷も軽減される可能性があります。


中間演出の実装:コルーチンでInput Systemの弱点を補う

Input Systemのイベント駆動モデルは、「押した」「離した」といった瞬間のイベントを捉えるのは得意ですが、「ボタンを押し続けている間の特定のタイミング(例: 溜め攻撃が可能になる瞬間)」で何か処理を行いたい場合には、少し工夫が必要です。

例えば、「溜め時間がしきい値に達したらキャラクターを光らせる」「溜め完了のSEを鳴らす」といった演出を入れたい場合、`started` と `canceled` イベントだけでは、その「中間点」を直接検知できません。

この問題を解決する一般的な方法の一つが、Unityの「コルーチン (Coroutine)」を利用することです。ボタンが押された (`started`) 時点でコルーチンを開始し、一定時間(溜め攻撃のしきい値)が経過したら、溜め完了の合図(シグナル)を送る、という仕組みを作ります。

以下は、先ほどの `InputManager.cs` にコルーチンを追加し、溜め完了のシグナル(ここではDebugログ出力)を実装した例です。

using UnityEngine;
using UnityEngine.InputSystem;
using System.Collections; // コルーチンのために必要

[RequireComponent(typeof(PlayerInput))]
public class InputManager : MonoBehaviour
{
    private float buttonPressStartTime;
    private const float specialAttackThreshold = 1.0f;
    // 実行中のコルーチンを保持する変数
    private Coroutine chargeCheckCoroutine;
    // 溜め完了シグナルが送られたかどうかのフラグ
    private bool isChargeComplete = false;

    public void OnAttackLeft(InputAction.CallbackContext context)
    {
        if (context.started)
        {
            Debug.Log("Attack button pressed (started)");
            buttonPressStartTime = Time.time;
            isChargeComplete = false; // 溜め開始時にフラグをリセット

            // もし既にコルーチンが動いていたら停止する(連打対策)
            if (chargeCheckCoroutine != null)
            {
                StopCoroutine(chargeCheckCoroutine);
            }
            // 溜め時間監視コルーチンを開始
            chargeCheckCoroutine = StartCoroutine(ChargeTimerCoroutine());
        }
        else if (context.canceled)
        {
            Debug.Log("Attack button released (canceled)");
            // ボタンが離されたら、溜め時間監視コルーチンを停止
            if (chargeCheckCoroutine != null)
            {
                StopCoroutine(chargeCheckCoroutine);
                chargeCheckCoroutine = null; // 保持しているコルーチン参照をクリア
            }

            float pressDuration = Time.time - buttonPressStartTime;
            Debug.Log($"Press duration: {pressDuration} seconds");

            // 溜め完了フラグが立っていれば(=しきい値を超えていれば)溜め攻撃
            if (isChargeComplete) // または pressDuration > specialAttackThreshold でも判定可能
            {
                PerformSpecialAttack();
            }
            else
            {
                PerformNormalAttack();
            }

            // 攻撃実行後にフラグをリセット
            isChargeComplete = false;
        }
    }

    // 溜め時間を監視するコルーチン
    private IEnumerator ChargeTimerCoroutine()
    {
        // しきい値の時間だけ待機
        yield return new WaitForSeconds(specialAttackThreshold);

        // しきい値に到達したら(かつボタンがまだ押されている場合)
        // isChargeComplete フラグを立て、溜め完了の合図を送る
        // ※ context.ReadValue() > 0 などでボタンが押され続けているか確認する方がより厳密
        Debug.Log("Charge Complete threshold reached!");
        isChargeComplete = true;

        // ここで溜め完了エフェクト(光る、SE鳴らすなど)をトリガーする
        TriggerChargeCompleteEffect();

        chargeCheckCoroutine = null; // コルーチン終了
    }

    private void PerformNormalAttack()
    {
        Debug.Log("Perform Normal Attack!");
        // 通常攻撃ロジック
    }

    private void PerformSpecialAttack()
    {
        Debug.Log("Perform Special Attack!");
        // 溜め攻撃ロジック
    }

    private void TriggerChargeCompleteEffect()
    {
         Debug.Log("Play Charge Complete Effect!");
        // 溜め完了時の演出処理(エフェクト表示、SE再生など)
    }
}

このコードでは、ボタンが押されたら `ChargeTimerCoroutine` を開始し、`specialAttackThreshold` 秒後に `isChargeComplete` フラグを `true` にして、溜め完了演出メソッド `TriggerChargeCompleteEffect()` を呼び出します。ボタンが離された (`canceled`) 時点で、このフラグが `true` になっているかどうかで溜め攻撃か通常攻撃かを判断します。

このようにコルーチンを組み合わせることで、Input Systemのイベント駆動のメリットを活かしつつ、溜め攻撃における中間的なタイミングでの処理も実現できます。一見すると `Update` で実装するより複雑に感じるかもしれませんが、入力の種類が増えたり、他のアクションとの連携が必要になったりした場合、Input SystemのAction Mapやイベントによる責務分離がコード全体の整理に役立ちます。


まとめ:Pressイベント方式のメリット・デメリットと使い分け

UnityのInput SystemにおけるPressイベント (started / canceled) を利用することで、イベント駆動に基づいた溜め攻撃の実装が可能になることを見てきました。

メリット:

  • Update関数を使わずに済み、コードがイベント単位で整理されやすい。
  • 押下開始・終了のタイミングを正確に捉えられる。
  • 短押し/長押しの判定や、それに応じた処理の分岐が比較的容易。
  • Input Actionsアセットによる入力マッピング管理が直感的で、キーコンフィグなどへの拡張性が高い。

デメリット(考慮点):

  • 「押され続けている間の特定のタイミング」での処理(溜め完了演出など)には、コルーチンなどの補完的な仕組みが必要になる。
  • 単純な溜め時間計測だけなら、Update関数で実装する方がシンプルに感じる場合もある。

どちらを選ぶべきか?

最終的な実装方法は、プロジェクトの規模や要件、開発チームの好みによって異なります。

  • 小規模なプロジェクトやプロトタイプで、溜め攻撃以外の入力が少ない場合は、Update関数によるシンプルな実装でも十分かもしれません。
  • 中規模以上のプロジェクトで、多様な入力(ゲームパッド対応、キーコンフィグなど)や、他のアクションとの連携、将来的な拡張性・保守性を重視する場合は、Input SystemのPressイベント + コルーチン等による補完という組み合わせが有力な選択肢となるでしょう。Hold Interactionも選択肢ですが、本記事で解説したような柔軟性を求める場合はPressイベント方式が有利な場面があります。

Input Systemは学習コストが少しありますが、慣れれば強力な武器になります。ぜひ、ご自身のプロジェクトに合った方法で溜め攻撃の実装に挑戦してみてください。

【Unity】シーン遷移はインスペクタのリスト選択で!SceneAsset+OnValidateで安全&効率UP

Unityでシーンを切り替える際、SceneManager.LoadScene("シーン名") を使うのが基本的な方法です。しかし、シーン名を直接文字列で指定するこの方法は、シーンファイルの名前を変更した際の修正漏れや、単純なタイプミスによるエラーの原因となりやすく、プロジェクトが大きくなるにつれて保守性を低下させる一因にもなります。

この記事では、こうした文字列指定のリスクを回避し、より安全で効率的にシーン遷移を実装するためのテクニックとして、UnityEditor.SceneAssetOnValidateメソッドを組み合わせる方法を紹介します。この方法を使えば、Unityエディタ上でインスペクタから直感的に遷移先のシーンを選択でき、かつビルド後も問題なく動作するため、日々の開発効率とコードの安全性を高めることができます。


この記事の内容

  1. 従来のシーン遷移(文字列指定)の問題点
  2. 基本的な実装方法(文字列指定)
  3. 改良版:SceneAssetとOnValidateを使った実装
  4. 少し詳しい技術解説
  5. その他のシーン管理アプローチ
  6. まとめ:安全なシーン遷移で開発効率を上げよう

従来のシーン遷移(文字列指定)の問題点

なぜ文字列指定は危険なのか?

SceneManager.LoadScene("MyScene") のようにシーン名を文字列で直接コードに書き込む方法は、一見シンプルですが、以下のような問題を引き起こしがちです。

  • シーンファイル名の変更に弱い: Projectウィンドウでシーンファイルの名前を変更しても、コード内の文字列は自動で更新されません。関連する全てのコードを手動で修正する必要があり、漏れが発生しやすいです。
  • タイプミス(Typo)によるエラー: “MyScene” を “MyScen” など、少しでも間違えて入力すると、実行時にシーンが見つからずエラーになります。コンパイル時にはエラーが出ないため、発見が遅れることがあります。
  • 管理の煩雑化: プロジェクト内のシーン数が増えるにつれて、どこでどのシーン名を文字列で使っているか把握するのが難しくなり、コードの保守性が低下します。

これらの問題は、特にチーム開発や長期にわたるプロジェクトでは、無視できない開発コストの増加につながります。

解決策:SceneAsset + OnValidate の概要

そこで役立つのが、UnityEditor.SceneAssetOnValidate メソッドを組み合わせたテクニックです。この方法には以下のメリットがあります。

  • インスペクタからシーンを選択可能に: SceneAsset 型の変数をスクリプトに用意することで、インスペクタ上でシーンファイルを直接ドラッグ&ドロップして指定できます。これにより、文字列の手入力が不要になり、タイプミスを防げます。
  • ビルド後も安全に動作: SceneAsset はエディタ専用ですが、OnValidate メソッドを使って、エディタでシーンが選択された瞬間にそのシーン名を内部的に文字列として保持します。これにより、ビルド後の実行環境でも正しいシーン名を扱えるようになります。
  • 保守性と開発効率の向上: シーン名の変更があっても、インスペクタで再設定するだけで済み、コード修正の手間が大幅に減ります。

基本的な実装方法(文字列指定)

まずは比較のために、従来の文字列指定による基本的なシーン遷移の実装を見てみましょう。

通常のシーン遷移コード例

以下は、特定のGameObject(例: ゴール地点)にプレイヤーが接触した際に、指定した名前のシーンに遷移するシンプルなスクリプトです。

using UnityEngine;
using UnityEngine.SceneManagement;

public class SceneTransitionTrigger : MonoBehaviour
{
    // インスペクタで遷移先のシーン名を入力する
    [SerializeField] private string sceneToLoad;

    private void OnTriggerEnter2D(Collider2D other)
    {
        // 接触したのがプレイヤーだったら
        if (other.CompareTag("Player"))
        {
            // 文字列で指定したシーンをロード
            SceneManager.LoadScene(sceneToLoad);
        }
    }
}

このスクリプトをアタッチしたGameObjectのインスペクタには、`Scene To Load` という文字列入力フィールドが表示されます。

基本的なシーン遷移コードのインスペクタ表示(文字列入力)

ここに遷移したいシーンの名前(例: “Stage2″)を正確に入力する必要があります。

【重要】Build Settingsへのシーン追加

非常に重要な注意点として、SceneManager.LoadScene でロードするシーンは、必ず事前に Build Settings に追加しておく必要があります。これを行わないと、エディタ上では動作してもビルド後にシーンが見つからずエラーになります。

  1. Unityメニューの File > Build Settings… を開きます。
  2. 「Scenes In Build」のリストに、遷移先のシーンファイル(Projectウィンドウから)をドラッグ&ドロップして追加します。

Build Settingsにシーンファイルを追加する画面

(↑ここにリストアップされているシーンのみ、ビルド後もロード可能です)


改良版:SceneAssetとOnValidateを使った実装

それでは、文字列指定の問題点を解決する改良版の実装を見ていきましょう。

SceneAssetを利用したコード例

以下のスクリプトでは、インスペクタ上にSceneAsset型のフィールドを用意し、そこにシーンファイルを直接ドラッグ&ドロップできるようにします。そして、OnValidateメソッドを使って、選択されたシーンの名前を内部的な文字列変数に自動でコピーします。

改良版コードのインスペクタ表示(SceneAsset選択フィールド)

(↑インスペクタでシーンファイルを直接アタッチできるように!)

using UnityEngine;
using UnityEngine.SceneManagement;
// UnityEditor 名前空間はエディタ専用機能を使う場合に必要
#if UNITY_EDITOR
using UnityEditor;
#endif

public class SceneLoaderSafe : MonoBehaviour
{
    // [HideInInspector] 実行時にはこの文字列だけあれば良いのでインスペクタからは隠す
    [HideInInspector]
    [SerializeField] private string sceneToLoad;

// #if UNITY_EDITOR ~ #endif で囲まれた部分はエディタ上でのみ有効になる
#if UNITY_EDITOR
    // インスペクタに表示するためのSceneAsset型変数
    [Header("遷移先シーン選択")] // インスペクタに見出しを表示
    [SerializeField] private SceneAsset sceneAsset; // ここにシーンファイルをD&Dする
#endif

    private void OnTriggerEnter2D(Collider2D other)
    {
        if (other.CompareTag("Player"))
        {
            // ロード時は保持しておいた文字列変数を使う
            if (!string.IsNullOrEmpty(sceneToLoad))
            {
                SceneManager.LoadScene(sceneToLoad);
            }
            else
            {
                Debug.LogError("遷移先のシーン名が設定されていません!");
            }
        }
    }

// OnValidateメソッドもエディタ専用
#if UNITY_EDITOR
    // インスペクタで値が変更された時などに自動で呼ばれるメソッド
    private void OnValidate()
    {
        // sceneAssetフィールドにシーンが設定されたら
        if (sceneAsset != null)
        {
            // そのシーンの名前(文字列)を sceneToLoad 変数にコピーする
            sceneToLoad = sceneAsset.name;
        }
        else
        {
            // SceneAssetが未設定なら文字列も空にする
            sceneToLoad = "";
        }
    }
#endif
}

コードのポイント:

  • using UnityEditor;#if UNITY_EDITOR / #endif: SceneAssetOnValidate はエディタ専用機能なので、これらを使ってビルドエラーを防ぎます。
  • [SerializeField] private SceneAsset sceneAsset;: インスペクタにシーンファイルを設定するためのフィールドです。
  • [HideInInspector] [SerializeField] private string sceneToLoad;: 実際にSceneManager.LoadSceneで使うシーン名を格納する文字列変数。インスペクタからは隠しておきます。
  • private void OnValidate(): インスペクタでsceneAssetが変更されるたびに呼び出されます。ここでsceneAsset.name(選択されたシーンの名前)を取得し、sceneToLoad変数に代入します。
  • SceneManager.LoadScene(sceneToLoad);: 実際のシーンロード時には、OnValidateによって事前に設定された文字列sceneToLoadを使用します。

なぜOnValidateが必要なのか?ビルドエラー回避の鍵

「インスペクタでSceneAssetを直接指定できるなら、OnValidateでわざわざ文字列に変換しなくても、SceneManager.LoadScene(sceneAsset.name)で直接ロードすれば良いのでは?」と疑問に思うかもしれません。

しかし、それはできません。なぜなら、UnityEditor.SceneAssetはUnityエディタ専用のクラスであり、ビルドされたゲーム内では利用できないからです。もしOnValidateを使わずにsceneAsset.nameを直接ロードしようとすると、エディタ上では動作しても、ビルドしたゲームを実行すると「UnityEditor名前空間が見つからない」というエラーが発生し、シーン遷移が失敗してしまいます。

OnValidateメソッドは、まさにこの問題を解決するための「橋渡し役」です。エディタ上でSceneAssetが選択・変更されるたびにOnValidateが自動的に実行され、その瞬間にシーン名を文字列としてsceneToLoad変数に保存してくれます。このsceneToLoad変数は通常の文字列なので、ビルド後のゲームでも問題なく参照できます。

つまり、OnValidateを使うことで、「エディタ上での安全で直感的なシーン選択(SceneAsset)」と「ビルド後も確実に動作するシーンロード(文字列)」という、両方のメリットを実現できるのです。


少し詳しい技術解説

UnityEditor.SceneAsset とは?

UnityEditor.SceneAssetクラスは、Unityエディタ内でシーンファイル(.unity)そのものをアセットとして参照するためのものです。インスペクタ上でシーンファイルをドラッグ&ドロップで設定できるため、視覚的で間違いが起こりにくいインターフェースを提供します。ただし、前述の通り、UnityEditor名前空間に属しており、ビルド後の実行ファイルには含まれません。

参考:Unity Documentation: UnityEditor.SceneAsset (※リンク先は古いバージョンの場合があるため、お使いのUnityバージョンに合わせて確認してください)

OnValidateメソッドの役割

OnValidate()は、MonoBehaviourを継承したクラス内で定義できる特殊なメソッドの一つです。これは、スクリプトがロードされたとき、またはインスペクタで値が変更されたときに、エディタ上でのみ呼び出されます。この性質を利用することで、インスペクタでの設定変更をトリガーにして、何らかの処理(今回の場合はシーン名の文字列への変換・保存)を自動的に行うことができます。

参考:Unity Documentation: MonoBehaviour.OnValidate()

エディタでの選択からビルド後の動作までの流れ

この改良版の実装における処理の流れを整理すると、以下のようになります。

  1. 開発時(エディタ上):
    1. 開発者は、スクリプトのインスペクタでsceneAssetフィールドに遷移したいシーンファイル(例: `Stage2.unity`)をドラッグ&ドロップする。
    2. 値が変更されたため、OnValidate()メソッドが自動的に実行される。
    3. OnValidate()内で、sceneAsset.name(この場合は “Stage2″)が取得され、sceneToLoad変数に文字列 “Stage2” が保存される。
  2. 実行時(ビルド後):
    1. プレイヤーがトリガーに接触する。
    2. OnTriggerEnter2Dメソッドが実行される。
    3. SceneManager.LoadScene(sceneToLoad) が呼び出される。この時、sceneToLoadにはエディタで設定された文字列 “Stage2” が格納されている。
    4. シーン “Stage2” が正常にロードされる。

このように、エディタ専用の機能と実行時にも有効なデータをOnValidateが繋ぐことで、安全で効率的なシーン遷移が実現します。


その他のシーン管理アプローチ

今回紹介したSceneAsset + OnValidate方式以外にも、Unityのシーン遷移を管理・実装する方法はいくつか存在します。

  • シーン名を定数やenum(列挙型)で管理する: シーン名をコード内で定数として定義したり、enum型で管理したりする方法。文字列のハードコーディングは避けられますが、シーン追加・削除時に定数やenumの定義も更新する必要があります。
  • ScriptableObjectでシーンリストを管理する: 遷移可能なシーンのリストをScriptableObjectアセットとして作成・管理する方法。中央集権的に管理できますが、設定の手間はかかります。
  • Addressable Asset Systemを使用する: シーン自体をAddressableアセットとして扱い、アドレス(文字列キー)でロードする方法。非同期ロードや動的なコンテンツ配信に適していますが、システム自体の学習コストがかかります。

それぞれにメリット・デメリットがありますが、今回紹介したSceneAsset + OnValidate方式は、特別なアセットや複雑なシステムを導入することなく、比較的手軽に実装でき、かつエディタ上での利便性とビルド後の安全性を両立できる点で、特に小~中規模のプロジェクトにおいて有効な選択肢の一つと言えるでしょう。


まとめ:安全なシーン遷移で開発効率を上げよう

Unityにおけるシーン遷移で、シーン名を文字列で直接指定する方法は、タイプミスやシーン名変更時の修正漏れといったリスクを伴い、プロジェクトの保守性を低下させる可能性があります。

この記事で紹介したUnityEditor.SceneAssetOnValidateメソッドを組み合わせる方法は、これらの問題を解決する効果的なアプローチです。

  • インスペクタからシーンファイルを直接選択できるため、タイプミスを防ぎ、設定が直感的になります。
  • OnValidateによって、エディタでの選択情報がビルド後も有効な文字列データとして保持されるため、安全なシーンロードが可能です。
  • シーン名の変更が必要になった場合も、インスペクタでシーンファイルを再設定するだけで済み、コード修正の手間が大幅に削減されます。

これにより、シーン遷移に関するエラーのリスクを低減し、開発効率とコードの保守性を向上させることが期待できます。シーン数が増えてきて管理が煩雑になってきたと感じたら、ぜひこの実装方法を試してみてください。

【Unity】マウス位置のTilemap座標を表示するCoordinate Brushの使い方と導入手順

Coordinate Brush使用中のUnityエディタ画面。マウス位置に座標が表示されている。

Unityで2Dゲームを開発する際、Tilemapを使ってマップを作成することは多いですよね。タイルを配置したり、イベントを設定したりする中で、「今マウスカーソルがある場所の座標っていくつだろう?」と正確な位置を知りたくなる場面は少なくありません。

そんな時に非常に便利なのが「Coordinate Brush」というツールです。このブラシを使うと、Tile Paletteウィンドウ上でマウスを動かすだけで、その位置に対応するTilemap座標をリアルタイムに表示してくれます。

この記事では、この便利なCoordinate Brushの機能と、現在のUnityバージョンでの導入方法(少し注意が必要です!)、そして基本的な使い方について詳しく解説します。


この記事の内容

  1. Coordinate Brushとは?
  2. 主な機能と利点
  3. 導入方法(※重要:現在の導入手順)
  4. Coordinate Brushの基本的な使い方
  5. トラブルシューティング
  6. まとめ:座標確認を効率化しよう

Coordinate Brushとは?

Coordinate Brushは、UnityのTilemapシステムで利用できるカスタムブラシの一種です。元々はUnity公式が提供する「2D Tilemap Extras」という便利な拡張機能パッケージに含まれていました。

その主な機能は、Tile Paletteウィンドウ上でタイルマップにマウスカーソルを合わせた際に、そのカーソル位置のセル座標(Tilemap座標)をリアルタイムで表示することです。

下の画像のように、マウスカーソルのすぐそばに (X, Y) 形式で座標が表示されるため、タイルの正確な配置や、イベント発生位置の確認などが格段にしやすくなります。

Coordinate Brush使用中のUnityエディタ画面。マウス位置に座標が表示されている。

主な機能と利点

Coordinate Brushを使用する主なメリットは以下の通りです。

  • リアルタイム座標表示: マウスを動かすだけでTilemap上の座標を即座に確認できます。
  • 正確な位置把握: グリッドに沿った正確な座標がわかるため、タイルの精密な配置が容易になります。
  • イベント・オブジェクト配置補助: 特定の座標にイベントやオブジェクトを配置したい場合に、目的の座標を素早く見つけることができます。
  • デバッグ・レベルデザイン効率化: マップ編集中やデバッグ時に、座標の確認作業にかかる時間を大幅に短縮できます。

導入方法(※重要:現在の導入手順)

Coordinate Brushの導入方法は、過去と現在で少し異なります。以前はこのツールが含まれていた「2D Tilemap Extras」パッケージの扱いが変わったため、注意が必要です。

注意点:2D Tilemap Extrasパッケージの現状

かつてCoordinate Brushは「2D Tilemap Extras」パッケージの一部として提供されていました。しかし、Unity公式ドキュメントによると、このパッケージのバージョン3.1.0以降では、Coordinate Brushは標準の機能セットから除外されています。(現在はGameObject Brush, Group Brush, Line Brush, Random Brushなどが標準に含まれています)

参考: 2D Tilemap Extras v3.1.0 Manual

そのため、Package Managerから最新の「2D Tilemap Extras」をインストールしただけでは、Coordinate Brushは利用できません。

Coordinate Brush本体の導入 (GitHubから)

最新のUnity環境でCoordinate Brushを使用するには、以下の手順でGitHubリポジトリから直接スクリプトファイルを導入する必要があります。

  1. 以下のリンクから、Unity公式が管理する2d-extrasのGitHubリポジトリにあるCoordinateBrush.csファイルを開きます。
    GitHub: Unity-Technologies/2d-extras/…/CoordinateBrush.cs
  2. ページ右側にある「Raw」ボタンを右クリックし、「名前を付けてリンク先を保存」などを選択して、CoordinateBrush.csファイルをダウンロードします。(または、コード全体をコピーして、手動で同名のファイルを作成します)
  3. ダウンロードしたCoordinateBrush.csファイルを、お使いのUnityプロジェクトのAssetsフォルダ内にあるEditorフォルダに配置します。(もしEditorフォルダがなければ、Assetsフォルダ直下に新規作成してください)
  4. ファイルを配置したら、Unityエディタが自動的にスクリプトをコンパイルします。もし自動で認識されない場合は、Unityエディタを再起動してみてください。

これで、Coordinate Brushがプロジェクトに導入され、使用できる状態になります。

Coordinate Brushの基本的な使い方

Coordinate Brushの使い方は非常にシンプルです。

  1. Unityエディタのメニューから Window > 2D > Tile Palette を選択し、Tile Paletteウィンドウを開きます。
  2. Tile Paletteウィンドウ上部にあるブラシ選択のドロップダウンメニュー(デフォルトでは標準のブラシアイコンが表示されている場所)をクリックします。
  3. ブラシのリストの中に「Coordinate Brush」が表示されているはずなので、それを選択します。
  4. Tile Paletteウィンドウ上で、Tilemapが存在するシーンビューにマウスカーソルを移動させます。
  5. すると、マウスカーソルの位置に合わせて、Tilemapのセル座標がリアルタイムで表示されます。

これで、タイルを配置したい場所や確認したい場所の座標を簡単に把握できます。

トラブルシューティング

Coordinate Brushがうまく動作しない場合に考えられる原因と対処法です。

  • ブラシリストにCoordinate Brushが表示されない:
    • CoordinateBrush.csファイルが、プロジェクト内のEditorフォルダに正しく配置されているか確認してください。
    • ファイルを配置した後、Unityエディタを再起動してみてください。
  • ブラシを選択しても座標が表示されない / エラーが出る:
    • CoordinateBrush.csスクリプトにコンパイルエラーが発生していないか、Unityエディタのコンソールウィンドウを確認してください。(GitHubから正しくダウンロード・コピーできていない可能性があります)
    • Unityのバージョンと互換性がない可能性もゼロではありませんが、通常は比較的新しいバージョンでも動作するはずです。
  • 古い2D-Extrasパッケージとの混同: もし過去にPackage Managerから古いバージョンの2D Tilemap Extrasをインストールしていた場合、予期せぬ競合が起きる可能性も考えられます。不要であれば古いパッケージは削除し、GitHubから最新のスクリプトを導入する方法に統一することをお勧めします。

まとめ:座標確認を効率化しよう

Coordinate Brushは、UnityのTilemapを使った2Dゲーム開発において、マップ上の座標をリアルタイムで確認できる非常に便利なエディタ拡張ツールです。

現在はUnityの標準パッケージには含まれていませんが、GitHubからスクリプトファイルを導入することで、最新のUnity環境でも利用可能です。

正確なタイル配置が求められるレベルデザイン時や、特定の座標にイベントを設定したい場合、あるいはデバッグ時にオブジェクトの位置を確認したい場合など、様々な場面で開発効率を向上させてくれます。導入も簡単なので、Tilemapを活用している方はぜひ試してみてはいかがでしょうか。

【Unity】Live2Dモーションに組み込んだイベントのタイミングで効果音を鳴らす方法

Live2Dで作成したキャラクターアニメーション(モーション)には、タイムライン上の特定のフレームに「イベント」というマーカーのようなものを埋め込む機能があります。このイベントは、Unityにモデルをインポートした後、モーション再生中の特定のタイミングでUnity側の処理(例えば効果音(SE)の再生、エフェクトの表示など)を呼び出すトリガーとして活用できます。

この記事では、Live2D Cubism Editorでモーションにイベントを設定し、それをUnityプロジェクトにインポート、そして設定したイベントのタイミングで効果音を鳴らすための具体的な手順と、さらに効率化するためのTipsを解説します。


この記事で解説する手順

  1. [Live2D] モーションデータ内にイベントを作成・設定する
  2. [Live2D] イベント情報を含めてモーションデータを書き出す
  3. [Unity] モデルとモーションファイルをUnityにインポートする
  4. [Unity] イベントを受け取り効果音を再生するスクリプトと設定
  5. [Unity] SDKスクリプト編集でイベント関数名の自動設定(効率化Tips)
  6. オススメ参考情報

[Live2D] モーションデータ内にイベントを作成・設定する

まず、Live2D Cubism Editorのアニメーション作成画面(タイムライン)を開き、効果音を鳴らしたいフレーム(タイミング)にイベントを設定します。

  1. タイムライン上で、イベントを設定したいパラメータグループ(例: `Expression`など、どこでも可)を選択します。
  2. 効果音を鳴らしたいフレームに再生ヘッド(赤い縦線)を移動させます。
  3. そのフレーム上で右クリックし、「イベントの設定」を選択します。(または、タイムライン上部のイベント用トラックで直接右クリック)

Live2D Editorのタイムラインでイベントを設定する

すると、イベントの文字列を入力するダイアログが表示されます。

Live2Dイベント設定の入力ダイアログ

ここに入力した文字列は、後でUnityにインポートした際に、モーション(AnimationClip)内のAnimationEventstringParameter(文字列パラメータ)として渡されます。

そのため、単に「SE」と入力するだけでなく、再生したい効果音を識別するためのIDや名前(例: `Footstep`, `AttackSE_01`, `Voice_Happy`など)を設定するのがおすすめです。こうすることで、Unity側のスクリプトでこの文字列を受け取り、PlaySFX(audioID) のように、再生する効果音を動的に切り替えることが可能になります。

必要なタイミングにイベントを設定していきましょう。タイムライン上に黄色いマーカーとして表示されます。

タイムライン上に設定されたイベントマーカー

[Live2D] イベント情報を含めてモーションデータを書き出す

イベントの設定が完了したら、モーションデータをUnityで読み込める形式(.motion3.json)で書き出します。

  1. Live2D Editorのメニューから「ファイル」>「組み込み用ファイル書き出し」>「モーションファイル書き出し」を選択します。
  2. 書き出し設定ダイアログが表示されます。
  3. 【重要】 設定項目の中にある「イベントを書き出し」のチェックボックスに必ずチェックを入れてください。これを忘れると、せっかく設定したイベント情報がファイルに含まれず、Unity側で受け取ることができません。

Live2Dモーションファイル書き出しメニュー

モーション書き出し設定で「イベントを書き出し」にチェックを入れる

設定を確認したら「OK」を押し、.motion3.jsonファイルを書き出します。

[Unity] モデルとモーションファイルをUnityにインポートする

Live2Dモデル本体のデータ(.model3.jsonファイルやテクスチャ等が含まれるフォルダ)と、先ほど書き出したモーションファイル(.motion3.json)をUnityプロジェクトにインポートします。

通常は、これらのファイルやフォルダをUnityのProjectウィンドウにドラッグ&ドロップするだけで、Live2D Cubism SDK for Unityが自動的に処理を行い、モデルのPrefabやモーションのAnimationClipなどが生成されます。

(Live2D Cubism SDK for Unityがプロジェクトに導入されていない場合は、先に導入が必要です)

参考: SDKをインポートしてモデルを表示する – Live2D公式ドキュメント

[Unity] イベントを受け取り効果音を再生するスクリプトと設定

Unityにモーションファイルをインポートすると、Live2D Editorで設定したイベントは、生成されたAnimationClip内の「Animation Event」として自動的に変換・登録されます。

確認してみましょう。生成されたAnimationClipアセットを選択し、Animationウィンドウ(またはInspectorウィンドウのEventsセクション)を開きます。

UnityのAnimationウィンドウで生成されたAnimationEventを確認

タイムライン上に、Live2Dで設定したタイミングでイベントマーカーが表示されているはずです。そのマーカーを選択すると、詳細を確認できます。

AnimationEventの詳細:Stringパラメータにイベント文字列が設定されている

String」パラメータには、Live2D Editorでイベントに設定した文字列(例: `Footstep`)が正しく入っていることがわかります。

しかし、このままではまだ効果音は鳴りません。なぜなら、「Function」欄が空欄だからです。Animation Eventは、指定された関数(Function Name)を、そのイベントを持つAnimatorコンポーネントと同じGameObjectにアタッチされているスクリプトから呼び出す仕組みになっています。FunctionNameが空だと、どの関数を呼び出せば良いか分からず、何も実行されません。

そこで、以下の手順で効果音再生用のスクリプトを作成し、設定を行います。

  1. 効果音再生用スクリプトを作成: 以下のような、文字列を引数として受け取り、対応する効果音を再生する関数を持つC#スクリプトを作成します(内容はあくまで例です。ご自身のプロジェクトの効果音再生システムに合わせて実装してください)。
    using UnityEngine;
    
        public class Live2DSoundPlayer : MonoBehaviour
        {
            // 効果音のAudioSourceなどを設定(Inspectorなどから)
            public AudioSource audioSource;
            // 効果音クリップの管理方法(例: Dictionaryや配列など)は適宜実装
            // public AudioClip footstepClip;
            // public AudioClip attackClip;
    
            /// 
            /// AnimationEventから呼び出される関数。
            /// 引数(audioID)にはAnimationEventのStringパラメータが渡される。
            /// 
            /// 再生したい効果音のID(Live2Dイベントで設定した文字列)
            public void PlaySFX(string audioID)
            {
                Debug.Log("PlaySFX called with ID: " + audioID);
                // audioIDに基づいて再生するAudioClipを決定し、再生する処理
                // 例:
                // if (audioID == "Footstep" && footstepClip != null)
                // {
                //     audioSource.PlayOneShot(footstepClip);
                // }
                // else if (audioID == "AttackSE_01" && attackClip != null)
                // {
                //     audioSource.PlayOneShot(attackClip);
                // }
                // ... など
            }
        }
        

  2. スクリプトをアタッチ: 作成した効果音再生用スクリプト(例: `Live2DSoundPlayer.cs`)を、Live2DモデルのPrefabのルートGameObjectAnimatorコンポーネントがアタッチされているGameObject)に追加(アタッチ)します。

    【重要】Animatorと同じGameObjectにアタッチしないと、Animation Eventが関数を見つけられません!
  3. FunctionNameを手動設定: 再度、モーションのAnimationClipをAnimationウィンドウで開きます。各Animation Eventマーカーを選択し、Inspectorウィンドウの「Function」欄に、作成したスクリプト内の関数名(例: `PlaySFX`)を手動で入力します。

これで、モーションが再生され、イベントが設定されたフレームに到達すると、指定した`PlaySFX`関数が呼び出され、引数としてLive2Dで設定した文字列(効果音ID)が渡されるようになります。関数内でそのIDに応じた効果音を再生する処理を実装すれば、目的の動作が実現します。

[Unity] SDKスクリプト編集でイベント関数名の自動設定(効率化Tips)

前のステップで、Animation EventのFunctionNameを手動で設定する方法を説明しましたが、イベントの数が多い場合や、Live2D側でモーションを修正して再インポートするたびに設定し直すのは非常に手間がかかります。

Live2D Editor側でイベントの文字列(パラメータ)は設定できますが、呼び出す関数名(FunctionName)を直接指定することはできません。しかし、Unity側でモーションファイル(.motion3.json)をインポートする際の処理をカスタマイズすることで、FunctionNameを自動的に設定させることが可能です。

これを行うには、Live2D Cubism SDK for Unityに含まれるスクリプトファイルを編集します。

  1. UnityのProjectウィンドウで、以下のパスにあるスクリプトファイルを探して開きます。
    Packages/Live2D Cubism/Framework/Json/CubismMotion3Json.cs
    (※SDKのバージョンやインストール方法によってパスが異なる場合があります。Assetsフォルダ内に直接インポートしている場合はそちらを探してください)
  2. スクリプト内でToAnimationClipメソッドの中を探し、AnimationEventを生成している箇所を見つけます。(new AnimationEvent と書かれているあたりです)
  3. そのAnimationEventを生成するコードに、functionNameの行を追加(または編集)し、呼び出したい関数名(例: `”PlaySFX”`)を直接文字列で指定します。
    // CubismMotion3Json.cs の ToAnimationClip メソッド内の一部を編集
    // (元のコード構造に合わせて編集してください)
    
    var animationEvent = new AnimationEvent
    {
        time = UserData[i].Time, // イベントの発生時間
        // ここに呼び出したい関数名を直接記述!
        functionName = "PlaySFX",
        stringParameter = UserData[i].Value, // Live2Dで設定したイベント文字列
    };
    

  4. スクリプトを保存します。

この変更により、今後.motion3.jsonファイルをUnityにインポート(または既存のファイルを右クリックして「Reimport」)した際に、生成されるAnimationEventFunctionNameフィールドに、指定した関数名(この例では “PlaySFX”)が自動的に設定されるようになります。これにより、手動でFunctionNameを入力する手間が省け、モーション更新時の作業も大幅に効率化されます。

【注意】SDKのスクリプトを直接編集する方法は、SDKのアップデート時に変更が上書きされてしまう可能性があるため、その点は留意が必要です。変更箇所を記録しておくか、可能であればより安全なカスタマイズ方法(例えば、インポート後の後処理スクリプトを作成するなど)を検討するのも良いでしょう。

参考: motion3.jsonに設定されたイベントを取得する – Live2D公式ドキュメント

オススメ参考情報

今回の実装にあたり、以下の情報が大変参考になりました。

【Unity】ゲームの画面サイズを1080pと720pで切り替えられるようにする Screen.SetResolution

Unityで開発中のゲームに、プレイヤーがグラフィック設定を変更できるオプション、特に「画面解像度」の切り替え機能を実装したい、と考えたことはありませんか? フルHD(1920×1080)で高画質を楽しみたいユーザーもいれば、PCスペックに合わせて少し軽い解像度(例: 1280×720)でプレイしたいユーザーもいます。こうしたニーズに応えるために、Unityでは Screen.SetResolution という便利な関数が用意されています。

この関数を使うことで、ゲームの実行中にプログラムから画面の解像度やフルスクリーン/ウィンドウモードを動的に変更することが可能です。

この記事では、Screen.SetResolution 関数の基本的な使い方から、UIボタンを使った具体的な実装例、そして使用する上での注意点やプラットフォーム別の挙動、UIスケーリングへの配慮といった実践的なTIPSまでを分かりやすく解説します。


この記事の内容

  1. はじめに:なぜ解像度変更機能が必要か?
  2. サンプル実装:解像度切り替えボタンの作り方
    1. C#スクリプト例 (ResolutionChangeButton.cs)
    2. UIボタンへのスクリプト割り当て方法
  3. Screen.SetResolution関数の詳細解説
    1. パラメータ (width, height, fullscreen) の意味
    2. ウィンドウモードとフルスクリーンモードの制御
    3. 解像度がうまく変更されない場合のチェックポイント
  4. 実装時のTIPSと注意点
    1. ターゲットプラットフォームによる挙動の違い
    2. アスペクト比の変化とUIスケーリングへの配慮
    3. より使いやすい解像度変更メニューのアイデア
  5. 参考資料
  6. まとめ:Screen.SetResolutionで快適なプレイ環境を提供

はじめに:なぜ解像度変更機能が必要か?

プレイヤーが使用するモニターの解像度やPCのスペックは様々です。開発者が意図した解像度だけでなく、プレイヤーが自身の環境に合わせて最適な画面サイズを選択できるようにすることは、ゲームのアクセシビリティと快適性を向上させる上で重要です。例えば、高解像度モニターを持つユーザーには鮮明な映像を提供し、一方でスペックが低いPCのユーザーには解像度を下げることでフレームレートを安定させる、といった選択肢を与えることができます。Unityの Screen.SetResolution は、これを実現するための基本的な機能を提供します。

サンプル実装:解像度切り替えボタンの作り方

ここでは、最もシンプルな例として、「1920×1080 (フルHD)」と「1280×720 (HD)」の2つの解像度に切り替えるためのUIボタンを実装する方法を紹介します。

C#スクリプト例 (ResolutionChangeButton.cs)

まず、解像度を変更するためのC#スクリプトを作成します。

using UnityEngine;

public class ResolutionChangeButton : MonoBehaviour
{
    /// <summary>
    /// 指定された幅と高さで解像度を設定する共通メソッド
    /// </summary>
    /// <param name="width">設定したい幅 (ピクセル)</param>
    /// <param name="height">設定したい高さ (ピクセル)</param>
    public void SetResolution(int width, int height)
    {
        // Screen.SetResolution(幅, 高さ, フルスクリーンかどうか);
        // 第3引数に Screen.fullScreen を渡すと、現在のフルスクリーン状態を維持したまま解像度を変更します。
        // フルスクリーンにしたければ true、ウィンドウモードにしたければ false を指定します。
        Screen.SetResolution(width, height, Screen.fullScreen);
        Debug.Log($"Resolution set to: {width}x{height}, Fullscreen: {Screen.fullScreen}");
    }

    /// <summary>
    /// 解像度を1920x1080に設定するメソッド (ボタンから呼び出す用)
    /// </summary>
    public void SetResolution1080p()
    {
        SetResolution(1920, 1080);
    }

    /// <summary>
    /// 解像度を1280x720に設定するメソッド (ボタンから呼び出す用)
    /// </summary>
    public void SetResolution720p()
    {
        SetResolution(1280, 720);
    }

    // --- 必要に応じて他の解像度用のメソッドも追加 ---
    // public void SetResolutionYourChoice()
    // {
    //     SetResolution(yourWidth, yourHeight);
    // }

    // --- フルスクリーン切り替えメソッドの例 ---
    // public void ToggleFullscreen()
    // {
    //     Screen.fullScreen = !Screen.fullScreen;
    //     // 解像度は現在のものを維持する場合
    //     // Screen.SetResolution(Screen.currentResolution.width, Screen.currentResolution.height, Screen.fullScreen);
    //     Debug.Log($"Fullscreen toggled: {Screen.fullScreen}");
    // }
}

このスクリプトは、特定の解像度を設定するメソッド(SetResolution1080p, SetResolution720p)と、それらの内部で共通して呼ばれるSetResolutionメソッドで構成されています。

UIボタンへのスクリプト割り当て方法

次に、作成したスクリプトをUnityのUIボタンに割り当てます。

  1. Unityエディタで、解像度変更用のUIボタン(Buttonコンポーネントを持つGameObject)を2つ作成します(例: 「1080p Button」、「720p Button」)。
  2. 空のGameObjectを作成し(例: `ResolutionManager`)、それに先ほど作成したResolutionChangeButton.csスクリプトをアタッチします。
  3. 「1080p Button」を選択し、InspectorウィンドウでButtonコンポーネントの「OnClick ()」イベントリストの「+」ボタンを押します。
  4. イベントリストの「None (Object)」と表示されているフィールドに、`ResolutionManager` GameObjectをHierarchyウィンドウからドラッグ&ドロップします。
  5. 右側のドロップダウンメニュー(初期状態では「No Function」)から、「ResolutionChangeButton」>「SetResolution1080p ()」を選択します。
  6. 同様に、「720p Button」を選択し、OnClick()イベントに`ResolutionManager`を割り当て、関数として「ResolutionChangeButton」>「SetResolution720p ()」を選択します。

UnityのButtonコンポーネントのOnClickイベント設定画面

これで、ゲーム実行中に各ボタンをクリックすると、対応するスクリプト内のメソッドが呼び出され、Screen.SetResolutionによって画面解像度が変更されるようになります。

Screen.SetResolution関数の詳細解説

パラメータ (width, height, fullscreen) の意味

Screen.SetResolution関数は、基本的に3つの引数を取ります。

Screen.SetResolution(int width, int height, bool fullscreen)

  • int width: 設定したい画面のをピクセル単位で指定します。
  • int height: 設定したい画面の高さをピクセル単位で指定します。
  • bool fullscreen: 画面モードを指定します。
    • true: フルスクリーンモードで指定した解像度に設定します。
    • false: ウィンドウモードで指定した解像度(ウィンドウサイズ)に設定します。

例えば、Screen.SetResolution(1920, 1080, true); と記述すると、画面は1920×1080ピクセルのフルスクリーン表示に切り替わります。

(注意:Unity 2021.2以降では、第4引数としてリフレッシュレートを指定できるオーバーロードも追加されていますが、基本的な使い方は上記の3引数版です。)

ウィンドウモードとフルスクリーンモードの制御

Screen.SetResolutionの第3引数fullscreenの扱いについて、もう少し詳しく見てみましょう。

  • 現在のモードを維持したい場合: サンプルコードのように Screen.SetResolution(width, height, Screen.fullScreen); と記述します。Screen.fullScreenは現在のフルスクリーン状態(trueまたはfalse)を返すプロパティなので、これを使うと、フルスクリーン状態は変えずに解像度だけを変更できます。
    • 現在ウィンドウモードなら、指定したwidth x heightのウィンドウサイズになります。
    • 現在フルスクリーンモードなら、指定したwidth x heightのフルスクリーン解像度に切り替わります(モニターが対応していれば)。
  • モードも同時に切り替えたい場合: 第3引数に直接 true または false を指定します。
    • Screen.SetResolution(1280, 720, false); // 1280×720のウィンドウモードにする
    • Screen.SetResolution(1920, 1080, true); // 1920×1080のフルスクリーンモードにする
  • フルスクリーン状態だけを切り替えたい場合: Screen.fullScreen = !Screen.fullScreen; のように、Screen.fullScreenプロパティに直接代入することで、解像度を変えずにフルスクリーン/ウィンドウモードをトグルできます。(ただし、モード切り替え時に一瞬画面が乱れることがあります)

解像度がうまく変更されない場合のチェックポイント

Screen.SetResolutionを呼び出しても期待通りに解像度が変更されない場合、以下の点を確認してみてください。

  • Unityエディタ上での制限: UnityエディタのGameビューでは、Screen.SetResolutionによる解像度変更が完全には反映されない、あるいは挙動が異なる場合があります。特にフルスクリーンモードのテストは制限されます。正確な動作確認は、必ずビルドした実行ファイルで行ってください
  • ターゲットプラットフォームの制限:
    • WebGL: ブラウザ上で実行されるため、ブラウザウィンドウ自体のサイズやHTML/CSSによる制御が優先され、Screen.SetResolutionが意図した通りに機能しないことが多いです。WebGLでは通常、Canvas要素のサイズ調整で対応します。
    • モバイル (iOS/Android): スマートフォンやタブレットは基本的にデバイス固有のネイティブ解像度で動作するため、Screen.SetResolutionで任意の解像度に設定することは推奨されませんし、機能しない場合が多いです。モバイルでは通常、描画負荷を軽減するために内部的なレンダリング解像度を調整する(Screen.SetResolutionとは異なる)アプローチが取られます。
  • 他のスクリプトやアセットとの競合: プロジェクト内で画面サイズやフルスクリーン設定を制御している他のスクリプトやアセット(特にウィンドウ管理系のアセットなど)が存在する場合、それらとScreen.SetResolutionの呼び出しが競合している可能性があります。
  • OS側の設定: OSのディスプレイ設定(スケーリング設定など)が影響を与える可能性もゼロではありません。

実装時のTIPSと注意点

ターゲットプラットフォームによる挙動の違い

前述の通り、Screen.SetResolutionの挙動はターゲットプラットフォームによって異なります。

  • PC (Windows, Mac, Linux) スタンドアロンビルド: 最も意図した通りに動作しやすいプラットフォームです。プレイヤーに解像度とフルスクリーン/ウィンドウモードの選択肢を提供することが一般的です。
  • WebGL: 基本的にScreen.SetResolutionは使わず、HTML/JavaScript側でCanvasサイズを制御することを検討します。
  • モバイル: 任意の解像度変更は行わず、ネイティブ解像度で動作させるのが基本です。描画負荷軽減は別の方法(レンダリングスケール調整など)で行います。

開発するゲームがどのプラットフォーム向けなのかを意識し、実装方法を検討する必要があります。

アスペクト比の変化とUIスケーリングへの配慮

解像度を変更すると、画面のアスペクト比(横縦比、例: 16:9, 4:3)が変わる可能性があります。これに伴い、UI要素のレイアウトが崩れてしまうことがよくあります。

この問題に対応するには、UnityのUIシステム(uGUI)の機能を活用します。

  • Canvas Scaler コンポーネント: CanvasオブジェクトにアタッチされているCanvas Scalerコンポーネントの「UI Scale Mode」を「Scale With Screen Size」に設定します。これにより、画面解像度が変わっても、UI要素が基準解像度に合わせて自動的に拡大縮小されるようになります。「Match」スライダーで、幅と高さのどちらを基準にスケーリングするかのバランスを調整できます。
  • アンカー (Anchors) とピボット (Pivot): 各UI要素(Image, Button, Textなど)のRect Transformコンポーネントで、アンカーとピボットを適切に設定します。これにより、画面の端からの相対位置や、要素自体の伸縮挙動を制御でき、異なるアスペクト比でもある程度レイアウトを保つことができます。
  • Layout Group コンポーネント: Vertical Layout GroupやHorizontal Layout Group, Grid Layout Groupなどを使うと、子要素を自動的に整列・配置してくれるため、解像度変更に強いレイアウトを作りやすくなります。

解像度変更機能を実装する場合は、必ず複数の異なる解像度とアスペクト比でUIの表示を確認し、適切にスケーリングされるように調整することが重要です。

より使いやすい解像度変更メニューのアイデア

サンプル実装では固定の解像度ボタンを配置しましたが、よりユーザーフレンドリーな設定メニューにするためのアイデアもいくつかあります。

  • 解像度リストのドロップダウンメニュー: Screen.resolutions プロパティを使うと、プレイヤーのモニターがサポートしている解像度のリストを取得できます。これをドロップダウンメニューに表示し、プレイヤーが選択できるようにすると親切です。
  • フルスクリーン/ウィンドウモード切り替えボタン/チェックボックス: 解像度とは別に、フルスクリーンモードとウィンドウモードを切り替えるための専用のボタンやチェックボックスを用意します。(Screen.fullScreenプロパティを操作)
  • 任意解像度の入力フィールド(上級者向け): 幅と高さを直接入力できるフィールドを用意する方法もありますが、無効な値が入力される可能性もあるため、入力値のバリデーションなどが必要です。
  • 設定の保存と読み込み: プレイヤーが選択した解像度やフルスクリーン設定をPlayerPrefsなどに保存し、次回起動時に自動で適用するようにすると、さらに利便性が向上します。

参考資料


まとめ:Screen.SetResolutionで快適なプレイ環境を提供

今回は、Unityの Screen.SetResolution 関数を使って、ゲーム実行中に画面の解像度フルスクリーン/ウィンドウモードを変更する方法について解説しました。

この機能を実装することで、プレイヤーは自身のPC環境や好みに合わせて最適な表示設定を選択できるようになり、より快適なゲーム体験を提供できます。

実装にあたっては、ターゲットとするプラットフォームによる挙動の違いや、解像度変更に伴うUIレイアウト(アスペクト比、スケーリング)への配慮が重要になります。Canvas Scalerやアンカー設定などを活用し、様々な画面サイズでも破綻しないUIを目指しましょう。

基本的なボタンによる切り替えから、サポート解像度リストの表示、設定の保存・読み込みなど、より高度なオプションメニューの実装も可能です。ぜひ、あなたのプロジェクトに解像度変更機能を取り入れて、プレイヤー満足度の向上につなげてください。

【Unity】Live2DモデルをUI Canvasに表示する方法 (RenderTexture + RawImage)

UnityでゲームのUIを作成する際、「メニュー画面やステータス画面にキャラクターのLive2Dモデルを表示して、もっとリッチでインタラクティブな画面にしたい!」と考えたことはありませんか?

しかし、実際に試してみると、通常のUI要素(ImageやTextなど)のようにUI Canvas上にLive2DモデルのPrefab(プレハブ)を直接配置しただけでは、うまく表示されないことが多いです。これは、Live2Dモデルの描画方法とUnityのUIシステム(uGUI)の仕組みが異なるためです。

この記事では、この問題を解決し、UnityのUI Canvas上にLive2Dモデルをきれいに表示させるための一般的なテクニックを解説します。具体的には、「RenderTexture」と「RawImage」というUnityの機能を利用して、Live2DモデルをUI要素として埋め込む方法をステップバイステップで紹介します。


前提:実装するUIの構成例

今回は例として、以下のようなボタンで表示を切り替えるシンプルなメニュー画面を想定し、「WindowStatus」の中にLive2Dモデルを表示するケースで解説します。


UICanvas (Canvasコンポーネントを持つGameObject)
 |- WindowItem (アイテム画面のGameObject)
 |- WindowStatus (ステータス画面のGameObject)
 |   |- TextMeshPro (キャラクター名など)
 |   |- Image (背景画像など)
 |   +- Live2DPrefab (ここにLive2Dモデルを表示したいが、直接配置ではうまくいかない)
 |- WindowOption (オプション画面のGameObject)

この構成の `WindowStatus` 内にLive2Dモデルを表示させるために、以下の要素を追加・設定していきます。

  1. Live2DモデルのPrefab: Live2D Cubism SDKを使ってUnityプロジェクトにインポートした際に自動生成されるPrefab。
  2. Live2D表示用の専用カメラ: シーン内の他の要素とは別に、Live2Dモデルだけを撮影するためのカメラ。
  3. RenderTexture: 上記の専用カメラが撮影した映像(Live2Dモデル)を、テクスチャとして描き込むためのアセット。
  4. RawImage: UI Canvas上に配置するコンポーネントで、RenderTextureの内容を画像として表示するもの。

つまり、「Live2D Prefabを専用カメラで撮影し、その映像をRenderTextureに焼き付け、最終的にそのRenderTextureをRawImageコンポーネントを通じてUI Canvas上に表示する」という流れになります。

なお、UI CanvasのRender Mode(「Screen Space – Overlay」、「Screen Space – Camera」、「World Space」)は、どのモードでも基本的にはこの方法で対応可能です。(今回は「Screen Space – Overlay」を想定して進めます)

最終的なHierarchy(ヒエラルキー)構成は以下のようになります。


UICanvas (Canvasコンポーネントを持つGameObject)
 |- WindowItem
 |- WindowStatus (ステータス画面のGameObject)
 |   |- TextMeshPro
 |   |- Image (背景など)
 |   |- RawImage (ここにRenderTextureを設定し、Live2Dモデルを表示) 【追加】
 |   |- Live2D Camera (Live2D撮影用のカメラ) 【追加】
 |   |- Live2D Prefab (表示したいLive2Dモデル) 【追加】
 |- WindowOption

それでは、具体的な手順を見ていきましょう。


UI CanvasにLive2Dモデルを表示する手順 (RenderTexture + RawImage)

Step 1: Live2D撮影用の専用カメラを作成する

まず、UIに表示したいLive2Dモデルだけを撮影するための専用カメラを用意します。シーン内の他のカメラとは別に、このLive2Dモデルだけを映し出す役割を持ちます。

作成方法としては、シーンに既に存在するカメラ(例: Main Camera)を複製(Ctrl+D または Cmd+D)するのが簡単でおすすめです。複製したら、分かりやすい名前(例: `Live2D Camera`)に変更しましょう。

次に、作成した `Live2D Camera` を選択し、Inspectorウィンドウでカメラの設定を確認・変更します。特に重要なのは「Projection」の設定で、これを「Orthographic」(平行投影)に設定します。UIに2D的に表示する場合は、通常 Orthographic が適しています。

カメラの位置やサイズ(Orthographicの場合のSize)を調整して、シーンに配置したLive2Dモデルがカメラのビュー内に適切に収まるようにしてください。

【トラブルシューティング】Live2Dモデル自体が正常に表示されない場合

シーンに配置したLive2DモデルのPrefabが、そもそも正しく表示されない(パーツが欠ける、表示順がおかしいなど)場合は、Live2D PrefabのGameObjectにアタッチされている「CubismRenderController」コンポーネントの設定を確認してください。

特に「Sorting」セクションにある「Mode」の設定が重要です。多くの場合、これを「Back To Front Order」に変更することで、描画順の問題が解決します。

CubismRenderControllerのSorting ModeをBack To Front Orderに変更

参考: モデルの描画順を正常にする (Live2D Manuals&Tutorials)

Step 2: Live2D Prefabに専用レイヤーを設定する

次に、Live2D撮影用カメラがLive2Dモデルだけを識別できるように、Live2DモデルのPrefabに専用のレイヤーを設定します。

  1. Unityエディタ右上の「Layers」ドロップダウンメニューを開き、「Edit Layers…」を選択します。
  2. 「User Layers」の空いている欄(例: Layer 8以降)に、新しいレイヤー名(例: `Live2D`)を入力します。名前は何でも構いません。
  3. シーンに配置したLive2DモデルのPrefabのGameObjectを選択します。
  4. Inspectorウィンドウ右上の「Layer」ドロップダウンメニューを開き、先ほど作成した`Live2D`レイヤーを選択します。「Change children?」と聞かれたら「Yes, change children」を選択し、子オブジェクトにも同じレイヤーを設定します。

Live2D Prefabに作成したLive2Dレイヤーを設定する

Step 3: カメラのCulling Maskを設定する

レイヤー設定が完了したら、各カメラが描画する対象を絞り込みます。

  1. メインカメラ (Main Camera):
    • Main Cameraオブジェクトを選択し、InspectorウィンドウのCameraコンポーネントにある「Culling Mask」ドロップダウンメニューを開きます。
    • 先ほど作成した`Live2D`レイヤーのチェックを外します。これにより、メインカメラはLive2Dモデルを描画しなくなります。
  2. Live2D撮影用カメラ (Live2D Camera):
    • `Live2D Camera`オブジェクトを選択し、同様に「Culling Mask」を開きます。
    • 「Nothing」を選択して一旦全てのチェックを外し、その後、`Live2D`レイヤーのみにチェックを入れます。これにより、このカメラはLive2Dレイヤーが設定されたオブジェクト(つまりLive2Dモデル)だけを描画するようになります。

メインカメラのCulling Mask設定 (Live2Dを除外):

メインカメラのCulling MaskからLive2Dレイヤーを除外

Live2DカメラのCulling Mask設定 (Live2Dのみ選択):

Live2DカメラのCulling MaskでLive2Dレイヤーのみを選択

Step 4: RenderTextureを作成する

Live2D用カメラの映像を焼き付けるための「RenderTexture」アセットを作成します。

  1. Projectウィンドウで右クリックし、Create > RenderTexture を選択します。
  2. 作成されたRenderTextureアセットに分かりやすい名前(例: `Live2DRenderTexture`)を付けます。
  3. 作成したRenderTextureアセットを選択し、Inspectorウィンドウで設定を行います。
    • Size: RenderTextureの解像度を設定します。UI上での表示サイズに合わせて調整しますが、まずは1024 x 10242048 x 2048 程度で試してみると良いでしょう。後でUIに表示した際にLive2Dモデルの表示が粗い場合は、ここの数値を大きくします。
    • Color Format: 通常はデフォルトのままで問題ありませんが、背景透過などを行いたい場合はアルファチャンネル付きのフォーマット(例: ARGB32)を選択します。

Step 1で作成したLive2D用カメラの出力先として、Step 4で作成したRenderTextureを指定します。

  1. `Live2D Camera`オブジェクトを選択します。
  2. InspectorウィンドウのCameraコンポーネントにある「Output Texture」フィールドに、作成した`Live2DRenderTexture`アセットをドラッグ&ドロップで設定します。

Live2DカメラのOutput TextureにRenderTextureを設定する

これで、`Live2D Camera`が捉えたLive2Dモデルの映像が、リアルタイムで`Live2DRenderTexture`に描き込まれる状態になりました。あとは、このRenderTextureをUI上に表示するだけです。

Step 6: RawImageでCanvas上に表示する

最後に、UI Canvas上に「RawImage」コンポーネントを配置し、そこにRenderTextureを表示させます。

  1. Live2Dモデルを表示したいUI要素(例: `WindowStatus` GameObject)を選択します。
  2. 右クリックメニューから UI > Raw Image を選択し、`RawImage`コンポーネントを持つ新しいGameObjectを子として作成します。GameObjectの名前を(例: `Live2DView`)に変更します。
  3. 作成した`Live2DView` GameObjectを選択します。
  4. InspectorウィンドウのRawImageコンポーネントにある「Texture」フィールドに、Step 4で作成した`Live2DRenderTexture`アセットをドラッグ&ドロップで設定します。
  5. Rect Transformコンポーネントを使って、UI上での表示位置とサイズを調整します。

RawImageコンポーネントのTextureにRenderTextureを設定

これで、UnityエディタのGameビュー上で、UI Canvasの指定した位置にLive2Dモデルが表示されるはずです!


最終的な配置と注意点

最終的に、今回作成した以下の要素は、それらを表示するUIパネル(例: `WindowStatus` GameObject)の子オブジェクトとしてまとめておくと管理がしやすいでしょう。

  • Live2D Prefab (Live2Dレイヤー設定済み)
  • Live2D Camera (Culling Mask設定済み、Output Texture設定済み)
  • RawImage GameObject (RenderTexture設定済み)

こうすることで、親である `WindowStatus` GameObjectの表示/非表示 (SetActive(true/false)) を切り替えるだけで、Live2Dモデルの表示も連動してON/OFFされるようになります。

以上が、RenderTextureとRawImageを使ってUnityのUI Canvas上にLive2Dモデルを表示する基本的な手順です。この方法を使えば、メニュー画面やキャラクター詳細画面など、様々なUIでLive2Dを活用できます。