This entry is part 6 of 6 in the series
Beginner's Guide to OOP
We’ve come a long way in
this beginner’s guide to object-oriented programming, discussing the principles of
cohesion,
coupling,
encapsulation, and
abstraction. In this final article, we’ll discuss the OOP principle of
inheritance and its uses in game development.
Note: Although this tutorial is written using Java, you should be able to use the same techniques and concepts in almost any game development environment.
What Is Inheritance?
Inheritance is the principle of class hierarchy. It is the ability for one object to take on the states, behaviors, and functionality of another object. A real-world example of inheritance is genetic inheritance. We all receive genes from both our parents that then define who we are. We share qualities of both our parents, and yet at the same time are different from them. Objects in OOP can do the same thing.
Parent classes can have child classes (also known as superclasses and subclasses respectively) which can have the same properties of the parent class, and can define new states, behaviors, and functionality of their own. As an example, consider the following class that could be used as a parent class to different shapes:
public class Shape {
protected int height;
protected int width;
public Shape(int h, int w) {
height = h;
width = w;
}
public int area() {
return height * width;
}
public int getHeight() { return height; }
public int getWidth() { return width; }
public void setHeight(int h) { return height; }
public void setWidth(int w) { return width; }
}
To extend this class to implement a triangle, it would look like this:
public class Triangle extends Shape {
public Triangle(int h, int w) {
super(h, w);
}
public int area() {
return super.area() / 2;
}
}
Triangle
has all the same states and functions as
Shape
, but redefines the
area()
function to return the proper area of a
Triangle
(half base times height). The keyword
super
is used to reference the superclass and any of its states and functions. This is why we can use
super()
to call the constructor of the superclass and
super.area()
to call the
area()
function of the superclass. So, in this case,
super.area()
returns
height * width
. The
protected
keyword is the last
access level modifier. It acts like the private access level modifier but also allows any subclasses to have access to the variable or function.
Why Is It Helpful?
As you can see, inheritance can greatly help reduce code redundancy between similar objects by taking what those objects have in common and putting them in one place. This also creates more maintainable code because it helps to comply with the principle of
DRY and to prevent the ripple effect in code changes. If all of this seems familiar, it’s probably because
abstraction had very similar benefits (as well as most of the other principles of OOP). Abstraction is closely related to inheritance as an abstracted class can be used as a superclass to create subclasses. The only difference between an abstract class and a normal class is that an abstract class cannot be used to create an object.
How to Apply It
Lets go back to our three games one more time to describe how to apply inheritance.
Asteroids
Recall that we defined an abstract class for moving objects across a screen. Also
recall that we defined a
Ship
class for the ship object. To apply inheritance to Asteroids, we can have the
Ship
class extend the
Movable
class as follows:
/**
* The Ship Class
*/
public class Ship extends Movable {
/**
* Function to rotate the ship
*/
public void rotate() {
// Code that turns the ship
}
/**
* Function to fire
*/
public void fire() {
// Code to fire
}
}
The code needed to move the ship is taken care of in the
Movable
abstract class, so we can remove it from the
Ship
class. All the other objects for Asteroids could also inherit from the
Movable
class, making it extremely easy to change how to move an object. One thing to note about inheritance is the ability to have
multiple inheritance, or the ability of one class to inherit from multiple classes at the same time. Some languages allow it, others do not. Java is one of the languages that does not allow multiple inheritance. Therefore, you couldn’t have the Ship object inherit from both a
Moveable
class and a
Drawable
class. Make sure you are familiar with what your programming language allows before you try to design inheritance into your game.
Tetris
Inheritance can be applied to Tetris by having the Tetrimino and all of the game visuals inherit from the
Drawable
class, which we defined in the last article.
Pac-Man
Recall that for Pac-Man we identified thee objects: Pac-Man, a Ghost, and a pac-dot. Throughout this series we’ve only discussed these three objects and have put off mentioning anything about the last critical piece of Pac-Man: the power pellet. With inheritance, we are now ready to talk about it. A power pellet is a special pac-dot that allows Pac-Man to eat ghosts. Its states and behaviors are exactly the same as a pac-dot, with really the only difference being its size and the ability to blink (remember that to keep the game
loosely coupled, we want another class to monitor when a power pellet is eaten and active the
changeState()
method of the ghosts). This is when inheritance comes in handy. Since a pac-dot and power pellet are practically the same object, we can create a
PowerPellet
class which extends the
PacDot
class. The
PowerPellet
class would just need to modify a few states to make it bigger and add the behavior of growing and shrinking to create a blinking effect. And that’s it – we now have a power pellet with little extra work. Not too shabby. The code for how this would look could be as follows:
Read more: Quick Tip: The OOP Principle of Inheritance