Basics of OOP from a Game Development Point of View
Objective: understand OOP basics by making a clear analogy with common game concepts
When I was first learning OOP (object-oriented programming) I tried to find a practical explanation of the concept of class and subclass.
You surely heard of
Classes are the blueprints for objects
Well, true. But since we are in game development, let’s find out a better analogy. The best I could get was a comparison between the class of an object and the class of an RPG character.
The Player is a class of its own, but it can be a Warrior or a Ranger or whatsoever. All of them share some common properties and methods, such as health or stamina and Move or Attack, from the parent class. Also, all of them has unique and personal properties and methods, special ones.
In this analogy, Warrior and Ranger inherit from Player.
In OOP there is no need to duplicate code in cases like this: extending a base class allows to inherit fields and methods without the need of rewriting them in the subclass.
Moreover, the Attack method may have a different implementation in Warrior and Ranger. For example Warrior could use the base implementation plus some more logic, while Ranger could have a different, new one.
In OOP, polymorphism is what we’re talking about: the possibility to provide a single “interface*” for multiple purposes. In particular, the example above is known as overriding: the act of rewriting the logic of an inherited method, so it is different from the parent one.
*By interface here I mean the layer between the consumer and the consumable entity — an example would be the method signature which stands between the one caller and the logic inside of the method.
Polymorphism is a rather complex concept and can be achieved in many other ways. Apart from overriding, there is also the concept of overloading: it is possible to have many different implementations of the same method in a class, each of them differs from the other in the signature: the type and/or the number of parameters accepted as arguments.
We could have a Cast method, to cast a spell, and two overloads: one with no parameters, another with a Weapon parameter to have different behaviours while casting a spell with bare hands or using a Mage Staff.
But that’s not all! Another interesting form of polymorphism is the one involving interfaces. Interfaces are not involved in class extension, but rather in interface implementations. Their difference with classes is in their content: they don’t carry field and methods but just signatures of methods — so no logic.
When a class implements an interface, it agrees to a sort of contract in which it has to implement the logic for all the methods listed in the interface — as signatures.
Player, Container, Obstacle and Enemy classed implement the IDamageable interface which forces them to implement the logic for the Damage method. It is understandable since all objects of those classes are something that could be damaged, maybe in different ways with different consequences.
An interface can also be used as a type for a parameter accepted by a method. This means that the method would accept any object, of any class implementing that interface!
The Player Attack method will accept an IDamageable as a parameter, the reason is that the player attack should be directed to anything damageable in the game world — containers, obstacles, enemies — and in this way we don’t need to write N different overloads! (being N the number of classes implementing the interface). The only thing in common between different classes implementing IDamageable is the presence of the Damage method, so the Player Attack would have inside a call on that method, which will have a different implementation for each class!
Im’ going to leave you with something a little more advanced. The SOLID principles of OOP, with a brief explanation of each principle. SOLID stands for:
- Single Responsibility Principle (SRP)
“Each class should have just one job”: omni-comprehensive “god-classes” are a bad practice.
- Open/Closed Principle
“Classes should be open for extension, but closed for modification”, you should be able to extend a class behaviour without modifying it. This means that classes should be designed with a strong basic core that could be easily extended with subclasses when needed, instead of updating existing code.
- Liskov’s Substitution Principle (LSP)
This is cool. “Subclasses must be substitutable for their base or parent classes“. This means that subclass must be designed in such a way that allows them to replace parent classes with no unexpected behaviour. For example, Rectangle extending Shape is ok, extending Car is not. Or better, Square extends Rectangle extends Shape. Square can be easily (and correctly) used in place of Rectangle.
- Interface Segregation Principle (ISP)
“A client should never be forced to implement an interface that it doesn’t use, or clients shouldn’t be forced to depend on methods they do not use.” This is easy to understand: don’t flood classes with methods they don’t need implementing useless interfaces: a LightBulb could implement ITurnable (to turn it on or off) but not IShift which better suits vehicles with the shift!
- Dependency Inversion Principle (DIP)
“Classes should depend on abstraction but not on concretion”. The last principle means that high-level entities and low-level ones should be loosely coupled, using abstraction. A simple example has already been provided: when the player attacks, it doesn’t depend on the specific target — a container, an enemy or an obstacle — the use of an interface loosely couples the player to the target (almost no coupling at all). If we would have done otherwise, for example writing 3 overloads, we could have found ourselves in need of changing the Player class code to include a fourth kind of target!
If you liked the article, please clap to it and share it!
Also take a look at my other games or my support page!