Hire the Author: Leander D

Introduction

While brainstorming ideas to simulate in Gazebo, I shortlisted some retro game ideas like Tetris, Snake, and Flappy Bird. I decided to go with Flappy Bird out of all of them for this project. My initial motivation for creating this project was to improve my knowledge of Gazebo as a simulator and understand its boundaries. With the help of this blog article, you will be able to grow your skills in Gazebo by building a simple yet fun game.

Flappy Bird is a popular mobile game in which players control a bird character that must navigate through a series of pipes. The bird automatically moves forward, and players must tap the screen to make the bird flap its wings and ascend. The objective is to guide the bird through pipe openings without touching them.

Gazebo ROS
Flappy Bird Game in Gazebo

Glossary

Gazebo

Gazebo is an open-source 3D robotics simulator. It has integrated the ODE physics engine and OpenGL rendering and supports sensor simulation and actuator control.

ROS

Robot Operating System (ROS) is an open-source framework and set of tools that provide a software platform for developing robot applications. Key features include message passing, services, package management, visualisation, etc.

Step-by-step Procedure

Step 1: Setting Up the Project

Initially, to adjust the logic flow for the code, we will use a box to represent the bird and vertical columns to represent obstacles.

It is always good practice to start the Gazebo world in a paused state. By doing so, we can simply press the spacebar to toggle physics on and off. This will help us gain a better understanding of our surroundings.

The spacebar toggles physics, causing the box to fall.

Step 2: Configuring Flying Mechanics

Next, to achieve a wing-flapping motion, we need to incorporate a Gazebo force sensor that will provide a push from the base of the robot.

Furthermore, we need to map the output topic of the sensor to a node that can control the force upon keypress and key release. In this project, we will use the Python library called pynput to listen to these actions. Therefore, whenever a user presses or holds down a key, we apply a specific upward force, and when they release the key, we continue to apply a gentle upward force to ensure a gradual descent.

"""fly() and dip() functions are called whenever the up arrow key is pressed or released respectively."""
def key_press(self, key):
"""Listen for key press."""
if key == Key.up:
self.fly()
return False
def key_release(self, _):
"""Listen for key release."""
self.dip()
return False
def keyboard_update(self, _):
"""ROS Timer Callback Keyboard Listener for a press and release."""
with keyboard.Listener(on_press=self.key_press) as listener_for_key_press:
listener_for_key_press.join()
with keyboard.Listener(on_release=self.key_release) as listener_for_key_release:
listener_for_key_release.join()
Flying using arrow keys through pynput library

Subsequently, we add the Gazebo planar plugin for continuous forward horizontal motion. This enables us to move in a horizontal plane, allowing independent movement along the x-axis and y-axis. For our specific use case, we always apply a constant velocity in the direction of the approach.

<!-- Planar Plugin to get Odometry data and enable command velocity from Flappy Bird model -->
<gazebo>
<plugin name="object_controller" filename="libgazebo_ros_planar_move.so">
<commandTopic>cmd_vel</commandTopic>
<odometryTopic>odom</odometryTopic>
<odometryFrame>odom</odometryFrame>
<odometryRate>50.0</odometryRate>
<robotBaseFrame>base_link</robotBaseFrame>
<cmdTimeout>1.0</cmdTimeout>
</plugin>
</gazebo>
Applying a constant velocity through the planar plugin while flying

Step 3: Spawning Obstacles

Initially, we decided to represent the obstacles as vertical columns. However, once we confirm the spawning mechanism and test it in different scenarios, we will replace them with the actual models from the flappy bird game.

First, let us record the spawning height of the obstacle when it is fully immersed below the ground plane and fully standing within the ground plane of the Gazebo world. In our project, we have standardised the height of the columns to be 10m.

Gazebo ROS
Noting the minimum and maximum vertical offset of the obstacles

This information is particularly valuable as it helps us determine the lower and upper limits of the bottom column of the obstacle setup. Hence, a randomisation function is applied to generate varying obstacle heights using these limits.

To create the iconic obstacles from the game, we add another at a certain distance from the base pipe. We generate the separation between each pipe randomly during every pipe spawn, considering the mentioned limits. Once the pipes are stacked, they are generated with a consistent offset, ensuring they are evenly spaced.

Gazebo ROS
Pipes spawned at a constant horizontal offset with randomized separation gaps.

Step 4: Handling Collision and Dynamic Spawn

The obstacles need to spawn dynamically once the robot reaches a column’s range. This range is chosen to ensure enough columns are spawned ahead so that, at least in a singular frame, the columns do not get spawned in front of the user. The robot’s position is provided by the odometry topic generated by the Planar Gazebo plugin.

The range is minimized to show the pronounced effect of column spawning.

Currently, our setup has no handler for collisions. Ideally, in the case of a collision, the robot should spawn at the origin, and all the deployed columns need to be deleted upon impact.

To achieve this behaviour, we need to use the bumper plugin in Gazebo, which provides us with a topic to detect collisions. Additionally, we can set a vertical limit as a respawn event to prevent the robot from flying out of the scene.

<!-- Bumper Plugin to get collision information from Flappy Bird model -->
<gazebo reference="base_link">
<sensor name="collision_sensor" type="contact">
<update_rate>100.0</update_rate>
<always_on>true</always_on>
<contact>
<!-- retrieved from converting urdf to sdf -->
<collision>base_footprint_fixed_joint_lump__collision_collision</collision>
<topic>bumper_contact</topic>
</contact>
<plugin name="collision_plugin" filename="libgazebo_ros_bumper.so">
<update_rate>100.0</update_rate>
<always_on>true</always_on>
<bumperTopicName>/robot_bumper</bumperTopicName>
<frameName>base_link</frameName>
</plugin>
</sensor>
</gazebo>
Columns are deleted, and the box respawns once collision is detected.

As you can visualise, the deletion of the models happens almost instantly. This limitation arises due to the structure of the Gazebo World plugin, which requires wrappers to connect ROS with Gazebo messages. As a result, it becomes challenging to establish a direct connection between ROS and Gazebo. However, this design helps reduce latency as service calls for deletion in ROS take a considerable amount of time.

Respawning objects using ROS services leads to increased latency.

Step 5: Following Model using Gazebo Camera

This is the most challenging step of the project. A straightforward approach to achieve model following in Gazebo is to utilise the built-in Follow feature.

Using the Follow feature of Gazebo to keep track of the box

As demonstrated above, the following feature does not render us a perspective capable of playing the game. This functionality is not constrained to particular axes; it revolves around the robot in the same plane.

Fortunately, the simulator provides us with gazebo topics to control the camera’s view. These topics provide options for both position and velocity. The presence of velocity is essential for tracking and keeping the object at the centre of the frame while flying. Additionally, the position factor proves to help respawn the box at the origin. This combination ensures smooth movement and accurate positioning of the object.

However, after an extended period of time, the visuals in the simulator glitch out, and only collisions remain active. Moreover, the simulator freezes at this point, and the camera following node stops working.

Following node fails to track object midrun

In an attempt to find a solution, I tried looking for a resource in the Issues of the Gazebo repository or ROS Answers that could solve my issue but was unable to resolve the issue. As a result,  I decided to switch to using the Visual Plugin template provided by Gazebo.

This plugin allows us to access the Gazebo camera in the scene directly, eliminating the need to modify position and velocity using Gazebo topics. Additionally, the plugin provides a smoother experience regarding framerate and usability.

Smooth tracking of object at all times

Step 6: Enhancing Visuals

Flappy Bird

The first step to visually enhance the simulation is to replace the box and columns with the game assets. Let us begin with the flappy bird model imported here.

Flappy Bird
Flappy Bird model in 3D Warehouse

We then download the model as a Collada file and use it as a mesh in our URDF files.
However, the model deployed in Gazebo has abysmal lighting and is covered with a mesh wireframe.

Flappy Bird
Flappy Bird model in Gazebo – Poor lighting and wireframe present

As a rule of thumb, it is always wise to set the origin at the surface or the model’s volume whenever we import mesh models into Gazebo. Subsequently, the model’s orientation was fixed to face the X-axis upon spawn. We can easily do the following processes using the tools present in Blender.

Before and After - Fixed origin in place and aligned model with the positive X-axis
Before and After – Fixed origin in place and aligned model with the positive X-axis

However, more is needed to solve the issue. To remove the wireframe from the model, we need to remove the contents of the <lines> tag in the Collada file. This would erase the line display in Gazebo.

To enhance the brightness of the model, we increase the ambient values present in the Collada file till the output visual of the simulator matches the Blender view. In addition, we also disable shadows in the Gazebo world to remove unnecessary shadows generated by the bird due to the dynamic spawn of the columns.

Resulting model in Gazebo after removing lines and increasing ambient values
Resulting model in Gazebo after removing lines and increasing ambient values

Pipe Models

Similarly, we follow the steps for importing the columns from the game assets. We adjust the origin and orientation of the model in Blender and increase the default ambient values mentioned in the Collada file.

Modified default columns to game assets' pipe obstacle
Modified default columns to game assets’ pipe obstacle

Game Background

Finally, we must spawn a background to mimic the flappy bird gameplay. To do so, we position the ground plane vertically and add a custom texture. The default texture is modified to include a high-resolution background display of the original game.

The spawning mechanism of the background and the pipes is similar, except that the background images do not need to be deleted after every run or collision.

The ground plane was modified to include a custom background image.
The ground plane was modified to include a custom background image.

Let us combine all these visual enhancements into the development steps. This would replace the box with the flappy bird model, columns with pipes, and add a background for contrast.

The complete game in Gazebo.

Learning Tools

Overall, the following methods made this project possible:

  • Using pynput as a Python library for capturing keypresses.
  • Attaching Gazebo plugins to monitor collisions, the robot’s odometry, and force plugins for controlling the robot’s flight.
  • Use of WorldPlugins and ModelPlugins for spawning/deleting objects and camera control.
  • Visual Enhancement using Blender and custom textures.

Learning Strategy

The main obstacles I faced during this project were as follows:

  • Reducing the cooldown of the SpawnModel and DeleteModel Services.
  • Getting the Gazebo Camera following the Flappy Bird reliably.

The reduction of cooldown was solved by using the WorldPlugin provided by the Gazebo API, whereas the reliable camera follow was achieved by using the VisualPlugin instead.

Conclusion and Future Directions

Simulating the Flappy Bird game in Gazebo is a good method for enhancing your skills with the simulator. We can improve the game in the future by adding more features to it.

The additional features to enhance the user experience and make the game more exciting are developing a high score feature and increasing the speed of obstacle spawn and the speed of the Flappy Bird as the game time passes.

All of the above code is available in our GitHub repository at flappy_bird_gazebo

If you have any questions regarding this project or would like to learn more about Gazebo or ROS, please feel free to comment on the blog post or raise queries in the form of Issues in the repository; feature requests are always welcome.

Additionally, you can check out our other blogs on simulation as well like How to Make Simple Car Physics on Unity.

Hire the Author: Leander D

A zealous and impassioned individual who strives to achieve in the robotics industry. Experienced in
ROS (Robot Operating System), Python, and C++ for over two years.

Managed a collective team of 5 members that included the Mechanical, Electronics, and AI sectors. Instilled a strong sense of putting theory into practice by collaborating in a thriving robotics startup for two years.