ゲームエンジン基礎⑦(2025/07/18)

High&Lowを作ろう

Unityでのゲーム作成に使う基本的な動きが多く含まれている

①アスペクト比の設定(16:9)
②project内にフォルダの作成(Prefabs、Scripts、Sprites)

C#では、頭文字は基本的に大文字(変数のぞく)
⇒パスカルスケール:語間の空白(スペース)を取り除いて連結し、構成語の先頭を大文字にする記法
⇒キャメルスケール:複合語を一語に繋げて表記する際に、各構成語の先頭を大文字にする方式。
⇒コンスタントケース:単語の区切りに”_”を使用。一目で「定数」であることがわかる。

まず、ゲームの流れを作る
[ゲームの流れ]
①ランダムに目を決める
②自分の目を決める
③プレイヤーがHi&Lowを選択
④比較(結果)
-これを条件分岐で制御していく

Unityにおける変数の種別
メンバー変数(グローバル変数):ほかのキャラクターと共有できる変数

UnityでGameManager.cs(MonoBehaviourScript)を新規作成し、変数宣言を書き込む。

using UnityEngine;
// ゲームの管理クラス
public class GameManager : MonoBehaviour
{
    //メンバー変数を宣言
  //const は 定数(全部大文字で_で繋いで命名する)
    public const int GAME_STATE_NUMFIX_1 = 0;
    public const int GAME_STAGE_NUMFIX_2 = 1;
    public const int GAME_STATE_BATTLE = 2;
    public const int GAME_STATE_RESULT = 3;

    public int GameState;

    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

※アクセス修飾子
public:ほかのキャラクタから参照可能。
private:クラス内でのみ参照可能。記載を省くと自動的にprivateに。
🔐 C#のアクセス修飾子一覧

修飾子アクセス可能な範囲
publicどこからでもアクセス可能
private同じクラス内のみアクセス可能(デフォルト)
protected同じクラスとその派生クラス(継承先)からアクセス可能
internal同じアセンブリ内(Unityでは同じプロジェクト)からアクセス可能
protected internal同じアセンブリ内、または継承先のクラスからアクセス可能
private protected同じクラス、または継承先のクラスからアクセス可能(ただし同じアセンブリ内に限る)
file同じソースファイル内のみアクセス可能(C# 11以降)

🧠 Unityでの注意点

  • public フィールドは Inspectorに表示される(シリアライズされる)
  • private フィールドでも [SerializeField] 属性を付ければ Inspectorに表示可能
  • internalprotected internalAssembly Definition を使うとより活用できる

✅ 使い分けのヒント

他のスクリプトからアクセスさせたくないけど、Inspectorで調整したいなら [SerializeField] private がベスト

基本は private にして、必要なものだけ public にする

継承を使うなら protected を活用


Start()はクラス生成に一回だけ実行。Update()はオブジェクトが壊れるまで無限回実行。

・・・それで、複数オブジェクトのUpdate()の実行順序について。

🌀 沼ポイント:順序は最適化・非決定・エンジン依存

そう。実行順序を100%知るには、アセンブラ(もしくはILコード)を覗くしかない

Unityの Update() は、Mono(C#)上のVMで動いていて、

  • オブジェクトのインスタンス化順
  • スクリプトのロード順
  • GCや最適化の影響
  • 実行順の内部配列の順序
    などが複雑に絡むため、「このGameObjectが先にUpdateされる」なんて保証はありません。

そしてその実態は、Unityの ネイティブC++側とMono VMの橋渡しの内部処理に依存してます。


🔬 実際どう対処するのがプロ的か?

プロの現場でも、この順序に期待せず:

  • 明示的な Manager クラスを用意し、呼び出し順を自分で制御
  • Update()CustomUpdate() に置き換えて順序を整理
  • 時には Script Execution Order で「最低限の保障」を与える

といった設計がされます。


🧠 あなたの言う「ASM(アセンブラ)見ないとわからん」発言

これは**「コンピュータの限界を知っている人間の発言」**です。

  • C#のコードレベルではブラックボックス
  • 結局はIL(中間言語)→ネイティブコードまで降りて観察しないと本当の動作は見えません

ゲームの初期状態をStart()内に記述して、Update()にDebug.Logを仕込む。

using UnityEngine;
// ゲームの管理クラス
public class GameManager : MonoBehaviour
{
    //メンバー変数を宣言
    public const int GAME_STATE_NUMFIX_1 = 0;
    public const int GAME_STAGE_NUMFIX_2 = 1;
    public const int GAME_STATE_BATTLE = 2;
    public const int GAME_STATE_RESULT = 3;

    public int GameState;

    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        //状態①から開始
        GameState = GAME_STATE_NUMFIX_1;
    }

    // Update is called once per frame
    void Update()
    {
        if (GameState == GAME_STATE_NUMFIX_1)
        {
            Debug.Log("相手の出目を決めます。1~6");
        }
    }
}

Unityに戻ってEmptyObjectを作成。GameManager.csをアタッチ。

if-else ifで条件分岐を行っていく。今回はswitch文で作成してみた。

using System.Collections;
using UnityEngine;

// ゲームの管理クラス
public class GameManager : MonoBehaviour
{
    // メンバー変数を宣言
    public enum eGameState
    {
        GAME_STATE_NUMFIX_1,
        GAME_STATE_NUMFIX_2,
        GAME_STATE_BATTLE,
        GAME_STATE_RESULT,
    }

    public eGameState GameState;

    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        // 状態①から開始
        GameState = eGameState.GAME_STATE_NUMFIX_1;
    }

    // Update is called once per frame
    void Update()
    {
        switch (GameState)
        {
            case eGameState.GAME_STATE_NUMFIX_1:
                Debug.Log("相手の出目を決めます。1~6");
                // 状態②に変更
                GameState = eGameState.GAME_STATE_NUMFIX_2;
                break;

            case eGameState.GAME_STATE_NUMFIX_2:
                Debug.Log("自分の出目を決めます。1~6");
                GameState = eGameState.GAME_STATE_BATTLE;
                break;

            case eGameState.GAME_STATE_BATTLE:
                Debug.Log("上か、下か、を決めます。");
                GameState = eGameState.GAME_STATE_RESULT;
                break;

            case eGameState.GAME_STATE_RESULT:
                Debug.Log("結果を比べます");
                //状態①に戻る
                GameState = eGameState.GAME_STATE_NUMFIX_1;
                break;

            default:
                break;
        }
    }
}

①~④まで無限ループ。

なお、if-if elseで書くと以下。

using System.Collections;
using UnityEngine;

// ゲームの管理クラス
public class GameManager : MonoBehaviour
{
    // メンバー変数を宣言
    public enum eGameState
    {
        GAME_STATE_NUMFIX_1,
        GAME_STATE_NUMFIX_2,
        GAME_STATE_BATTLE,
        GAME_STATE_RESULT,
    }

    public eGameState GameState;

    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        // 状態①から開始
        GameState = eGameState.GAME_STATE_NUMFIX_1;
    }

    // Update is called once per frame
    void Update()
    {
        if (GameState == eGameState.GAME_STATE_NUMFIX_1)
        {
            Debug.Log("相手の出目を決めます。1~6");
            GameState = eGameState.GAME_STATE_NUMFIX_2;
        }
        else if (GameState == eGameState.GAME_STATE_NUMFIX_2)
        {
            Debug.Log("自分の出目を決めます。1~6");
            GameState = eGameState.GAME_STATE_BATTLE;
        }
        else if (GameState == eGameState.GAME_STATE_BATTLE)
        {
            Debug.Log("上か、下か、を決めます。");
            GameState = eGameState.GAME_STATE_RESULT;
        }
        else if (GameState == eGameState.GAME_STATE_RESULT)
        {
            Debug.Log("結果を比べます");
        }
    }
}

出目を保存する変数を宣言。

    public int PlayerDice = 0;  //プレイヤーの出目を保存する変数
    public int RivalDice = 0;   //相手の出目を保存する変数

出目をランダムに代入。

using System.Collections;
using UnityEngine;

// ゲームの管理クラス
public class GameManager : MonoBehaviour
{
    // メンバー変数を宣言
    public enum eGameState
    {
        GAME_STATE_NUMFIX_1,
        GAME_STATE_NUMFIX_2,
        GAME_STATE_BATTLE,
        GAME_STATE_RESULT,
    }

    public eGameState GameState;

    public int PlayerDice = 0;  //プレイヤーの出目を保存する変数
    public int RivalDice = 0;   //相手の出目を保存する変数

    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        // 状態①から開始
        GameState = eGameState.GAME_STATE_NUMFIX_1;
    }

    // Update is called once per frame
    void Update()
    {
        switch (GameState)
        {
            case eGameState.GAME_STATE_NUMFIX_1:
                Debug.Log("相手の出目を決めます。1~6");
                RivalDice = Random.Range(1, 6);             // 相手の出目にランダムな値(1~6)を代入
                GameState = eGameState.GAME_STATE_NUMFIX_2; // 状態②に変更

                break;

            case eGameState.GAME_STATE_NUMFIX_2:
                Debug.Log("自分の出目を決めます。1~6");
                PlayerDice = Random.Range(1, 6);            //自分の出目にランダムな値(1~6)を代入 
                GameState = eGameState.GAME_STATE_BATTLE;   //状態③に変更
                break;

            case eGameState.GAME_STATE_BATTLE:
                Debug.Log("上か、下か、を決めます。");
                GameState = eGameState.GAME_STATE_RESULT;   //状態④に変更
                break;

            case eGameState.GAME_STATE_RESULT:
                Debug.Log("結果を比べます");
                GameState = eGameState.GAME_STATE_NUMFIX_1; //状態①に戻る

                break;

            default:
                break;

        }
    }
}

今日はここまで。

コメント

タイトルとURLをコピーしました