In the last chapter we used and shared an image resource between sprites, now we will see how we manage shared resources within our framework. It’s easy to manage a shared resource when you have a single image and handful of sprites using the same image. However, games have a lot of images and even more sprites sharing those images. A lot of entities load and use sprites all over the place, and a naive way to load and use them like we did in the previous chapter would lead to a chaos in our code. Keeping track of shared resources would become a nightmare. Besides, images aren’t the only resources a game has. There are sounds, music, scripts, particle effects, shaders and a game can have even more resource categories, all of which are usually shared. Wouldn’t it be great if we could some how defer the management of resources to an automated class in our framework? A class that keeps track of the resources loaded and disallows multiple loads of the same resource would be an ideal solution. Well, lets see how that can be done.
Wait! Before we jump into the code, let’s first understand how resources in our game project will be organized. Understanding the organization is crutial to understand resource management. Almost always all game resources are The topmost folder of our project is the game folder, which currently has 3 child folders doc, res and src. The src folder holds our source code, the doc holds the documents and the res folder holds the resources. All our resources are currently placed int the res folder under the main game folder (fig 8.1). Each folder can have sub-folders which further facilitate categorization and organization. In our case the res folder will have child folders for each category of resources (images, sounds, partfx, etc). We will refer to this structure all throughout this chapter. Most games will have a similar organization structure [1].
So, now let’s look at the code –
class ResourceManager(): '''Does the resource management for the game.''' def __init__(self): '''Constructor for the Resource Manager.''' self.__resources = dict() def Cache(self, resourceList): '''Loads all the resources into the manager.''' def Purge(self): '''Deletes and removes all the resources from the manager.''' def __getitem__(self, resourceName): '''Returns an image from archive. If an image is not loaded, it throws and exeption.''' return self.__resources[resourceName]
Central to the resource management is a class we will call the ResourceManager. It’s the base class for several resource managers we will eventually write. At it’s heart is a dictionary (__resources) for resources, which is an associative array of resource names as keys and actual resource objects as values. The resources can be accessed directly using resource names via the [] (__getitem__) operator. The resource manager has 2 other functions — the Cache function which caches all the resources into the manager by loading them from a resourceList, and the Purge function which deletes all the resources loaded by the manager. We will have different resource managers for different resources as we go along, so to make the Cache function more generic we add another function to the manager code to load resources and call it inside the Cache function.
def Cache(self, resourceList): '''Loads all the resources from the list into the manager.''' for resourceName in resourceList: res = self.LoadResource(resourceName) if res != None: self.__resources[resourceName] = res def LoadResource(self, resourceName): '''Loads a resource. (Implemented in child classes)''' pass
Our ResourceManager template is ready. Since we are already familiar with image resources, we will first begin with the ImageResourceManager — which manages image resources. Let’s see how it’s LoadResource function will look like.
class ImageResouceManager(ResourceManager): '''Manages Image resources.''' def LoadResource(self, resourceName): '''Loads an image.''' img = sf.Image() img.LoadFromFile(resourceName) return img
If you recall, this is the same code we used to load our images in the previous chapter — and that’s about it! The way to use the manager is simple.
def Init(self): .... # create a resource manager for images. self.imgResMan = ImageResouceManager() # cache our image resources. self.imgResMan.Cache(['../res/images/squares.png', '../res/images/crate.jpg']) ... #later in our code image = self.imgResMan['../res/images/squares.png']
The code above creates the manager and caches the images in it. To access the images, you can simply call the [] operator on the manager and pass it the resource name. To further illustrate how the resource manager is used, I have created a file called gamesprites.py found in the zip file below. In the file you have a few sprite objects. The Create function of the sprites should give you a clear idea on how the resource manager will be used in our game. The Init function of the GameManager creates the ImageResourceManager and caches the resources in it. It then passes the manager to the Create function of each individual sprite object where it is used by the sprites to create themselves. The OnDraw functions of the sprites are connected to the drawEvent which is fired from DrawEntities function of GameManager. The manager is finally used to Purge the resources loaded and is itself destroyed in the Cleanup function thus freeing the resources.
We have created and used our first resource manager. Resource management can be a complicated topic in game engines and it’s important to understand how resources are managed within any kind of game framework. I would say it’s one of the most important things for advanced games that use gigabytes of resources, especially on memory limited environments like game consoles. For now, I have purposefully kept our resource manager simple. We want to understand the function of the resource manager and not overly complicate it right from the word go. In the next chapter we will extend the resource manager to handle archives and resource groups.
The last thing we will touch upon in this chapter is the logging system. A logging system is crucial for any game. It’s can be the only savior when things go wrong and the debugger isn’t available — for example, when your game crashes on the player’s machine. Without logging there is no real way to troubleshoot in such situations. We must therefore, have a logging system for our framework. Since Python already has a logging system built-in, we use the same for our game. I will not be explaining the logging system since it’s already clearly explained in the Python documentation (15.7). The logging system can be found in the log.py file.
Downloads:
[1] When a team work on a software project, the organization structure of a project is generally closely tied with some kind of a version control system. This allows multiple people to work within the project and facilitates project management.