A month has passed, and another blog is due. I am in disbelief that only a month has gone by. I say that because of this:
Not only do I have a functional car-like object, but it successfully navigates along a path. Last month, I had big dreams of just getting the car moving in 8 directions by now. I had anticipated that being able to drive the car around manually would be a lofty, but not outrageous goal. Well, I blew past that and it travels in all 360 degrees of a circle, and while I still can control it manually, I simply don’t anymore.
Now, while I am excited and still riding the high of where the project is at, there is still a mountain of work to be done. In the last blog, I briefly described what PID control is, and what is displayed in the gif above is absolutely not PID. The sensors (white boxes) do check error, and do make a simple calculation to eliminate said error, but the algorithm for even proportional control is more complicated. Furthermore, I have only worked on proportional control, and in reality there should always be some error if that is the only type of control that you have. In the above gif, the error is corrected entirely, and that should not be the case, currently.
The road less traveled
So how did I get here in such a short amount of time? Oddly (to me), was that I actually did not spend all my time working on the project. I had to take a significant detour learning pygame. In the last blog, I had random road generation going, but it was sloppy, and that was really all I had.
I followed this tutorial on youtube. After 9 days of watching and following the videos, this was the end result:
A lot of the work that went in to building this game could be directly translated in to my project. I did not know that going in, and it was a huge worry that I would have all this information and nothing really gained at the end of it. Fortunately, that was not the case. Of note, the things that I learned and added in to my project were:
- Breaking up components in to classes
- The scrolling player object and the scrolling background
- Adding GUI features like a menu with buttons, as well as the ability to pause
A Class of Their Own
As mentioned, the tutorial I followed used classes to build the game. Since the first week or so of when I first started to learn Python, I was told to use classes. That did not really mean anything to me, and when I tried I failed to do anything productive, and ended up breaking my scripts entirely. Since moving back to my own project, I have started using classes left, right, and center. It has become natural for me to organize and encapsulate things that are ultimately objects and their related functions in to classes. I have gone from never using classes to using them everywhere:
def __init__(self, gameWindow):
self.gameWindow = gameWindow
self.clock = pg.time.Clock()
self.FPS = 60
self.map = GameMap()
self.car = CarActive()
self.direction = DirectionOfMotion(self.car.image, (self.car.rect.centerx, self.car.rect.centery))
self.dirReticle = DirectionReticle(self.car.image, (self.car.rect.centerx,
self.controller = Controller()
self.sensor1 = Sensor(self.gameWindow, True, .1665, 165, 1) # Top right
self.sensor2 = Sensor(self.gameWindow, True, 1.8335, 185, 1) # Bottom right
self.sensor3 = Sensor(self.gameWindow, True, .9, 115, 1) # Behind center
self.sensor4 = Sensor(self.gameWindow, True, 1.1, 115, 1) # Behind center
self.sensorList = [self.sensor1, self.sensor2, self.sensor3, self.sensor4]
In 28 lines of code, 10 classes are used or instantiated, with the last line being a list of the instantiated sensor class (and a lot of lines are simply overruns of class instantiation that wouldn’t fit on a single line). I now worry I might be overdoing it.
As far as I’ve gotten, it was not without issues. The current issue I faced I already mentioned about the PID not really being PID. But as with all projects, you iterate until you get it right (or simply until it is due). Another issue I had was originally the sensors that were shown off in the first gif were originally completely filled, white squares. Only in the past week did I change them from pygame surfaces in the shape of a rectangle, to drawn rectangles. This sounds pedantic, but the way surfaces are rendered versus how shapes are drawn in pygame made all the difference. When the sensors were filled squares, the sensors were non-functional. I had to stop blitting them to the game window for them to work, which meant I had a case not unlike Schrodinger’s Cat: observing the sensors caused them to not work, but making the sensors work caused me to not be able to see them. By drawing the sensors as empty squares I could both have functioning sensors that I could observe as they changed positions in the game world.
Getting the translation of the car object (the blue arrow, currently) was another hassle. Pygame has a built-in transform function that I first thought to use. It led to interesting results:
In the end the solution was to rotate the arrow image about its center and create individual pictures that represent the arrow when it is moving at a certain angle. This was a quick and dirty solution, and I imagine if I was better at software like blender I would create animated sprites with key frames.
# TO DO
The final month has begun, and with all the progress I have made has inspired me to be more ambitious for April. Maybe this is the reason companies never meet deadlines? Anyway, here’s what I hope to have this time next month:
- Fix the PID algorithm
The current controller is not even proportional control, let alone PID control. However, what was shown in the first image is probably what the final product should look like. Just how it works under the hood will be more fleshed out.
- Add the “I” and the “D” of PID
Right now I have only worked on proportional control. The integral and derivative controls come next.
- Add weights to sensors
Sensors that are further out from the car are more likely to come across things that are not road. Conversely, sensors that are close that pick up things that are not road need to react much more aggressively to avoid the car hitting something. Higher weight means more aggressively making a correction to avoid obstacles.
- Add hazards
Currently there’s only road and not road. There should be other moving bodies (such as people) that are generated that the car should avoid.
- Harder to navigate roads
The road presented in the first gif is a simple square shape. The car can effectively navigate around the 4 corners. It’s kind of boring and small. I have a stretch goal of procedurally generating the road such that it is equivalent to miles long and so that I would not necessarily know the path beforehand. This will be a lot of work. At the very least, I am already working on a manually-coded path (like the square) that has more zig-zags.