【Unity】イベントでゲームの進行管理をする #3

投稿者: | 2020-12-10

イベントでゲームの進行管理をしてみました。

シーンに色違いの4つのCubeがあり、左からCube1~4と名前が付いています。

白と黄色のCubeはプレイヤーが掴んで動かせます。

赤と青のCubeはプレイヤーが触ると破壊されます。

プレイヤーはUpdate()でデリゲートを呼び、シフトキーでジャンプしたときは、進行管理クラスのジャンプのイベントを実行するメソッドを呼んでいます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;

public class Player : MonoBehaviour
{
    [SerializeField] Text centerText;
    [SerializeField] ProgressManager2 progressManager;
    Action action;

    Collider coll;

    // Start is called before the first frame update
    void Start()
    {
        action = Raycast;
    }

    // Update is called once per frame
    void Update()
    {
        if(action != null) action();

        // ジャンプ
        if(Input.GetKeyDown(KeyCode.Space))
        {
            progressManager.PlayerJump(this.gameObject,new EventArgs());
        }
    }

デリゲートでは、普段はレイキャストを飛ばして、左クリックした時にCubeのスクリプトを取得し、Cubeの共通のメソッドを実行しています。Cubeのクラスは同じインターフェースを継承しています。

    public void Raycast()
    {
        RaycastHit hitInfo;
        if (Physics.Raycast(transform.position, transform.forward, out hitInfo, 4f, ~(1 << 9)))
        {
            centerText.text = "[LMB]";

            if (Input.GetMouseButtonDown(0))
            {
                centerText.text = "";
                coll = hitInfo.collider;

                // スクリプトを取得
                var sc = hitInfo.collider.GetComponent();

                if (sc != null)
                {
                    sc.Touch();

                    // 掴めるCubeのとき
                    if (sc.GetType() == typeof(Item))
                    {
                        action = Hold;
                        progressManager.HoldItem(this.gameObject, new ItemEventArgs() { itemName = coll.name }); 
                    }
                    // 壊れやすいCubeのとき
                    else if (sc.GetType() == typeof(Fragile))
                    {
                        progressManager.DestroyItem(this.gameObject, new ItemEventArgs() { itemName = coll.name });

                    }
                }
            }
        }
        else
        {
            centerText.text = "";
        }
    }

掴めるCubeをクリックしたときは、デリゲートの中身を変えてレイを飛ばすのを止め、Cubeを持ち上げます。その最中に左クリックを離すとCubeを離して、レイを飛ばすメソッドに戻します。

    public void Hold()
    {
        coll.attachedRigidbody.velocity = (transform.forward * 2f + transform.position - coll.transform.position) * 10f;

        if (Input.GetMouseButtonUp(0))
        {
            action = Raycast;
            progressManager.ReleaseItem(this.gameObject, new ItemEventArgs() { itemName = coll.name });
        }
    }

}

プレイヤーがジャンプしたときだけでなく、アイテムを掴んだときや離したとき、破壊したときに、進行管理クラスのイベントが呼ばれるようにしています。

進行管理クラスではイベントの中身のメソッドを入れ替えています。

using UnityEngine;
using UnityEngine.UI;
using System;
using UnityEngine.SceneManagement;

public class ItemEventArgs : EventArgs
{
    public string itemName { get; set; }
}

public class ProgressManager2 : MonoBehaviour
{
    [SerializeField] Text text;
    float second;

    delegate void EventHandler(GameObject sender, ItemEventArgs e);
    delegate void PlayerJumpEventHandler(GameObject sender, EventArgs e);
    
    event EventHandler holdItem;
    event EventHandler releaseItem;
    event EventHandler destroyItem;
    event PlayerJumpEventHandler playerJump;

    public void HoldItem(GameObject sender , ItemEventArgs e)
    {
        if (holdItem != null) holdItem(sender,e);
    }

    public void ReleaseItem(GameObject sender, ItemEventArgs e)
    {
        if (releaseItem != null) releaseItem(sender, e);
    }

    public void DestroyItem(GameObject sender, ItemEventArgs e)
    {
        if(destroyItem != null) destroyItem(sender, e);
    }

    public void PlayerJump(GameObject sender, EventArgs e)
    {
        if (playerJump != null) playerJump(sender, e);
    }

    Action updateAction;

    // Start is called before the first frame update
    void Start()
    {
        holdItem = HoldItem1;
        text.text = "白い箱を掴む";
    }

    // Update is called once per frame
    void Update()
    {
        if (updateAction != null) updateAction();
    }

    void HoldItem1(GameObject sender, ItemEventArgs e)
    {
        if(e.itemName == "Cube1")
        {
            text.text = "箱を離す";
            holdItem = null;
            releaseItem = ReleaseItem1;
        }
    }

    void HoldItem2(GameObject sender, ItemEventArgs e)
    {
        if (e.itemName == "Cube2")
        {
            playerJump = PlayerJump1;
        }
    }

    void ReleaseItem1(GameObject sender, ItemEventArgs e)
    {
        if (e.itemName == "Cube1")
        {
            releaseItem = null;
            text.text = "2秒待つ";
            updateAction = UpdateAction1;
        }
    }

    void ReleaseItem2(GameObject sender, ItemEventArgs e)
    {
        playerJump = null;
    }

    void UpdateAction1()
    {
        second += Time.deltaTime;
        if (second >= 2f)
        {
            second = 0f;
            updateAction = null;
            text.text = "赤い箱を破壊する";
            destroyItem = DestroyItem1;
        }
    }

    void UpdateAction2()
    {
        second += Time.deltaTime;
        if (second >= 2f)
        {
            second = 0f;

            // 現在のシーンを読み込み
            SceneManager.LoadScene(SceneManager.GetActiveScene().name);
        }
    }

    void DestroyItem1(GameObject sender, ItemEventArgs e)
    {
        if (e.itemName == "Cube3")
        {
            text.text = "黄色い箱を掴んだままジャンプする";
            destroyItem = null;
            holdItem = HoldItem2;
            releaseItem = ReleaseItem2;
        }
    }

    void PlayerJump1(GameObject sender, EventArgs e)
    {
        text.text = "終了(2秒後にリロード)";
        releaseItem = null;
        playerJump = null;
        holdItem = null;
        updateAction = UpdateAction2;
    }
}

まずEventHandler型の変数を宣言しています。System.EventHandlerでは、第1引数にイベントを呼んだobject、第2引数にEventArgsクラスやそれを継承したクラスのオブジェクトを指定するそうですが、object でなく GameObject にするとうまくいかなかったので、同じ名前の型を宣言しました。

その他にUpdate()でデリゲートを呼んでいます。スタート時には、アイテムを掴むイベントに1つ目のメソッドを入れてUpdate()のデリゲートや他のイベントの中身はnullになっています。

Action updateAction;

// Start is called before the first frame update
void Start()
{
    holdItem = HoldItem1;
    text.text = "白い箱を掴む";
}

// Update is called once per frame
void Update()
{
    if (updateAction != null) updateAction();
}

プレイヤーがCube1を掴むと、「白い箱を掴む」という表示を「箱を離す」という表示にして、イベントの中身を変えます。

void HoldItem1(GameObject sender, ItemEventArgs e)
{
    if(e.itemName == "Cube1")
    {
        text.text = "箱を離す";
        holdItem = null; // 掴むイベントを空に
        releaseItem = ReleaseItem1; // 離すイベントに1つ目を入れる
    }
}

アイテムの名前を知るために、EventArgsクラスを継承したクラスを作って、プレイヤーがイベントを呼ぶ時に名前を入れました。

public class ItemEventArgs : EventArgs
{
    public string itemName { get; set; }
}

// ---
// Player
progressManager.HoldItem(this.gameObject, new ItemEventArgs() { itemName = coll.name }); 

箱を離すとUpdate()のデリゲートにメソッドが入って時間をカウントします。

void ReleaseItem1(GameObject sender, ItemEventArgs e)
{
    if (e.itemName == "Cube1")
    {
        releaseItem = null;
        text.text = "2秒待つ";
        updateAction = UpdateAction1;
    }
}

カウントが終わると赤い箱を破壊する表示に変わります。

void UpdateAction1()
{
    second += Time.deltaTime;
    if (second >= 2f)
    {
        second = 0f;
        updateAction = null;
        text.text = "赤い箱を破壊する";
        destroyItem = DestroyItem1;
    }
}

箱が破壊されると、箱を掴む/離すイベント両方にメソッドが入ります。

void DestroyItem1(GameObject sender, ItemEventArgs e)
{
    if (e.itemName == "Cube3")
    {
        text.text = "黄色い箱を掴んだままジャンプする";
        destroyItem = null;
        holdItem = HoldItem2;
        releaseItem = ReleaseItem2;
    }
}

掴む方では黄色い箱を掴んだ時にジャンプイベントが有効になります。

void HoldItem2(GameObject sender, ItemEventArgs e)
{
    if (e.itemName == "Cube2")
    {
        playerJump = PlayerJump1;
    }
}

離すとジャンプイベントが無効になります。

void ReleaseItem2(GameObject sender, ItemEventArgs e)
{
    playerJump = null;
}

そして、ジャンプイベントが実行されると、2秒後に今のシーンを再読み込みします。

void PlayerJump1(GameObject sender, EventArgs e)
{
    text.text = "終了(2秒後にリロード)";
    releaseItem = null;
    playerJump = null;
    holdItem = null;
    updateAction = UpdateAction2;
}

// --- 
void UpdateAction2()
{
    second += Time.deltaTime;
    if (second >= 2f)
    {
        second = 0f;

        // 現在のシーンを読み込み
        SceneManager.LoadScene(SceneManager.GetActiveScene().name);
    }
}

つまり、黄色い箱を掴んだままジャンプしないと進行しません。

同じ様に、白い箱を掴む時に黄色い箱を掴んだり、赤い箱でなく青い箱を破壊しても進まないようにできました。

コメントを残す

メールアドレスが公開されることはありません。