flame_behaviors 0.1.1 flame_behaviors: ^0.1.1 copied to clipboard
Flame Behaviors applies separation of concerns to game logic in the form of Entities and Behaviors, built by Very Good Ventures.
Flame Behaviors #
Developed with 💙 by Very Good Ventures 🦄
Flame Behaviors was created to make it easier to create scalable, testable games with a well-defined structure. It applies the separation of concerns to the game logic, in the form of Entities and Behaviors.
Imagine you want to build an old school Pong game. At its very core are two objects: a paddle and a ball. If you have a look at the paddle, you could say its game logic is: move up and move down. The ball has the simple game logic of: on collision with a paddle reverse the movement direction.
These objects, paddles and balls, are what we call entities and the game logics we just described are their behaviors. By applying these behaviors to each individual entity we get the core gameplay loop of Pong: hitting balls with our paddles until we win.
By defining what kind of entities our game has and describing what type of behaviors they may hold, we can easily turn a gameplay idea into a structured game that is both testable and scalable.
Installation 💻 #
flutter pub add flame_behaviors
Usage ✨ #
Entity #
The entity is the building block of a game. It represents a visual game object that can hold
multiple Behavior
s, which in turn define how the entity behaves.
// Define a custom entity by extending `Entity`.
class MyEntity extends Entity {
MyEntity() : super(behaviors: [MyBehavior()]);
}
Behavior #
A behavior is a component that defines how an entity behaves. It can be attached to an Entity
and handle a specific behavior for that entity. Behaviors can either be generic for any entity
or you can specify the specific type of entity that a behavior requires:
// Can be added to any type of Entity.
class MyGenericBehavior extends Behavior {
...
}
// Can only be added to MyEntity and subclasses of it.
class MySpecificBehavior extends Behavior<MyEntity> {
...
}
Each behavior can have its own Component
s for adding extra functionality related to the
behavior. For instance a TimerComponent
can implement a time-based behavioral activity:
class MyBehavior extends Behavior {
@override
Future<void> onLoad() async {
await add(TimerComponent(period: 5, repeat: true, onTick: _onTick));
}
void _onTick() {
// Do something every 5 seconds.
}
}
Note: A Behavioris a non-visual component that describes how a visual component (Entity) behaves. To ensure this rule, a behavior can't have its own
Behavior`s.
Input behaviors
The flame_behaviors
package also provides input behaviors. These behaviors are a
layer over the existing Flame input mixins for components. These behaviors will
trigger when their parent entity is being interacted with by the user. So these events
are always relative to the parent entity.
class MyDraggableBehavior extends DraggableBehavior<MyEntity> {
@override
bool onDragUpdate(DragUpdateInfo info) {
// Do something on drag update event.
return super.onDragUpdate(info);
}
}
Note: You still need to add the corresponding input mixins to your game class, see the Flame input docs for more information.
Collision detection #
Flame comes with a powerful built-in collision detection system,
but this API is not strongly typed. Components always get the colliding component as a
PositionComponent
and developers need to manually check what type of class it is.
flame_behaviors
is all about enforcing a strongly typed API. It provides a special behavior
called CollisionBehavior
that describes what type of entity it will target for collision. It
does not, however, do any real collision detection. That is done by the
PropagatingCollisionBehavior
.
The PropagatingCollisionBehavior
handles the collision detection by registering a hitbox on the
parent entity. When that hitbox has a collision, the PropagatingCollisionBehavior
checks if the
component that the parent entity is colliding with contains the target entity type specified in
CollisionBehavior
.
By letting the PropagatingCollisionBehavior
handle the collision detection we gain two benefits,
the first and most important one is performance. By only registering collision callbacks on the
entities themselves, the collision detection system does not have to go through any "collidable"
behaviors, for which there could be many per entity. We only do that now if we confirm a collision
has happened.
The second benefit is that it allows for separation of concerns.
Each CollisionBehavior
handles a specific collision use case and ensures that the developer does
not have to write a bunch of if statements in one big method to figure out what it is colliding
with.
A good use case of this collisional behavior pattern can be seen in the flame_behaviors
example
class MyEntityCollisionBehavior extends CollisionBehavior<MyCollidingEntity, MyParentEntity> {
@override
void onCollisionStart(Set<Vector2> intersectionPoints, MyCollidingEntity other) {
// We are starting colliding with MyCollidingEntity
}
@override
void onCollisionEnd(MyCollidingEntity other) {
// We stopped colliding with MyCollidingEntity
}
}
class MyParentEntity extends Entity {O
MyParentEntity() : super(
behaviors: [
PropagatingCollisionBehavior(RectangleHitbox()),
MyEntityCollisionBehavior(),
],
);
...
}