From 7eaa1b5d0046d59a92ec051f556c685c221eadd2 Mon Sep 17 00:00:00 2001 From: RLMarvin <35970829+RLMarvin@users.noreply.github.com> Date: Mon, 7 Jan 2019 17:35:11 +0000 Subject: [PATCH 01/30] Change default appearance. (#20) --- python_example/appearance.cfg | 58 +++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/python_example/appearance.cfg b/python_example/appearance.cfg index 911990d..468bbe3 100644 --- a/python_example/appearance.cfg +++ b/python_example/appearance.cfg @@ -1,29 +1,49 @@ [Bot Loadout] -team_color_id = 27 -custom_color_id = 75 +team_color_id = 60 +custom_color_id = 0 car_id = 23 -decal_id = 307 -wheels_id = 1656 -boost_id = 0 -antenna_id = 287 +decal_id = 0 +wheels_id = 1565 +boost_id = 35 +antenna_id = 0 hat_id = 0 -paint_finish_id = 1978 -custom_finish_id = 1978 +paint_finish_id = 1681 +custom_finish_id = 1681 engine_audio_id = 0 -trails_id = 0 -goal_explosion_id = 1971 +trails_id = 3220 +goal_explosion_id = 3018 [Bot Loadout Orange] -team_color_id = 1 -custom_color_id = 1 +team_color_id = 3 +custom_color_id = 0 car_id = 23 decal_id = 0 -wheels_id = 818 -boost_id = 0 -antenna_id = 287 +wheels_id = 1565 +boost_id = 35 +antenna_id = 0 hat_id = 0 -paint_finish_id = 266 -custom_finish_id = 266 +paint_finish_id = 1681 +custom_finish_id = 1681 engine_audio_id = 0 -trails_id = 0 -goal_explosion_id = 1971 \ No newline at end of file +trails_id = 3220 +goal_explosion_id = 3018 + +[Bot Paint Blue] +car_paint_id = 12 +decal_paint_id = 0 +wheels_paint_id = 7 +boost_paint_id = 7 +antenna_paint_id = 0 +hat_paint_id = 0 +trails_paint_id = 2 +goal_explosion_paint_id = 0 + +[Bot Paint Orange] +car_paint_id = 12 +decal_paint_id = 0 +wheels_paint_id = 14 +boost_paint_id = 14 +antenna_paint_id = 0 +hat_paint_id = 0 +trails_paint_id = 14 +goal_explosion_paint_id = 0 From b40b781fc3e84d667b84cec18f85787e3fdbee70 Mon Sep 17 00:00:00 2001 From: Tyler Arehart Date: Sun, 27 Jan 2019 17:37:14 -0800 Subject: [PATCH 02/30] Converting tab in config file to spaces. --- python_example/python_example.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python_example/python_example.cfg b/python_example/python_example.cfg index f68a898..99dd157 100644 --- a/python_example/python_example.cfg +++ b/python_example/python_example.cfg @@ -15,7 +15,7 @@ developer = The RLBot community # Short description of the bot description = This is a multi-line description - of the official python example bot + of the official python example bot # Fun fact about the bot fun_fact = From e4706b4f7fdc153a762987a2c4563d28b33de1d5 Mon Sep 17 00:00:00 2001 From: Dominik Schmid Date: Mon, 18 Feb 2019 10:02:22 -0800 Subject: [PATCH 03/30] Show how to use rlbottraining (#21) --- requirements.txt | 1 + training/drive_to_ball_grader.py | 48 ++++++++++++++++ training/example_playlist.py | 12 ++++ training/hello_world_training.py | 96 ++++++++++++++++++++++++++++++++ training/training_util.py | 7 +++ training/unit_tests.py | 37 ++++++++++++ 6 files changed, 201 insertions(+) create mode 100644 training/drive_to_ball_grader.py create mode 100644 training/example_playlist.py create mode 100644 training/hello_world_training.py create mode 100644 training/training_util.py create mode 100644 training/unit_tests.py diff --git a/requirements.txt b/requirements.txt index acbc863..25c0c97 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ # Include everything the framework requires # You will automatically get updates for all versions starting with "1.". rlbot==1.* +rlbottraining # This will cause pip to auto-upgrade and stop scaring people with warning messages pip diff --git a/training/drive_to_ball_grader.py b/training/drive_to_ball_grader.py new file mode 100644 index 0000000..948be82 --- /dev/null +++ b/training/drive_to_ball_grader.py @@ -0,0 +1,48 @@ +from dataclasses import dataclass, field +from math import sqrt +from typing import Optional + +from rlbot.training.training import Grade, Pass, Fail + +from rlbottraining.grading.training_tick_packet import TrainingTickPacket +from rlbottraining.common_graders.timeout import FailOnTimeout +from rlbottraining.common_graders.compound_grader import CompoundGrader +from rlbottraining.grading.grader import Grader + + +""" +This file shows how to create Graders which specify when the Exercises finish +and whether the bots passed the exercise. +""" + + +class DriveToBallGrader(CompoundGrader): + """ + Checks that the car gets to the ball in a reasonable amount of time. + """ + def __init__(self, timeout_seconds=4.0, min_dist_to_pass=200): + super().__init__([ + PassOnNearBall(min_dist_to_pass=min_dist_to_pass), + FailOnTimeout(timeout_seconds), + ]) + +@dataclass +class PassOnNearBall(Grader): + """ + Returns a Pass grade once the car is sufficiently close to the ball. + """ + + min_dist_to_pass: float = 200 + car_index: int = 0 + + def on_tick(self, tick: TrainingTickPacket) -> Optional[Grade]: + car = tick.game_tick_packet.game_cars[self.car_index].physics.location + ball = tick.game_tick_packet.game_ball.physics.location + + dist = sqrt( + (car.x - ball.x) ** 2 + + (car.y - ball.y) ** 2 + ) + if dist <= self.min_dist_to_pass: + return Pass() + return None diff --git a/training/example_playlist.py b/training/example_playlist.py new file mode 100644 index 0000000..860ba20 --- /dev/null +++ b/training/example_playlist.py @@ -0,0 +1,12 @@ +import hello_world_training +import rlbottraining.common_exercises.bronze_goalie as bronze_goalie + +def make_default_playlist(): + exercises = ( + hello_world_training.make_default_playlist() + + bronze_goalie.make_default_playlist() + ) + for exercise in exercises: + exercise.match_config = hello_world_training.make_match_config_with_my_bot() + + return exercises diff --git a/training/hello_world_training.py b/training/hello_world_training.py new file mode 100644 index 0000000..f25bb2e --- /dev/null +++ b/training/hello_world_training.py @@ -0,0 +1,96 @@ +from pathlib import Path +from dataclasses import dataclass, field +from math import pi + +from rlbot.utils.game_state_util import GameState, BoostState, BallState, CarState, Physics, Vector3, Rotator +from rlbot.matchconfig.match_config import MatchConfig, PlayerConfig, Team +from rlbottraining.common_exercises.common_base_exercises import StrikerExercise +from rlbottraining.rng import SeededRandomNumberGenerator +from rlbottraining.match_configs import make_empty_match_config +from rlbottraining.grading.grader import Grader +from rlbottraining.training_exercise import TrainingExercise, Playlist + +import training_util +from drive_to_ball_grader import DriveToBallGrader + + +def make_match_config_with_my_bot() -> MatchConfig: + # Makes a config which only has our bot in it for now. + # For more defails: https://youtu.be/uGFmOZCpel8?t=375 + match_config = make_empty_match_config() + match_config.player_configs = [ + PlayerConfig.bot_config( + Path(__file__).absolute().parent.parent / 'python_example' / 'python_example.cfg', + Team.BLUE + ), + ] + return match_config + +@dataclass +class StrikerPatience(StrikerExercise): + """ + Drops the ball from a certain height, requiring the bot to not drive + underneath the ball until it's in reach. + """ + + car_start_x: float = 0 + + def make_game_state(self, rng: SeededRandomNumberGenerator) -> GameState: + return GameState( + ball=BallState(physics=Physics( + location=Vector3(0, 4400, 1000), + velocity=Vector3(0, 0, 200), + angular_velocity=Vector3(0, 0, 0))), + cars={ + 0: CarState( + physics=Physics( + location=Vector3(self.car_start_x, 3000, 0), + rotation=Rotator(0, pi / 2, 0), + velocity=Vector3(0, 0, 0), + angular_velocity=Vector3(0, 0, 0)), + jumped=False, + double_jumped=False, + boost_amount=0) + }, + boosts={i: BoostState(0) for i in range(34)}, + ) + +@dataclass +class DrivesToBallExercise(TrainingExercise): + """ + Checks that we drive to the ball when it's in the center of the field. + """ + grader: Grader = field(default_factory=DriveToBallGrader) + + def make_game_state(self, rng: SeededRandomNumberGenerator) -> GameState: + return GameState( + ball=BallState(physics=Physics( + location=Vector3(0, 0, 100), + velocity=Vector3(0, 0, 0), + angular_velocity=Vector3(0, 0, 0))), + cars={ + 0: CarState( + physics=Physics( + location=Vector3(0, 2000, 0), + rotation=Rotator(0, -pi / 2, 0), + velocity=Vector3(0, 0, 0), + angular_velocity=Vector3(0, 0, 0)), + jumped=False, + double_jumped=False, + boost_amount=100) + }, + boosts={i: BoostState(0) for i in range(34)}, + ) + + +def make_default_playlist() -> Playlist: + exercises = [ + StrikerPatience('start perfectly center'), + StrikerPatience('start on the right', car_start_x=-1000), + DrivesToBallExercise('Get close to ball'), + DrivesToBallExercise('Get close-ish to ball', grader=DriveToBallGrader(min_dist_to_pass=1000)) + ] + for exercise in exercises: + exercise.match_config = make_match_config_with_my_bot() + + return exercises diff --git a/training/training_util.py b/training/training_util.py new file mode 100644 index 0000000..4ac53da --- /dev/null +++ b/training/training_util.py @@ -0,0 +1,7 @@ +from rlbottraining.rng import SeededRandomNumberGenerator + +from rlbot.utils.game_state_util import Vector3 + + +def get_car_start_near_goal(rng: SeededRandomNumberGenerator) -> Vector3: + return Vector3(rng.uniform(1000, 2000), 3000, 0) diff --git a/training/unit_tests.py b/training/unit_tests.py new file mode 100644 index 0000000..a147c35 --- /dev/null +++ b/training/unit_tests.py @@ -0,0 +1,37 @@ +import unittest + +from rlbot.training.training import Pass, Fail +from rlbottraining.exercise_runner import run_playlist + +from hello_world_training import StrikerPatience + +class PatienceTest(unittest.TestCase): + """ + These units check that this bot behaves as we expect, + with regards to the StrikerPatience exercise. + + By default, the bot isn't very smart so it'll fail in the cases where + patience is required but passes in cases where no patience is required. + + Tutorial: + https://youtu.be/hCw250aGN8c?list=PL6LKXu1RlPdxh9vxmG1y2sghQwK47_gCH&t=187 + """ + + def test_patience_required(self): + result_iter = run_playlist([StrikerPatience(name='patience required')]) + results = list(result_iter) + self.assertEqual(len(results), 1) + result = results[0] + self.assertEqual(result.exercise.name, 'patience required') + self.assertIsInstance(result.grade, Fail) # If you make the bot is smarter, update this assert that we pass. + + def test_no_patience_required(self): + result_iter = run_playlist([StrikerPatience(name='no patience required', car_start_x=-1000)]) + results = list(result_iter) + self.assertEqual(len(results), 1) + result = results[0] + self.assertEqual(result.exercise.name, 'no patience required') + self.assertIsInstance(result.grade, Pass) + +if __name__ == '__main__': + unittest.main() From 5c263e0736589eb440ed0fae5917b8c93778e485 Mon Sep 17 00:00:00 2001 From: Ken Colton Date: Fri, 15 Mar 2019 20:41:54 -0400 Subject: [PATCH 04/30] Update readme & link to python 3.7 (#22) The [rlbottraining](https://pypi.org/project/rlbottraining/) dependency from requirements.txt now requires Python 3.7. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b1435e5..8f22427 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ https://www.youtube.com/watch?v=UjsQFNN0nSA ### Plain instructions -1. Make sure you've installed [Python 3.6 64 bit](https://www.python.org/ftp/python/3.6.5/python-3.6.5-amd64.exe). During installation: +1. Make sure you've installed [Python 3.7 64 bit](https://www.python.org/downloads/). During installation: - Select "Add Python to PATH" - Make sure pip is included in the installation 2. Open Rocket League From 2066e39e2f0322a2393b1e1b739cc49275537fb5 Mon Sep 17 00:00:00 2001 From: Tyler Date: Tue, 11 Jun 2019 19:37:42 -0700 Subject: [PATCH 05/30] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 8f22427..a5eb46d 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,6 @@ https://www.youtube.com/watch?v=UjsQFNN0nSA 1. Make sure you've installed [Python 3.7 64 bit](https://www.python.org/downloads/). During installation: - Select "Add Python to PATH" - Make sure pip is included in the installation -2. Open Rocket League 3. Download or clone this repository 3. In the files from the previous step, find and double click on run-gui.bat 4. Click the 'Run' button From b65d3f66081c190b32cb71bf39edbfe2c30a0067 Mon Sep 17 00:00:00 2001 From: Dominik Schmid Date: Sun, 16 Jun 2019 19:13:32 +1000 Subject: [PATCH 06/30] Make numbered bullet points consistent --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a5eb46d..8134594 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,9 @@ https://www.youtube.com/watch?v=UjsQFNN0nSA 1. Make sure you've installed [Python 3.7 64 bit](https://www.python.org/downloads/). During installation: - Select "Add Python to PATH" - Make sure pip is included in the installation -3. Download or clone this repository -3. In the files from the previous step, find and double click on run-gui.bat -4. Click the 'Run' button +1. Download or clone this repository +1. In the files from the previous step, find and double click on run-gui.bat +1. Click the 'Run' button ## Changing the bot From 5777fcac95435fe1ecc374ef4b3a63d0a037836e Mon Sep 17 00:00:00 2001 From: NicEastvillage Date: Wed, 3 Jul 2019 23:44:58 +0200 Subject: [PATCH 07/30] Added Vec3 with essential functionality (#25) --- python_example/python_example.py | 60 ++++++++---------- python_example/util/orientation.py | 47 ++++++++++++++ python_example/util/vec.py | 98 ++++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+), 35 deletions(-) create mode 100644 python_example/util/orientation.py create mode 100644 python_example/util/vec.py diff --git a/python_example/python_example.py b/python_example/python_example.py index 60fd081..ccaabfe 100644 --- a/python_example/python_example.py +++ b/python_example/python_example.py @@ -3,22 +3,29 @@ from rlbot.agents.base_agent import BaseAgent, SimpleControllerState from rlbot.utils.structures.game_data_struct import GameTickPacket +from util.orientation import Orientation +from util.vec import Vec3 + class PythonExample(BaseAgent): def initialize_agent(self): - #This runs once before the bot starts up + # This runs once before the bot starts up self.controller_state = SimpleControllerState() def get_output(self, packet: GameTickPacket) -> SimpleControllerState: - ball_location = Vector2(packet.game_ball.physics.location.x, packet.game_ball.physics.location.y) + ball_location = Vec3(packet.game_ball.physics.location) my_car = packet.game_cars[self.index] - car_location = Vector2(my_car.physics.location.x, my_car.physics.location.y) - car_direction = get_car_facing_vector(my_car) + car_location = Vec3(my_car.physics.location) + car_to_ball = ball_location - car_location - steer_correction_radians = car_direction.correction_to(car_to_ball) + # Find the direction of our car using the Orientation class + car_orientation = Orientation(my_car.physics.rotation) + car_direction = car_orientation.forward + + steer_correction_radians = find_correction(car_direction, car_to_ball) if steer_correction_radians > 0: # Positive radians in the unit circle is a turn to the left. @@ -35,42 +42,25 @@ def get_output(self, packet: GameTickPacket) -> SimpleControllerState: return self.controller_state -class Vector2: - def __init__(self, x=0, y=0): - self.x = float(x) - self.y = float(y) - - def __add__(self, val): - return Vector2(self.x + val.x, self.y + val.y) - def __sub__(self, val): - return Vector2(self.x - val.x, self.y - val.y) +def find_correction(current: Vec3, ideal: Vec3) -> float: + # Finds the angle from current to ideal vector in the xy-plane. Angle will be between -pi and +pi. - def correction_to(self, ideal): - # The in-game axes are left handed, so use -x - current_in_radians = math.atan2(self.y, -self.x) - ideal_in_radians = math.atan2(ideal.y, -ideal.x) + # The in-game axes are left handed, so use -x + current_in_radians = math.atan2(current.y, -current.x) + ideal_in_radians = math.atan2(ideal.y, -ideal.x) - correction = ideal_in_radians - current_in_radians + diff = ideal_in_radians - current_in_radians - # Make sure we go the 'short way' - if abs(correction) > math.pi: - if correction < 0: - correction += 2 * math.pi - else: - correction -= 2 * math.pi - - return correction - - -def get_car_facing_vector(car): - pitch = float(car.physics.rotation.pitch) - yaw = float(car.physics.rotation.yaw) + # Make sure that diff is between -pi and +pi. + if abs(diff) > math.pi: + if diff < 0: + diff += 2 * math.pi + else: + diff -= 2 * math.pi - facing_x = math.cos(pitch) * math.cos(yaw) - facing_y = math.cos(pitch) * math.sin(yaw) + return diff - return Vector2(facing_x, facing_y) def draw_debug(renderer, car, ball, action_display): renderer.begin_rendering() diff --git a/python_example/util/orientation.py b/python_example/util/orientation.py new file mode 100644 index 0000000..2cf2e9a --- /dev/null +++ b/python_example/util/orientation.py @@ -0,0 +1,47 @@ +import math + +from util.vec import Vec3 + + +# This is a helper class for calculating directions relative to your car. You can extend it or delete if you want. +class Orientation: + """ + This class describes the orientation of an object from the rotation of the object. + Use this to find the direction of cars: forward, right, up. + It can also be used to find relative locations. + """ + + def __init__(self, rotation): + self.yaw = float(rotation.yaw) + self.roll = float(rotation.roll) + self.pitch = float(rotation.pitch) + + cr = math.cos(self.roll) + sr = math.sin(self.roll) + cp = math.cos(self.pitch) + sp = math.sin(self.pitch) + cy = math.cos(self.yaw) + sy = math.sin(self.yaw) + + self.forward = Vec3(cp * cy, cp * sy, sp) + self.right = Vec3(cy*sp*sr-cr*sy, sy*sp*sr+cr*cy, -cp*sr) + self.up = Vec3(-cr*cy*sp-sr*sy, -cr*sy*sp+sr*cy, cp*cr) + + +# Sometimes things are easier, when everything is seen from your point of view. +# This function lets you make any location the center of the world. +# For example, set center to your car's location and ori to your car's orientation, then the target will be +# relative to your car! +def relative_location(center: Vec3, ori: Orientation, target: Vec3) -> Vec3: + """ + Returns target as a relative location from center's point of view, using the given orientation. The components of + the returned vector describes: + + * x: how far in front + * y: how far right + * z: how far above + """ + x = (target - center).dot(ori.forward) + y = (target - center).dot(ori.right) + z = (target - center).dot(ori.up) + return Vec3(x, y, z) diff --git a/python_example/util/vec.py b/python_example/util/vec.py new file mode 100644 index 0000000..6850e4f --- /dev/null +++ b/python_example/util/vec.py @@ -0,0 +1,98 @@ +import math + + +# This is a helper class for vector math. You can extend it or delete if you want. +class Vec3: + """ + This class should provide you with all the basic vector operations that you need, but feel free to extend its + functionality when needed. + The vectors found in the GameTickPacket will be flatbuffer vectors. Cast them to Vec3 like this: + `car_location = Vec3(car.physics.location)`. + + Remember that the in-game axis are left-handed. + + When in doubt visit the wiki: https://github.com/RLBot/RLBot/wiki/Useful-Game-Values + """ + + def __init__(self, x: float or 'Vec3'=0, y: float=0, z: float=0): + """ + Create a new Vec3. The x component can alternatively be another vector with an x, y, and z component, in which + case the created vector is a copy of the given vector and the y and z parameter is ignored. Examples: + + a = Vec3(1, 2, 3) + + b = Vec3(a) + + """ + + if hasattr(x, 'x'): + # We have been given a vector. Copy it + self.x = float(x.x) + self.y = float(x.y) if hasattr(x, 'y') else 0 + self.z = float(x.z) if hasattr(x, 'z') else 0 + else: + self.x = float(x) + self.y = float(y) + self.z = float(z) + + def __getitem__(self, item: int): + return (self.x, self.y, self.z)[item] + + def __add__(self, other: 'Vec3') -> 'Vec3': + return Vec3(self.x + other.x, self.y + other.y, self.z + other.z) + + def __sub__(self, other: 'Vec3') -> 'Vec3': + return Vec3(self.x - other.x, self.y - other.y, self.z - other.z) + + def __neg__(self): + return Vec3(-self.x, -self.y, -self.z) + + def __mul__(self, scale: float) -> 'Vec3': + return Vec3(self.x * scale, self.y * scale, self.z * scale) + + def __rmul__(self, scale): + return self * scale + + def __truediv__(self, scale: float) -> 'Vec3': + scale = 1 / float(scale) + return self * scale + + def __str__(self): + return "Vec3(" + str(self.x) + ", " + str(self.y) + ", " + str(self.z) + ")" + + def flat(self): + """Returns a new Vec3 that equals this Vec3 but projected onto the ground plane. I.e. where z=0.""" + return Vec3(self.x, self.y, 0) + + def length(self): + """Returns the length of the vector. Also called magnitude and norm.""" + return math.sqrt(self.x**2 + self.y**2 + self.z**2) + + def dist(self, other: 'Vec3') -> float: + """Returns the distance between this vector and another vector using pythagoras.""" + return (self - other).length() + + def normalized(self): + """Returns a vector with the same direction but a length of one.""" + return self / self.length() + + def rescale(self, new_len: float) -> 'Vec3': + """Returns a vector with the same direction but a different length.""" + return new_len * self.normalized() + + def dot(self, other: 'Vec3') -> float: + """Returns the dot product.""" + return self.x*other.x + self.y*other.y + self.z*other.z + + def cross(self, other: 'Vec3') -> 'Vec3': + """Returns the cross product.""" + return Vec3( + self.y * other.z - self.z * other.y, + self.z * other.x - self.x * other.z, + self.x * other.y - self.y * other.x + ) + + def ang_to(self, ideal: 'Vec3') -> float: + """Returns the angle to the ideal vector. Angle will be between 0 and pi.""" + cos_ang = self.dot(ideal) / (self.length() * ideal.length()) + return math.acos(cos_ang) From 037c76991ddb0030d452eb15b237f72255db20fd Mon Sep 17 00:00:00 2001 From: Tyler Date: Sat, 17 Aug 2019 08:34:11 -0700 Subject: [PATCH 08/30] Update README.md --- README.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8134594..7adc2f4 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,18 @@ # RLBotPythonExample Example of a python bot using the RLBot framework -## Installation +## Quick Start +The easiest way to start a python bot is demonstrated here! +https://youtu.be/YJ69QZ-EX7k -### Video guide +It shows you how to: +- Install the RLBot GUI +- Use it to create a new bot -https://www.youtube.com/watch?v=UjsQFNN0nSA -### Plain instructions +## The Hard Way + +https://www.youtube.com/watch?v=UjsQFNN0nSA 1. Make sure you've installed [Python 3.7 64 bit](https://www.python.org/downloads/). During installation: - Select "Add Python to PATH" From 1f142c19eca29dde45ff6121be200e59f2242300 Mon Sep 17 00:00:00 2001 From: Tyler Date: Thu, 29 Aug 2019 08:42:51 -0700 Subject: [PATCH 09/30] Update README.md --- README.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 7adc2f4..e486516 100644 --- a/README.md +++ b/README.md @@ -9,21 +9,22 @@ It shows you how to: - Install the RLBot GUI - Use it to create a new bot +## Changing the bot + +- Bot behavior is controlled by `python_example/python_example.py` +- Bot appearance is controlled by `python_example/appearance.cfg` + +See https://github.com/RLBot/RLBotPythonExample/wiki for documentation and tutorials. + +### Older Setup Technique -## The Hard Way +**Please don't do this unless you've followed the quick start video and it doesn't work!** https://www.youtube.com/watch?v=UjsQFNN0nSA -1. Make sure you've installed [Python 3.7 64 bit](https://www.python.org/downloads/). During installation: +1. Make sure you've installed [Python 3.7 64 bit](https://www.python.org/ftp/python/3.7.4/python-3.7.4-amd64.exe). During installation: - Select "Add Python to PATH" - Make sure pip is included in the installation 1. Download or clone this repository 1. In the files from the previous step, find and double click on run-gui.bat 1. Click the 'Run' button - -## Changing the bot - -- Bot behavior is controlled by `python_example/python_example.py` -- Bot appearance is controlled by `python_example/appearance.cfg` - -See https://github.com/RLBot/RLBotPythonExample/wiki for documentation and tutorials. From 36bb2c6e55a3a40851588185a5c93ef453791827 Mon Sep 17 00:00:00 2001 From: Tyler Date: Fri, 30 Aug 2019 20:27:01 -0700 Subject: [PATCH 10/30] Renaming python_example to 'bot' so people won't feel compelled to change it. (#28) --- rlbot.cfg | 20 +++++++++---------- {python_example => src}/__init__.py | 0 {python_example => src}/appearance.cfg | 0 .../python_example.cfg => src/bot.cfg | 4 ++-- .../python_example.py => src/bot.py | 0 {python_example => src}/util/orientation.py | 0 {python_example => src}/util/vec.py | 0 training/hello_world_training.py | 2 +- 8 files changed, 13 insertions(+), 13 deletions(-) rename {python_example => src}/__init__.py (100%) rename {python_example => src}/appearance.cfg (100%) rename python_example/python_example.cfg => src/bot.cfg (93%) rename python_example/python_example.py => src/bot.py (100%) rename {python_example => src}/util/orientation.py (100%) rename {python_example => src}/util/vec.py (100%) diff --git a/rlbot.cfg b/rlbot.cfg index 3424a1a..b8553d8 100644 --- a/rlbot.cfg +++ b/rlbot.cfg @@ -16,16 +16,16 @@ game_map = Mannfield [Participant Configuration] # Put the name of your bot config file here. Only num_participants config files will be read! # Everything needs a config, even players and default bots. We still set loadouts and names from config! -participant_config_0 = python_example/python_example.cfg -participant_config_1 = python_example/python_example.cfg -participant_config_2 = python_example/python_example.cfg -participant_config_3 = python_example/python_example.cfg -participant_config_4 = python_example/python_example.cfg -participant_config_5 = python_example/python_example.cfg -participant_config_6 = python_example/python_example.cfg -participant_config_7 = python_example/python_example.cfg -participant_config_8 = python_example/python_example.cfg -participant_config_9 = python_example/python_example.cfg +participant_config_0 = src/bot.cfg +participant_config_1 = src/bot.cfg +participant_config_2 = src/bot.cfg +participant_config_3 = src/bot.cfg +participant_config_4 = src/bot.cfg +participant_config_5 = src/bot.cfg +participant_config_6 = src/bot.cfg +participant_config_7 = src/bot.cfg +participant_config_8 = src/bot.cfg +participant_config_9 = src/bot.cfg # team 0 shoots on positive goal, team 1 shoots on negative goal participant_team_0 = 0 diff --git a/python_example/__init__.py b/src/__init__.py similarity index 100% rename from python_example/__init__.py rename to src/__init__.py diff --git a/python_example/appearance.cfg b/src/appearance.cfg similarity index 100% rename from python_example/appearance.cfg rename to src/appearance.cfg diff --git a/python_example/python_example.cfg b/src/bot.cfg similarity index 93% rename from python_example/python_example.cfg rename to src/bot.cfg index 99dd157..c27f0fb 100644 --- a/python_example/python_example.cfg +++ b/src/bot.cfg @@ -3,7 +3,7 @@ looks_config = ./appearance.cfg # Path to python file. Can use relative path from here. -python_file = ./python_example.py +python_file = ./bot.py # Name of the bot in-game name = PythonExampleBot @@ -18,7 +18,7 @@ description = This is a multi-line description of the official python example bot # Fun fact about the bot -fun_fact = +fun_fact = # Link to github repository github = https://github.com/RLBot/RLBotPythonExample diff --git a/python_example/python_example.py b/src/bot.py similarity index 100% rename from python_example/python_example.py rename to src/bot.py diff --git a/python_example/util/orientation.py b/src/util/orientation.py similarity index 100% rename from python_example/util/orientation.py rename to src/util/orientation.py diff --git a/python_example/util/vec.py b/src/util/vec.py similarity index 100% rename from python_example/util/vec.py rename to src/util/vec.py diff --git a/training/hello_world_training.py b/training/hello_world_training.py index f25bb2e..c1ea9e3 100644 --- a/training/hello_world_training.py +++ b/training/hello_world_training.py @@ -20,7 +20,7 @@ def make_match_config_with_my_bot() -> MatchConfig: match_config = make_empty_match_config() match_config.player_configs = [ PlayerConfig.bot_config( - Path(__file__).absolute().parent.parent / 'python_example' / 'python_example.cfg', + Path(__file__).absolute().parent.parent / 'src' / 'bot.cfg', Team.BLUE ), ] From 6227873e05a252f5b0b8020572d8312efd8bc12b Mon Sep 17 00:00:00 2001 From: Robbie <42142350+robbai@users.noreply.github.com> Date: Sun, 1 Sep 2019 22:36:11 +0100 Subject: [PATCH 11/30] Eradicate all traces of "python example" (#29) To match with the previous commit --- src/bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bot.py b/src/bot.py index ccaabfe..58511a2 100644 --- a/src/bot.py +++ b/src/bot.py @@ -7,7 +7,7 @@ from util.vec import Vec3 -class PythonExample(BaseAgent): +class MyBot(BaseAgent): def initialize_agent(self): # This runs once before the bot starts up From f73f0f80e3fc5f0537ce45cddbf591008bf3ee53 Mon Sep 17 00:00:00 2001 From: NicEastvillage Date: Mon, 2 Sep 2019 17:42:39 +0200 Subject: [PATCH 12/30] Removed reference to python_examplebot in readme. (#30) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e486516..3771948 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,8 @@ It shows you how to: ## Changing the bot -- Bot behavior is controlled by `python_example/python_example.py` -- Bot appearance is controlled by `python_example/appearance.cfg` +- Bot behavior is controlled by `src/bot.py` +- Bot appearance is controlled by `src/appearance.cfg` See https://github.com/RLBot/RLBotPythonExample/wiki for documentation and tutorials. From 083f6c50fddefa3c692892c8a1aafa8db23cfbdd Mon Sep 17 00:00:00 2001 From: Tyler Arehart Date: Mon, 18 Nov 2019 01:14:45 -0800 Subject: [PATCH 13/30] Using pipmain.main to fix #462 --- run.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/run.py b/run.py index a679076..6a8272e 100644 --- a/run.py +++ b/run.py @@ -18,7 +18,7 @@ logger.log(logging_utils.logging_level, 'Skipping upgrade check for now since it looks like you have no internet') elif public_utils.is_safe_to_upgrade(): - pipmain(['install', '-r', 'requirements.txt', '--upgrade', '--upgrade-strategy=eager']) + pipmain.main(['install', '-r', 'requirements.txt', '--upgrade', '--upgrade-strategy=eager']) # https://stackoverflow.com/a/44401013 rlbots = [module for module in sys.modules if module.startswith('rlbot')] @@ -26,7 +26,7 @@ sys.modules.pop(rlbot_module) except ImportError: - pipmain(['install', '-r', 'requirements.txt', '--upgrade', '--upgrade-strategy=eager']) + pipmain.main(['install', '-r', 'requirements.txt', '--upgrade', '--upgrade-strategy=eager']) try: if len(sys.argv) > 1 and sys.argv[1] == 'gui': From 13776388257f5e03a019ad8381ecc51d9b1b67d5 Mon Sep 17 00:00:00 2001 From: Tyler Arehart Date: Thu, 21 Nov 2019 19:19:35 -0800 Subject: [PATCH 14/30] Fixing the pipmain issue in a backwards compatible manner. --- run.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/run.py b/run.py index 6a8272e..60af312 100644 --- a/run.py +++ b/run.py @@ -6,6 +6,11 @@ except ImportError: from pip._internal import main as pipmain +# More pip changes breaking us. +main_fn = pipmain +if hasattr(pipmain, 'main'): + main_fn = pipmain.main + DEFAULT_LOGGER = 'rlbot' if __name__ == '__main__': @@ -18,7 +23,7 @@ logger.log(logging_utils.logging_level, 'Skipping upgrade check for now since it looks like you have no internet') elif public_utils.is_safe_to_upgrade(): - pipmain.main(['install', '-r', 'requirements.txt', '--upgrade', '--upgrade-strategy=eager']) + main_fn(['install', '-r', 'requirements.txt', '--upgrade', '--upgrade-strategy=eager']) # https://stackoverflow.com/a/44401013 rlbots = [module for module in sys.modules if module.startswith('rlbot')] @@ -26,7 +31,7 @@ sys.modules.pop(rlbot_module) except ImportError: - pipmain.main(['install', '-r', 'requirements.txt', '--upgrade', '--upgrade-strategy=eager']) + main_fn(['install', '-r', 'requirements.txt', '--upgrade', '--upgrade-strategy=eager']) try: if len(sys.argv) > 1 and sys.argv[1] == 'gui': From b5b2b1f27fcc672c47db353c335155d63bc2082e Mon Sep 17 00:00:00 2001 From: kipje13 <13234441+kipje13@users.noreply.github.com> Date: Thu, 26 Dec 2019 19:53:49 +0100 Subject: [PATCH 15/30] Call pip with the subprocess module. --- run.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/run.py b/run.py index 60af312..6a223de 100644 --- a/run.py +++ b/run.py @@ -1,16 +1,6 @@ +import subprocess import sys -# https://stackoverflow.com/a/51704613 -try: - from pip import main as pipmain -except ImportError: - from pip._internal import main as pipmain - -# More pip changes breaking us. -main_fn = pipmain -if hasattr(pipmain, 'main'): - main_fn = pipmain.main - DEFAULT_LOGGER = 'rlbot' if __name__ == '__main__': @@ -23,7 +13,7 @@ logger.log(logging_utils.logging_level, 'Skipping upgrade check for now since it looks like you have no internet') elif public_utils.is_safe_to_upgrade(): - main_fn(['install', '-r', 'requirements.txt', '--upgrade', '--upgrade-strategy=eager']) + subprocess.call([sys.executable, "-m", "pip", "install", '-r', 'requirements.txt', '--upgrade', '--upgrade-strategy=eager']) # https://stackoverflow.com/a/44401013 rlbots = [module for module in sys.modules if module.startswith('rlbot')] @@ -31,7 +21,7 @@ sys.modules.pop(rlbot_module) except ImportError: - main_fn(['install', '-r', 'requirements.txt', '--upgrade', '--upgrade-strategy=eager']) + subprocess.call([sys.executable, "-m", "pip", "install", '-r', 'requirements.txt', '--upgrade', '--upgrade-strategy=eager']) try: if len(sys.argv) > 1 and sys.argv[1] == 'gui': From b5eb38e5c68ca5043c8dacd40ee621c724d0ef8f Mon Sep 17 00:00:00 2001 From: Viliam Vadocz Date: Tue, 21 Jan 2020 04:23:49 +0100 Subject: [PATCH 16/30] 64 is the new 10 (#32) --- rlbot.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rlbot.cfg b/rlbot.cfg index b8553d8..7b7051a 100644 --- a/rlbot.cfg +++ b/rlbot.cfg @@ -5,7 +5,7 @@ # Visit https://github.com/RLBot/RLBot/wiki/Config-File-Documentation to see what you can put here. [Match Configuration] -# Number of bots/players which will be spawned. We support up to max 10. +# Number of bots/players which will be spawned. We support up to max 64. num_participants = 2 game_mode = Soccer game_map = Mannfield From c5603c7f2f561fed434a25dbbce3b01762fb9273 Mon Sep 17 00:00:00 2001 From: RLMarvin <35970829+RLMarvin@users.noreply.github.com> Date: Tue, 11 Feb 2020 00:19:40 +0100 Subject: [PATCH 17/30] Update bot.cfg (#34) --- src/bot.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/bot.cfg b/src/bot.cfg index c27f0fb..3aa2023 100644 --- a/src/bot.cfg +++ b/src/bot.cfg @@ -8,6 +8,9 @@ python_file = ./bot.py # Name of the bot in-game name = PythonExampleBot +# The maximum number of ticks per second that your bot wishes to receive. +maximum_tick_rate_preference = 120 + [Details] # These values are optional but useful metadata for helper programs # Name of the bot's creator/developer From 98119c2b0ece83491d09d2b40206bceaea98eb39 Mon Sep 17 00:00:00 2001 From: Tyler Date: Sun, 17 May 2020 18:32:22 -0700 Subject: [PATCH 18/30] Setting enable_rendering and enable_state_setting to true --- rlbot.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rlbot.cfg b/rlbot.cfg index 7b7051a..a568454 100644 --- a/rlbot.cfg +++ b/rlbot.cfg @@ -5,10 +5,13 @@ # Visit https://github.com/RLBot/RLBot/wiki/Config-File-Documentation to see what you can put here. [Match Configuration] +# Visit https://github.com/RLBot/RLBot/wiki/Config-File-Documentation to see what you can put here. # Number of bots/players which will be spawned. We support up to max 64. num_participants = 2 game_mode = Soccer game_map = Mannfield +enable_rendering = True +enable_state_setting = True [Mutator Configuration] # Visit https://github.com/RLBot/RLBot/wiki/Config-File-Documentation to see what you can put here. From 63e71b2e493fb60845d4049a5858f1877c849517 Mon Sep 17 00:00:00 2001 From: Robbie <42142350+robbai@users.noreply.github.com> Date: Thu, 21 May 2020 01:11:53 +0100 Subject: [PATCH 19/30] pylint fixes (#42) Co-authored-by: L0laapk3 --- .env | 1 + .gitignore | 3 --- .pylintrc | 2 ++ src/util/__init__.py | 0 4 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 .env create mode 100644 .pylintrc create mode 100644 src/util/__init__.py diff --git a/.env b/.env new file mode 100644 index 0000000..56a282d --- /dev/null +++ b/.env @@ -0,0 +1 @@ +PYTHONPATH=src diff --git a/.gitignore b/.gitignore index 51410e9..5c8c774 100644 --- a/.gitignore +++ b/.gitignore @@ -79,9 +79,6 @@ celerybeat-schedule # SageMath parsed files *.sage.py -# dotenv -.env - # virtualenv .venv venv/ diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..9db66d4 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,2 @@ +[TYPECHECK] +generated-members=QuickChats.* diff --git a/src/util/__init__.py b/src/util/__init__.py new file mode 100644 index 0000000..e69de29 From eb4a22ea0e38bd200886025c3aafce03b32e8fdd Mon Sep 17 00:00:00 2001 From: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com> Date: Sat, 6 Jun 2020 21:43:27 -0700 Subject: [PATCH 20/30] :sparkles: Mac/linux scripts (#41) --- run-gui.sh | 5 +++++ run.sh | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 run-gui.sh create mode 100644 run.sh diff --git a/run-gui.sh b/run-gui.sh new file mode 100644 index 0000000..89c6a0b --- /dev/null +++ b/run-gui.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +cd "$(dirname "$0")" + +python3 ./run.py gui \ No newline at end of file diff --git a/run.sh b/run.sh new file mode 100644 index 0000000..4598972 --- /dev/null +++ b/run.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +cd "$(dirname "$0")" + +python3 ./run.py \ No newline at end of file From 375f69780067e5048f0b001be3b5b96c34dc3454 Mon Sep 17 00:00:00 2001 From: Viliam Vadocz Date: Fri, 14 Feb 2020 09:07:37 +0100 Subject: [PATCH 21/30] Vec3 improvements (#37) --- .gitignore | 6 +++++- src/util/vec.py | 26 +++++++++++++++++++------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 5c8c774..122e90e 100644 --- a/.gitignore +++ b/.gitignore @@ -105,4 +105,8 @@ ENV/ /build # Gradle files -/.gradle \ No newline at end of file +/.gradle + +# VSCode +.vscode/ +*.code-workspace diff --git a/src/util/vec.py b/src/util/vec.py index 6850e4f..055bc93 100644 --- a/src/util/vec.py +++ b/src/util/vec.py @@ -1,4 +1,5 @@ import math +from typing import Union # This is a helper class for vector math. You can extend it or delete if you want. @@ -13,8 +14,14 @@ class Vec3: When in doubt visit the wiki: https://github.com/RLBot/RLBot/wiki/Useful-Game-Values """ - - def __init__(self, x: float or 'Vec3'=0, y: float=0, z: float=0): + # https://docs.python.org/3/reference/datamodel.html#slots + __slots__ = [ + 'x', + 'y', + 'z' + ] + + def __init__(self, x: Union[float, 'Vec3']=0, y: float=0, z: float=0): """ Create a new Vec3. The x component can alternatively be another vector with an x, y, and z component, in which case the created vector is a copy of the given vector and the y and z parameter is ignored. Examples: @@ -58,27 +65,32 @@ def __truediv__(self, scale: float) -> 'Vec3': return self * scale def __str__(self): - return "Vec3(" + str(self.x) + ", " + str(self.y) + ", " + str(self.z) + ")" + return f"Vec3({self.x:.2f}, {self.y:.2f}, {self.z:.2f})" + + def __repr__(self): + return self.__str__() def flat(self): """Returns a new Vec3 that equals this Vec3 but projected onto the ground plane. I.e. where z=0.""" return Vec3(self.x, self.y, 0) + @property def length(self): """Returns the length of the vector. Also called magnitude and norm.""" return math.sqrt(self.x**2 + self.y**2 + self.z**2) def dist(self, other: 'Vec3') -> float: """Returns the distance between this vector and another vector using pythagoras.""" - return (self - other).length() + return (self - other).length + @property def normalized(self): """Returns a vector with the same direction but a length of one.""" - return self / self.length() + return self / self.length def rescale(self, new_len: float) -> 'Vec3': """Returns a vector with the same direction but a different length.""" - return new_len * self.normalized() + return new_len * self.normalized def dot(self, other: 'Vec3') -> float: """Returns the dot product.""" @@ -94,5 +106,5 @@ def cross(self, other: 'Vec3') -> 'Vec3': def ang_to(self, ideal: 'Vec3') -> float: """Returns the angle to the ideal vector. Angle will be between 0 and pi.""" - cos_ang = self.dot(ideal) / (self.length() * ideal.length()) + cos_ang = self.dot(ideal) / (self.length * ideal.length) return math.acos(cos_ang) From 986dbca5d3e7174256c9f3fb18147e8a5471a51a Mon Sep 17 00:00:00 2001 From: Tyler Arehart Date: Sat, 6 Jun 2020 20:27:59 -0700 Subject: [PATCH 22/30] New version of example bot which is more advanced and easier to learn from. --- README.md | 13 --- run.py | 3 +- src/bot.py | 117 ++++++++++++++------------- src/util/ball_prediction_analysis.py | 33 ++++++++ src/util/drive.py | 25 ++++++ src/util/sequence.py | 49 +++++++++++ src/util/vec.py | 15 ++-- 7 files changed, 177 insertions(+), 78 deletions(-) create mode 100644 src/util/ball_prediction_analysis.py create mode 100644 src/util/drive.py create mode 100644 src/util/sequence.py diff --git a/README.md b/README.md index 3771948..9c32c22 100644 --- a/README.md +++ b/README.md @@ -15,16 +15,3 @@ It shows you how to: - Bot appearance is controlled by `src/appearance.cfg` See https://github.com/RLBot/RLBotPythonExample/wiki for documentation and tutorials. - -### Older Setup Technique - -**Please don't do this unless you've followed the quick start video and it doesn't work!** - -https://www.youtube.com/watch?v=UjsQFNN0nSA - -1. Make sure you've installed [Python 3.7 64 bit](https://www.python.org/ftp/python/3.7.4/python-3.7.4-amd64.exe). During installation: - - Select "Add Python to PATH" - - Make sure pip is included in the installation -1. Download or clone this repository -1. In the files from the previous step, find and double click on run-gui.bat -1. Click the 'Run' button diff --git a/run.py b/run.py index 6a223de..061aa9c 100644 --- a/run.py +++ b/run.py @@ -13,7 +13,8 @@ logger.log(logging_utils.logging_level, 'Skipping upgrade check for now since it looks like you have no internet') elif public_utils.is_safe_to_upgrade(): - subprocess.call([sys.executable, "-m", "pip", "install", '-r', 'requirements.txt', '--upgrade', '--upgrade-strategy=eager']) + subprocess.call([sys.executable, "-m", "pip", "install", '-r', 'requirements.txt']) + subprocess.call([sys.executable, "-m", "pip", "install", 'rlbot', '--upgrade']) # https://stackoverflow.com/a/44401013 rlbots = [module for module in sys.modules if module.startswith('rlbot')] diff --git a/src/bot.py b/src/bot.py index 58511a2..0d6ef84 100644 --- a/src/bot.py +++ b/src/bot.py @@ -1,71 +1,76 @@ -import math - from rlbot.agents.base_agent import BaseAgent, SimpleControllerState +from rlbot.messages.flat.QuickChatSelection import QuickChatSelection from rlbot.utils.structures.game_data_struct import GameTickPacket -from util.orientation import Orientation +from util.ball_prediction_analysis import find_slice_at_time +from util.drive import steer_toward_target +from util.sequence import Sequence, ControlStep from util.vec import Vec3 class MyBot(BaseAgent): - def initialize_agent(self): - # This runs once before the bot starts up - self.controller_state = SimpleControllerState() + def __init__(self, name, team, index): + super().__init__(name, team, index) + self.active_sequence: Sequence = None def get_output(self, packet: GameTickPacket) -> SimpleControllerState: - ball_location = Vec3(packet.game_ball.physics.location) - - my_car = packet.game_cars[self.index] - car_location = Vec3(my_car.physics.location) - - car_to_ball = ball_location - car_location - - # Find the direction of our car using the Orientation class - car_orientation = Orientation(my_car.physics.rotation) - car_direction = car_orientation.forward - - steer_correction_radians = find_correction(car_direction, car_to_ball) - - if steer_correction_radians > 0: - # Positive radians in the unit circle is a turn to the left. - turn = -1.0 # Negative value for a turn to the left. - action_display = "turn left" - else: - turn = 1.0 - action_display = "turn right" + """ + This function will be called by the framework many times per second. This is where you can + see the motion of the ball, etc. and return controls to drive your car. + """ - self.controller_state.throttle = 1.0 - self.controller_state.steer = turn + # Start the renderer. Make sure you call end_rendering at the end of this function. + self.renderer.begin_rendering() - draw_debug(self.renderer, my_car, packet.game_ball, action_display) + # This is good to keep at the beginning of get_output. It will allow you to continue + # any sequences that you may have started during a previous call to get_output. + if self.active_sequence and not self.active_sequence.done: + return self.active_sequence.tick(packet) - return self.controller_state - - -def find_correction(current: Vec3, ideal: Vec3) -> float: - # Finds the angle from current to ideal vector in the xy-plane. Angle will be between -pi and +pi. - - # The in-game axes are left handed, so use -x - current_in_radians = math.atan2(current.y, -current.x) - ideal_in_radians = math.atan2(ideal.y, -ideal.x) - - diff = ideal_in_radians - current_in_radians + # Gather some information about our car and the ball + my_car = packet.game_cars[self.index] + car_location = Vec3(my_car.physics.location) + car_velocity = Vec3(my_car.physics.velocity) + ball_location = Vec3(packet.game_ball.physics.location) - # Make sure that diff is between -pi and +pi. - if abs(diff) > math.pi: - if diff < 0: - diff += 2 * math.pi + if car_location.dist(ball_location) > 1000: + # We're far away from the ball, let's try to lead it a little bit + ball_prediction = self.get_ball_prediction_struct() # This can predict bounces, etc + ball_in_future = find_slice_at_time(ball_prediction, packet.game_info.seconds_elapsed + 2) + target_location = Vec3(ball_in_future.physics.location) else: - diff -= 2 * math.pi - - return diff - - -def draw_debug(renderer, car, ball, action_display): - renderer.begin_rendering() - # draw a line from the car to the ball - renderer.draw_line_3d(car.physics.location, ball.physics.location, renderer.white()) - # print the action that the bot is taking - renderer.draw_string_3d(car.physics.location, 2, 2, action_display, renderer.white()) - renderer.end_rendering() + target_location = ball_location + + # Draw some things to help understand what the bot is thinking + self.renderer.draw_line_3d(car_location, target_location, self.renderer.white()) + self.renderer.draw_string_3d(car_location, 1, 1, f'Speed: {car_velocity.length():.1f}', self.renderer.white()) + + if 750 < car_velocity.length() < 800: + # We'll do a front flip if the car is moving at a certain speed. + return self.begin_front_flip(packet) + + controls = SimpleControllerState() + controls.steer = steer_toward_target(my_car, target_location) + controls.throttle = 1.0 + # You can set more controls if you want, like controls.boost. + + # Send any drawing we may have done + self.renderer.end_rendering() + return controls + + def begin_front_flip(self, packet): + # Send some quickchat just for fun + self.send_quick_chat(team_only=False, quick_chat=QuickChatSelection.Information_IGotIt) + + # Do a front flip. We will be committed to this for a few seconds and the bot will ignore other + # logic during that time because we are setting the active_sequence. + self.active_sequence = Sequence([ + ControlStep(duration=0.05, controls=SimpleControllerState(jump=True)), + ControlStep(duration=0.05, controls=SimpleControllerState(jump=False)), + ControlStep(duration=0.2, controls=SimpleControllerState(jump=True, pitch=-1)), + ControlStep(duration=0.8, controls=SimpleControllerState()), + ]) + + # Return the controls associated with the beginning of the sequence so we can start right away. + return self.active_sequence.tick(packet) diff --git a/src/util/ball_prediction_analysis.py b/src/util/ball_prediction_analysis.py new file mode 100644 index 0000000..8ed0fc1 --- /dev/null +++ b/src/util/ball_prediction_analysis.py @@ -0,0 +1,33 @@ +from typing import Callable + +from rlbot.utils.structures.ball_prediction_struct import BallPrediction, Slice + +# field length(5120) + ball radius(93) = 5213 however that results in false positives +GOAL_THRESHOLD = 5235 + +# We will jump this number of frames when looking for a moment where the ball is inside the goal. +# Big number for efficiency, but not so big that the ball could go in and then back out during that +# time span. Unit is the number of frames in the ball prediction, and the prediction is at 60 frames per second. +COARSE_SEARCH_INCREMENT = 20 + + +def find_slice_at_time(ball_prediction: BallPrediction, game_time: float): + start_time = ball_prediction.slices[0].game_seconds + approx_index = int((game_time - start_time) * 60) # We know that there are 60 slices per second. + if 0 <= approx_index < ball_prediction.num_slices: + return ball_prediction.slices[approx_index] + return None + + +def predict_future_goal(ball_prediction: BallPrediction): + return find_matching_slice(ball_prediction, 0, lambda s: abs(s.physics.location.y) >= GOAL_THRESHOLD) + + +def find_matching_slice(ball_prediction: BallPrediction, start_index: int, predicate: Callable[[Slice], bool]): + for coarse_index in range(start_index, ball_prediction.num_slices, COARSE_SEARCH_INCREMENT): + if predicate(ball_prediction.slices[coarse_index]): + for j in range(max(start_index, coarse_index - COARSE_SEARCH_INCREMENT), coarse_index): + ball_slice = ball_prediction.slices[j] + if predicate(ball_slice): + return ball_slice + return None diff --git a/src/util/drive.py b/src/util/drive.py new file mode 100644 index 0000000..b89160b --- /dev/null +++ b/src/util/drive.py @@ -0,0 +1,25 @@ +import math + +from rlbot.utils.structures.game_data_struct import PlayerInfo + +from util.orientation import Orientation, relative_location +from util.vec import Vec3 + + +def limit_to_safe_range(value: float) -> float: + """ + Controls like throttle, steer, pitch, yaw, and roll need to be in the range of -1 to 1. + This will ensure your number is in that range. Something like 0.45 will stay as it is, + but a value of -5.6 would be changed to -1. + """ + if value < -1: + return -1 + if value > 1: + return 1 + return value + + +def steer_toward_target(car: PlayerInfo, target: Vec3) -> float: + relative = relative_location(Vec3(car.physics.location), Orientation(car.physics.rotation), target) + angle = math.atan2(relative.y, relative.x) + return limit_to_safe_range(angle * 5) diff --git a/src/util/sequence.py b/src/util/sequence.py new file mode 100644 index 0000000..5ebae2d --- /dev/null +++ b/src/util/sequence.py @@ -0,0 +1,49 @@ +from dataclasses import dataclass +from typing import List + +from rlbot.agents.base_agent import SimpleControllerState +from rlbot.utils.structures.game_data_struct import GameTickPacket + + +@dataclass +class StepResult: + controls: SimpleControllerState + done: bool + + +class Step: + def tick(self, packet: GameTickPacket) -> StepResult: + raise NotImplementedError + + +class ControlStep(Step): + def __init__(self, duration: float, controls: SimpleControllerState): + self.duration = duration + self.controls = controls + self.start_time: float = None + + def tick(self, packet: GameTickPacket) -> StepResult: + if self.start_time is None: + self.start_time = packet.game_info.seconds_elapsed + elapsed_time = packet.game_info.seconds_elapsed - self.start_time + return StepResult(controls=self.controls, done=elapsed_time > self.duration) + + +class Sequence: + def __init__(self, steps: List[Step]): + self.steps = steps + self.index = 0 + self.done = False + + def tick(self, packet: GameTickPacket): + while True: + if self.index >= len(self.steps): + self.done = True + return SimpleControllerState() + step = self.steps[self.index] + result = step.tick(packet) + if result.done: + self.index += 1 + if self.index >= len(self.steps): + self.done = True + return result.controls diff --git a/src/util/vec.py b/src/util/vec.py index 055bc93..9ce74ac 100644 --- a/src/util/vec.py +++ b/src/util/vec.py @@ -1,8 +1,9 @@ import math from typing import Union +from rlbot.utils.structures.game_data_struct import Vector3 + -# This is a helper class for vector math. You can extend it or delete if you want. class Vec3: """ This class should provide you with all the basic vector operations that you need, but feel free to extend its @@ -21,7 +22,7 @@ class Vec3: 'z' ] - def __init__(self, x: Union[float, 'Vec3']=0, y: float=0, z: float=0): + def __init__(self, x: Union[float, 'Vec3', 'Vector3']=0, y: float=0, z: float=0): """ Create a new Vec3. The x component can alternatively be another vector with an x, y, and z component, in which case the created vector is a copy of the given vector and the y and z parameter is ignored. Examples: @@ -74,23 +75,21 @@ def flat(self): """Returns a new Vec3 that equals this Vec3 but projected onto the ground plane. I.e. where z=0.""" return Vec3(self.x, self.y, 0) - @property def length(self): """Returns the length of the vector. Also called magnitude and norm.""" return math.sqrt(self.x**2 + self.y**2 + self.z**2) def dist(self, other: 'Vec3') -> float: """Returns the distance between this vector and another vector using pythagoras.""" - return (self - other).length + return (self - other).length() - @property def normalized(self): """Returns a vector with the same direction but a length of one.""" - return self / self.length + return self / self.length() def rescale(self, new_len: float) -> 'Vec3': """Returns a vector with the same direction but a different length.""" - return new_len * self.normalized + return new_len * self.normalized() def dot(self, other: 'Vec3') -> float: """Returns the dot product.""" @@ -106,5 +105,5 @@ def cross(self, other: 'Vec3') -> 'Vec3': def ang_to(self, ideal: 'Vec3') -> float: """Returns the angle to the ideal vector. Angle will be between 0 and pi.""" - cos_ang = self.dot(ideal) / (self.length * ideal.length) + cos_ang = self.dot(ideal) / (self.length() * ideal.length()) return math.acos(cos_ang) From ce1db1916758a2df8a44a18b0d30f347f204ba23 Mon Sep 17 00:00:00 2001 From: Tyler Arehart Date: Sun, 7 Jun 2020 12:19:23 -0700 Subject: [PATCH 23/30] Boost pad tracker, improved sequence, relying on framework to begin and end rendering. --- src/bot.py | 20 +++++++++---- src/util/ball_prediction_analysis.py | 29 ++++++++++++++----- src/util/boost_pad_tracker.py | 43 ++++++++++++++++++++++++++++ src/util/sequence.py | 26 +++++++++++++---- 4 files changed, 99 insertions(+), 19 deletions(-) create mode 100644 src/util/boost_pad_tracker.py diff --git a/src/bot.py b/src/bot.py index 0d6ef84..3e2aa43 100644 --- a/src/bot.py +++ b/src/bot.py @@ -3,6 +3,7 @@ from rlbot.utils.structures.game_data_struct import GameTickPacket from util.ball_prediction_analysis import find_slice_at_time +from util.boost_pad_tracker import BoostPadTracker from util.drive import steer_toward_target from util.sequence import Sequence, ControlStep from util.vec import Vec3 @@ -13,6 +14,11 @@ class MyBot(BaseAgent): def __init__(self, name, team, index): super().__init__(name, team, index) self.active_sequence: Sequence = None + self.boost_pad_tracker = BoostPadTracker() + + def initialize_agent(self): + # Set up information about the boost pads now that the game is active and the info is available + self.boost_pad_tracker.initialize_boosts(self.get_field_info()) def get_output(self, packet: GameTickPacket) -> SimpleControllerState: """ @@ -20,13 +26,15 @@ def get_output(self, packet: GameTickPacket) -> SimpleControllerState: see the motion of the ball, etc. and return controls to drive your car. """ - # Start the renderer. Make sure you call end_rendering at the end of this function. - self.renderer.begin_rendering() + # Keep our boost pad info updated with which pads are currently active + self.boost_pad_tracker.update_boost_status(packet) # This is good to keep at the beginning of get_output. It will allow you to continue # any sequences that you may have started during a previous call to get_output. if self.active_sequence and not self.active_sequence.done: - return self.active_sequence.tick(packet) + controls = self.active_sequence.tick(packet) + if controls is not None: + return controls # Gather some information about our car and the ball my_car = packet.game_cars[self.index] @@ -34,17 +42,19 @@ def get_output(self, packet: GameTickPacket) -> SimpleControllerState: car_velocity = Vec3(my_car.physics.velocity) ball_location = Vec3(packet.game_ball.physics.location) - if car_location.dist(ball_location) > 1000: + if car_location.dist(ball_location) > 1500: # We're far away from the ball, let's try to lead it a little bit ball_prediction = self.get_ball_prediction_struct() # This can predict bounces, etc ball_in_future = find_slice_at_time(ball_prediction, packet.game_info.seconds_elapsed + 2) target_location = Vec3(ball_in_future.physics.location) + self.renderer.draw_line_3d(ball_location, target_location, self.renderer.cyan()) else: target_location = ball_location # Draw some things to help understand what the bot is thinking self.renderer.draw_line_3d(car_location, target_location, self.renderer.white()) self.renderer.draw_string_3d(car_location, 1, 1, f'Speed: {car_velocity.length():.1f}', self.renderer.white()) + self.renderer.draw_rect_3d(target_location, 8, 8, True, self.renderer.cyan(), centered=True) if 750 < car_velocity.length() < 800: # We'll do a front flip if the car is moving at a certain speed. @@ -55,8 +65,6 @@ def get_output(self, packet: GameTickPacket) -> SimpleControllerState: controls.throttle = 1.0 # You can set more controls if you want, like controls.boost. - # Send any drawing we may have done - self.renderer.end_rendering() return controls def begin_front_flip(self, packet): diff --git a/src/util/ball_prediction_analysis.py b/src/util/ball_prediction_analysis.py index 8ed0fc1..31f6e81 100644 --- a/src/util/ball_prediction_analysis.py +++ b/src/util/ball_prediction_analysis.py @@ -8,10 +8,14 @@ # We will jump this number of frames when looking for a moment where the ball is inside the goal. # Big number for efficiency, but not so big that the ball could go in and then back out during that # time span. Unit is the number of frames in the ball prediction, and the prediction is at 60 frames per second. -COARSE_SEARCH_INCREMENT = 20 +GOAL_SEARCH_INCREMENT = 20 def find_slice_at_time(ball_prediction: BallPrediction, game_time: float): + """ + This will find the future position of the ball at the specified time. The returned + Slice object will also include the ball's velocity, etc. + """ start_time = ball_prediction.slices[0].game_seconds approx_index = int((game_time - start_time) * 60) # We know that there are 60 slices per second. if 0 <= approx_index < ball_prediction.num_slices: @@ -20,13 +24,24 @@ def find_slice_at_time(ball_prediction: BallPrediction, game_time: float): def predict_future_goal(ball_prediction: BallPrediction): - return find_matching_slice(ball_prediction, 0, lambda s: abs(s.physics.location.y) >= GOAL_THRESHOLD) - - -def find_matching_slice(ball_prediction: BallPrediction, start_index: int, predicate: Callable[[Slice], bool]): - for coarse_index in range(start_index, ball_prediction.num_slices, COARSE_SEARCH_INCREMENT): + """ + Analyzes the ball prediction to see if the ball will enter one of the goals. Only works on standard arenas. + Will return the first ball slice which appears to be inside the goal, or None if it does not enter a goal. + """ + return find_matching_slice(ball_prediction, 0, lambda s: abs(s.physics.location.y) >= GOAL_THRESHOLD, + search_increment=20) + + +def find_matching_slice(ball_prediction: BallPrediction, start_index: int, predicate: Callable[[Slice], bool], + search_increment=1): + """ + Tries to find the first slice in the ball prediction which satisfies the given predicate. For example, + you could find the first slice below a certain height. Will skip ahead through the packet by search_increment + for better efficiency, then backtrack to find the exact first slice. + """ + for coarse_index in range(start_index, ball_prediction.num_slices, search_increment): if predicate(ball_prediction.slices[coarse_index]): - for j in range(max(start_index, coarse_index - COARSE_SEARCH_INCREMENT), coarse_index): + for j in range(max(start_index, coarse_index - search_increment), coarse_index): ball_slice = ball_prediction.slices[j] if predicate(ball_slice): return ball_slice diff --git a/src/util/boost_pad_tracker.py b/src/util/boost_pad_tracker.py new file mode 100644 index 0000000..3b6a7ef --- /dev/null +++ b/src/util/boost_pad_tracker.py @@ -0,0 +1,43 @@ +from dataclasses import dataclass +from typing import List + +from rlbot.utils.structures.game_data_struct import GameTickPacket, FieldInfoPacket + +from util.vec import Vec3 + + +@dataclass +class BoostPad: + location: Vec3 + is_full_boost: bool + is_active: bool # Active means it's available to be picked up + timer: float # Counts the number of seconds that the pad has been *inactive* + + +class BoostPadTracker: + """ + This class merges together the boost pad location info with the is_active info so you can access it + in one convenient list. For it to function correctly, you need to call initialize_boosts once when the + game has started, and then update_boost_status every frame so that it knows which pads are active. + """ + + def __init__(self): + self.boost_pads: List[BoostPad] = [] + self._full_boosts_only: List[BoostPad] = [] + + def initialize_boosts(self, game_info: FieldInfoPacket): + raw_boosts = [game_info.boost_pads[i] for i in range(game_info.num_boosts)] + self.boost_pads: List[BoostPad] = [BoostPad(Vec3(rb.location), rb.is_full_boost, False, 0) for rb in raw_boosts] + # Cache the list of full boosts since they're commonly requested. + # They reference the same objects in the boost_pads list. + self._full_boosts_only: List[BoostPad] = [bp for bp in self.boost_pads if bp.is_full_boost] + + def update_boost_status(self, packet: GameTickPacket): + for i in range(packet.num_boost): + our_pad = self.boost_pads[i] + packet_pad = packet.game_boosts[i] + our_pad.is_active = packet_pad.is_active + our_pad.timer = packet_pad.timer + + def get_full_boosts(self) -> List[BoostPad]: + return self._full_boosts_only diff --git a/src/util/sequence.py b/src/util/sequence.py index 5ebae2d..af53895 100644 --- a/src/util/sequence.py +++ b/src/util/sequence.py @@ -13,10 +13,20 @@ class StepResult: class Step: def tick(self, packet: GameTickPacket) -> StepResult: + """ + Return appropriate controls for this step in the sequence. If the step is over, you should + set done to True in the result, and we'll move on to the next step during the next frame. + If you panic and can't return controls at all, you may return None and we will move on to + the next step immediately. + """ raise NotImplementedError class ControlStep(Step): + """ + This allows you to repeat the same controls every frame for some specified duration. It's useful for + scheduling the button presses needed for kickoffs / dodges / etc. + """ def __init__(self, duration: float, controls: SimpleControllerState): self.duration = duration self.controls = controls @@ -36,14 +46,18 @@ def __init__(self, steps: List[Step]): self.done = False def tick(self, packet: GameTickPacket): - while True: - if self.index >= len(self.steps): - self.done = True - return SimpleControllerState() + while self.index < len(self.steps): step = self.steps[self.index] result = step.tick(packet) - if result.done: + if result is None or result.controls is None or result.done: self.index += 1 if self.index >= len(self.steps): + # The bot will know not to use this sequence next frame, even though we may be giving it controls. self.done = True - return result.controls + if result is not None and result.controls is not None: + # If the step was able to give us controls, return them to the bot. + return result.controls + # Otherwise we will loop to the next step in the sequence. + # If we reach here, we ran out of steps to attempt. + self.done = True + return None From 98f266bfa2f79f9284abfaca3f4ffc3713b72412 Mon Sep 17 00:00:00 2001 From: Blocks <21197843+TheBlocks@users.noreply.github.com> Date: Wed, 10 Jun 2020 17:19:12 +0200 Subject: [PATCH 24/30] Capitalising Python in README (#44) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9c32c22..61129d2 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # RLBotPythonExample -Example of a python bot using the RLBot framework +Example of a Python bot using the RLBot framework ## Quick Start -The easiest way to start a python bot is demonstrated here! +The easiest way to start a Python bot is demonstrated here! https://youtu.be/YJ69QZ-EX7k It shows you how to: From 69d697b7230f344bfd9e1ea695dc663fd1661158 Mon Sep 17 00:00:00 2001 From: Azeem Bande-Ali Date: Sat, 11 Jul 2020 12:05:16 -0400 Subject: [PATCH 25/30] Example unit test should load the example bot (#45) A match config was not being set on the excercise, resulting in Simple Bot being initialized instead of the bot in the project. --- training/hello_world_training.py | 16 ++++++++++++---- training/unit_tests.py | 8 +++++--- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/training/hello_world_training.py b/training/hello_world_training.py index c1ea9e3..00b77b1 100644 --- a/training/hello_world_training.py +++ b/training/hello_world_training.py @@ -26,6 +26,17 @@ def make_match_config_with_my_bot() -> MatchConfig: ] return match_config + +def add_my_bot_to_playlist(exercises: Playlist) -> Playlist: + """ + Updates the match config for each excercise to include + the bot from this project + """ + for exercise in exercises: + exercise.match_config = make_match_config_with_my_bot() + return exercises + + @dataclass class StrikerPatience(StrikerExercise): """ @@ -90,7 +101,4 @@ def make_default_playlist() -> Playlist: DrivesToBallExercise('Get close to ball'), DrivesToBallExercise('Get close-ish to ball', grader=DriveToBallGrader(min_dist_to_pass=1000)) ] - for exercise in exercises: - exercise.match_config = make_match_config_with_my_bot() - - return exercises + return add_my_bot_to_playlist(exercises) diff --git a/training/unit_tests.py b/training/unit_tests.py index a147c35..9326280 100644 --- a/training/unit_tests.py +++ b/training/unit_tests.py @@ -3,7 +3,7 @@ from rlbot.training.training import Pass, Fail from rlbottraining.exercise_runner import run_playlist -from hello_world_training import StrikerPatience +from hello_world_training import StrikerPatience, add_my_bot_to_playlist class PatienceTest(unittest.TestCase): """ @@ -18,7 +18,8 @@ class PatienceTest(unittest.TestCase): """ def test_patience_required(self): - result_iter = run_playlist([StrikerPatience(name='patience required')]) + playlist = [StrikerPatience(name='patience required')] + result_iter = run_playlist(add_my_bot_to_playlist(playlist)) results = list(result_iter) self.assertEqual(len(results), 1) result = results[0] @@ -26,7 +27,8 @@ def test_patience_required(self): self.assertIsInstance(result.grade, Fail) # If you make the bot is smarter, update this assert that we pass. def test_no_patience_required(self): - result_iter = run_playlist([StrikerPatience(name='no patience required', car_start_x=-1000)]) + playlist = [StrikerPatience(name='no patience required', car_start_x=-1000)] + result_iter = run_playlist(add_my_bot_to_playlist(playlist)) results = list(result_iter) self.assertEqual(len(results), 1) result = results[0] From 47baf73c949a1ba1aee28469ed0b997f6a3b3725 Mon Sep 17 00:00:00 2001 From: Tyler Arehart Date: Sun, 12 Jul 2020 15:10:57 -0700 Subject: [PATCH 26/30] Removing references to legacy GUI. --- run-gui.bat | 11 ----------- run-gui.sh | 5 ----- run.py | 10 ++-------- 3 files changed, 2 insertions(+), 24 deletions(-) delete mode 100644 run-gui.bat delete mode 100644 run-gui.sh diff --git a/run-gui.bat b/run-gui.bat deleted file mode 100644 index a1c00d9..0000000 --- a/run-gui.bat +++ /dev/null @@ -1,11 +0,0 @@ -@echo off - -@rem Change the working directory to the location of this file so that relative paths will work -cd /D "%~dp0" - -@rem Make sure the environment variables are up-to-date. This is useful if the user installed python a moment ago. -call ./RefreshEnv.cmd - -python run.py gui - -pause diff --git a/run-gui.sh b/run-gui.sh deleted file mode 100644 index 89c6a0b..0000000 --- a/run-gui.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -cd "$(dirname "$0")" - -python3 ./run.py gui \ No newline at end of file diff --git a/run.py b/run.py index 061aa9c..23ea98a 100644 --- a/run.py +++ b/run.py @@ -25,14 +25,8 @@ subprocess.call([sys.executable, "-m", "pip", "install", '-r', 'requirements.txt', '--upgrade', '--upgrade-strategy=eager']) try: - if len(sys.argv) > 1 and sys.argv[1] == 'gui': - from rlbot.gui.qt_root import RLBotQTGui - - RLBotQTGui.main() - else: - from rlbot import runner - - runner.main() + from rlbot import runner + runner.main() except Exception as e: print("Encountered exception: ", e) print("Press enter to close.") From 86236afdf3b8333f2c5d08336442d656f0c3bc9d Mon Sep 17 00:00:00 2001 From: Dominik Schmid Date: Thu, 27 Aug 2020 23:03:01 +1000 Subject: [PATCH 27/30] Fix typo --- training/hello_world_training.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/training/hello_world_training.py b/training/hello_world_training.py index 00b77b1..f1cba03 100644 --- a/training/hello_world_training.py +++ b/training/hello_world_training.py @@ -16,7 +16,7 @@ def make_match_config_with_my_bot() -> MatchConfig: # Makes a config which only has our bot in it for now. - # For more defails: https://youtu.be/uGFmOZCpel8?t=375 + # For more details: https://youtu.be/uGFmOZCpel8?t=375 match_config = make_empty_match_config() match_config.player_configs = [ PlayerConfig.bot_config( From f61a8108778b1f1f9855c76d1a6287087c26eaa2 Mon Sep 17 00:00:00 2001 From: Tyler Date: Wed, 7 Oct 2020 09:10:30 -0700 Subject: [PATCH 28/30] Including a script that launches RLBotGUI, and a spike rush utility. (#47) --- requirements.txt | 1 + run.bat | 11 ----------- run.sh | 5 ----- run_gui.py | 7 +++++++ src/util/spikes.py | 36 ++++++++++++++++++++++++++++++++++++ 5 files changed, 44 insertions(+), 16 deletions(-) delete mode 100644 run.bat delete mode 100644 run.sh create mode 100644 run_gui.py create mode 100644 src/util/spikes.py diff --git a/requirements.txt b/requirements.txt index 25c0c97..008fac3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ # Include everything the framework requires # You will automatically get updates for all versions starting with "1.". rlbot==1.* +rlbot_gui rlbottraining # This will cause pip to auto-upgrade and stop scaring people with warning messages diff --git a/run.bat b/run.bat deleted file mode 100644 index b3318d9..0000000 --- a/run.bat +++ /dev/null @@ -1,11 +0,0 @@ -@echo off - -@rem Change the working directory to the location of this file so that relative paths will work -cd /D "%~dp0" - -@rem Make sure the environment variables are up-to-date. This is useful if the user installed python a moment ago. -call ./RefreshEnv.cmd - -python run.py - -pause diff --git a/run.sh b/run.sh deleted file mode 100644 index 4598972..0000000 --- a/run.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -cd "$(dirname "$0")" - -python3 ./run.py \ No newline at end of file diff --git a/run_gui.py b/run_gui.py new file mode 100644 index 0000000..a3bced8 --- /dev/null +++ b/run_gui.py @@ -0,0 +1,7 @@ +from rlbot_gui import gui + +# This is a useful way to start up RLBotGUI directly from your bot project. You can use it to +# arrange a match with the settings you like, and if you have a good IDE like PyCharm, +# you can do breakpoint debugging on your bot. +if __name__ == '__main__': + gui.start() diff --git a/src/util/spikes.py b/src/util/spikes.py new file mode 100644 index 0000000..582c30a --- /dev/null +++ b/src/util/spikes.py @@ -0,0 +1,36 @@ +from rlbot.utils.structures.game_data_struct import PlayerInfo, GameTickPacket + +from util.vec import Vec3 + +# When the ball is attached to a car's spikes, the distance will vary a bit depending on whether the ball is +# on the front bumper, the roof, etc. It tends to be most far away when the ball is on one of the front corners +# and that distance is a little under 200. We want to be sure that it's never over 200, otherwise bots will +# suffer from bad bugs when they don't think the ball is spiked to them but it actually is; they'll probably +# drive in circles. The opposite problem, where they think it's spiked before it really is, is not so bad because +# they usually spike it for real a split second later. +MAX_DISTANCE_WHEN_SPIKED = 200 + +class SpikeWatcher: + def __init__(self): + self.carrying_car: PlayerInfo = None + self.spike_moment = 0 + self.carry_duration = 0 + + def read_packet(self, packet: GameTickPacket): + ball_location = Vec3(packet.game_ball.physics.location) + closest_candidate: PlayerInfo = None + closest_distance = 999999 + for i in range(packet.num_cars): + car = packet.game_cars[i] + car_location = Vec3(car.physics.location) + distance = car_location.dist(ball_location) + if distance < MAX_DISTANCE_WHEN_SPIKED: + if distance < closest_distance: + closest_candidate = car + closest_distance = distance + if closest_candidate != self.carrying_car and closest_candidate is not None: + self.spike_moment = packet.game_info.seconds_elapsed + + self.carrying_car = closest_candidate + if self.carrying_car is not None: + self.carry_duration = packet.game_info.seconds_elapsed - self.spike_moment From 04b4b55a122e7c9f582caff8e2737429a16e0b2c Mon Sep 17 00:00:00 2001 From: Tyler Date: Wed, 30 Dec 2020 19:54:54 -0800 Subject: [PATCH 29/30] Checking for None when doing ball prediction to avoid errors. (#48) --- src/bot.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/bot.py b/src/bot.py index 3e2aa43..8d21345 100644 --- a/src/bot.py +++ b/src/bot.py @@ -31,7 +31,7 @@ def get_output(self, packet: GameTickPacket) -> SimpleControllerState: # This is good to keep at the beginning of get_output. It will allow you to continue # any sequences that you may have started during a previous call to get_output. - if self.active_sequence and not self.active_sequence.done: + if self.active_sequence is not None and not self.active_sequence.done: controls = self.active_sequence.tick(packet) if controls is not None: return controls @@ -42,14 +42,19 @@ def get_output(self, packet: GameTickPacket) -> SimpleControllerState: car_velocity = Vec3(my_car.physics.velocity) ball_location = Vec3(packet.game_ball.physics.location) + # By default we will chase the ball, but target_location can be changed later + target_location = ball_location + if car_location.dist(ball_location) > 1500: # We're far away from the ball, let's try to lead it a little bit ball_prediction = self.get_ball_prediction_struct() # This can predict bounces, etc ball_in_future = find_slice_at_time(ball_prediction, packet.game_info.seconds_elapsed + 2) - target_location = Vec3(ball_in_future.physics.location) - self.renderer.draw_line_3d(ball_location, target_location, self.renderer.cyan()) - else: - target_location = ball_location + + # ball_in_future might be None if we don't have an adequate ball prediction right now, like during + # replays, so check it to avoid errors. + if ball_in_future is not None: + target_location = Vec3(ball_in_future.physics.location) + self.renderer.draw_line_3d(ball_location, target_location, self.renderer.cyan()) # Draw some things to help understand what the bot is thinking self.renderer.draw_line_3d(car_location, target_location, self.renderer.white()) From adea532f640fab25d22bd5692dec7efe80c833c7 Mon Sep 17 00:00:00 2001 From: Darxeal Date: Sat, 13 Mar 2021 21:31:46 +0100 Subject: [PATCH 30/30] Add a note to appearance.cfg (#50) This will hopefully make more people aware that the appearance editor exists. --- src/appearance.cfg | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/appearance.cfg b/src/appearance.cfg index 468bbe3..45840e3 100644 --- a/src/appearance.cfg +++ b/src/appearance.cfg @@ -1,3 +1,7 @@ +# You don't have to manually edit this file! +# RLBotGUI has an appearance editor with a nice colorpicker, database of items and more! +# To open it up, simply click the (i) icon next to your bot's name and then click Edit Appearance + [Bot Loadout] team_color_id = 60 custom_color_id = 0