PUN Classic (v1)、PUN 2、Boltはメンテナンスモードとなっております。Unity2022についてはPUN 2でサポートいたしますが、新機能が追加されることはありません。お客様のPUNプロジェクトおよびBoltプロジェクトが停止することはなく、将来にわたってパフォーマンス性能が落ちることはありません。 今後の新しいプロジェクトについては、Photon FusionまたはQuantumへ切り替えていただくようよろしくお願いいたします。

Bolt 104 - イベント

Bolt 102Bolt 103では、Boltの基本的な実行方法や、プロパティおよびゲームオブジェクトをネットワーク上に複製する方法について学びました。このセッションではBoltでの イベント を見ていき、イベントが何であるか、またイベントの使用方法について説明します。

まず、イベントの新規作成をおこなう必要があります。これは、新しいステートの作成と同様に行います。Bolt Assetsウィンドウを開き、空白部分で右クリックをしてドロップダウンメニューから New Eventを選択してください。

Create new Event
新しいイベントの作成

イベントを作成すると、BoltはそれをBolt Editorウィンドウ内に開きます。まず始めに、ゲーム内の全員に誰かの参加と退出を知らせるためのシンプルなゲームログを作成してみましょう。

イベントの名前をLogEventに変更し、名前欄の隣にあるNew Propertyをクリックしてください。新たなプロパティにMessageと名前をつけて、タイプをStringに設定してください。'Encoding & Length'の設定は、UTF864にしてください。

Configure the new Event
新しいイベントの設定

先に進む前に、イベントアセット自体の2つの設定、Global SendersEntity Sendersについて見てみましょう。
Bolt内のイベントの送信方法には、全体への送信と、特定のエンティティに向けての2通りがあります。

グローバルイベントはBolt.GlobalEventListenerから継承するクラス内で受信され、実際のBoltエンティティをターゲットとしなくても送信することができます。
エンティティイベントの受信は、イベントの送信先となる、Bolt.EntityEventListenerから継承する、エンティティ上のスクリプト上でのみ行われます。

Global SendersEntity Sendersに関するオプションでは、 がイベントを送信するかを管理できます。今回の場合、LogEventは、サーバーだけがその送信を行うのが望ましいグローバルイベントです。そのため、Global SendersOnly Serverに変更しましょう。また、Entity Eventとして送信しないため、Entity SendersNoneに設定してください。

Configure Event Senders
Event Sendersの設定

重要: Boltにイベントを認識させるためには、再度コンパイルする必要があります。Bolt Assetsウィンドウにある緑の'矢印'ボタンか、Bolt/Compile Assemblyメニューから行ってください。

Boltのコンパイルが完了したら、新たなC#スクリプトをTutorial/Scripts内に作成し、ServerCallbacksとします。
これをBolt.GlobalEventListenerから継承させ、[BoltGlobalBehaviour(BoltNetworkModes.Server)]属性を付与します。これにより、Boltはサーバー上のこのクラスのインスタンスのみを作成および実行するようになります。グローバルコールバックについての詳細は、詳細な解説 - グローバルコールバックを参照してください。

C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Bolt;

[BoltGlobalBehaviour(BoltNetworkModes.Server)]
public class ServerCallbacks : Bolt.GlobalEventListener
{
    // Your code here...
}

Connected および Disconnectedコールバックの実装もおこないます。

C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Bolt;

[BoltGlobalBehaviour(BoltNetworkModes.Server)]
public class ServerCallbacks : Bolt.GlobalEventListener
{
    public override void Connected(BoltConnection connection)
    {

    }

    public override void Disconnected(BoltConnection connection)
    {

    }
}

ConnectedDisconnected内のコードは、LogEvent上のMessageプロパティ内に送る文字列以外は同一です。

C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Bolt;

[BoltGlobalBehaviour(BoltNetworkModes.Server)]
public class ServerCallbacks : Bolt.GlobalEventListener
{
    public override void Connected(BoltConnection connection)
    {
        var log = LogEvent.Create();
        log.Message = string.Format("{0} connected", connection.RemoteEndPoint);
        log.Send();
    }

    public override void Disconnected(BoltConnection connection)
    {
        var log = LogEvent.Create();
        log.Message = string.Format("{0} disconnected", connection.RemoteEndPoint);
        log.Send();
    }
}

Boltでは、EventName.Create();を用いて新たなイベントを作成します。その後、必要なプロパティを割り当て、eventObject.Send();を呼び出し、それを送信します。Createメソッドには異なるパラメータをともなう複数のオーバーロードがあり、イベントが誰に送信されるのか、またどのように届けられるのかを指定することができます。

最後に、イベントのリッスンを行います。前章からのNetworkCallbacksスクリプトを開きます。そこに、void OnEvent(LogEvent evnt)というメソッドが確認できます。これを以下のように実装します。

  1. タイプList<string>のクラスに対して新たな変数を追加し、logMessagesと名前をつけます。List<T>クラスへのアクセスのためのusing System.Collections.Generic;がファイルのトップにあることを確認してください。
  2. OnEvent内で、evnt.Messageプロパティの値をlogMessagesリストに追加してください:

C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Bolt;

[BoltGlobalBehaviour]
public class NetworkCallbacks : GlobalEventListener
{
    List<string> logMessages = new List<string>();

    public override void SceneLoadLocalDone(string scene)
    {
        // randomize a position
        var spawnPosition = new Vector3(Random.Range(-8, 8), 0, Random.Range(-8, 8));

        // instantiate cube
        BoltNetwork.Instantiate(BoltPrefabs.Cube, spawnPosition, Quaternion.identity);
    }

    public override void OnEvent(LogEvent evnt)
    {
        logMessages.Insert(0, evnt.Message);
    }
}

最後に、ログを表示する必要があります。一般的なUnityのOnGUIメソッドを使用して表示します。

C#

void OnGUI()
{
    // only display max the 5 latest log messages
    int maxMessages = Mathf.Min(5, logMessages.Count);

    GUILayout.BeginArea(new Rect(Screen.width / 2 - 200, Screen.height - 100, 400, 100), GUI.skin.box);

    for (int i = 0; i < maxMessages; ++i)
    {
        GUILayout.Label(logMessages[i]);
    }

    GUILayout.EndArea();
}

400x100ピクセルの、画面中央下部にあるボックスをレンダリングします。そして、ログに届いた最大5つの最新のメッセージを印刷します。NetworkCallbacksスクリプトの全体は以下のようになります。

C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Bolt;

[BoltGlobalBehaviour]
public class NetworkCallbacks : GlobalEventListener
{
    List<string> logMessages = new List<string>();

    void OnGUI()
    {
        // only display max the 5 latest log messages
        int maxMessages = Mathf.Min(5, logMessages.Count);

        GUILayout.BeginArea(new Rect(Screen.width / 2 - 200, Screen.height - 100, 400, 100), GUI.skin.box);

        for (int i = 0; i < maxMessages; ++i)
        {
            GUILayout.Label(logMessages[i]);
        }

        GUILayout.EndArea();
    }

    public override void SceneLoadLocalDone(string scene)
    {
        // randomize a position
        var spawnPosition = new Vector3(Random.Range(-8, 8), 0, Random.Range(-8, 8));

        // instantiate cube
        BoltNetwork.Instantiate(BoltPrefabs.Cube, spawnPosition, Quaternion.identity);
    }

    public override void OnEvent(LogEvent evnt)
    {
        logMessages.Insert(0, evnt.Message);
    }
}

ゲームを起動して複数のクライアントを接続すると、以下のようになります:

Event logs on Gameplay
ゲームプレイ上のイベントログ

エンティティイベントグローバルイベントとどう異なるのかについて確認するために、もう1つイベントを実装してみましょう。
Bolt Assetsウィンドウから新規にイベントを作成し、その名前をFlashColorEventと設定してください。Global SendersNoneに、Entity SendersOnly Ownerに設定してください。新たなプロパティを作成してその名前をFlashColorとし、タイプはColorに設定してください。

Create and Configure the FlashColorEvent
FlashColorEventの作成と設定

このイベントを用いるとゲーム内でダメージを受けた際に通知するよう、キューブを赤く点滅できます。

重要: イベントを作成した後には、Boltをコンパイルするのを忘れないでください。

Tutorial/ScriptsCubeBehaviourスクリプトを開き、ベースクラスをBolt.EntityBehaviour<ICubeState>からBolt.EntityEventListener<ICubeState>に変更します。以下のようになります:

C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Bolt;

public class CubeBehaviour : Bolt.EntityEventListener<ICubeState>
{
  // ...
}

Bolt.EntityEventListener<T>クラスは、実際にはBolt.EntityBehaviour<T>から継承しています。そのため、Bolt.EntityBehaviour<T>と同じメソッドをそのまま用いることが可能です。

SimulateOwnerメソッドの終了時には、複数の行が追加されます。これにより、Fキーを押すとFlashColorEventが送信されます。

C#

// ...
if (Input.GetKeyDown(KeyCode.F))
{
    var flash = FlashColorEvent.Create(entity);
    flash.FlashColor = Color.red;
    flash.Send();
}
// ...

エンティティイベントの送信方法はグローバルイベントの場合とほぼ同一です。唯一の違いは、イベントを送信するエンティティを Create メソッドに渡すという点です。

CubeBehaviour クラスにはもう少し追加しなければならないものがあります。まず、FlashColorEventによる色の点滅をいつ停止するかトラッキングする必要があります。そのため、resetColorTimeという名前の変数を追加します。
またrendererという変数を追加し、この変数を使用してGameObjectのレンダラーのAttachedメソッド内のコンポーネントに対するリファレンスを保存します。これによって、GetComponent<Renderer>を繰り返し呼び出す必要がなくなります。

C#

// ...

private float _resetColorTime;
private Renderer _renderer;

public override void Attached()
{
    _renderer = GetComponent<Renderer>();

    // ...
}

// ...

新たなFlashColorEventと共にBoltをコンパイルすると、Bolt.EntityEventListener<T>クラス上に新たなメソッドが作成されます。これはOnEvent(FlashColorEvent evnt)と呼ばれ、これをCubeBehaviour内に実装します。

C#

public override void OnEvent(FlashColorEvent evnt)
{
    _resetColorTime = Time.time + 0.2f;
    _renderer.material.color = evnt.FlashColor;
}

ここで、点滅のリセット時間を今後は0.2秒に設定し、マテリアルの色をイベント内で受信した色に変更します。 最後に標準的なUnityのUpdateメソッドを実装し、時間が経過したら色がリセットされるようにします。

C#

void Update()
{
    if (_resetColorTime < Time.time)
    {
        _renderer.material.color = state.CubeColor;
    }
}

CubeBehaviourスクリプトの全体は以下のようになります。

C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Bolt;

public class CubeBehaviour : Bolt.EntityEventListener<ICubeState>
{
    private float _resetColorTime;
    private Renderer _renderer;

    public override void Attached()
    {
        _renderer = GetComponent<Renderer>();

        state.SetTransforms(state.CubeTransform, transform);

        if (entity.IsOwner)
        {
            state.CubeColor = new Color(Random.value, Random.value, Random.value);
        }

        state.AddCallback("CubeColor", ColorChanged);
    }

    public override void SimulateOwner()
    {
        var speed = 4f;
        var movement = Vector3.zero;

        if (Input.GetKey(KeyCode.W)) { movement.z += 1; }
        if (Input.GetKey(KeyCode.S)) { movement.z -= 1; }
        if (Input.GetKey(KeyCode.A)) { movement.x -= 1; }
        if (Input.GetKey(KeyCode.D)) { movement.x += 1; }

        if (movement != Vector3.zero)
        {
            transform.position = transform.position + (movement.normalized * speed * BoltNetwork.FrameDeltaTime);
        }

        if (Input.GetKeyDown(KeyCode.F))
        {
            var flash = FlashColorEvent.Create(entity);
            flash.FlashColor = Color.red;
            flash.Send();
        }
    }

    public override void OnEvent(FlashColorEvent evnt)
    {
        _resetColorTime = Time.time + 0.2f;
        _renderer.material.color = evnt.FlashColor;
    }

    void Update()
    {
        if (_resetColorTime < Time.time)
        {
            _renderer.material.color = state.CubeColor;
        }
    }

    void OnGUI()
    {
        if (entity.IsOwner)
        {
            GUI.color = state.CubeColor;
            GUILayout.Label("@@@");
            GUI.color = Color.white;
        }
    }

    void ColorChanged()
    {
        GetComponent<Renderer>().material.color = state.CubeColor;
    }
}

ゲームのプレイ中に任意のインスタンス上のFを押すと、そのインスタンスに制御されるキューブがすべての画面上ですぐに点滅します。

FlashColor Event on Gameplay
ゲームプレイ上のFlashColor Event

なぜ2種類のイベントがあるのか?

Boltにはなぜ2つイベントの種類があるのかというのは、多く寄せられる質問です。PhotonやUnityネットワーキングでのRPCのように1つでは駄目なのでしょうか。

グローバルイベントは、異なるパラメータによって Create メソッドに渡されるため、信頼性が高い 場合と 信頼性が低い 場合があります。
一般にほとんどのグローバルイベントは 信頼性が高く 、変更を行わなければそれがデフォルトとなっています。
グローバルイベントは認証や、プレイヤーのインベントリーなどゲーム全般の事項に用いられます。

エンティティイベントは常に 信頼性が低く、ダメージの表示や爆発などといった、プレイヤーが見逃しても問題が無いような一時的な小さなエフェクトを対象としています。

次章 >>に続きます。

Back to top