農工大アドカレ6日目、当日生成記事でございます!
内容は薄め、C#初心者向けだよ!
イベントとは
C# でイベントはこんな感じのやつだよ!
using System;
public class Example
{
public event Action OnEventTriggered;
public void TriggerEvent()
{
OnEventTriggered?.Invoke();
}
}
OnEventTriggered に処理を登録しておくと、Invoke() でイベント発火されたときに処理を実行できる!あとでもうちょっとちゃんと説明するよ!
イベントを使うと何が嬉しいのか
一言でいうと、コードが綺麗になるよ!
イベントを使わない場合
ゲームの中で状態を遷移させたいことがあるよね?例えば、待機状態からプレイ状態に変えたいとするよ。これに伴ってメニューのUIも変化するとしよう。こんな風にステートが定義されている。
public enum GameState
{
Waiting,
Playing,
}
GameStateManager クラスからステートを変えられるようにしよう。
using System;
public class GameStateManager
{
private MenuPresenter menuPresenter;
public void ChangeGameState(GameState gameState)
{
switch (gameState)
{
case GameState.Waiting:
break;
case GameState.Playing:
// プレイ状態になるときの処理
menu.toPlaying();
break;
}
}
}
一見問題なさそうだけど、GameStateManager が MenuPresenter のメソッドを直接呼び出しているよね?これはよくないよ!なぜなら、
- メニュー以外にも UI がある場合、
GameStateManagerはどんどん肥大化していく - このままだと、
MenuPresenterが変わったらGameStateManagerも変えないといけない - これは 「
GameStateManagerがMenuPresenterに依存している状態」だよ
イベントを使う場合
まずは GameStateManager に OnGameStateChanged というイベントを追加するよ。ステートを変更するときはこのイベントを発火するだけ!
using System;
public class GameStateManager
{
public event Action<GameState> OnGameStateChanged;
public void ChangeGameState(GameState gameState)
{
OnGameStateChanged?.Invoke(gameState);
}
}
MenuPresenter はこのイベントをサブスクライブするよ!上の OnGameStateChanged.Invoke(gameState) が呼ばれると、
OnGameStateChanged に登録されているメソッドが呼ばれるよ!+= で登録、-= で解除するよ!-= を忘れるとメモリリークする可能性があるから気をつけてね!
using System;
public class MenuPresenter: MonoBehaviour
{
private GameStateManager gameStateManager; // DI などで注入すると良い(今回は省略)
private void Start()
{
// シーン開始時にイベントに登録
gameStateManager.OnGameStateChanged += OnGameStateChanged;
}
private void OnDestroy()
{
// シーン終了時にイベントから解除
gameStateManager.OnGameStateChanged -= OnGameStateChanged;
}
public void toPlaying()
{
// プレイ状態になるときのUI処理。
}
private void OnGameStateChanged(GameState gameState)
{
switch (gameState)
{
case GameState.Waiting:
break;
case GameState.Playing:
toPlaying();
break;
}
}
}
これで、GameStateManager は MenuPresenter に依存していないよ!だから、
-
MenuPresenterが変わっても、GameStateManagerは変更しなくていい - メニュー以外の UI があっても、
GameStateManagerは変更しなくていいし、肥大化しない
ちなみに、IGameStateManager というインターフェースを作ってあげると、MenuPresenter と GameStateManager の依存関係をさらに緩和できるよ!
using System;
public interface IGameStateManager
{
event Action<GameState> OnGameStateChanged { get; }
}
R3 を用いる
R3はC#でリアクティブプログラミングを行うためのライブラリだよ!R3を使うと、イベントを使ったコードをさらに綺麗に書くことができるよ!
上記の例を R3 を使って書くとこんな風に書けるよ!
using System;
using R3;
public class MenuPresenter: MonoBehaviour
{
private GameStateManager gameStateManager; // DI などで注入すると良い(今回は省略)
private void Start()
{
Observable.FromEvent<GameState>(h => gameStateManager.OnGameStateChanged += h, h => gameStateManager.OnGameStateChanged -= h)
.Where(gameState => gameState == GameState.Playing)
.Subscribe(_ => toPlaying());
.AddTo(this); // このコンポーネントが破棄されたら購読を解除する
}
public void toPlaying()
{
// プレイ状態になるときのUI処理。
}
}
なんかスッキリしたね!Where でフィルタリング、Subscribe で購読、AddTo で購読解除を自動化しているよ!OnDestroy での購読解除を忘れる心配もなくなった!
R3は普通のイベントだけでなく、Unity の Update やインターバルなど色々なものをリアクティブに扱うことができるよ!ぜひ使ってみてね!
まとめ
自信ないので何か間違ったことを言っていたら教えてください!