Splitting Your Space Rocks Into Smaller Rocks
00:00 In the previous lesson, you made things crash into each other instead of floating on by. In this lesson, you’ll make your big rocks turn into little rocks and your little rocks into space pebbles. Bamm-Bamm would be proud.
00:13 In the current version of the game, when you hit a rock with a bullet, the rock goes away. As any fan of bad Bruce Willis movies will tell you, hitting a rock with a bullet isn’t going to make it just disappear.
Let’s break it into smaller pieces instead. To do this, your
Rock constructor will need to change. Right now, your rock positions are randomly generated, but if a rock is supposed to split into pieces, you don’t want that.
00:38 So the constructor needs to handle both the random appearance case for start conditions and the position specific case for rock splits. It will also need the ability to construct different sizes of rocks.
01:01 What I’ve decided to do here is create a factory method. A factory method is a class method that constructs and returns an object. This pattern is useful if there are a bunch of different ways of constructing something.
Factory methods can sometimes make your code clearer. Instead of having to read all the various parameters to interpret how a constructor is going to be called, you can have a factory method called
.create_random() whose name might be considered clearer than having to use a
01:32 The end result is the same, and a matter of style and preference. If you’ve come to Python from other languages, especially Java, you’ll find there are far fewer factories in the snake-based coding world, but the occasional one used judiciously can still make your code easier to read.
The contents of this factory are pretty much a copy of the old
.__init__() method. A random position is created. It is checked if it is in a valid starting position. If so, the rock is constructed. If not, a new position is tried until a rock outside the minimum start gap is generated. Line 83 here is the end of the factory method, which actually creates the new rock with the random position and returns it.
.__init__() method has also changed. In addition to position, you now need to be concerned about the size of the rock being created. I’m going to use three sizes and an integer value of
3 to indicate the size.
02:41 Most of the time, you want to use a constant, instead, with a good name. In this case, though, I’ll make an exception, and you’ll see why in a second. Magic number or not, it needs to be stored in the class. And then based on this number, you need a scaling factor. This scaling factor is used to calculate the size of the rock. Large rocks will be the size currently in use, medium rocks will be half as big, and small rocks will be half as big again.
03:10 Line 94 scales the sprite based on the size just determined. Lines 97 through 99 determine the speed of the rock. This is the same random velocity code used before the change to different sized rocks. Let me scroll down just a little more.
First, it checks if the rock is bigger than the smallest size. You don’t want to split rocks of size
1. Next, as you’re impacting the number of rocks in the game, you have to access the thing containing the rocks. Before now, the container for rocks lived inside of the
SpaceRocks game object, same as the bullets.
04:06 Encapsulation is a good thing. It tends to organize your code in a more readable fashion. That being said, if you need to access something that is encapsulated, you always have to pass that something around.
You saw the consequence of this earlier when the reference to the bullet container had to be added to the
Spaceship class. To show you a different way of thinking about the same problem, I’ve changed the way the code stores rocks and bullets.
I’ve made them global variables inside of the
game.py module. You can decide for yourself which you think is more readable: breaking the rules about encapsulation or passing the container around every time you need it.
Having this file import something from
game.py is what’s called a circular import. Python won’t let you do this. This is why this import is inside of this method on line 105 instead of at the top of the file. If the import had happened when the module was loaded the first time, there would be a circular import and the compiler would complain.
05:16 Putting it inside of this method is fine though. The module is already loaded into memory, so this code simply accesses the already-loaded module. Circular import problem resolved. Now, what was I talking about?
05:30 Oh yeah, splitting rocks. Lines 107 and 108 are where two new rocks are generated. They are both generated in the exact same space as the current rock. Note that the value for size is one less than the current size.
If I had used a constant or an enum instead of a magic number, this would end up having to be an
else condition block. Like a lot of coding, this is a trade-off. Magic numbers are harder to read, but in this case, it gives you easier-to-read splitting code. A better compromise would be to add some comments around the use of the magic numbers. You should do that. Comments are good.
All right. This is
game.py. The first change in here is turning the bullet and rock containers into global variables as promised. Anywhere where you had
self.bullets in the
SpaceRocks object will now need to change to a global reference.
And here’s the first case of that.
global rocks sounds like a bad name for a band, but what it is actually doing is telling Python you want to use the global variable instead of creating a new overriding local one. With the global established, the rock generation code is similar.
And because our ship now might have gone away, it should only be included with the game objects if it is still there. With the
.game_objects() property up-to-date, now the collision logic needs to change. Line 66 indicates that bullets and rocks will be global, and then line 79 is what removes the rock when a collision between a rock and a bullet is found.
Now that the rock is removed, it can be replaced with two rocks through the call to
.split(). Remember that
.split() has a check in it for tiny rocks. If the rock is too small, split does nothing, so it is safe to call it either way.
07:57 The remaining code is the same as before: removing the bullet and carrying on. Let’s break some rocks, hot sun optional. Kudos to the tiny little subset of Python coders with the appropriate taste in well-aged music that got that reference. I suspect it was one of you.
Become a Member to join the conversation.