This tutorial covers custom events in Zilch Engine.
Learning Objectives
- Custom events
- The sends keyword
- Event dispatching
- Custom event data
While the most common events are built in, there are times when you will need to create your own events based on things that happen within your own game logic. They are especially useful if multiple components connect to the same event, causing multiple unique responses. This tutorial continues with the project started in the previous Events tutorial.
Level Setup
- Reopen the Events zero project project from the previous tutorial
Declaring Custom Events
Just as instances of the class CollisionEvent are dispatched with multiple event IDs (CollisionStarted, CollisionPersisted, CollisionEnded), you may register an event class as multiple custom events. Data may be sent along with a custom event (using a custom event class, discussed below), but this isn't always necessary; if no data is needed, the event can just use the type NadaEvent.
The sends
Keyword
- Command : Add Resource
- Create a NadaScript resource using the Component template template and name it
CreateObjectOnInput
- Update the
CreateObjectOnInput
script to the following:
class CreateObjectOnInput : NadaComponent
{
sends CreateObject : NadaEvent;
[Property]
var CreateObjectKey : Keys = Keys.Space;
var CooldownTimer : Real = 0;
var CooldownDuration : Real = 0.25;
function Initialize(init : CogInitializer)
{
Zilch.Connect(this.Space, Events.LogicUpdate, this.OnLogicUpdate);
}
function OnLogicUpdate(event : UpdateEvent)
{
this.CooldownTimer -= event.Dt;
if (this.CooldownTimer <= 0)
{
this.CooldownTimer = 0;
if (Zilch.Keyboard.KeyIsDown(this.CreateObjectKey))
{
this.Owner.DispatchEvent(Events.CreateObject, NadaEvent());
this.CooldownTimer = this.CooldownDuration;
}
}
}
}
- Select : Player object
- In the
Properties Window
- Add Component :
CreateObjectOnInput
- Add Component :
The first part of this script that should be new is the line with the sends
keyword. If you recall from the previous events tutorial, the second parameter in the Zilch.Connect function is a String ID that identifies the event. The sends
keyword defines this ID, and also declares the event type the custom event will use:
sends EventId : EventType;
...where EventId
is a string value that is added to the Events namespace, where it can be found under the same name, and EventType
is the event class type that is registered with the new event ID. In our case, Events.CreateObject
equals the string "CreateObject"
, which is registered with the event type NadaEvent.
(NOTE) Why Use sends
? It's not strictly necessary to register custom events with sends
. So why bother? There are two main benefits. The first is that they will show up in code completion, saving you from time-consuming typos. The second is that it allows you to use event connection auto-complete functionality just like any other event. That is, if you type Zilch.Connect(this.Owner, Events.MyEvent,
in a function and press the Tab key following the comma after the second argument, the event connection code will complete itself, and a callback function will automatically be generated directly beneath the function scope the connection is made in.
Dispatching Custom Events
The next relevant line is where the CreateObject
event is dispatched:
this.Owner.DispatchEvent(Events.CreateObject, NadaEvent());
Let's look at the DispatchEvent function's parameters.
Sample Value | Parameter Type | Description |
---|---|---|
Events.CreateObject |
string | The event identifier |
NadaEvent() |
NadaEvent | The event to be dispatched |
This function dispatches an event to this.Owner
. Dispatching events to this.Owner
is a common way for one component to communicate with other components on the same object. In this case, we're dispatching a NadaEvent with the ID Events.CreateObject
.
Before creating the component that will connect to this event, we need to create a projectile archetype that can be spawned as long as the key to create an object is down.
- Command : CreateSprite
- In the
Properties Window
- Rename Sprite object to
Projectile
- Set Archetype to
Projectile
- Under transform
- Set Scale to
[0.35, 0.35, 0.35]
- Set Scale to
- Under Sprite
- Set VertexColor to
[R:0, G:255, B:0, A:1.00]
- Set SpriteSource enum to
Circle
- Add Component : SphereCollider
- Add Component : RigidBody
- Upload to Archetype
The completed Projectile object
Now we can create the component that will spawn and set the velocity of the object from the player when it "hears" the SpawnObject event.
Responding to Custom Events
- Command : Add Resource
- Create a NadaScript resource using the Component template template and name it
SpawnObjectWithVelocity
- Update the
SpawnObjectWithVelocity
script to the following:
class SpawnObjectWithVelocity : NadaComponent
{
[Dependency] var Transform : Transform;
[Property]
var ArchetypeToSpawn : Archetype = Archetype.Projectile;
[Property]
var Direction : Real3 = Real3.XAxis;
[Property]
var Speed : Real = 25;
function Initialize(init : CogInitializer)
{
Zilch.Connect(this.Owner, Events.CreateObject, this.OnCreateObject);
}
function OnCreateObject(event : NadaEvent)
{
var obj = this.Space.CreateAtPosition(this.ArchetypeToSpawn, this.Transform.Translation);
obj.RigidBody.Velocity = this.Direction * this.Speed;
}
}
- Select : Player object
- In the
Properties Window
- Add Component :
SpawnObjectWithVelocity
- Add Component :
In the Initialize function, we connect to the CreateObject
event just like any of the built-in events we've connected to previously.
We also need to add our DestroyOnCollide
component to the enemy so that the projectiles will destroy it.
- Select : Enemy object
- In the
Properties Window
- Add Component :
DestroyOnCollide
- Command : PlayGame
- Hold key the
Space
bar to fire
Well, that didn't behave how we wanted it to. Here's what happened: first, the projectile was spawned at the player's position. Then, since both player and projectile have colliders, they collided. In response to the CollisionStarted event, the player's DestroyOnCollide
component caused the player to destroy itself. To fix this, we can make a simple change to our DestroyOnCollide component that will make use of Archetype checking within the OnCollisionStarted callback function. (We'll also make it so that the projectile is destroyed in the process.)
- Update the
DestroyOnCollide
script to the following:
class DestroyOnCollide : NadaComponent
{
[Property]
var CollisionArchetype : Archetype = Archetype.Projectile;
function Initialize(init : CogInitializer)
{
Zilch.Connect(this.Owner, Events.CollisionStarted, this.OnCollisionStarted);
}
function OnCollisionStarted(event : CollisionEvent)
{
if(event.OtherObject.Archetype == this.CollisionArchetype)
{
this.Owner.Destroy();
event.OtherObject.Destroy();
}
}
}
Now we can check to see what we're colliding with, and whether it's the correct object. If it is, we destroy not only the object with this component, but also the one it collided with.
Select : Enemy object
In the
Properties Window
Set Archetype to
Enemy
Select : Player object
In the
Properties Window
Under
DestroyOnCollide
Set CollisionArchetype enum to
Enemy
Hold key the
Space
bar to fire
Now we can move on to defining your own custom event class, which will allow you to specify and set the data to be sent with the event.
Custom Event Classes
Sometimes it's necessary to create your own custom event class that contains data relevant to the situation in which it's dispatched. To demonstrate this, we'll create an event class that contains a data member that determines whether the created object should move fast or slow.
To start, we need to define our own event class that inherits from NadaEvent.
- Add the following to the top of the
CreateObjectOnInput
script:
class ObjectCreationEvent : NadaEvent
{
sends CreateObject : ObjectCreationEvent;
var ObjectShouldBeFast : Boolean;
}
Note that this custom event class uses the sends
keyword, registering CreateObject
as the event name for a custom event. Previously, this appeared in the CreateObjectOnInput
class, where it registered that same event ID with the NadaEvent class. (Now that we're changing CreateObject
to use this new ObjectCreationEvent
class, we should remove the sends
declaration from CreateObjectOnInput
.) Now let's update the CreateObjectOnInput
component to use the new event type.
- In the
CreateObjectOnInput
script - Update the
CreateObjectOnInput
class to the following:
class CreateObjectOnInput : NadaComponent
{
[Property]
var CreateObjectKey : Keys = Keys.Space;
[Property]
var FastObjectKey : Keys = Keys.Shift;
var CooldownTimer : Real = 0;
var CooldownDuration : Real = 0.25;
function Initialize(init : CogInitializer)
{
Zilch.Connect(this.Space, Events.LogicUpdate, this.OnLogicUpdate);
}
function OnLogicUpdate(event : UpdateEvent)
{
this.CooldownTimer -= event.Dt;
if (this.CooldownTimer <= 0)
{
this.CooldownTimer = 0;
if (Zilch.Keyboard.KeyIsDown(this.CreateObjectKey))
{
var objectCreationEvent = ObjectCreationEvent();
objectCreationEvent.ObjectShouldBeFast = Zilch.Keyboard.KeyIsDown(this.FastObjectKey);
this.Owner.DispatchEvent(Events.CreateObject, objectCreationEvent);
this.CooldownTimer = this.CooldownDuration;
}
}
}
}
We've removed the sends
declaration, since we've moved it to the ObjectCreationEvent
class. Also, instead of calling DispatchEvent with an unnamed instance of NadaEvent created right on the spot inside the function call, we create objectCreationEvent
, an instance of our new event class. We set ObjectShouldBeFast
to equal Zilch.Keyboard.KeyIsDown(this.FastObjectKey)
. The result is that if the FastObjectKey
is down, the object will be fast.
Now that we have our new event and have dispatched it, we'll need to update the component that is listening for the event.
- Update the
SpawnObjectWithVelocity
script to the following:
class SpawnObjectWithVelocity : NadaComponent
{
[Dependency] var Transform : Transform;
[Property]
var ArchetypeToSpawn : Archetype = Archetype.Projectile;
[Property]
var Direction : Real3 = Real3.XAxis;
[Property]
var SlowSpeed : Real = 10;
[Property]
var FastSpeed : Real = 25;
function Initialize(init : CogInitializer)
{
Zilch.Connect(this.Owner, Events.CreateObject, this.OnCreateObject);
}
function OnCreateObject(event : ObjectCreationEvent)
{
var obj = this.Space.CreateAtPosition(this.ArchetypeToSpawn, this.Transform.Translation);
if (event.ObjectShouldBeFast)
obj.RigidBody.Velocity = this.Direction * this.FastSpeed;
else
obj.RigidBody.Velocity = this.Direction * this.SlowSpeed;
}
}
Notice that the parameter for OnCreateObject
is now of type ObjectCreationEvent
instead of NadaEvent
. The parameter for an event callback function must always match the type of event it is responding to. The event
argument is then used to access the Boolean data member, which determines the speed of the object that is spawned.
Related Materials
Tutorial
Manual
- commands
- launchernewproject
- resourceadding
- selectobject
- keywords
- Upload to Archetype
- addremovecomponent
Reference
Classes
Commands
Events
Nada Base Types
Development Task
- T1281