|
Managing my ressources |
SpectreNectar
Member #10,969
May 2009
|
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
|
SpectreNectar said: 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
|
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. 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. 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 My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
Audric
Member #907
January 2001
|
Edgar Reynaldo said: 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
|
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 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. 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... 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
|
SpectreNectar said: 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... 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 : 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*. My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
SpectreNectar
Member #10,969
May 2009
|
This is coming along nicely. I still have a few loose ends. Edgar Reynaldo said: 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 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. 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"} |
Edgar Reynaldo
Major Reynaldo
May 2007
|
SpectreNectar said: 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. My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
SpectreNectar
Member #10,969
May 2009
|
This is a messy example of what I've used in the past: 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}
Edgar Reynaldo said: 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
|
SpectreNectar said: 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). My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
William Labbett
Member #4,486
March 2004
|
Edgar Reynaldo said: password protect it (if PhysFS supports that). Unfortunately it doesn't.
|
Thomas Fjellstrom
Member #476
June 2000
|
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. -- |
Arthur Kalliokoski
Second in Command
February 2005
|
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
|
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) 2) 3) 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. |
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
|
SpectreNectar said: 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. -------------------------------------------------- My blog: http://joshuadover.tumblr.com |
Edgar Reynaldo
Major Reynaldo
May 2007
|
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. SpectreNectar said:
2) 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: 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) 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. My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
|