UnityでUIシーンとゲームシーンを分けて共存させる方法
― ゲームシーンからUIテキストを安全に変更する設計 ―
近年のUnity開発では、UIを独立したシーンとして管理する手法が一般的になっています。
これにより、UIの再利用・デバッグ・画面遷移がシンプルになり、ゲーム全体の構造も明確になります。
しかし、「UIシーンとゲームシーンを分けると、どうやってUIにアクセスすればいいのか?」という疑問を持つ方も多いでしょう。
本記事では、UIシーンを常駐させ、ゲームシーンから安全にUIを更新する構成と実装例を紹介します。
1. シーンを分けるメリット
まず、UIを別シーンに分ける理由を整理します。
項目 | メリット |
---|---|
責務分離 | ゲームロジックとUI制御を明確に分離できる |
再利用性 | UIを別プロジェクト・別モード(タイトル、メニュー)でも再利用可能 |
ロード時間短縮 | UIを再ロードせず、ゲームシーンだけを切り替えられる |
デバッグ効率 | UIだけ単体でテストできる |
2. 基本構成
フォルダとシーン構成の一例を示します。
Assets/
├─ Scenes/
│ ├─ UI.unity ← 常駐(Additiveロード)
│ ├─ GameStage1.unity
│ └─ GameStage2.unity
├─ Scripts/
│ ├─ UIManager.cs
│ ├─ GameManager.cs
│ └─ SceneLoader.cs
UIシーンはアプリ起動時にロードし、
DontDestroyOnLoadを使って破棄されないようにします。
3. UIManagerの実装(UIシーン)
UIシーンには、Canvas配下にText(またはTMP_Text)と
UIManagerスクリプトを配置します。
using TMPro;
using UnityEngine;
public class UIManager : MonoBehaviour
{
public static UIManager Instance { get; private set; }
[SerializeField] private TMP_Text scoreText;
private void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject); // シーン切り替え時に破棄しない
}
else
{
Destroy(gameObject);
}
}
public void SetScore(int value)
{
scoreText.text = $"Score: {value}";
}
}
ポイント
- Singletonパターンを採用して、どのシーンからでもアクセス可能に。
- DontDestroyOnLoad() でUIシーンを常駐化。
4. GameManagerの実装(Gameシーン)
ゲームシーン側では、UIManager.Instance を介してUIを更新します。
using UnityEngine;
public class GameManager : MonoBehaviour
{
private int score;
public void AddScore(int value)
{
score += value;
UIManager.Instance?.SetScore(score);
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
AddScore(10);
}
}
}
スペースキーを押すたびにスコアが加算され、UI側のテキストが更新されます。
UIシーンが別でも問題なく動作します。
5. UIシーンを自動ロードする(SceneLoader)
アプリ起動時に自動でUIシーンをAdditiveロードする方法です。
using UnityEngine;
using UnityEngine.SceneManagement;
public class SceneLoader
{
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void Init()
{
SceneManager.LoadScene("UI", LoadSceneMode.Additive);
}
}
このクラスをどこかに置いておけば、
Unity起動時に必ずUIシーンがロードされます。
以降、ゲームシーンを自由に切り替えてもUIは残ります。
6. 応用:イベントベース設計(依存をさらに減らす)
上記の方法は簡単で強力ですが、
GameManager → UIManager の直接参照が残ります。
より拡張性を高めるには、イベント駆動にします。
// GameManager.cs
public static event Action<int> OnScoreChanged;
public void AddScore(int value)
{
score += value;
OnScoreChanged?.Invoke(score);
}
// UIManager.cs
private void OnEnable()
{
GameManager.OnScoreChanged += SetScore;
}
private void OnDisable()
{
GameManager.OnScoreChanged -= SetScore;
}
こうすると、UIはGameManagerの存在を知らずに動作します。
依存方向が一方通行となり、保守性が向上します。
7. 注意点とベストプラクティス
注意点 | 対応策 |
---|---|
UIシーンのロード忘れ | RuntimeInitializeOnLoadMethod で自動ロード |
シングルトンの重複生成 | Awakeでチェック&破棄 |
テキスト参照の未設定 | [SerializeField]でInspectorから設定 |
シーン切り替え時のNull | Instance? で安全呼び出し |
8. まとめ
手法 | メリット | 想定規模 |
---|---|---|
Findで直接アクセス | 簡単だが壊れやすい | 小規模・試作 |
Singleton + DontDestroyOnLoad | 安全で汎用的 | 中規模 |
イベント駆動 | 結合度が低く拡張性◎ | 大規模・商用 |
9. まとめコード一式
起動時
SceneManager.LoadScene("UI", LoadSceneMode.Additive);
UIManager.cs
public void SetScore(int value)
{
scoreText.text = $"Score: {value}";
}
GameManager.cs
UIManager.Instance?.SetScore(score);
結論
Find は便利なようで、スケールが大きくなるとすぐ破綻します。代わりに、UIは常駐シーン+シングルトン or イベントベースで構成するのが最も堅牢です。
この設計を導入すれば、「シーンをまたいでも安定してUIを更新できる」だけでなく、ロード順や依存関係のバグを未然に防げるようになります。
ディスカッション
コメント一覧
まだ、コメントがありません