Let's write the code for our basic boost stealing bot! Conceptually it's simple: 1. Figure out where the boosts are located on the pitch and 2. Tell our car to drive to the nearest boost we find. In this video, I take you step-by-step through how I would write this code in Python.
If you'd like to learn Python by making a game AI, this is a great beginner project to get you started.
GitHub repo for this project: https://github.com/learncodebygaming/rlbot_boosthog
Download RLBot: https://www.rlbot.org/
RLBot Wiki for Input and Output Data: https://github.com/RLBot/RLBotPythonExample/wiki/Input-and-Output-Data
So our goal again is simple: we just need to find the nearest boost and go get it.
To begin, let's dig into the data. Make sure you have the "Input and Output Data" section of the RLBot wiki open. Looking through the example GameTickPacket there, we can see that it contains some information about the boosts (whether a boost is active or not, and when it will respawn if not) but it doesn't contain the information we need: the location of the boosts.
Scrolling further down this page in the documentation, we get to see how the SimpleControllerState is structured. It's a simple dictionary with all the input values.
Keep scrolling down until you come to the Field Info section. In the example data, we can immediately see that this contains the information we need: the location vector of each boost pad. Certain information never changes during the course of a game, like the boost and goal locations, so these values are kept separate from the dynamic game tick information. Our bot can use the self.get_field_info()
method to access this data.
Let's confirm that the packet and the field info contain the data we expect from reading the documentation. One option is to simply print the values we're interested in to the console. But since the console would quickly fill with text each tick, it would be nicer to write these values to the Rocket League game window itself.
To do that, we can modify draw_debug()
to call renderer.draw_string_2d()
with the text we want to see. I'll add a new parameter to draw_debug()
to pass along the string we want to draw from the main game loop.
def draw_debug(renderer, car, target, action_display, corner_debug=None):
renderer.begin_rendering()
# draw a line from the car to the target
renderer.draw_line_3d(car.physics.location, target, renderer.white())
# print the action that the bot is taking
renderer.draw_string_3d(car.physics.location, 2, 2, action_display, renderer.white())
# print the corner debug string
# adjust y position depending on how many lines of text there are
if corner_debug:
corner_display_y = 900 - (corner_debug.count('\n') * 20)
renderer.draw_string_2d(10, corner_display_y, 1, 1, corner_debug, renderer.white())
renderer.end_rendering()
Now that we've confirmed how the boost information can be accessed from the field info and the game tick packet, it's time to use that information to figure out which boost is closest to our car. To do that, we simply loop over all the boosts, ignore the small and inactive boosts, and calculate the distance to each boost by using car_location.dist()
, which is a function provided to us by the Vec3 class. When we've identified the nearest boost, we can return that from our get_nearest_boost()
helper function.
def get_nearest_boost(info, packet, car_location):
nearest_boost_loc = None
# loop over all the boosts
for i, boost in enumerate(info.boost_pads):
# only want large boosts that haven't been taken
if boost.is_full_boost and packet.game_boosts[i].is_active:
# if we haven't found any boosts yet, use this one
if not nearest_boost_loc:
nearest_boost_loc = boost.location
else:
# if this boost is closer, save that
if car_location.dist(Vec3(boost.location)) < car_location.dist(Vec3(nearest_boost_loc)):
nearest_boost_loc = boost.location
return Vec3(nearest_boost_loc)
Confirm that get_nearest_boost()
is working for you by printing its return value in the corner debug string. If everything looks good, we can now use that location instead of the ball location in get_output()
to tell our car to drive to the boost we've identified. You'll also want to tell your bot to use the boost it collects by setting self.controller_state.boost = True
. You now have a functional boost stealer!
If you run your bot in a couple games, you'll quickly notice a few issues, though. First, you'll want to update draw_debug()
to draw a line to the boost your bot is grabbing instead of to the ball.
You might also notice that it'd be better to go for the ball on kickoff, and only boost steal after that. In preparation for doing this in the next video, I recommend moving the logic in the middle of your get_output()
method to its own generic action_goto()
function. We want this function to allow us to steer our car towards any location we give it. You should test this by alternating between giving it the ball location and the nearest boost location. Remember you can change the code while your bot is playing.
The major issue you'll notice is all the errors we get in the console when all the large boosts are taken (run BoostHog 3v3 against itself if you haven't seen this). We'll discuss strategies for fixing this in the next tutorial. There we'll also introduce some intelligent decision making into our bot, so it's not merely chasing boost all the time.