Bolt 104 - イベント

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

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

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

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

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

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

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

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

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

Configure Event Senders
Event Sendersの設定

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

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

using UnityEngine;
using System.Collections;

[BoltGlobalBehaviour(BoltNetworkModes.Server)]
public class ServerCallbacks : Bolt.GlobalEventListener
{

}

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

using UnityEngine;
using System.Collections;

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

    }

    public override void Disconnected(BoltConnection connection)
    {

    }
}

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

using UnityEngine;
using System.Collections;

[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リストに追加してください:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

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

    public override void SceneLoadLocalDone(string map)
    {
        // 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 メソッドを使用します。

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 スクリプトの全体は以下のようになります。

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

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

    public override void SceneLoadLocalDone(string map)
    {
        // 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);
    }

    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();
    }
}

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

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>に変更します。以下のようになります:

using UnityEngine;
using System.Collections;

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

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

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

// ...
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> を繰り返し呼び出す必要がなくなります。

// ...

float resetColorTime;
Renderer renderer;

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

    // ...
}

// ...

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

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

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

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

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

using UnityEngine;
using System.Collections;

public class CubeBehaviour : Bolt.EntityEventListener<ICubeState>
{
    float resetColorTime;
    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);
    }

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

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

    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();
        }
    }

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

    public override void OnEvent(FlashColorEvent evnt)
    {
        resetColorTime = Time.time + 0.25f;
        renderer.material.color = evnt.FlashColor;
    }
}

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

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

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

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

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

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

次章 >>に続きます。

ドキュメントのトップへ戻る