Bolt 104 - イベント
Bolt 102
と Bolt 103
では、Bolt
の基本的な実行方法や、プロパティおよびゲームオブジェクトをネットワーク上に複製する方法について学びました。このセッションではBolt
での イベント を見ていき、イベントが何であるか、またイベントの使用方法について説明します。
まず、イベント
の新規作成をおこなう必要があります。これは、新しいステート
の作成と同様に行います。Bolt Assets
ウィンドウを開き、空白部分で右クリックをしてドロップダウンメニューから New Event
を選択してください。
イベントを作成すると、Bolt
はそれをBolt Editor
ウィンドウ内に開きます。まず始めに、ゲーム内の全員に誰かの参加と退出を知らせるためのシンプルなゲームログを作成してみましょう。
イベントの名前をLogEvent
に変更し、名前欄の隣にあるNew Property
をクリックしてください。新たなプロパティにMessage
と名前をつけて、タイプをString
に設定してください。'Encoding & Length'の設定は、UTF8
と64
にしてください。
先に進む前に、イベントアセット自体の2つの設定、Global Senders
とEntity Senders
について見てみましょう。
Bolt内のイベントの送信方法には、全体への送信と、特定のエンティティに向けての2通りがあります。
グローバルイベントはBolt.GlobalEventListener
から継承するクラス内で受信され、実際のBoltエンティティをターゲットとしなくても送信することができます。
エンティティイベントの受信は、イベントの送信先となる、Bolt.EntityEventListener
から継承する、エンティティ上のスクリプト上でのみ行われます。
Global Senders
とEntity Senders
に関するオプションでは、誰 がイベントを送信するかを管理できます。今回の場合、LogEvent
は、サーバーだけがその送信を行うのが望ましいグローバルイベントです。そのため、Global Senders
をOnly Server
に変更しましょう。また、Entity Event
として送信しないため、Entity Senders
をNone
に設定してください。
重要: 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)
{
}
}
Connected
とDisconnected
内のコードは、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)
というメソッドが確認できます。これを以下のように実装します。
- タイプ
List<string>
のクラスに対して新たな変数を追加し、logMessages
と名前をつけます。List<T>
クラスへのアクセスのためのusing System.Collections.Generic;
がファイルのトップにあることを確認してください。 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);
}
}
ゲームを起動して複数のクライアントを接続すると、以下のようになります:
エンティティイベント
がグローバルイベント
とどう異なるのかについて確認するために、もう1つイベントを実装してみましょう。
Bolt Assets
ウィンドウから新規にイベントを作成し、その名前をFlashColorEvent
と設定してください。Global Senders
はNone
に、Entity Senders
はOnly Owner
に設定してください。新たなプロパティを作成してその名前をFlashColor
とし、タイプはColor
に設定してください。
このイベントを用いるとゲーム内でダメージを受けた際に通知するよう、キューブを赤く点滅できます。
重要: イベントを作成した後には、Boltをコンパイルするのを忘れないでください。
Tutorial/Scripts
のCubeBehaviour
スクリプトを開き、ベースクラスを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
を押すと、そのインスタンスに制御されるキューブがすべての画面上ですぐに点滅します。
なぜ2種類のイベントがあるのか?
Boltにはなぜ2つイベントの種類があるのかというのは、多く寄せられる質問です。PhotonやUnityネットワーキングでのRPCのように1つでは駄目なのでしょうか。
グローバルイベントは、異なるパラメータによって Create
メソッドに渡されるため、信頼性が高い 場合と 信頼性が低い 場合があります。
一般にほとんどのグローバルイベントは 信頼性が高く 、変更を行わなければそれがデフォルトとなっています。
グローバルイベントは認証や、プレイヤーのインベントリーなどゲーム全般の事項に用いられます。
エンティティイベントは常に 信頼性が低く、ダメージの表示や爆発などといった、プレイヤーが見逃しても問題が無いような一時的な小さなエフェクトを対象としています。
次章 >>に続きます。
Back to top