-
-
Notifications
You must be signed in to change notification settings - Fork 625
Events and Communication
There are two established types of communication between FXGL and your game: listeners and events. Both typically emit the same callback reports. Roughly speaking, listeners were designed to be used by internal FXGL modules, while events fired via the global event bus were designed for games that use FXGL. In short, you can use either but event handling is preferred.
Note: generally where a listener and an event handler can be used, the listeners will be fired first and then the event will be fired via the event bus.
A common listener that you might want to implement is FXGLListener.
public interface FXGLListener {
/**
* Fired on main loop paused.
*/
void onPause();
/**
* Fired on main loop resumed.
*/
void onResume();
/**
* Fired on FXGL reset.
* This is where all resources should be freed if they are no longer used.
*/
void onReset();
/**
* Fired before the system is about to shut down.
* Do NOT make any asynchronous calls as they may not complete.
*/
void onExit();
}It provides the game that uses FXGL the core callbacks that can be used to process important phases of the application.
Once implemented, you can attach the listener in your instance of GameApplication, say in initGame().
this.addFXGLListener(new MyFXGLListener());There is a single global event bus that brings all FXGL modules together, including your game. You can obtain the reference to it as follows:
EventBus bus = FXGL.getEventBus();Then you can either add (register) or remove (unregister) a handler for certain event types.
EventHandler<MyGameEvent> handler = event -> {
// do something with event
};
// start listening for MyGameEvent.ANY and subsequently handling them
getEventBus().addEventHandler(MyGameEvent.ANY, handler);
// when no longer interested in those events or cleaning up
getEventBus().removeEventHandler(MyGameEvent.ANY, handler);All the events that get passed between the modules are standard JavaFX events. This means that you can easily create new custom events by simply extending JavaFX Event class.
So, let's create our own event. Imagine that you have a game where the player can pick up various things by going over them (essentially colliding with them). You might have implemented the collision handler in a different class (which is the preferred way). However, now you need to communicate this back to the game app class and using events is a very neat way of doing so. Consider the PickupEvent class below:
public class PickupEvent extends Event {
public static final EventType<PickupEvent> ANY
= new EventType<>(Event.ANY, "PICKUP_EVENT");
public static final EventType<PickupEvent> COIN
= new EventType<>(ANY, "COIN_PICKUP");
public static final EventType<PickupEvent> POWERUP
= new EventType<>(ANY, "POWERUP_PICKUP");
private Entity pickup;
public PickupEvent(EventType<? extends Event> eventType, Entity pickup) {
super(eventType);
this.pickup = pickup;
}
public Entity getPickup() {
return pickup;
}
}We first extend Event, then we define types of our custom event. This allows us to add a subtype, so to speak, to the event because perhaps we want to know whether it was a coin or a powerup that we picked up. Of course we could query the pickup itself but with event types we can later make the event bus listen for particular events and filter out those we don't need. Therefore, it is best to use event types where appropriate. As you can see, the event object also carries the entity that we picked up, so this demonstrates how you "attach" objects to the event.
We can slightly automate the process of firing events based on certain special conditions.
We can do so by adding an EventTrigger to game world.
getGameWorld().addEventTrigger(new EventTrigger<EntityEvent>(
() -> player.getRightX() > enemy.getX(),
() -> new EntityEvent(Events.PASSED, player, enemy)
));This is a rather contrived example but should give you an idea how triggers work. We first pass a predicate function that is checked every frame. If the function returns true, in our case player passes some special enemy, then the trigger produces an event based on the supplier function. The event is then fired normally.
Note: by default the triggers fire only once. This can be changed in the constructor by passing the number of times to fire, as well as the interval between events.
If an event is quite general, you might do this in the main loop by scanning through all enemies for example. However, triggers might come in handy when you need to play a cut-scene or fire some special event that may occur only once.