4. Understanding the Code
REMINDER: Indent your code using TABS or SPACES but be consistent, just use one or the other to avoid ‘indentation errors’.
Before we get going, take note that the last two lines of our code (both beginning ‘my_shooter_game’) are the only two lines of code that actually do anything! Everything prior you can consider to be ‘setup’. We have given the last two lines their own section in this Issue for clarity (see below). For now, before we delve into the rest of the code, remember that the first ‘my_shooter_game’ line creates an ‘object’ of type ‘ShooterGame’ while the second calls a ‘method’ named ‘run’ on that object. You do not need to understand that yet, but keep it in the back of your mind as you read the remainder of this quite tricky section!
from direct.showbase.ShowBase import ShowBase
At the start of any Python program you begin by ‘importing’ any Python libraries you may need. Think of it this way – if you did not explicitly import a library and libraries were always ‘just there’, you would end up with some massive executable programs where the vast majority of the code included was never used nor executed. The bottom line – only ever import what you need! ShowBase includes enough of the Panda3D library for us to get going. It also controls the program main loop (see below).
….this line, early in the code, actually warrants the most explanation. Take a deep breath! The line declares a class. A class is a blueprint. It does nothing on its own, it simply ‘defines’ something. In this case, we have a class defining something called ‘ShooterGame’. We could have called it anything: ‘MGFApp’, ‘YourApp’, whatever, ‘ShooterGame’ is just the name of the class (blueprint!) we have chosen.
In computer programming a class is a blueprint for an ‘object’. An ‘object’ is considered to be an ‘instance’ of a class. Let’s see an example:
- we ‘define’ a class called MotorCar
- we create an ‘object’ called TheMGFCompanyCar
- the TheMGFCompanyCar is an ‘instance’ of a MotorCar.
(wishful thinking on our part, but hopefully makes things a little clearer)
In our code, everything below this line that is indented more than this line is considered part of this class definition (ShooterGame). The class definition ends when the indentation returns to the level at which this line is at .
This approach to programming (termed ‘object oriented’) might sound confusing at first but it is the most common approach in use today and for good reason. There are different approaches but the object oriented paradigm has many benefits. We will cover them briefly in the “Summary and Wrap Up” section of this issue.
Now, you probably noticed the ShowBase bit in the brackets. Ready for another concept? 🙂 This is something we call ‘inheritance’. Our ShooterGame class ‘inherits’ from the ShowBase class. What the heck does that mean!?! Well, using the example of a car again, we might see something like this:
class AlfaRomeoCar: # lots of things about the alfa car go here # ...that are applicable to ALL models class 146(AlfaRomeo): # things _specific_ to the 146 class 156(AlfaRomeo): # things _specific_ to the 156
The class AlfaRomeoCar contains everything that is ‘common’ to all models. The 146 class ‘inherits’ the AlfaRomeoCar – it has everything that it has – but goes on to add some extra bits (specifics) to the 146 model. The 156 model does the same. Think about it – it would be a bit silly if the 146 and 156 blueprints ‘repeated’ the same common information. So they inherit from a common ‘parent class’ and are called ‘derived classes’.
In the case of our game – ShooterGame inherits from ShowBase. It has everything that ShowBase has and ‘more’ (the code we have written). ShowBase is a Panda3D class that does massive amounts of work for us – it contains the code that creates the window on your screen, updates it frame by frame, and so on. Hence the flying start we had in Issue 1!
So, looking forward, we know the next few lines will be defining our class, onwards!….
def __init__(self): ShowBase.__init__(self) self.world = self.loader.loadModel("world.bam") self.world.reparentTo(self.render)
The word ‘def’ tells the Python interpreter that we are ‘defining’ a method for our class (in the non-object oriented world, you might hear people call this a function or procedure, but the correct terminology in our world is a method!). A method is a collection of code grouped together for a logical purpose. For example, the AlfaRomeoCar might have methods for ‘StartEngine’, ‘StopEngine’, ‘Accelerate’, ‘Brake’ and so on. Each method might have several lines of code and so, by grouping them together, life gets easier because we do not have to repeat code. To accelerate, we simply ‘call’ the Accelerate method. You’ll see this in action in a moment (yes, those last two lines again!).
Indentation is again very important. We noted previously that everything following the ‘class’ line that was indented more/further was part of the class. Likewise, everything that follows our ‘def’ line that is further indented is part of the method (which is part of the class, or a ‘class method’). Our method is short, only 3 lines, and is called “__init__”.
Uh-Oh, more lingo! In Python, the method named __init__ has a special purpose. It is called a ‘constructor’, and ‘init’ is short for initialise. Unlike other methods, the constructor is executed automatically the second we create an object of type ‘ShooterGame’. You do not have to explicitly ‘call’ the constructor as you would with other methods. Think of it as the ‘class setup method’. In the case of our car example the constructor might do things such as ‘set speed to zero’, ‘set engine off’, ‘set handbrake on’ etc.
The next line of code calls init on ShowBase. We just said you don’t have to call constructors so we would understand if that’s a head scratching call! The explanation is simple enough though. ShooterGame inherited (extended) from ShowBase . It then provided a constructor. As such, by default, the ShowBase constructor (or ‘parent constructor’) no longer applies because we provided a new one. But – the ShowBase constructor includes all manner of setup that is needed for our game! So we have to manually (explicitly) call it. It’s a quirk of inheritance but is actually sometimes very useful (there are instances where you might want to inherit, provide your own constructor, and completely ignore the parent constructor).
Next, we declare a variable – “self.world”. The word “self” crops up a lot, we’ll cover that in Issue 3. The variable name is ‘world’. Think of a variable as a box, a named entity that holds a value. Some examples are below:
a = 10 # a variable called 'a' created holding a value of 10 (an integer) a = a+5 # the variable 'a' now holds the value of 15 b = "hello world" # 'b' is a variable holding a 'string' of characters c = ShowBase() # 'c' is a variable holding an 'object' of type ShowBase
…and so, variables can hold most any value, even an instance of a class (object). In Python, once you have set an initial value on a variable you can never change the variables ‘type‘. Variable ‘a’ will always hold an integer (whole number), ‘b’ always a string (sequence of characters). If you try to change the type later in your code, you will get an error!
Tip: If you are not certain what type a variable should be when you declare it, you can simply write “myVariable = None”. In several other programming languages they term “None” as “Null”. In all cases, it means “nothing / not defined”.
So, ‘world’ gets a value of self.loader.loadModel(‘world.bam’). Remember our inheritance discussion? By inheriting from ShowBase our ShooterGame class already has a variable called loader. Loader is an object and one of its methods is ‘loadModel’. Thus, we load our model using the ‘loader’ object. loadModel returns ‘a handle’ to our new model – the world.bam that is being loaded, and so “self.world” contains the result of our model loading – it contains our world, our terrain (our 3D model) as an object (in this case, an object of type NodePath).
Next, we call ‘reparentTo’ on our world. Remember, world is an object, the NodePath class defines the method ‘reparentTo’. We ‘reparentTo’ to another object called ‘render’ (again, it exists by virtue of us inheriting ShowBase). The render object contains the Scene Graph. Very simply put, if something is to appear on the screen, it must have the scene graph as its parent!