Flutter Tutorial

Flutter Intermediate Lesson 108 of 135 3 min read

Collision Detection in Flame: Hitboxes and CollisionCallbacks

Detect collisions in Flutter Flame games with hitboxes, CollisionCallbacks and onCollisionStart, in a runnable catch-the-coins game.

On this page

A collision is when two things in the game touch: the ball hits a brick, the player picks up a coin, a bullet hits an enemy. Flame finds collisions for you in three steps:

  1. Add HasCollisionDetection to the game.
  2. Give each component a hitbox child: RectangleHitbox(), CircleHitbox() or PolygonHitbox.
  3. Add CollisionCallbacks to a component and override onCollisionStart.

Catch the falling coins

import 'dart:math';
import 'package:flame/collisions.dart';
import 'package:flame/components.dart';
import 'package:flame/events.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';

void main() => runApp(GameWidget(game: CoinGame()));

class CoinGame extends FlameGame with HasCollisionDetection {
  final random = Random();
  int score = 0;
  late final TextComponent scoreText;

  @override
  Future<void> onLoad() async {
    camera.viewfinder.anchor = Anchor.topLeft;
    scoreText = TextComponent(text: 'Score: 0', position: Vector2(16, 16));
    world.add(scoreText);
    world.add(Basket()..position = Vector2(size.x / 2, size.y - 40));
    world.add(TimerComponent(period: 0.8, repeat: true, onTick: dropCoin));
  }

  void dropCoin() {
    world.add(Coin(Vector2(20 + random.nextDouble() * (size.x - 40), -20)));
  }

  void addPoint() {
    score++;
    scoreText.text = 'Score: $score';
  }
}

class Basket extends RectangleComponent with DragCallbacks, HasGameReference<CoinGame> {
  Basket()
      : super(
          size: Vector2(110, 26),
          anchor: Anchor.center,
          paint: Paint()..color = Colors.brown.shade300,
          children: [RectangleHitbox()], // the area that can collide
        );

  @override
  void onDragUpdate(DragUpdateEvent event) {
    position.x = (position.x + event.localDelta.x).clamp(size.x / 2, game.size.x - size.x / 2);
  }
}

class Coin extends CircleComponent with CollisionCallbacks, HasGameReference<CoinGame> {
  Coin(Vector2 position)
      : super(
          position: position,
          radius: 14,
          anchor: Anchor.center,
          paint: Paint()..color = Colors.amber,
          children: [CircleHitbox()],
        );

  @override
  void update(double dt) {
    super.update(dt);
    position.y += 220 * dt;
    if (position.y > game.size.y + 20) removeFromParent(); // missed
  }

  @override
  void onCollisionStart(Set<Vector2> intersectionPoints, PositionComponent other) {
    super.onCollisionStart(intersectionPoints, other);
    if (other is Basket) {
      game.addPoint();
      removeFromParent();
    }
  }
}

Drag the basket to catch the coins.

The collision methods

MethodCalled
onCollisionStart(points, other)Once, when two hitboxes start touching
onCollision(points, other)Every frame while they touch
onCollisionEnd(other)Once, when they stop touching

Use onCollisionStart for “picked up”, “hit” and “damage”. Use onCollision for things like standing on a platform.

other is Basket checks what you collided with, so one component can react differently to walls, enemies and coins.

Hitbox shapes

HitboxBest for
RectangleHitbox()Boxes, platforms, most characters
CircleHitbox()Balls, coins, round enemies
PolygonHitbox([...])Odd shapes (a triangle ship)
ScreenHitbox()The screen edges (add it to the game)

A hitbox fills its parent by default. Make it smaller for fairer games: RectangleHitbox(size: Vector2(40, 40), position: Vector2(5, 5)).

Seeing the hitboxes

Set debugMode = true on a component (or on the whole game) to draw outlines of every component and hitbox. It is the fastest way to find out why something does or does not collide.

Performance tips

  • Only things that need to collide should have hitboxes.
  • Use CollisionType.passive on hitboxes that never need to check each other, such as walls and coins: RectangleHitbox(collisionType: CollisionType.passive). Two passive hitboxes are never compared, which saves work.

Try it yourself

Add red “bomb” circles that also fall. Catching a bomb sets the score back to zero. (Tip: make Bomb a second class and check if (other is Basket) in it too.)

Practise in the playground Updated