Super Game Engine Pre-alpha Help

Events and Actions | C Plus Plus

Summary

Outlines how to use Events in Super Game Engine, the pitfalls and methods.

1. FEvent, FEventArguments and FEventObserver

Events are split into two parts with information sent between Subjects (Events) and Observers. FEvents are stored on Objects which wish to invoke (inform) the outside world about something. For instance an FEvent might be Location Changed, or Collided, or Destroyed. Observers are Objects which wish to react to these events. The FEventObserver knows about the events using FEventArguments, these are custom class objects you inspect for data when reacting.

2. The Setup

This is how to setup an Event in the system.

Let's say we have a Transform which would like to broadcast it has moved. The Collider would like to know when the object has moved, say to dirty a flag.

  1. The Transform will hold the Subject or FEvent

  2. The Collider is an Observer to this event or FEventObserver

  3. Arguments passed between them will give more arguments in the form of FVectorLocationEventArguments

class Transform { public: Transform(); virtual ~Transform(); FEventSubscriptions* OnLocationChanged(); void SetLocation(float x, float y); private: FEvent* m_onLocationChanged; FVector2D* m_location; }

The transform gives the ability to subscribe to the event and store data. FEventSubscriptions and FEventAction give the ability to only provide the functionality you wish to the outside classes.

Transform::Transform() { m_location = new FVector2D(); m_onLocationChanged = new FEvent(); } Transform::~Transform() { delete m_location; delete m_onLocationChanged; }

If you have only ever given out FEventSubscriptions it is safe to self-manage the creation and deletion of the event handler. This is because as soon as the event is no longer needed the observations are no longer needed - no harm no fowl.

FEventSubscriptions* Transform::OnLocationChanged() { return m_onLocationChanged; } void Transform::SetLocation(float x, float y) { if(x != m_location.GetX() || y != m_location.GetY()) { m_location->SetXY(x, y); FVectorLocationEventArguments* arguments = new FVectorLocationEventArguments(); arguments->X = m_location->GetX(); arguments->Y = m_location->GetY(); m_onValueChangedEvent->Invoke(arguments); delete arguments; } }

When the value has changed the event is called and called with the correct arguments. Any arguments which inherit from FEventArguments however if observers have multiple observations the type is what sets them apart.

class Collider2D : public FEventObserver { public: virtual void Invoke(FEventArguments* arguments) override; } void Collider2D::Invoke(FEventArguments* arguments) { if (TypeHelpers::IsDerivedFrom<FEventArguments, FVectorLocationEventArguments>()) { FVectorLocationEventArguments* locationArguments = dynamic_cast<FVectorLocationEventArguments*>(arguments); // Whatever logic would go here. } }

When invoking a change you must look at the arguments to understand it. The type helpers can help to narrow down if the class is what you expect. Dynamic casting for now is the method for this implementation and should give us the results we are looking for although will have a performance hit if observations are numerous.

3. How to connect a Subject and Observer?

The missing piece of the puzzle is connecting the two together. The majority of the time this is done by an outside force, a GameObject or something controlling the object, however it could be something an object has a reference to.

class Collider2D : public FEventObserver { public: Collider2D(Transform* transform); virtual ~Collider2D(); private: Transform* m_transform; } Collider2D::Collider2D(Transform* transform) { transform->OnLocationChanged()->Subscribe(this); m_transform = transform; } Collider2D::~Collider2D() { m_transform->OnLocationChanged()->Unsubscribe(this); }

If you subscribe to an event you should unsubscribe also.

There is an additional problem with this code, the pointer to Transform might point to nothing if Transform no longer exists. Consider a smart pointer or weak pointer in cases as the above to avoid such.

Last modified: 04 April 2024