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.

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.
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() |
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> |
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.

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.

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.
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> |
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.
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.
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.
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.
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.

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.

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.

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.

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.

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.

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.
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.