Q's: const tables and missing features

BlitzMax Forums/BlitzMax Programming/Q's: const tables and missing features

Zauron(Posted 2006) [#1]
Greetings!

I am a professional game developer, and am used to using C/C++ to code directly for game hardware. But, I'd like to do some hobbyist PC games on the side, and find that PC game development is too slow for what little spare time I have without the use of something like Blitz.

I previously did a BlitzPlus game featured in the Blitz Newsletter called Minebot Arena - ( http://www.minebotarena.com ), but I've been thinking of getting BlitzMax, mostly for access to alpha blending, rotation, scaling, etc. and had some questions about it.

One of the things we commonly do in C game programming is use const tables. Basically, a multi-dimensional array that is declared as const, meaning that all the values in the array are un-changable and are stored on disc/cartridge instead of in RAM.

I understand you can store constant data as individual constants or using the Data commands, however, const tables are much more convenient, as you can access any element at any time without having to use labels or a different constant name for each element of a bunch of constants referring to the same thing. Anywhere you can use an Array stored in RAM, you can use a const table stored in the executable code itself.

For example, after defining a const table of, say, enemy data, you could have a command that says something like:
enemyPtr->hitPoints = enemyData[DifficultySetting][enemyPtr->type].maxHitPoints;

If you have lots of enemy types each with their own max hit point value in Blitz, I can't see a way to assign default values as conveniently. The tables can also be formatted to be much more readable than BASIC data statements.

So my question is, is there a way to define const tables in BlitzMAX to have the convenience of quickly accessing data by index without having to use RAM to store the data? Or another method to quickly assign specific constants based on an index or set of indexes?

My other question is, what other features are missing from BlitzMax that BlitzPlus can do? The ones I've seen so far are full music support for XM's, IT's, etc. and the lack of the ImageBuffer command (something I made heavy use of in Minebot - notice the damage on the boss bots for example - that can't be done by just drawing every decal every update loop, because the decals had to be clipped to the non-square shape of the boss, which I did in an image buffer by drawing the decal then using a mask image to erase any part of the decal that might extend past the boss's shape).

Before I switch to BlitzMax to get alpha blending and such, I'd like to know what I'm losing and if there's a way around the missing elements.

Thanks much for any answers you can give!


Abomination(Posted 2006) [#2]
That array you speak of. Is that a virtual array? Meaning just a way to adres constants "mentaly"? Because as soon as a value is stored somewere it can be anything except a constant.
Anyway; BlizMAx has no such thing.

It's time one of the IDE's supports the use of Macro's; then one could do stuff like this with ease.


Who was John Galt?(Posted 2006) [#3]
Max has pixmaps which allow you to modify images from within your program.

You can define arrays with notation like this
x[]=[1,2,3]
but arrays are run time allocated, as are types, so there
is no direct equivalent of the example you gave.

Apart from the music commands you mention, I can't think of anything B+ has that Max doesn't. GUI components (which I doubt you'll want to use in a game anyways) are an extra for Max, but if you have a B+ license you'll get this free.


Zauron(Posted 2006) [#4]
Perhaps it is called a virtual array, we just call it a const table. Here's an example from C from a project I'm working on now:
static const u16 EnemyJumpType[ENEMY_MAX_JUMP_CHARS_X][ENEMY_MAX_JUMP_CHARS_Y] =
{//X = 	 	0		1		2		3		4		5		6
	{	EJ_HIGH,	EJ_NORM,	EJ_NORM,	EJ_NONE,	EJ_NONE,	EJ_NONE,	EJ_NONE	}, // Y = 0
	{	EJ_HIGH,	EJ_NORM,	EJ_NORM,	EJ_NORM,	EJ_NONE,	EJ_NONE,	EJ_NONE	}, // Y = 1
	{	EJ_SHRT,	EJ_MIDL,	EJ_NORM,	EJ_NORM,	EJ_NONE,	EJ_NONE,	EJ_NONE	}, // Y = 2
	{	EJ_SHRT,	EJ_SHRT,	EJ_MIDL,	EJ_NORM,	EJ_NORM,	EJ_LONG,	EJ_NONE	}, // Y = 3
	{	EJ_SHRT,	EJ_SHRT,	EJ_MIDL,	EJ_MIDL,	EJ_NORM,	EJ_LONG,	EJ_LONG	}, // Y = 4
};

Then, whenever I need to use any element of that table, I use it just like an array, such as:
if (EnemyJumpType[x][y] == EJ_NORM) enemyJump(NORMAL_JUMP_HEIGHT);

I can assure you, the data in this table is not stored in RAM (I only have 128k of RAM to play with, I'd very quickly run out if we had to store the const tables in RAM). It is stored as part of the binary executable file just like the data in DATA statements in BASIC. We use this ALL the time to store quick data we need. The table can even be made of data from a struct (kind of like a Type), such as this one:
typedef struct _STAGE_DATA_TYPE
{
	u16 *BG0MapFile;
	u16 *BG1MapFile;
	s16 HeroXPos;
	s16 HeroYPos;
} STAGE_DATA_TYPE;

static const STAGE_DATA_TYPE StageData[NUM_STAGES] =
{// 		BG0MapFile			BG1MapFile			HeroXPos		HeroYPos
	{	(u16*)&RES_MAP1_BG0_BMP_SA,	(u16*)&RES_MAP1_BG1_BMP_SA,	8,			33,	},	// Map 0
	{	(u16*)&RES_MAP2_BG0_BMP_SA,	(u16*)&RES_MAP2_BG1_BMP_SA,	44,			33,	},	// Map 1
	{	(u16*)&RES_MAP3_BG0_BMP_SA,	(u16*)&RES_MAP3_BG1_BMP_SA,	35,			33,	},	// Map 2
};

Which can then be accessed by things like:
InitHero( StageData[curStage].HeroXPos, StageData[curStage].HeroYPos );

You can even make a multi-dimensional const table with each element containing a type (after a while the formatting gets harder to read when you add too much complexity, but it is doable).

I don't know how you guys can live without having access to large organized tables of data that don't take up RAM or require reading from a data file. How do all the larger game projects in Blitz deal with the raw data needed for code? Do you have to use individual constants for each variation on a single type, like ENEMY_HIT_POINTS_DROID and ENEMY_HIT_POINTS_PIRATE and ENEMY_HIT_POINTS_ZOMBIE and so on instead of just enemyHitPoints[enemyType]?


Gabriel(Posted 2006) [#5]
I don't know how you guys can live without having access to large organized tables of data that don't take up RAM or require reading from a data file. How do all the larger game projects in Blitz deal with the raw data needed for code? Do you have to use individual constants for each variation on a single type, like ENEMY_HIT_POINTS_DROID and ENEMY_HIT_POINTS_PIRATE and ENEMY_HIT_POINTS_ZOMBIE and so on instead of just enemyHitPoints[enemyType]?


With Type Constants. ( EnemyType.HitPoints )


Zauron(Posted 2006) [#6]
I'm not very familiar with Type constants. Could you please explain Type constants and how they add the same functionality? And are they avilable in Types in BlitzPlus/3D or only BlitzMax?

Lets take a very small and easy example for a simple game. Say I have Type called Enemy that has all the fields needed to deal with an enemy. But, each enemy has fields like HitPoints, Mana, etc. that are initialized to different pre-set values based on an enemy identifier of some kind (let's say, Race, for example).

Now, in C, I would make a table like this:
typedef struct _ENEMY_INIT_DATA_TYPE
{
	u16 maxHitPoints;
	u16 maxMana;
} ENEMY_INIT_DATA_TYPE;

static const enemyInitData[ENEMY_RACE_NUM] =
{//		maxHitPoints	maxMana
	{	100,		50		}	// Enemy Race: Orc
	{	150,		90		}	// Enemy Race: Goblin
	{	200,		30		}	// Enemy Race: Skeleton
};

Then, when I initialize a new enemy type, I would use something like this:
// Reserve memory for the enemy struct and get a pointer to it
enemyPtr->race = newEnemyRace;
enemyPtr->hitPoints = enemyInitData[newEnemyRace].maxHitPoints;
enemyPtr->mana = enemyInitData[newEnemyRace].maxMana;

Now for every enemy I create, I can run that exact same code and just specify a different value for newEnemyRace, and all the enemy's initial data will be set according to the race I specified. No additional code or constants are needed to add more races - I just add more to the table.

In Blitz, it seems from what I can see that I would have to have a different init function for every different enemy race, to make sure I use the correct individiually defined constants or data label for that particular enemy race's hit points and mana values (and any number of other data fields). While this would be fine for just 3 enemy races, lets say you had 50 enemy races each with their own initial hit points and mana. I don't see how you'd easily assign their initial values if you can't access constant values by an index number rather than only by individual constant name or data label.

How is this handled with Type constants?


On the subject of BlitzMax features and ImageBuffer/Pixmaps:

I've read about the pixmaps, and I've read about how it is NOT the same as ImageBuffer, and not as useful. For example, I found the following post and reply:


How would you go about drawing a random rect directly to a PixMap/Image every loop, without clearing it, and display it?



That would be impossible with the current BlitzMax featureset.


So, while pixmaps offer some ways to sorta do the things you could do with ImageBuffer, it is still a feature I consider unavailable in BlitzMax for the types of things I used ImageBuffer for.


neilo(Posted 2006) [#7]
@Zauron,

Regarding static arrays, you can do this easily enough:
Global LANGUAGE_NAMES:String[]=["English","Spanish","French","German","Dutch","Arabic","Farsi","Hindi","Greek","Russian"]

and now I can read all the languages I currently support. Heck; you can even do fun things like
	For Local lan:String=EachIn LANGUAGE_NAMES
		AddGadgetItem lstLanguages,lan
	Next

to get all the items. Doing this with multidimensional arrays isn't so hard.

One really nice thing that seems to be missing from BlitzMax (that is in BlitzPlus) is the ImageBuffer() function to allow you to draw directly into a pixmap (or whatever) with the standard commands. Still, you can get at the pixels (or bytes) directly, so you can code your own circle, line, rectangle etc. routines.

Neil


Zauron(Posted 2006) [#8]
Neil -

That does help a bit, but wouldn't that data take up RAM even though you have no intention of changing it and it doesn't need to be read super fast?

Is there no way to say "This array is read-only data, so don't take up RAM with it?" Or is that already the case for the statement you showed me?

In C, you'd probably put "const" right after that "Global" and it would know to make that array read-only and not take up precious RAM. It would work just like any other array, and can be used for any functions that expect an array, except that you could not change its values.

I guess maybe it just doesn't matter on a PC game. Most modern games seem to be RAM hogs anyway, I suppose data tables being stored in RAM aren't going to make much difference?

Also, how DO you define a multi-dimensional static array in that way? Like, what's the format Blitz expects?

On ImageBuffer:

I probably would use ImageBuffer more for drawing composite sprites or permenant decals on a certain sprite or background tile rather than circles and such. Which, from what I understand, is pretty hard to do with any speed in BlitzMax.


Basically at this point I'm just trying to figure out which Blitz product would best serve my needs for my next couple "indie" game side projects, and whether or not it would be worth extra cost for BlitzMax or Blitz3D+nSprite 2 (the other possibility I'm considering), or if I should just stick to BlitzPlus and use the Extended B2D library (which I'd already have picked except that the Extended B2D library is full-screen only and one of my game designs needs windowed support).


Zauron(Posted 2008) [#9]
I know this thread is really old but no one answered the last few question I had. I was too busy to pursue it any more with big game projects at work, but now I'm thinking of taking another crack at indie development on the side. Can anyone answer the questions in my last post, like how to define a multi-dimensional array of static data, and what exactly Type Constants are and how they would help with having different enemies that are the exact same class and code but have different initial values pulled from data somewhere based on some kind of "enemy ID"?

I'm still debating on whether to stick with BlitzPlus and just avoid game designs that require alpha and rotation (the better built-in license-free sound support, compatibility with a wider range of hardware due to using DX1.0 as a base, and more versatile ImageBuffers still appeals to me), or bump up to BlitzMax and get the benefit of function pointers, rotation, scaling, and alpha (classes and inheritance and polymorphism are also great tools but I find them to be unnecessary for small hobby projects as you can accomplish pretty much the same thing with careful coding and function pointers - man I wish B+ had function pointers though, kind of a pain to use select statements to accomplish the same thing).

Oh, one other quick question - does BlitzMax have support for destructors? That would also be handy, especially since the garbage collection isn't too useful if you stick your objects into lists anyway so you still need to essentially manually delete objects.

Thanks much for any info!


Kurator(Posted 2008) [#10]
BlitzMax does not directly support destructors (like c++)

Objects are deleted if there is no more reference to itsself, so its relatively easy to delete them with: "MyObject = NULL" - but you have to be sure, that there are no other refences in other part of the code -> in this case you can have functions in types which work like a destructor.

But there is no way to get the destructor automatically called.


Czar Flavius(Posted 2008) [#11]
Zauron, if you are using Blitzmax, I assume you are creating games for a PC.. and not a device with small memory like the 128kb you mentioned. In that case, the lack of constant tables is not nearly as important as you may think it is - normal tables that you just don't modify will perform the exact same role. They will use up more RAM yes, but negligable on a modern computer.

Non-constant tables also have the advantage that they are not fixed within the program - you can load the data from a data file on the disk at run-time, and put this data into the tables. The point of this is it allows users to modify games with their own rules and values, which may be something you would like to consider.

In Blitzmax I would do something like the following - have a unitdata type, containing hitpoints etc, with one unitdata type for each type of enemy or thing. It is basically a library. These can be stored either in an array with id number or a list. Then for each instance of this unit you have another type, I will just call unit. This has as a field a reference to a unidata, allowing it to directly link up to the data it needs, and avoids having to redunantly store the data in each instance of the unit.

Example:
Type TUnitData
   Field hitpoints:Int
   Field attack:Int
End Type

Type TUnit
   Field data:TUnitData
   Field x:Int, y:Int ' here the info unique to each instance
End Type



Otus(Posted 2008) [#12]
I know this thread is really old but no one answered the last few question I had. I was too busy to pursue it any more with big game projects at work, but now I'm thinking of taking another crack at indie development on the side. Can anyone answer the questions in my last post, like how to define a multi-dimensional array of static data, and what exactly Type Constants are and how they would help with having different enemies that are the exact same class and code but have different initial values pulled from data somewhere based on some kind of "enemy ID"?


Regarding "Type Constants" (which would be called static constant fields in many languages), you would use them something like the code below. You'll probably want to have different Types for different kinds of enemies though.

EDIT: Removed example, as it didn't work. Didn't have Blitz open at the time.

Then you can just go Respawn() your TCharacters, without worrying about their actual type. For something more complicated than default values, the approach Czar Flavius showed probably works better.

Oh, one other quick question - does BlitzMax have support for destructors? That would also be handy, especially since the garbage collection isn't too useful if you stick your objects into lists anyway so you still need to essentially manually delete objects.


BlitzMax does not have destructors. However, it does have finalizers and the garbage collector can be suspended, resumed and forced to collect when you need it to. Usually that's enough for memory management (at least for "small hobby projects").

BTW. Regarding constant tables, does "not loaded to RAM" mean the program reads them from the disk, when they are needed?!?


Zauron(Posted 2008) [#13]
Well, in my case it means that it reads it from the ROM instead of the RAM, but I suppose on a PC it would either read it from the disk or, more likely, it probably loads all the .exe's static data into RAM anyway to increase code execution time so it ends up in RAM anyway - but just declared in a way that I can't change it.

I guess it doesn't really matter though, looking back I was so concerned with optimization due to the hardware limitations I was working with at the time that I didn't really think about the fact that on a PC it would probably be fine to load whatever tables I need into RAM anyway, or if it was a HUGE table, change it to be a binary file that I read in when I need it.

What's a "finalizer?"


Otus(Posted 2008) [#14]
What's a "finalizer?"


http://en.wikipedia.org/wiki/Finalizer

In BlitzMax one is declared as a Delete method. It gets called when the garbage collector wants to free the object.