11 min read

(For more resources related to this topic, see here.)

Resource definitions

In order to be able to define resources, we need to create a module that will be in charge of handling this. The main idea is that before calling a certain asset through ResourceManager, it has to be defined inResourceDefinitions. In this way, ResourceManager will always have access to some metadata it needs to create the asset (filenames, sizes, volumes, and so on).

In order to identify the asset types (sounds, images, tiles, and fonts), we will define some constants (note that the number values of these constants are arbitrary; you could use whatever you want here). Let’s call them RESOURCE_TYPE_[type] (feel free to use another convention if you want to). To make things easier, just follow this convention for now since it’s the one we’ll use in the rest of the book. You should enter them in main.lua as follows:

RESOURCE_TYPE_IMAGE = 0

RESOURCE_TYPE_TILED_IMAGE = 1

RESOURCE_TYPE_FONT = 2

RESOURCE_TYPE_SOUND = 3

If you want to understand the actual reason behind these resource type constants, take a look at the load function of our ResourceManager entity in the next section.

We need to create a file named resource_definitions.lua and add some simple methods that will handle it.

Add the following line to it:

module ( “ResourceDefinitions”, package.seeall )

The preceding line indicates that all of the code in the file should be treated as a module function, being accessed through ResourceDefinitions in the code. This is one of many Lua patterns used to create modules.

If you’re not used to the Lua’s module function, you can read about it in the modules tutorial at http://lua-users.org/wiki/ModulesTutorial.

Next, we will create a table that contains these definitions:

local definitions = {}

This will be used internally and is not accessible through the module API, so we create it using the keyword local.

Now, we need to create the setter, getter, and unload methods for the definitions.

The setter method (called set) stores the definition parameter (a table) in the definitions table, using the name parameter (a string) as the key, as follows:

function ResourceDefinitions:set(name, definition)

definitions[name] = definitionend

The getter method (called get, duh!) retrieves the definition that was previously stored (by use of ResourceDefinitions:set ()) using the name parameter as the key of the definitions table, as follows:

function ResourceDefinitions:get(name) return definitions[name]end

The final method that we’re creating is remove. We use it to clear the memory space used by the definition. In order to achieve this we assign nil to an entry in the definitions table indexed by the name parameter as follows:

function ResourceDefinitions:remove (name) definitions[name] = nilend

In this way, we remove the reference to the object, allowing the memory to be released by the garbage collector. This may seem useless here, but it’s a good example of how you should manage your objects to be removed from memory by the garbage collector. And besides this, we don’t know information comes in a resource definition; it may be huge, we just don’t know.

This is all we need for the resource definitions. We’re making use of the dynamism that Lua provides. See how easy it was to create a repository for definitions that is abstracted from the content of each definition. We’ll define different fields for each asset type, and we don’t need to define them beforehand as we probably would have needed to do in C++.

Resource manager

We will now create our resource manager. This module will be in charge of creating and storing our decks and assets in general. We’ll retrieve the assets with one single command, and they’ll come from the cache or get created using the definition.

We need to create a file named resource_manager.lua and add the following line to it:

module ( “ResourceManager”, package.seeall )

This is the same as in the resource definitions; we’re creating a module that will be accessed using ResourceManager.

ASSETS_PATH = ‘assets/’

We now create the ASSETS_PATH constant. This is the path where we will store our assets. You could have many paths for different kinds of assets, but in order to keep things simple, we’ll keep all of them in one single directory in this example. Using this constant will allow us to use just the filename instead of having to write the whole path when creating the actual resource definitions, saving us some phalanx injuries!

local cache = {}

Again, we’re creating a cache table as a local variable. This will be the variable that will store our initialized assets.

Now we should take care of implementing the important functionality. In order to make this more readable, I’ll be using methods that we define in the following pages. So, I recommend that you read the whole section before trying to run what we code now. The full source code can be downloaded from the book’s website, featuring inline comments. In the book, we removed the comments for brevity’s sake.

Getter

The first thing we will implement is our getter method since it’s simple enough:

function ResourceManager:get ( name ) if (not self:loaded ( name )) then self:load ( name ) end return cache[name] end

This method receives a name parameter that is the identifier of the resource we’re working with. On the first line, we call loaded (a method that we will define soon) to see if the resource identified by name was already loaded. If it was, we just need to return the cached value, but if it was not we need to load it, and that’s what we do in the if statement. We use the internalload method (which we will define later as well) to take care of the loading. We will make this load method store the loaded object in the cache table. So after loading it, the only thing we have to do is return the object contained in the cache table indexed by name.

One of the auxiliary functions that we use here is loaded. Let’s implement it since it’s really easy to do so:

function ResourceManager:loaded ( name ) return cache[name] ~= nil end

What we do here is check whether the cache table indexed by the name parameter is not equal to nil. If cache has an object under that key, this will return true, and that’s what we were looking for to decide whether the object represented by the name parameter was already loaded.

Loader

load and its auxiliary functions are the most important methods of this module. They’ll be slightly more complex than what we’ve done so far since they make the magic happen. Pay special attention to this section. It’s not particularly hard, but it might get confusing. Like the previous methods, this one receives just the name parameter that represents the asset we’re loading as follows:

function ResourceManager:load ( name )

First of all, we retrieve the definition for the resource associated to name. We make a call to the get method from ResourceDefinitions, which we defined earlier as follows:

local resourceDefinition = ResourceDefinitions:get( name )

If the resource definition does not exist (because we forgot to define it before), we print an error to the screen, as follows:

if not resourceDefinition then

print(“ERROR: Missing resource definition for “ .. name )

If the resource definition was retrieved successfully, we create a variable that will hold the resource and (pay attention) we call the correct load auxiliary function, depending on the asset type.

else local resource

Remember the RESOURCE_TYPE_[type] constants that we created in the ResourceDefinitions module? This is the reason for their existence. Thanks to the creation of the RESOURCE_TYPE_[type] constants, we now know how to load the resources correctly. When we define a resource, we must include a type key with one of the resource types. We’ll insist on this soon. What we do now is call the correct load method for images, tiled images, fonts, and sounds, using the value stored in resourceDefinition.type as follows:

if (resourceDefinition.type == RESOURCE_TYPE_IMAGE) then

resource = self:loadImage ( resourceDefinition )

elseif (resourceDefinition.type == RESOURCE_TYPE_TILED_IMAGE) then

resource = self:loadTiledImage ( resourceDefinition )

elseif (resourceDefinition.type == RESOURCE_TYPE_FONT) then

resource = self:loadFont ( resourceDefinition )

elseif (resourceDefinition.type == RESOURCE_TYPE_SOUND) then

resource = self:loadSound ( resourceDefinition ) end

After loading the current resource, we store it in the cache table, in an entry specified by the name parameter, as follows:

-- store the resource under the name on cache cache[name] = resource

endend

Now, let’s take a look at all of the different load methods. The expected definitions are explained before the actual functions so you have a reference when reading them.

Images

Loading images is something that we’ve already done, so this is going to look somewhat familiar.

In this book, we’ll have two ways of defining images. Let’s take a look at them:

{

type = RESOURCE_TYPE_IMAGE

fileName = “tile_back.png”,

width = 62,

height = 62,

}

As you may have guessed, the type key is the one used in the load function. In this case, we need to make it of type RESOURCE_TYPE_IMAGE.

Here we are defining an image that has specific width and height values, and that is located at assets/title_back.png. Remember that we will use ASSET_PATH in order to avoid writing assets/ a zillion times. That’s why we’re not writing it on the definition.

Another useful definition is:

{

type = RESOURCE_TYPE_IMAGE

fileName = “tile_back.png”,

coords = { -10, -10, 10, 10 }}

This is handy when you want a specific rectangle inside a bigger image. You can use the cords attribute to define this rectangle. For example, we get a square with 20 pixel long sides centered in the image by specifying coords = { -10, -10, 10, 10 }.

Now, let’s take a look at the actual loadImage method to see how this all falls into place:

function ResourceManager:loadImage ( definition ) local image

First of all, we use the same technique of defining an empty variable that will hold our image:

local filePath = ASSETS_PATH .. definition.fileName

We create the actual full path by appending the value of fileName in the definition to the value of the ASSETS_PATH constant. if checks whether the coords attribute is defined:

if definition.coords then

image = self:loadGfxQuad2D

( filePath, definition.coords )

Then, we use another auxiliary function called loadGfxQuad2D. This will be in charge of creating the actual image. The reason why we’re using another auxiliary function is that the code used to create the image is the same for both definition styles, but the data in the definition needs to be processed differently. In this case, we just pass the coordinates of the rectangle.

else

local halfWidth = definition.width / 2

local halfHeight = definition.height / 2

image = self:loadGfxQuad2D(filePath,

{-halfWidth, -halfHeight, halfWidth, halfHeight} )

If there were no coords attribute, we’d assume the image is defined using width and height. So what we do is to define a rectangle that covers the whole width and height for the image. We do this by calculating halfWidth and halfHeight and then passing these values to theloadGfxQuad2D method. Remember the discussion about the texture coordinates in Moai SDK; this is the reason why we need to divide the dimensions by 2 and pass them as negative and positive parameters for the rectangle. This allows it to be centered on (0, 0). After loading the image, we return it so it can be stored in the cache by the load method:

end

return image

end

Now the last method we need to write is loadGfxQuad2D. This method is basically to display an image as follows:

function ResourceManager:loadGfxQuad2D ( filePath, coords )

local image = MOAIGfxQuad2D.new ()

image:setTexture ( filePath )

image:setRect ( unpack(cords) )

return image

end

Lua’s unpack method is a nice tool that allows you to pass a table as separate parameters. You can use it to split a table into multiple variables as well:

x, y = unpack ( position_table )

 

What we do here is instantiate the MOAIGfxQuad2D class, set the texture we defined in the previous function, and use the coordinates we constructed to set the rectangle this image will use from the original texture. Then we return it so loadImage can use it.

Well! That was it for images. It may look complicated at first, but it’s not that complex.

The rest of the assets will be simpler than this, so if you understood this one, the rest will be a piece of cake.

LEAVE A REPLY

Please enter your comment!
Please enter your name here