Allegro.cc - Online Community

Allegro.cc Forums » Game Design & Concepts » Managing my ressources

This thread is locked; no one can reply to it. rss feed Print
Managing my ressources
SpectreNectar
Member #10,969
May 2009
avatar

I'm attempting a RPG and needs some advice concerning images and sound etc. as it it something that has raised some thoughts.

There's more than one thing to it really. One thing being that I have yet to become a skilled A5 C++ coder and as such haven't yet the clue as to how to use datafiles or somehow include the sounds, images, levels etc. in the executeable. This means that what I make is subject to change.

Another thing (besides how to actually import ressources into the game and not distribute them along with the executable) is that I like to have all files the same place. In a RessourceHandler class to name it nicely. The thing I've planned out is a "structure" like this for each ressource (and a little extra):

/* RESSOURCE */
bool is_loaded; // If the file has been loaded and is avaiable to use
const char *path; // Filename and preceeding path
bool load(); // Returns succes
bool unload(); // Call when no longer needed
FileType * get(); // Returns a class derived from FileType eg. ImgFile

/* IMGFILE (extends FileType) */
bool load(); // Builds upon virtual original, creating a bitmap in case of error so the game doesn't crash
int width, height;
draw();

This is already firing up brain activity for me. How would this even be possible to do? It wouldn't because it's just something I typed in here, and I mixed ressource struct with filetype for instance. But I hope you see my idea. To have a class tree of ressuorces, all available from the same place. For instance I would like to have the files currently loaded to be only the ones used in the current area (if in a dungeon don't waste memory on forest tiles and dryad sprites (unless there's a dryad in the dungeon)).

A third or forth issue is accessing images etc. from different places in the code. Like, if I define my RessourceHandler somewhere and am on a level that have a forest with actual dryads, they would want to do

// Assumes there's a static pointer to a RH obj, and an enumeration with all sprites available with one used as argument
ImgFile * sprite = RessourceHandler::gameRessources -> get(SPRITE_NPC_FOREST_DRYAD_ATTACK);
// Draw a dryad (or a yellow image with a cross over it on failure)
sprite->draw();

This looks ok in my example but is horrific in my current implementation (which just looks up the filename based on enum argument and returns that) because I need to chain paths together with "constants" which are actually just integers used for lookup in my yet to become massive ressource array.

Am I doing it all wrong or am I on the exact and ideal track?

kenmasters1976
Member #8,794
July 2007

I have yet to become a skilled A5 C++ coder and as such haven't yet the clue as to how to use datafiles or somehow include the sounds, images, levels etc. in the executeable

With Allegro 5 you can use the PhysFS addon. It's really easy to use and it's a neat way to do just that.

Quote:

A third or forth issue is accessing images etc. from different places in the code.

I'd say you have to load all resources for a level at once. So, when you load a level, load all tiles or resources that can be potentially found in that level even if you may not actually use them. That way your code gets simpler.

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

I use a single Resource class that understands how to load/destroy BITMAP*'s, SAMPLE*'s, MIDI*'s, and DATAFILE*'s. There's a ResourceManager class that sits on top of it and creates/destroys/identifies Resources.

It's better to identify a resource by name than by a statically coded id.

My ResourceManager holds a std::map<std::string , Resource*> object. You ask your ResourceManager for a Resource*/BITMAP*/SAMPLE*/MIDI*/DATAFILE* by name and it retrieves it. This way you can have separate functions that load in all your resources by name.

Here's a working example program and source code that you could take a look at (and use if you want to) that shows Resource and ResourceManager in action. I ripped off the demo.dat file from Allegro 4's shooter example program to show how easy it is to use.

ResourceTest.zip

And here's the simple code for the test program - You don't have to worry about managing memory for your BITMAP's, SAMPLE's, MIDI's or DATAFILE's anymore because they're automatically freed for you. NOTE : The title bitmap looks wrong because the shooter demo must have used a custom palette, which I didn't bother to do here.

#SelectExpand
1 2#include <iostream> 3using std::cout; 4using std::endl; 5 6#include "Resource.hpp" 7#include "demoindex.h" 8 9 10 11int main(int argc , char** argv) { 12 13 if (allegro_init() != 0) { 14 cout << "Allegro init failed (" << allegro_error << ")." << endl; 15 return 1; 16 } 17 if (install_keyboard() != 0) { 18 cout << "Install keyboard failed." << endl; 19 return 1; 20 } 21 if (install_sound(DIGI_AUTODETECT , MIDI_AUTODETECT , NULL) != 0) { 22 cout << "Install sound failed." << endl; 23 return 1; 24 } 25 26 int dcd = desktop_color_depth(); 27 if (dcd == 0) {dcd = 32;} 28 set_color_depth(dcd); 29 30 if (set_gfx_mode(GFX_AUTODETECT_WINDOWED , 800 , 600 , 0 , 0) != 0) { 31 cout << "Set Gfx Mode failed." << endl; 32 return 1; 33 } 34 clear(screen); 35 36 ResourceManager resman; 37 38 if (!resman.LoadFileResource("datafile" , "demo.dat" , RES_TYPE_DATAFILE)) { 39 cout << "Loading demo.dat failed." << endl; 40 return 1; 41 } 42 Resource* datres = resman.GetResourceByName("datafile"); 43 if (!datres) { 44 cout << "datres is NULL." << endl; 45 return 1; 46 } 47 if (!resman.LoadDataFileResource("Music" , *datres , GAME_MUSIC , RES_TYPE_MIDI)) { 48 cout << "Loading 'Music' from datafile failed." << endl; 49 return 1; 50 } 51 if (!resman.LoadDataFileResource("TitleImage" , *datres , TITLE_BMP , RES_TYPE_BITMAP)) { 52 cout << "Loading 'TitleImage' from datafile failed." << endl; 53 return 1; 54 } 55 if (!resman.LoadDataFileResource("BoomSound" , *datres , BOOM_SPL , RES_TYPE_SAMPLE)) { 56 cout << "Loading 'BoomSound' from datafile failed." << endl; 57 return 1; 58 } 59 60 play_midi(resman.GetMidiByName("Music") , 1); 61 62 BITMAP* bmp = resman.GetBitmapByName("TitleImage"); 63 64 blit(bmp , screen , 0 , 0 , (SCREEN_W - bmp->w)/2 , (SCREEN_H - bmp->h)/2 , bmp->w , bmp->h); 65 66 clear_keybuf(); 67 68 while (1) { 69 if (keypressed()) { 70 int rkey = readkey(); 71 if ((rkey >> 8) == KEY_ESC) { 72 break; 73 } else { 74 play_sample(resman.GetSampleByName("BoomSound") , 255 , 127 , 1000 , 0); 75 } 76 } 77 rest(50); 78 } 79 80 return 0; 81} 82END_OF_MAIN()

And, if you don't want to use the ResourceManager class, the Resource class is a very simple way to manage your resources :

   Resource bgpic("PrettyPicture.bmp" , RES_TYPE_BITMAP);// Loads automatically
   Resource bgpic("AnotherPrettyPicture.bmp" , RES_TYPE_BITMAP , false);// Does not load until Acquire is called.
   
   Resource datfile("resources.dat" , RES_TYPE_DATAFILE);
   Resource spritesheet(datfile , PLAYER_SPRITE , RES_TYPE_BITMAP);// Grabs the PLAYER_SPRITE BITMAP* from the datafile
                                                                   // just loaded from 'resources.dat' in the previous line.
   Resource sound_of_waves(datfile , WAVES_SOUND , RES_TYPE_SAMPLE);

   /// You can use a Resource object mostly like it's actual counter part -
   // Show off the spritesheet -
   blit(spritesheet , screen , 0 , 0 , 0 , 0 , ((BITMAP*)spritesheet)->w , ((BITMAP*)spritesheet)->h);
   // to the soothing sound of ocean waves. Why not?
   play_sample(sound_of_waves , 255 , 127 , 1000 , 1);

Edit
I fixed a memory leak in Resource.cpp.
Here's the new Resource.cpp

Audric
Member #907
January 2001

It's better to identify a resource by name than by a statically coded id.

It depends. Strings are required for interfacing with a scripting language or generic behavior based on text files, sure. But if you don't have such needs, using enums will gain you compile-time checks against typos, and the comfort of code completion if your IDE supports it.

SpectreNectar
Member #10,969
May 2009
avatar

Woah what a beautiful way of doing it using that ResourceManager class. Unfortunately I can't use it because the game is written in Allegro5, but I can definately base my own class heavily upon it also using PhysicsFS.

I honestly had no idea that's what PhysicsFS was for... I thought it was something used for providing info about what files existed on disk or something. I really don't know why. In general I think there's some parts of the manual that still needs some demystifying for me at least. Like what on earth is a configuration file? It just says what functions it has. So I am left to assume that a function like al_add_config_comment() is a very broad purpose function. Is it? As mentioned I can only guess what other people are using these for. A database like file maybe? If so why not just write that on the main Configuration file doc page: what it's for.

Right enough about the otherwise well written manual right now :)
So here comes the time where I admit I haven't used std::Vector a lot. Probably because when I read up on C++, Templates was too much of a mouthful at the time and I've managed without it so I've never gotten around to reading about it. I've just done that, after you reminded me about the map structure. I had completely forgotten that something like that existed. It will be perfect for what I require. Brilliant.

Some other things I don't fully understand are various lines in the code provided. This line for instance:

ResourceManager() : resmap() , resvec() {}

I'm guessing they initialize the map and vector somehow.
And these ones:

Resource::Resource(const string& file , RES_TYPE res_type , bool acquire) :
         src(RES_SRC_FILE) , 
         type(res_type) , 
         location(file) , 
         pdatfile(0) , 
         dat_index(0) , 
         resource(0)
{
   if (acquire) {Acquire();}
}

I'm really wondering if src(), type() etc are a cast, a function call, or what... :o
Not that it obscures the concepts in any way, I can do things my own way. I've just felt the last coupe of days that it's time to learn some more.

The discussion on strings is also quite relevant. I agree that bugtracing is extremely difficult if the cause is a typo in a string, yet with enums you have to do a lot of work maintaining the list of known resources. I'm attempting a solution that uses strings but check the string somehow. Like throw an error if I attempt to grab spirte_assasin instead of sprite_assassin when it hasn't been loaded.

Thanks for all the input :)

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

I'm guessing they initialize the map and vector somehow.

Correct, they call the default constructor for the resmap and resvec objects.

SpectreNectar said:

I'm really wondering if src(), type() etc are a cast, a function call, or what... :o

Those are all constructor calls. Each member of the class is initialized using a constructor call in the initialization list (that's what the colon and the comma separated list are for). Always use an initialization list that calls the constructor for every member of your class in your class's constructor, otherwise you get the default compiler generated code which uses default constructors, and the default constructors for built in types do not initialize the data, so they could have any value until you assign them one or initialize them.

SpectreNectar said:

So here comes the time where I admit I haven't used std::Vector a lot. Probably because when I read up on C++, Templates was too much of a mouthful at the time and I've managed without it so I've never gotten around to reading about it. I've just done that, after you reminded me about the map structure. I had completely forgotten that something like that existed. It will be perfect for what I require. Brilliant.

You don't have to understand much about templates to use the STL's vector and map classes. All you have to do is give it the proper type in the declaration.

There's a pretty good reference for the STL here :
Standard Template Library Programmer's Guide

SpectreNectar said:

The discussion on strings is also quite relevant. I agree that bugtracing is extremely difficult if the cause is a typo in a string, yet with enums you have to do a lot of work maintaining the list of known resources. I'm attempting a solution that uses strings but check the string somehow. Like throw an error if I attempt to grab spirte_assasin instead of sprite_assassin when it hasn't been loaded.

With the ResourceManager class, all you would have to do is modify ResourceManager::GetResourceByName a little bit :

Resource* ResourceManager::GetResourceByName(const std::string& name) {
   RMIT it = resmap.find(name);
   if (it != resmap.end()) {
      return it->second;
   }
   /// Not found, log the error and ASSERT
   cout << "GetResourceByName failed to locate resource '" << name << "'." << endl;
   ASSERT(it != resmap.end());
   return 0;
}

Since you're using Allegro 5 and not 4, it shouldn't be too hard to modify Resource to accept ALLEGRO_BITMAP* and ALLEGRO_SAMPLE*.

SpectreNectar
Member #10,969
May 2009
avatar

This is coming along nicely. I still have a few loose ends.

Those are all constructor calls. Each member of the class is initialized using a constructor call in the initialization list (that's what the colon and the comma separated list are for).

Yeah. That was a feature of c++ I wasn't aware of. But it makes perfect sense now - I really ought to use less pointers in general :)
-And more STL data structures.

I changed your resource classes by adding in the assertion check in GetResourceByName(), temporarily commenting out midis, and everything related to datafiles in an effort to make it "A5 compatible".

---

Regarding PhysFS I'm still having trouble figuring it out. I don't want to store my music, graphics and levels in a *.zip. It hardly seems like a standard way to do it either. I like to have as few external resources as possible when distributing my game. Preferably just the *.exe, savegames etc., some data files with the resources in them, a readme.txt and not much more really, but all inside a single *.zip though.
In case my point or whatever doesn't stand out too clear, I'm referring to when people download a game from a website in zip format, and what is going to be in it:

#SelectExpand
1//If I just use al_load_bitmap() it would look like this: 2[Undesired] 3game.zip( 4 play.exe 5 readme.txt 6 savegames() 7 screenshots() 8 data( 9 gfx( 10 dryad.bmp 11 grass.bmp 12 ) 13 sound( 14 music.ogg 15 attack.wav 16 ) 17 levels( 18 dungeon.txt 19 forest.txt 20 ) 21 ) 22) 23 24//Files included in the executable. Not the only valid structure though 25[Better] 26game.zip( 27 play.exe( 28 gfx( 29 dryad.bmp 30 grass.bmp 31 ) 32 sound( 33 music.ogg 34 attack.wav 35 ) 36 levels( 37 dungeon.txt 38 forest.txt 39 ) 40 ) 41 readme.txt 42 savegames() 43 screenshots() 44)

---

Lastly I'm just wondering if it is good practice to make global objects with the static keyword? Like for random instance, if I in main.cpp operated on a class named application that had an object pointed named game which among other things had a level with a grid with a cell with a tile with an ALLEGRO_BITMAP that needs a tileset for it's resource class because it actually is derived from just that. In general is it bad to access members of a class that the childs childs child is a member of?

{"name":"603930","src":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/e\/6\/e626250791dfff9262bd34289922239e.png","w":650,"h":393,"tn":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/e\/6\/e626250791dfff9262bd34289922239e"}603930

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

In general is it bad to access members of a class that the childs childs child is a member of?

I'd say yes. If you want to stick with Object Oriented Programming (OOP) techniques, then accessing members directly is not good practice. For one, the object that owns those members should be the only one to directly manipulate them through it's own member functions, and only it's interface should be public.

SpectreNectar said:

Lastly I'm just wondering if it is good practice to make global objects with the static keyword?

Someone correct me if I'm wrong, but I don't think the static keyword would do anything in that case. The only uses I've seen for the 'static' keyword in C++ is for inline functions, and for making only a single instance of a class member variable.

As far as embedding data directly into your executable goes, I think it's possible on Windows by compiling the data into a .res file and linking that file with your executable, but I don't know how to access it then, and I don't think there's a portable way to do it either. What exactly is wrong with distributing a single data (zip) file with your resources in it? With PhysFS you can access that data (zip) file as if it were a directory.

SpectreNectar
Member #10,969
May 2009
avatar

This is a messy example of what I've used in the past:

#SelectExpand
1class Actor { 2 3 public: 4 5 Actor(Game *g, double startx, double starty); 6 ~Actor(); 7 8 void update(); 9 void draw(); 10 11 void set_isPly(); 12 13 static void init(); 14 static void deinit(); 15 16 static Actor * create(Game *g, double startx, double starty); 17 static void destroy(Actor *who); 18 19 static void update_all(); 20 static void draw_all(); 21 22 23 protected: 24 25 double x; 26 double y; 27 28 Game * g; 29 Anim * spr; 30 31 // ACTOR CONTROL -->> 32 33 static Actor *playerAct; 34 static int list_actors_len; 35 static int list_actors_max; 36 static Actor **list_actors; 37 38 // <<-- 39 40}; 41 42Actor** Actor::list_actors; 43int Actor::list_actors_len; 44int Actor::list_actors_max; 45 46Actor* Actor::playerAct; 47 48 49void Actor::set_isPly() { 50 playerAct = this; 51} 52 53void Actor::init() { 54 55 list_actors_len = 0; 56 list_actors_max = 20; 57 list_actors = new Actor*[list_actors_max]; 58 59 playerAct = 0; 60 61} 62 63void Actor::deinit() { 64 65 for( int i=list_actors_len-1; i>=0; i-- ) { 66 delete list_actors[i]; 67 for( int j=i; j<list_actors_len-1; j++ ) { 68 list_actors[j] = list_actors[j+1]; 69 } 70 list_actors_len--; 71 list_actors[list_actors_len] = 0; 72 } 73 74 delete [] list_actors; 75 76} 77 78 79int main() { 80 81Actor * myActor = Actor::create(); 82myActor->set_isPly(); 83 84Actor::playerAct->draw(); //Using a global actor 85 86}

What exactly is wrong with distributing a single data (zip) file with your resources in it?

Not sure. It's just my experience that when people see a zip file they tend to unzip it using Winrar or the build in extractor in windows. Then there's the topic of pixel art/music theft, but my true concern is that I don't see other games do it really. It's not a strong argument, but it bothers me. Not as much if it was a *.7z or if I converted my bmps to pcx maybe. It all comes down to presentation and professional appearance etc...

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

This is a messy example of what I've used in the past:

From your use of so many static functions, it looks like you are trying to combine two classes functionality into one - the Actor class, along with a list of Actors. Why not just make two classes - an Actor class, and an ActorGroup class? Using static members as regular members limits the functionality of the class and is akin to using a singleton. Why not just create a single ActorGroup class with regular members?

SpectreNectar said:

but my true concern is that I don't see other games do it really.

I see tons of games that use a 'data' folder with sub-folders for music/sounds/graphics/levels and it doesn't turn me off to using their game. As for not using a 'zip' file, rename it to 'dat' and password protect it (if PhysFS supports that).

William Labbett
Member #4,486
March 2004
avatar

password protect it (if PhysFS supports that).

Unfortunately it doesn't.

Thomas Fjellstrom
Member #476
June 2000
avatar

Not like it would stop real hackers. just renaming a zip file would be more than enough to stop most casual users from playing with the data.

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

Arthur Kalliokoski
Second in Command
February 2005
avatar

If there isn't a lot of data, you could encrypt it before zipping, it wouldn't compress then, but the zip file would still keep it in one package.

They all watch too much MSNBC... they get ideas.

SpectreNectar
Member #10,969
May 2009
avatar

This has been most helpful. I've also read up on http://icculus.org/physfs/ and has come to like it. Quite clever in how it's used. So I'm making a data folder with zipped archives with the extension renamed to dat and am actually quite happy with the solution.

I'm still kind off stuck though.

Now comes the rethinking of my application. Along not using pointers for everything, using the STL library and letting tasks be performed by relevant classes, I've got some work to do. But what's still confusing me a lot is availability. Making the ALLEGRO_BITMAPS deep down in the class hierarchy of resource handling classes available to the game objects deep down some other hierarchy. Is there no way to make say tile be aware of resource?

I've put some further thought into my application and basically am left with 3 alternatives in regards to how classes interact and make themselves aware and available to each other.

1)
The class structure that I already use, where deep down the hierarchy there's a specialized class in need of another class down some other branch.
Example1 (Tile needs to access Resource, and does so by going up the hierarchy, and then down again):
ALLEGRO_BITMAP *gfx = tile->cell->grid->view->level->resourceManager->resources[5]->get();
This raises some concerns to me. I particular that the path traversal is pretty ugly.

2)
The class structure that I mentioned before, where global pointers are used to make shortcuts throughout the hierarchy.
Example2 (Tile needs to access Resource, and does so by using a global reference):
ALLEGRO_BITMAP *gfx = ResMan::resourceManager->resources[5]->get();
This raises some concerns to me as well, but is also more elegant to me as it makes sense to make ResMan public as it is something the entire game needs. Even the menu needs sprites. It's not very OOP though, right? Are there any good articles on this subject?

3)
A new way that I'm considering. Instead of having just one resman, make several depending on what needs it.
Example3 (Tile needs to access Resource, and does so by using it's own):
ALLEGRO_BITMAP *gfx = tile->resourceManager->resources[5]->get();
I don't like this. I mean, there's good sides to it. For instance a background or an actor would never need the tile graphics. However, wouldn't it be hard to manage?
Have any of you experience doing things like this? Is it how you normally do?
I can think of one scenario where it wouldn't work. Well... not very optimal at least. Input. As far as I've understood, in Allegro5 the way of doing it is to have an eventQueue. You wait for the event, and if there is one you act upon it. So if input is an event, and it occurs in the main application class, in order for it to be handled you would A) pass it as an argument to your game class and then distribute it along all needed paths B) just feed it to your InputManager class which again should be available to various parts of the program.
The thing is, with A there can be no InpMan, and with B how would you make it global? Follow me? If you want input handled in a single way, say only react to it if it's on the list of used buttons/keys/axes/whatever, then the managing class would somehow have to take care of all of it, while it's arguments comes from main application class which isn't bad but sort of forces me to position it high in the hierarchy whereas I'd rather have it handled all by itself, but in this way you can't make multiple instances of it. The way I see it there's no way this would work at all. The 3) thing.

So I'm left with 1) which is ineffective, takes forever to type out, and makes ugly long lines of code, or 2) which you say defies the purpose of OOP.
I give up :o

Audric
Member #907
January 2001

Congratulations, you've just proven that eliminating global variables is bad. It deprives you from solution 2 that is the best tradeoff between : amount of code, readability, complexity, error-risk.

_Kronk_
Member #12,347
November 2010

So I'm left with 1) which is ineffective, takes forever to type out, and makes ugly long lines of code, or 2) which you say defies the purpose of OOP.

I'm having the same exact problem. OOP seems to a pain in the butt sometimes.

--------------------------------------------------
"If only our dreams were fires to ignite, then we could let the whole world burn" -Emery

My blog: http://joshuadover.tumblr.com

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

1) Is unnecessary, and more work than it's worth. Just because you're using OOP doesn't mean you can't have global access to common data.

2)
The class structure that I mentioned before, where global pointers are used to make shortcuts throughout the hierarchy.
Example2 (Tile needs to access Resource, and does so by using a global reference):
ALLEGRO_BITMAP *gfx = ResMan::resourceManager->resources[5]->get();
This raises some concerns to me as well, but is also more elegant to me as it makes sense to make ResMan public as it is something the entire game needs. Even the menu needs sprites. It's not very OOP though, right? Are there any good articles on this subject?

As I see it, this is your best option. And it doesn't violate OOP. Your ResourceManager is still in charge of your resources, but your other classes need references to the resources it provides, which is fine, and is the entire reason for the existence of a ResourceManager in the first place - easy access to resources.

SpectreNectar said:

ALLEGRO_BITMAP *gfx = ResMan::resourceManager->resources[5]->get();

This is not exactly OOP here - it violates encapsulation. Since ResourceManager owns the resources container, it should provide sole access to what it contains, and not provide direct access to the container itself. Something better would be :

ALLEGRO_BITMAP* gfx = ResMan.GetBitmap("Background");
ALLEGRO_BITMAP* gfx = ResMan.GetBitmap(BACKGROUND);

SpectreNectar said:

3)
A new way that I'm considering. Instead of having just one resman, make several depending on what needs it.

There's nothing wrong with this either, assuming it fits your situation. For example, your character will not likely change during the course of your game, so a separate ResourceManager for your character is a good idea. You will probably also want a ResourceManager to handle the current level. You can clear it when the level is complete without worrying about clearing out your player resources.

_Kronk_ said:

I'm having the same exact problem. OOP seems to a pain in the butt sometimes.

There's nothing that says you can't use global data in an OOP paradigm.

Go to: