---
title: Composition (mixins)
description: >-
  Build Bonfire components like LEGO: combine small behavior mixins to assemble
  players, enemies and anything else.
---
> ## Documentation Index
> Fetch the complete documentation index at: https://docs.page/rafaelbarbosatec/bonfire/llms.txt
> Use this file to discover all available pages before exploring further.


> With mixins you can add different behaviors to your component.

## Components are LEGO bricks

Bonfire does not give you a closed `Player`/`Enemy` hierarchy that you have to fight against. **Everything is a `GameComponent` plus a stack of small mixins.** Each mixin adds exactly one capability — move, have life, see, collide, jump, be pushed, follow, sense — and exposes it through a single API object.

Think of a component as a LEGO figure:

- `GameComponent` is **the base plate**. It already brings the engine plumbing every component needs: game reference, pointer detection, paint, collision callbacks, `util` helpers and `attack` (yes — any `GameComponent` can attack, even a rock).
- Each mixin is **a brick** you snap on top. It carries its own state and methods, so it never pollutes the component namespace: you reach it through `component.life`, `component.collision`, `component.vision`, `component.forces`, and so on.
- A class like `SimplePlayer` is **just a pre-built recipe**: a base plate with a specific set of bricks snapped in a specific order.

This is the actual definition of `Player` in Bonfire 4.x:

```dart
class Player extends GameComponent
    with
        Movement,
        WithLife,
        WithVision,
        PlayerControllerListener,
        MovementByJoystick {
  Player({
    required Vector2 position,
    required Vector2 size,
    double life = 100,
    double? speed,
  }) {
    this.speed = speed ?? this.speed;
    this.life.receivesAttackFrom = AcceptableAttackOriginEnum.ENEMY;
    this.life.initial(life);
    this.position = position;
    this.size = size;
  }
}
```

There is **no sprite, no animation and no keyboard in `Player`**. It only bundles what every controllable character needs. The players you already know are assembled on top of it:

```dart
class SimplePlayer extends Player with WithDirectionAnimation { ... }

class RotationPlayer extends Player with UseSpriteAnimation, WithAssetsLoader { ... }

class PlatformPlayer extends SimplePlayer
    with WithCollision, WithJumper, JumperAnimation { ... }
```

The same is true everywhere in the engine:

| Built-in class | How it is assembled |
|---|---|
| `GameComponent` | `PositionComponent` + `BonfireHasGameRef`, `PointerDetectorHandler`, `HasPaint`, `CollisionCallbacks`, `WithUtil`, `WithAttack` |
| `Player` | `GameComponent` + `Movement`, `WithLife`, `WithVision`, `PlayerControllerListener`, `MovementByJoystick` |
| `SimplePlayer` | `Player` + `WithDirectionAnimation` |
| `RotationPlayer` | `Player` + `UseSpriteAnimation`, `WithAssetsLoader` |
| `PlatformPlayer` | `SimplePlayer` + `WithCollision`, `WithJumper`, `JumperAnimation` |
| `Npc` | `GameComponent` + `Movement`, `WithVision` |
| `Enemy` / `Ally` | `Npc` + `WithLife` |
| `SimpleEnemy` / `SimpleAlly` | `Enemy` / `Ally` + `WithDirectionAnimation` |
| `GameObject` | `GameComponent` + `UseSprite`, `WithLighting`, `WithAssetsLoader` |
| `GameDecoration` | `AnimatedGameObject` (which is `GameObject` + `UseSpriteAnimation`) |

<Info>
  Read that table backwards and you get the customization strategy: **if a built-in serves 90% of what you need, extend it and swap the last brick. If it serves 0%, start from `GameComponent` and snap only the bricks you want.**
</Info>

### One API object per brick

Starting with Bonfire 4.0, most mixins follow a consistent API-first pattern:

- The mixin is named `WithFeature`.
- The mixin exposes a single API object: `feature`.
- All related methods, listeners and state are accessed through `component.feature.method()`.

This reduces namespace pollution on components and improves code discoverability when combining multiple mixins.

`Movement` is the deliberate exception: it keeps its original direct API (`moveUp`, `stop`, `speed`, `velocity`, `direction`...) because almost every other mixin builds on it.

> For a complete migration guide from Bonfire 3.x, see [What's New in 4.0](/bonfire-4).

### Recipe 1 — a player without sprite animations

`Player` has no animation brick at all. If you do not want `SimpleDirectionAnimation`, do not extend `SimplePlayer` — extend `Player` and draw whatever you want:

```dart
import 'dart:ui';

class NeonCube extends Player with WithCollision {
  NeonCube(Vector2 position)
      : super(
          position: position,
          size: Vector2.all(28),
          speed: 140,
          life: 100,
        );

  @override
  Future<void> onLoad() async {
    add(RectangleHitbox(size: size));
    return super.onLoad();
  }

  @override
  void render(Canvas canvas) {
    super.render(canvas);
    canvas.drawRect(
      Rect.fromLTWH(0, 0, size.x, size.y),
      Paint()..color = const Color(0xFF22D3EE),
    );
  }
}
```

It is still a `Player`, so `BonfireWidget(player: NeonCube(...))` and the joystick keep working, and you still have the bricks you kept: `Movement`, `life`, `vision`, `collision` and `attack`.

You can go the other way too: keep `SimplePlayer` and replace just the animation API:

```dart
class Ghost extends SimplePlayer {
  Ghost(Vector2 position)
      : super(position: position, size: Vector2.all(24)) {
    animation = SimpleDirectionAnimation(
      idleRight: _idleRight,
      runRight: _runRight,
    );
  }
}
```

### Recipe 2 — no `Player` at all

A component does not need to extend `Player` to be controlled. `BonfireWidget` only needs a *listener* of the joystick, and you can wire any component with `PlayerControllerListener` yourself:

```dart
class Drone extends GameComponent
    with
        Movement,
        WithLife,
        WithVision,
        PlayerControllerListener,
        MovementByJoystick,
        WithCollision {
  Drone(Vector2 position) {
    this.position = position;
    size = Vector2.all(20);
    speed = 90;
    life.initial(50);
  }

  @override
  Future<void> onLoad() async {
    add(RectangleHitbox(size: size));
    return super.onLoad();
  }

  @override
  void render(Canvas canvas) {
    super.render(canvas);
    canvas.drawCircle(
      Offset(size.x / 2, size.y / 2),
      size.x / 2,
      Paint()..color = const Color(0xFFFFB300),
    );
  }
}
```

```dart
// inside BonfireGame.onReady, for example
game.add(drone);
game.addJoystickObserver(drone);
```

`addJoystickObserver` is the same mechanism `BonfireWidget(player: ...)` uses internally. Use it when the "player" is a drone, a cursor, a car — or when you want to switch control between components at runtime.

### Swapping bricks

You do not need to rewrite a class to change it — just change its `with` clause (or extend it and add one more mixin):

| I want to... | Snap on | Needs |
|---|---|---|
| take damage and die | `WithLife` | — |
| show a health bar | `WithLifeBar` | `WithLife` |
| collide with the map and other hitboxes | `WithCollision` | `Movement` |
| bounce on collision | `WithElasticCollision` | `WithCollision` |
| jump | `WithJumper` | `Movement`, `WithCollision` |
| apply gravity, wind, friction, drag | `WithForces` | `Movement` |
| walk around randomly | `WithRandomMovement` | `Movement` |
| follow a path with A* | `WithPathFinding` | `Movement` |
| be moved one cell at a time | `WithMovePerCell` | `Movement` |
| be pushed by other components | `WithPushable` | `Movement` |
| follow a target | `WithFollower` | — |
| detect contact without blocking | `WithSensor` | — |
| see other components | `WithVision` | — |
| emit light | `WithLighting` | — |
| draw a `Sprite` | `UseSprite` | — |
| draw a `SpriteAnimation` | `UseSpriteAnimation` | — |
| apply a fragment shader | `WithShader` | `PositionComponent` |
| load async assets in `onLoad` | `WithAssetsLoader` | — |
| use a joystick | `MovementByJoystick` | `Movement`, `PlayerControllerListener` |
| run `Behavior` objects | `UseBehavior` | — |

### Rules of the stack

Mixins declare their requirements with `on`, and Dart checks them when you build the class:

```dart
mixin WithCollision on Movement { ... }
mixin WithJumper on Movement, WithCollision { ... }
mixin WithLifeBar on WithLife { ... }
```

Two practical consequences:

1. **Order matters.** The mixins a brick depends on must appear before it in the `with` clause: `GameComponent with Movement, WithCollision, WithJumper`. Dart reports a compile error if you invert it.
2. **A brick can be removed.** Remove `MovementByJoystick` and the joystick no longer moves your component — you can drive it with `moveToward`, `moveByAngle` or your own AI instead. Remove `WithLife` and the component simply cannot die. The engine does not assume a brick is there.

### Brick catalog

#### Movement

Mixin responsible for adding movement to your component.

`Movement` is the **only mixin that keeps its original direct API**. It is the foundation used by almost every other mixin.

Your component gains properties like:

```dart
bool isIdle;
bool isMoving;
double speed;
Vector2 velocity;
Direction direction;
Direction hDirection;
Direction vDirection;
```

And methods to move your component:

```dart
void moveUp({double? speed, bool resetCrossAxis = false})
void moveDown({double? speed, bool resetCrossAxis = false})
void moveLeft({double? speed, bool resetCrossAxis = false})
void moveRight({double? speed, bool resetCrossAxis = false})
void moveByAngle(double angle, {double? speed})
void moveToward(Vector2 target, {double? speed})
void moveFromDirection(Direction direction, {double? speed, ...})
bool moveToPosition(Vector2 target, {double? speed, ...})
bool canMove(Direction direction, {double? displacement, ...})
void stop()
void idle()
```

---

## WithJumper

> To use this mixin your component must contain `Movement` and `WithCollision` mixins.

Adds jump behavior for platform games.

```dart
class MyPlayer extends SimplePlayer with WithJumper {
  @override
  void onMount() {
    super.onMount();
    jumper.setMaxJump(2);
    jumper.onJumpStateChangedListener(_onJumpStateChanged);
  }

  void _onJumpStateChanged(JumpingStateEnum state) {
    // update animation based on state
  }
}
```

Main API:

```dart
jumper.jump({double? jumpSpeed, bool force = false})
jumper.setMaxJump(int maxJump)
jumper.onJumpStateChangedListener(JumpStateChangedCallback callback)
bool jumper.isJumping
JumpingStateEnum jumper.jumpingState
```

---

## WithDirectionAnimation

> To use this mixin your component must contain the `Movement` mixin.

Adds animations synchronized with movement directions.

```dart
class MyComponent extends GameComponent with Movement, WithDirectionAnimation {
  MyComponent() {
    animation = SimpleDirectionAnimation(
      idleRight: idleRightAnimation,
      runRight: runRightAnimation,
    );
  }
}
```

You can replace the animation at any time with the `animation` setter (or `directionAnimation.animation`):

```dart
animation = SimpleDirectionAnimation(
  idleRight: newIdleRight,
  runRight: newRunRight,
);
```

---

## WithPathFinding

> To use this mixin your component must contain the `Movement` mixin.

Finds paths using `a_star_algorithm` and moves the component through the path.

See [PathFinding](/path-finding) for details.

```dart
class MyPlayer extends SimplePlayer with WithPathFinding, TapGesture {
  MyPlayer(Vector2 position) : super(position: position) {
    pathFinding.setup(
      pathLineStrokeWidth: 2,
      useOnlyVisibleBarriers: false,
    );
  }

  @override
  void onTapDownScreen(GestureEvent event) {
    pathFinding.moveToPosition(event.worldPosition);
    super.onTapDownScreen(event);
  }
}
```

Main API:

```dart
pathFinding.setup({...})
Future<List<Vector2>> pathFinding.moveToPosition(Vector2 position, {...})
void pathFinding.moveAlongThePath(List<Vector2> path, {...})
List<Vector2> pathFinding.getPathToPosition(Vector2 position, {...})
void pathFinding.stop()
bool pathFinding.isMoving
```

---

## WithRandomMovement

> To use this mixin your component must contain the `Movement` mixin.

Adds random movement behavior, useful for enemies and NPCs walking around.

Set the parameters once with `setup`, then call `run(dt)` from `update`:

```dart
class MyEnemy extends SimpleEnemy with WithRandomMovement {
  MyEnemy(Vector2 position) : super(position: position, size: Vector2.all(32)) {
    randomMovement.setup(speed: 20, maxDistance: 100);
  }

  @override
  void update(double dt) {
    super.update(dt);
    randomMovement.run(dt);
  }
}
```

Main API:

```dart
randomMovement.setup({
  double? speed,
  double? maxDistance,
  double? minDistance,
  int? timeKeepStopped, // milliseconds
  bool? updateAngle,
  bool? checkDirectionWithRayCast,
  RandomMovementDirections? directions,
})
void randomMovement.run(double dt)
randomMovement.onStartMoveListener(OnRandomMovementStartCallback callback)
randomMovement.onStopMoveListener(OnRandomMovementStopCallback callback)
ShapeHitbox? randomMovement.area
```

---

## MovementByJoystick

> To use this mixin your component must contain the `Movement` and `PlayerControllerListener` mixins.

Adds movement controlled by joystick events.

```dart
class MyComponent extends GameComponent
    with Movement, PlayerControllerListener, MovementByJoystick {
  MyComponent() {
    setupMovementByJoystick(
      moveType: MovementByJoystickType.direction, // or .angle for 360 degrees
      intensityEnabled: false,
      diagonalEnabled: true,
      enabled: true,
      startOnIdle: true,
    );
  }
}
```

`MovementByJoystickType.direction` gives the 8 directions; `MovementByJoystickType.angle` gives full 360 movement (the mode used by `RotationPlayer`). Call `setupMovementByJoystick` again at runtime to change any of these options.

---

## WithLife

Mixin responsible for adding damage-taking and health behavior.

```dart
class MyEnemy extends SimpleEnemy with WithLife {
  MyEnemy(Vector2 position) : super(position: position) {
    life.initial(200);
    life.onDieListener(_onDie);
    life.onRemoveLifeListener(_onDamage);
  }
}
```

Main API:

```dart
life.initial(double value)
life.add(double value)
life.remove(double value)
life.update(double value, {bool verifyDieOrRevive = true})
life.handleAttack(AttackOriginEnum attacker, double damage, dynamic identify)
bool life.isDead
double life.value
double life.max
life.onDieListener(LifeCallback callback)
life.onRemoveLifeListener(LifeChangeCallback callback)
life.onRestoreLifeListener(LifeChangeCallback callback)
```

---

## WithLifeBar

> To use this mixin your component must contain the `WithLife` mixin.

Adds a life bar above the component.

```dart
class MyEnemy extends SimpleEnemy with WithLife, WithLifeBar {
  MyEnemy(Vector2 position) : super(position: position) {
    lifeBar.setup(
      borderRadius: BorderRadius.circular(2),
      borderWidth: 2,
    );
  }
}
```

---

## WithVision

Mixin responsible for adding vision to the component. Components like `Player`, `Npc` and `Decoration` use this mixin.

Your component gains `vision.seeComponent(...)` and `vision.seeComponentType(...)` methods.

```dart
vision.setup(drawVision: true);
```

When you use any vision method, the vision area will be drawn if `drawVision` is enabled.

---

## WithSensor

Mixin responsible for adding a trigger to detect other objects without blocking their passage.

Example: fire on the ground, spikes, etc. Things that the character or enemy can go through and take damage or activate any other behavior.

```dart
class Spikes extends GameDecoration with WithSensor<GameComponent> {
  Spikes(Vector2 position)
      : super.withAnimation(
          Future<SpriteAnimation>.value(...),
          size: Vector2.all(32),
          position: position,
        ) {
    sensor.onContactListener((component) {
      if (component is Player) {
        component.life.remove(10);
      }
    });
  }
}
```

See [Sensor](/sensor) for more.

---

## WithLighting

Mixin used to configure lighting in your component.

See [Lighting](/lighting)

---

## WithCollision

Mixin responsible for adding collision.

See [Collision System](/collision-system)

---

## WithPushable

> To use this mixin your component must contain the `Movement` mixin.

Mixin responsible for enabling push behavior on the component.

```dart
class MyBox extends GameObject with Movement, WithCollision, WithPushable {
  @override
  void onMount() {
    super.onMount();
    pushable.setup(pushableFrom: PushableFromEnum.ALL);
    pushable.onPushListener((component) => component is Enemy);
  }
}
```

Use `pushable.onPushListener(...)` to control when the component can be pushed.

---

## WithFollower

Makes the component follow the position of a target.

```dart
class MyFollower extends GameComponent with WithFollower {
  @override
  void onMount() {
    super.onMount();
    follower.setup(target: player, offset: Vector2(10, 10));
  }
}
```

Main API:

```dart
follower.setup({GameComponent? target, Vector2? offset})
follower.removeTarget()
GameComponent? follower.target
Vector2? follower.offset
```

---

## WithForces

> To use this mixin your component must contain the `Movement` mixin.

Mixin used to apply physics forces to your component like gravity, wind, friction and drag.

```dart
class MyObject extends GameObject with Movement, WithForces {
  MyObject() {
    forces.setGravity(Vector2(0, 300));
  }
}
```

Your component gains the `forces` API. See [Forces](/forces) for the complete list of methods.

---

## WithMovePerCell

> To use this mixin your component must contain the `Movement` mixin.

Restricts component movement to discrete cells.

```dart
class MyCharacter extends SimplePlayer with WithMovePerCell {
  @override
  void onMount() {
    super.onMount();
    movePerCell.setup(cellSize: Vector2.all(32));
  }
}
```

---

## WithAssetsLoader

Mixin used to load assets during `onLoad`.

```dart
class MyComponent extends GameComponent with WithAssetsLoader {
  SpriteAnimation? animation;

  MyComponent(Future<SpriteAnimation> animIdle) {
    assetsLoader.add(
      AssetToLoad<SpriteAnimation>(animIdle, (value) {
        animation = value;
      }),
    );
  }
}
```

> `AssetsLoader` and `AssetToLoad` classes remain public and exported.

---

## UseSpriteAnimation

Mixin that adds easy use of `SpriteAnimation` to your component.

Your component gains `setAnimation`, `playSpriteAnimationOnce` and the animation state getters. There is no `animation` field to assign: pass the animation to `setAnimation`.

```dart
class MyComponent extends GameComponent with UseSpriteAnimation {
  void playJump() {
    playSpriteAnimationOnce(MySpriteSheetLoader.getJumpAnimation());
  }

  @override
  Future<void> onLoad() async {
    setAnimation(await MySpriteSheetLoader.getAnimation());
    return super.onLoad();
  }
}
```

---

## UseSprite

Mixin that adds easy use of `Sprite` to your component.

Your component gains the `sprite` property (and `spriteOffset`).

```dart
class MyComponent extends GameComponent with UseSprite {
  @override
  Future<void> onLoad() async {
    sprite = await MySpriteSheetLoader.getSprite();
    return super.onLoad();
  }
}
```

---

## WithShader

Mixin that applies a fragment shader over a component.

```dart
class MyLayer extends PositionComponent with WithShader {
  @override
  Future<void> onLoad() async {
    super.onLoad();
    shader.shader = await loadFragmentShader(...);
    shader.canvasScale = 2;
    shader.componentStatic = true;
  }
}
```

See [Shader](/shader) for more.

---

## TileRecognizer

Mixin responsible for detecting which tiles the component is over.

Your component gains the `tileRecognizer` API to query tile information.

---

## UpdateCameraByPinchGesture

Mixin that enables camera zoom control using pinch gesture.

> Requires `PinchGesture`.

---

## Combining mixins

The `with` clause is just a shopping list. This enemy:

- already has `Movement`, `WithVision` and `WithLife` (from `SimpleEnemy`);
- snaps on `WithCollision` to respect the map;
- snaps on `WithRandomMovement` to walk around;
- snaps on `WithLifeBar` to show a health bar.

```dart
class Goblin extends SimpleEnemy
    with WithCollision, WithRandomMovement, WithLifeBar {
  Goblin(Vector2 position) : super(position: position, size: Vector2.all(32)) {
    life.initial(100);
    lifeBar.setup(borderWidth: 2);
    randomMovement.setup(speed: speed / 2, maxDistance: 64);
  }

  @override
  void update(double dt) {
    super.update(dt);
    randomMovement.run(dt);
  }
}
```

Its full brick stack, from the base plate up:

```text
GameComponent                     <- engine plumbing + attack + util
  + Movement, WithVision          <- from Npc
  + WithLife                      <- from Enemy
  + WithDirectionAnimation        <- from SimpleEnemy
  + WithCollision                 <- this class
  + WithRandomMovement            <- this class
  + WithLifeBar                   <- this class
```

That is the whole idea: **you never need a new engine class to get a new kind of character — you need a new combination.**

> Remember: `Movement` is the only mixin accessed directly. All other mixins use their respective API object.

