andoowhy / EgoCS

EgoCS: An Entity (GameObject) Component System framework for Unity3D
MIT License
225 stars 31 forks source link

Event interface #11

Closed rams3s closed 7 years ago

rams3s commented 7 years ago

This PR changes the base event abstract class (EgoEvent) to an interface (IEgoEvent).

Doing so allows custom event types to inherit from other classes such as Unity3D's ScriptableObject, allowing them to be created and used efficiently in the editor.

This PR also allows, at a small performance cost, to call EgoEvents.AddEvent(IEgoEvent e) and still have the event dispatched to the proper queue.

Example use case:

// ScriptableEgoEvent.cs

public class ScriptableEgoEvent : UnityEngine.ScriptableObject, IEgoEvent {}
// CustomEvent1.cs

using UnityEngine;

[CreateAssetMenu( fileName = "CustomEvent1", menuName = "EgoEvents/CustomEvent1" )]
public class CustomEven1t : ScriptableEgoEvent
{
    public string param;
}
// CustomEvent2.cs

using UnityEngine;

[CreateAssetMenu( fileName = "CustomEvent2", menuName = "EgoEvents/CustomEvent2" )]
public class CustomEven1t : ScriptableEgoEvent
{
    public int param;
}
// SomeComponent.cs

using UnityEngine;

[DisallowMultipleComponent]
public class SomeComponent: MonoBehaviour
{
    public ScriptableEgoEvent egoEvent;
}
// SomeSystem.cs
    ...
    public override void Start()
    {
        EgoEvents<CustomEvent2>.AddHandler( Handle );
    }
    ...

Events created using those menus can be dragged and dropped to components' public fields of type ScriptableEgoEvent and dispatched later on using EgoEvents.AddEvent( component.egoEvent ) to the proper queues.

andoowhy commented 7 years ago

Congrats! You've submitted the first pull request for EgoCS! However, I do not think I will accept it:

EgoEvents are essentially bundles of runtime-only data and references: These objects collided, this player won and was going this fast, etc. They don't own anything themselves and is why I recommend using a suitable OO-style constructor when creating them. Following this, I don't see how an EgoEvent could or even should be setup in the editor / inspector.

You could use data in whatever ScriptableObjects to determine what gets passed into an EgoEvent's constructor, or pass in the entire ScriptableObject instance, but these should solely be done at runtime.

EgoEvents definitely should never be members of another class, especially a member of a Component or MonoBehaviour. Systems construct EgoEvents exactly when and where they happen, with whatever data they require.

rams3s commented 7 years ago

Thank you for your feedback.

It seems that the example use case I've provided has masked the real content of the PR:

  1. use an interface instead of abstract base class for EgoEvent
  2. allow dispatching of events without specifying the type of the event in the call (EgoEvents.AddEvent(IEgoEvent e) vs EgoEvents<MyEvent>.AddEvent(MyEvent e))

Nothing more than that... and I could/should have split the 2 features into 2 separate PRs as they are orthogonal.

The example I've provided is based on a problem I encountered in a toy project I'm using to evaluate EgoCS but remains only one example of what these changes allow. Here is a small summary of what I was trying to achieve.

I have implemented a waypoint system (using DOTween internally) based on a waypoint follower component. This component holds a list of waypoints (transform + extra parameters for timing, ease function, ...). I want a designer (not a coder) to be able to optionally attach events to each waypoint to be emitted whenever it is reached by the entity (e.g. emit sound event, shoot bullet event, ...). I could of course go with a factory to create those events from user parameters but using a scriptable object asset (or instantiating a copy) seemed to make sense to me and avoids having to write specific editor code to adapt the GUI depending on the chosen event type.

Nevertheless, what is more important to me is that I would like to be able to dispatch the created event easily, using a base class/interface reference (EgoEvents.AddEvent(IEgoEvent e) vs EgoEvents<MyEvent>.AddEvent(MyEvent e)).

What would be the recommended way of handling this in EgoCS?

EDIT: After sleeping on it, I think I understand a bit better your concern. EgoEvents should not be used to pass arbitrary messages between systems?

If so, I guess another way of handling my problem could then be based on enabling components dynamically when waypoints are reached...

andoowhy commented 7 years ago

EgoEvents should not be used to pass arbitrary messages between systems?

They pass messages and data, but not "arbitrary" or "generic" data. An EgoEvent should be very specific and EgoEvent types should be discrete and explicit.

If so, I guess another way of handling my problem could then be based on enabling components dynamically when waypoints are reached...

Yes, that's what I was going to suggest. Upon reaching the waypoint, you could destroy the "Waypoint follower" Component, and attach different Component(s) (that correspond to appropriate Systems) to get the new behaviour you want.

I do agree that EgoEvents<MyEvent>.AddEvent(new MyEvent())) has some redundant type info. I'll look into it this weekend.