Abstract Objects and Pygame
After reading this blog post from Hacker School my eyes have been opened up to the world of abstract objects. I always knew the concept of 'interface vs implementation', but now I actually understand it's real-world use!
Admittedly, I still don't completely understand the difference between objects and Abstract Data Types. To me an object that is dependent on it's implementation is just a badly coded object. Although, I guess an object can be used only for the benefits encapsulating everything inside them. Maybe someday I will more fully understand the difference.
Anyways, on to the problem that I've had in my adventures with Pygame (and every other game library in any language); getting objects to display themselves on the screen. No, I am not talking about my artistic ineptitude, but instead of technical aspect of encapsulation. How do you make an object display itself while only allowing it to know about itself?
Up until now I have always been passing pygame globals to my objects in order to execute this job. But this is always a problem because the whole point of creating an object was for encapsulation; that an object should only know about itself and nothing else. Else, you spend an hour of debugging just to find out that a random object you use once somewhere was screwing with your globals...
So the solution that just dawned on me is to use abstract objects. Basically, just define as many generic parts of an object you can, without making assumptions on how the object will be implemented. Here is one way to do this with a simple car class:
import abc class AbstractCar(): """ A simple car class. """ __metaclass__ = abc.ABCMeta def __init__(self, position , size, speed, color): self.position = position self.size = size self.speed = speed self.color = color @abc.abstractmethod def display(self): pass def move(self): self.position += self.speed self.position += self.speed
As you can see, our car class can take care of moving itself all on it's own. But that's not the case with displaying itself. It would need to know outsider knowledge of what game library was being used in order to do this. By leaving out the implementation of the display method we allow the user of the class to define how they want to display it based on whatever environments they are using. So now you can ship this one class out to anyone who needs to use it, as opposed to having to create a different class for every implementation that a user might need.
For instance, the class user can now override the display method and put this in the main pygame file:
import pygame from abstract_car import AbstractCar pygame.init() window = pygame.display.set_mode([200, 200]) class Car(AbstractCar): def display(self, window): pygame.draw.rect( window, self.color, ( self.position, self.position, self.size, self.size ) ) car = Car([10,10], [20,20], [1, 0], (100, 0, 0))
This allows the users of object to now display the object anyway they like! Heck, they could even print a text representation to stdout!
It also allows us to keep all pygame library usage all in one place. Instead of having to pass pygame and the window to all of your objects in many files, we can now just do all needed implementation in one central file. Yay, easier debugging!
To wrap things up, all we really did was move code from one file to another, because somehow it is better design.
So hopefully this concept more concrete (heh, concrete, I feel like I just killed my whole point) in all of our minds now. I think everything has been said. So I'll just abruptly end...
// TODO: Maybe try to consistently use 'I' or 'we' and not both.
I also have vivid memories of my father saying, "you need to draw things to the screen in terms of undefined units, not pixels! Because then when you change to different screen resolutions and sizes it will change how your whole game is displayed!"
This can also be easily solved by using abstract objects by...
... to be continued ...