Legacy GM 4D array equivialent

G

Guest User 1

Guest
I'm working with isometrics and so am working with a 3D grid of "blocks", all of which need a variety of properties. The ideal way of doing this would just to use an array as such:
Code:
world[x, y, z, p];
//x is the numbered block along the isometric x axis
//y is the numbered block along the isometric y axis
//z is the numbered block along the isometric z axis
//p is the numbered property of the specified block
But I recently learnt that GMS can only use up to 2D arrays.
How can I get around this problem, maybe using nested arrays of some kind where every single block as its own single number like an ID, that being the first dimension, and then the property being the next? The only problem there is I need a 3D coordinate way of referencing them for ease of programming.

Thanks for any help.
 

Hyomoto

Member
When it comes to this you'd need to learn to use data structures effectively. For example, a 40 x 40 ds_grid where each index points to a ds_list. Each index in the ds_list could point to a ds_map. Then you'd need to write a function something like this:

Code:
/// mixed getArray4D( id, x, y, z, p )
var _id = argument0, _x = argument1, _y = argument2, _z = argument3, _p = argument4;
var _index, _property;

// # if our x, y value is beyond the dimensions of our array, return undefined; you'd have to perform error handling or let it crash on the other side
if ( 0 > _x ) | ( _x => ds_grid_width( _id ) ) | ( 0 > _y ) | ( _y >= ds_grid_height( _id )  ) { return undefined }
_index = _id[# _y, _x ];

// # same thing here
if ( 0 > _z ) | ( _z >= ds_list_size( _index )  ) { return undefined }
_property = _index[| _z ];

// # Because properties are a ds_list it will return undefined if the specified _p doesn't exist anyways, so no need to perform checking.
return _property[? _p ]
If you KNOW you will be providing legitimate values you can reduce the above into:

Code:
// mixed getArray4D( id, x, y, z, p );
var _id = argument0, _x = argument1, _y = argument2, _z = argument3, _p = argument4;

_index = _id[# _y, _x ];
_property = _index[| _z ];
return _property[? _p ]
There are probably plenty of ways to do this, in fact, you could ditch the original grid and do them it with only ds_lists, or ds_maps. This should give you an idea of what the layout of such a system would look like. I provide this as an alternative to what jo posted since the topic there seems to want to make use of local_array_2d_set which as far as I know is a fairly slow solution, and perhaps even a bit inelegant. The truth is, though, no matter what route you go here you'll be doing a bit of the legwork on your own.

There is one other idea that I've mulled around a bit. GML has objects which can be used for more than just representing a wall or player. Building a 2d array where each index points to an instance_id, and simply deactivating all of those instances allows for some interesting possibilities. In the above example you could do something like this:

(data[ _x, _y ]).value[ _z, _p ].

But even better would be something like:

( data[ _x, _y] ).property

For example, let's say your player has a sword. You can create a new instance of that sword and save its id to an inventory, for example, then deactivate. No further processing occurs effectively turning it into a variable container. That allows you to do things you might see in other languages like:

sell_value = sword.value

Or:

attack = player.strength + (player.equip[ 0 ]).attack;

Anyways, perhaps something to play around with. GML has a lot of limitations, but a bit of clever coding can really yield some interesting solutions.
 
Last edited:
G

Guest User 1

Guest
As much GML as I do know, I barely understood any of that. On the other hand, I've had an idea. If I use a function to turn a set of 3D coordinates into a block ID, then I can reference this ID using the function, I get the ID for the programming side, but I reference it as 3D coordinates. Like so:
Code:
//world[coord(x, y, z), p]
//code for coord() function:
return argument0 + (argument1 * world_x) + (argument2 * world_x * world_y)
If my maths is correct, then if you start at the very bottom, you have 0, then as you increase x, this goes up in 1s. Then you increase y by 1 and it goes up by the x size of the world. Then when you increase z, it goes up by the number of blocks one z level down.

So if my width, length, and height are 10 blocks. coord(1, 2, 3) would return 1+20+300, that being 321. If all the dimensions were 15 then coord(3, 2, 1) would return 1 + 30 + 675 = 706.
 

jo-thijs

Member
Yeah, that should work.
You can optimize it a bit to:
Code:
return argument0 + world_x * (argument1 + world_y * argument2);
This is in fact how C compilers usually treat multi-dimensional arrays.
Make sure though that you don't need to resize your arrays too much.
Also, make sure your arrays don't contain too many elements.
GameMaker has a limit on them.
 
G

Guest User 1

Guest
What is the limit? If I make world_z 100 for instance, how big can I make the other two before it reaches its limit? Keeping in mind I'll probably end up having between 10 and 20 different values for p.
 
Do note the while arrays have an index limit, DS lists do not and you can use the same formula to fake multi dimensional structures.
 
G

Guest User 1

Guest
What are the differences in use of a ds_list and an array? Would I still reference it in essentially the same manner?
 

jo-thijs

Member
I actually can't find the limit in the manual.
Did they get rid of the limit?

It was 32000 for each index and 1000000 on the total array size in GameMaker 8.
The limitations might now depend on the target and running device.

The differences between a ds_list and an array, is that lists have more functionality and features.
Lists are not contained in single variables though.
You create lists through ds_list_create().
This function returns a reference id you need to store in a variable to use in future list functions.
You also need to manually destroy lists yourself with ds_list_destroy(...)
or you'll create a memory leak.
 
G

Guest User 1

Guest
Why might I need to destroy it if I will always be using it and referencing it? Also how do I reference it in 2 dimensions with the id from the function and the numbered property? What do I replace "world[coord(x, y, z), p]" with?
 

Hyomoto

Member
There are a lot of misconceptions about data structures, but what jo is pointing out is that IF you no longer need them, GameMaker does not manually free the memory they use. The GameMaker manual explicitly tells you to destroy them to prevent memory leaks which I think causes quite a bit of confusion about them. The simplest way to explain it is when you create a variable using var it is freed once the script ends. When you create a local variables, they are freed when the instance they belong to is destroyed. Data structures are like global variables in that the memory they use is never automatically freed by anything other than closing the program since they don't 'belong' to anything. Thus if you create new ones without destroying old ones, you could get a memory leak. You'd get the same effect creating variables you don't use.

If you are using your data structures, which you should be if you created them, there's probably no reason to destroy them.

The difference between an array and a grid is functionality. Arrays are technically faster than grids, but that's probably only an issue if you are really scraping for performance and grids have some built in features to manipulate their data you might find useful. However, arrays can be 'ragged' and expanded as needed. That is to say, a ds_grid is X by Y, and resizing it requires you to destroy it and make a new one. An array can be enlarged simply by adding new entries to it, and each row can be variable in length. That means, if you don't know how big your array needs to be and it might get larger, or each row may have a different number of columns, they are a superior choice. They are limited in size, 32000 isn't a hard limit, it's just that entries above that cannot be created or accessed as opposed to the program crashing like checking a non-existent array entry normally would.

Lastly, yes, your idea should work fine.
Code:
world[# coord(x, y, z), p ];
If you were using a ds_list, it does not have key -> value pairs. A list is just a list of values, which means your P would actually be the x, y, z entry.

Code:
p = world[| coord(x, y, z) ]
You'd probably only find that useful if that was the pointer to a ds_map or ds_list that then held the properties for that block. Check out the accessors section of the manual for more information about these and how ?, | and # are used with data structures.

Code:
_ds = world[| coord( x, y, z) ]
return p = _ds[? p ]
The value in using a map here is that it will return undefined if the property doesn't exist, the property can be reference by number OR name, and it won't crash if you try to reference a property that isn't defined so you can handle it however you want.
 
Last edited:
However, arrays can be 'ragged' and expanded as needed. That is to say, a ds_grid is X by Y, and resizing it requires you to destroy it and make a new one. An array can be enlarged simply by adding new entries to it, and each row can be variable in length. That means, if you don't know how big your array needs to be and it might get larger, or each row may have a different number of columns, they are a superior choice.
ds_grid_resize()
You don't have to destroy a grid to resize.
 

TheouAegis

Member
You create a list.
You populate the list.
You play through the game.
You die.
You go back to the main room.
You create a list.
You populate the list.
You play through the game.
You win.
You go back to the main room.
You create a list....

That's 3 lists now because you didn't delete the lists.


You create a list.
You populate the list.
You play through the game.
You die.
You call game_restart().
You create a list.
You populate the list...

And so on and so on. Lots of things people take for granted in GM that actually cause memory leaks as a result.
 

Hyomoto

Member
Use the list you already created. There's no reason to keep recreating a list. If you are still using room_restart, you probably shouldn't be using data structures and definitely shouldn't be worried about building 4 dimensional structures. That said, the object that created the data structures should always know to destroy them if/when it is destroyed.

Besides, 3 unused lists while bad practice is not going to crash your program. You might as well be beating about not using local variables you don
ds_grid_resize()
You don't have to destroy a grid to resize.
Touche.
 
G

Guest User 1

Guest
Ok I've got the hang of it now. I use ds_map_create() (I'm using a map now as it's like a 2D array which is what I need now that I'm using the coord() script). I delete it when I no longer need it. I can resize it and everything else and it has theoretically infinite size - the limits being memory and bits.
 
Top