+5

Events with ScritpableObjects

Thomas S. 8 months ago updated by Finntroll 7 months ago 3

Hi all, 

I've started with an own very small EventSystem for Bolt. It is based on ScriptableObjects and type-safe. So you don't have to deal with string literals and the type of the output is always known (if present). 

Because of the ScriptableObjects you don't have to register to a certain GameObject (like you'd do with CustomEvents). 

Let me show you how it works:

You easily can trigger them how you are used to within Unity:

or right within Bolt:

You can get argument by just reading out the Value:

And it is usable in StateMachines like this:

The whole system is based on three classes:

GameEvents itself

using System;
using UnityEngine;
using UnityEngine.Events;

namespace Game.EventSystem {
[CreateAssetMenu(menuName = MenuBase + "/GameEvent")]
public class GameEvent : ScriptableObject {
protected const string MenuBase = "Events";

[SerializeField] private string eventId = Guid.NewGuid().ToString();
[SerializeField] private UnityEvent onGameEvent;

public string EventId => eventId;

public UnityEvent OnGameEvent => onGameEvent;

public void Fire() {
OnGameEvent?.Invoke();
}

private void OnEnable() {
OnGameEvent?.RemoveAllListeners();
}
}

public abstract class GameEvent<T> : GameEvent {
public T Value { get; private set; }

public void Fire(T value) {
Value = value;
Fire();
}
}
}

And the Listener for Bolt:

using Bolt;
using Ludiq;
using UnityEngine.Events;

namespace Game.EventSystem {

[UnitCategory("Events")]
public class OnGameEvent : EventUnit<GameEvent> {

[DoNotSerialize]
[PortLabelHidden]
public ValueInput gameEvent;

protected override bool register => false;

protected override void Definition() {
base.Definition();

gameEvent = ValueInput<GameEvent>("gameEvent", default);
}

public override IGraphElementData CreateData() {
return new GameEventData();
}

public override void StartListening(GraphStack stack) {
base.StartListening(stack);
var reference = stack.ToReference();
var data = stack.GetElementData<GameEventData>(this);
var ge = GetGameEvent(reference);

if (ge == null) {
return;
}

data.unityAction = () => {
Trigger(reference, ge);
};

ge.OnGameEvent.AddListener(data.unityAction);
}

public override void StopListening(GraphStack stack) {
base.StopListening(stack);
var data = stack.GetElementData<GameEventData>(this);
var reference = stack.ToReference();
var ge = GetGameEvent(reference);

if (ge != null && data.unityAction != null) {
ge.OnGameEvent.RemoveListener(data.unityAction);
}
}

private GameEvent GetGameEvent(GraphReference stack) {
return Flow.FetchValue<GameEvent>(gameEvent, stack);
}

}

public class GameEventData : EventUnit<GameEvent>.Data {
public UnityAction unityAction;
}
}

To create own TypeSafe events (like my FishGameEvent) you just have to create an own class extending GameEvent<> like this:

using Game.EventSystem;
using UnityEngine;

namespace Game {

[CreateAssetMenu]
public class FishGameEvent : GameEvent<Fish> {

}
}

This whole concept is based on this article/talk: https://unity3d.com/how-to/architect-with-scriptable-objects

And I like the idea very much. So the first thing I did after buying Bolt yesterday was to adapt this to Bolt.

It might could need some tidy up (for sure it does). But it already works really nice in my playground-project :-). 

Bolt Version:
Unity Version:
Platform(s):
Scripting Backend:
.NET Version (API Compatibility Level):

This looks amazing. Gonna try it out tomorrow. Thank you for sharing. 

I've just found out that the Listener wasn't supporting coroutines. So I updated the source above. By changing the unityAction to the following:

data.unityAction = () => {
Trigger(reference, ge);
};

it works as expected. 

Also I renamed the Data-Class and updated the namespace. 

Wow, looks interesting.