Build Collision-Based Game Using Flame in Flutter

Happy Makadiya
Simform Engineering
7 min readOct 7, 2022

We can take advantage of Flame’s every functionality if our game has a strong basis. In this second part, we’ve covered the creation of the collision-based game.

Check out part one

Please have a look at part one if you have no prior knowledge of the Flame’s nature or functioning or if you wish to start with the fundamentals.

Game Concept

Here I’m going to explain my journey of creating Brick Breaker, a popular and fascinating collision-based game.

At the bottom of the screen, there’s a single ball and, at the top, square blocks with corresponding numbers. The player can strike the squares using their fingers to pull back and release the ball at the desired angle to gradually reduce the number until all of them have been shot.

Take a glance at the game.

This game has three fundamental components.

  1. The Ball can collide with and deflect away from other objects.
  2. Board has boundaries where the ball keeps bouncing and a draggable region where the ball is released at a particular angle.
  3. Bricks contain random numbers, which are reduced by one upon contact with the ball and destroyed at the zeroth value.

The score is determined by how many more bricklayers are added until the first brick reaches the baseline of the board. The game is about crashing the best score; there is no requirement for winning.

Soooo, Let’s get started!

The main required things for this game app are Flame — Flutter Game engine, Mobx — for State Management, and get_it — Service locator for store objects.

States Of Game:

The GameApp widget will observe the Game state and return the screen accordingly. So we have created enum for the GameState.

enum GameState { launch, play, pause, gameOver }

The primary game view is controlled by Play State, and if the game has been restarted, a new instance of the Game will be allocated to Game Board.

Launch, Pause, and GameOver are game states that provide basic pause and resume functionality with the navigate to home choice.

Game Board Widget

This screen is responsible for displaying the current and best score with the pause button feature. Apart from it, the Game will be loaded into the remaining part using Game Widget provided by Flame.

From this point on, the game’s primary logic begins…

Flame Game Class:

Extend FlameGame with a new game class — BreakerGame. Mixins HasCollisionDetection and HasDraggables must be added if any game components have collision callbacks and draggable events respectively.

According to the Flame Life cycle, the onLoad() is called once during the component’s lifetime to run asynchronous initialization code for components. All sub-components will be loaded in the onLoad(). Components are added to the component tree using the add() method.

Priority of components allows you to control the order in which your components are rendered. For example, the ball should always be visible on top of all other components. And so, its priority has been set to 1.

Game Components:

As mentioned earlier, the Game will have Bricks, Board, and Ball. Let’s explore each one individually.

1. Brick Component:

It will be a rectangle component having collision callbacks. And this component will use the reference of the game, so hasGameRef<BreakerGame> mixin is added.

Generate a random bricklayer:

A list of BrickComponents with a brick size and a random count will be produced by getBrickLayer(). The brick size is calculated using — the number of bricks in a row and the size of the game widget.

Implementation of BrickComponent Class:

Let’s examine the steps performed on the brick component.

  1. We will put an initialization code for a brick within onLoad(). Since a parent generates the count value at random, we first use removeFromParent() to remove it from a parent if the count value is less than zero ().
  2. The Brick has textComponent added because we will present the count value in text form.
  3. To determine the collision of brick, RectangleHitbox() has been included. The HitBox is the standard building block to ascertain whether two objects have collided.
  4. Every game tick causes onUpdate() to be invoked. The value textComponent will be set to count on each collision.
  5. onCollision() callback gives PositionComponent as the parameter with whom the brick collides. So will check if the collided component is Ball then the count value will be reduced by one.

Until the component stops colliding with other objects, onCollision() will keep getting called. Therefore, we always maintain the hasCollided boolean flag and call this function only once.

2. Ball Component:

The ball Component is in charge of rendering the aim liner and alters position when colliding with various hitboxes.

The BallState enum was developed to manage the state efficiently, giving users more control over whether to permit drag events, collisions, and ball movement.

enum BallState { ideal, drag, release, completed }
  1. Render the aim liner and movement of the ball:
  1. On each game tick, render() will also be called. The path of the aim liner is drawn based on the aimAngle which will be determined by the drag methods of the board.
  2. In update() : If the ball’s location is close to the board’s baseline, the ball state will change to BallState.complete.

When the game state is changed to BallState.complete, the new bricklayer is placed on top of the old layer by repositioning each layer one step downward.

3. moveBall() will adjust the ball’s position in accordance with the directional variables (xSign & ySign) and nextPosition variable,

xSign and ySign hold positive or negative signs; on that basis, the ball will move to the specific coordinate.

2. Collision Callbacks with different hitboxes:

Here, the ball may strike one of two possible Hitboxes: the Board or the Brick.

The law of reflection states that…

When a ray of light reflects off a surface, the angle of incidence is equal to the angle of reflection.

In our case, the same rule is in effect. Simply invert the sign of y — ySign — if the ball collides with the top/bottom side of the object. Invert the sign of x — xSign — if it collides with the left /right side of the object.

The intersection point of two colliding objects and the component with which it collided are provided to us by onCollision() callbacks. A distinct approach is being used depending on the component type.

Note: In our case, we will get one or two intersection points while colliding.

  1. Collision with the board: By using the intersection point and position-size of the board, we can identify where the ball hit the board. To have a better grasp, review the logic.
  2. Collision with brick: If the intersection point is one and if either x or y of two intersection points are the same, then we can say that ball might collide with the sides of the brick and change the directional Variable accordingly. Or else Ball would collide with the corner of the brick.

3. Board Component:

The board component is responsible for providing an area in which the ball moves around. It has a drag gesture to determine in which direction the ball should be triggered. For that, we have added aDraggable mixin to override drag event callbacks.

  1. Rectangle hitbox is added to the board to make it collidable.
  2. Using onDragStart() will get _dragStartPosition, the starting point of the drag event.
  3. On every, onDragUpdate(), we will get the event position. Using _dragStartPosition and info.eventPosition, the angle of the aim line is calculated. The Aim line is rendered into a ball component, which we’ll discuss in a while.
  4. Using the onDragCancel event, the next position and direction (xSign & ySign) of the ball has been calculated.

We can determine slope via two known points: y — y1 = m (x — x1)

Here 2 points are known: dragStartPosition and info.eventPosition; using that, we can determine the slope.

If you have basic knowledge of trigonometry, then

tanθ = slope 
θ = tan-1(slope)

So using this θ, we can set the angle of the aim line.

Now we have the slope and center position of the ball, so using the above line formula, we can determine nextPosition of the ball.

And Booom! Done with all the required concepts to develop a fantastic collision-based game.

You can play this game live from here.

Conclusion:

We’ve designed the Brick Breaker game to demonstrate the concepts of flame lifecycle methods, collision callbacks, and Drag events.

Also, we’ve seen that using enum will make it easier to maintain effective control over the game’s state and components.

References:

Docs: https://docs.flame-engine.org/1.3.0/

Examples: https://examples.flame-engine.org/#/

All in one: https://github.com/flame-engine/awesome-flame

--

--

Happy Makadiya
Simform Engineering

Flutter Developer at Simform Solutions. Love to explore new things. Happy Coding ;)