GML Saving and retoring objects to/from Ini when out of range of player

B

Brandon Gale

Guest
I'm attempting to destroy enemy objects, as they go out of range of the player, and save them to an ini file. Then I am trying to restore those enemies as the player comes within range again.

I'm horribly green and reading on is probably going to cause a few heart-attacks.

Prepare to avert your eyes from the painful view...

This code is in the player step event:
Code:
if obj_player.x < room_width/8 {
    with (all) {
    x += room_width/2;
    }
    script_execute(script_mem_check);
}
if obj_player.x > room_width - (room_width/8) {
    with (all) {
    x += -(room_width/2);
    } 
    script_execute(script_mem_check);
}

if obj_player.y < room_height/8 {
    with (all) {
    y += room_height/2;
    }
    script_execute(script_mem_check);
}
if obj_player.y > room_height - (room_height/8) {
    with (all) {
    y += -(room_height/2);
    } 
    script_execute(script_mem_check);
}
This is the script called to try and achieve the lovely/lofty goals I stated previously (script_mem_check):
Code:
globalvar enemy_x;
globalvar enemy_y;
globalvar arry_enemy_hp;
globalvar arry_enemy_spd;
globalvar enemy_save_temp;
globalvar player_prox;
globalvar playerprox_temp;
playerprox_temp = 0;

var i;
for (i = 0; i < instance_number(obj_enemy); i += 1)
   {
    enemy[i] = instance_find(obj_enemy,i);
          with enemy[i]{
            playerprox_temp = playerprox;
        }
        if playerprox_temp > 3000
        {      
          enemy_x[i] = enemy[i].x;
          enemy_y[i] = enemy[i].y;
          with enemy[i]{
              arry_enemy_hp[i] = enemy_hp;
              arry_enemy_spd[i] = spd;
              player_prox[i] = playerprox;
              arry_enemy_id[i] = enemy_id;
                instance_destroy();
          }
          enemy_save_temp = i + 1;
        }
   }
   enemy = 0;
ini_open( "save_mem_dump.ini" );

for(var i = 0; i  <  enemy_save_temp; i ++)
    {
        ini_write_real(string(arry_enemy_id[i]), "enemy_x" , enemy_x[i]);
    }
for(var i = 0; i  <  enemy_save_temp; i ++)
    {
        ini_write_real(string(arry_enemy_id[i]), "enemy_y", enemy_y[i]);
    }

for(var i = 0; i  <  enemy_save_temp; i ++)
    {
        ini_write_real(string(arry_enemy_id[i]), "arry_enemy_hp", arry_enemy_hp[i]);
    }
for(var i = 0; i  <  enemy_save_temp; i ++)
    {
        ini_write_real(string(arry_enemy_id[i]), "arry_enemy_spd" , arry_enemy_spd[i]);
    }
    for(var i = 0; i  <  enemy_save_temp; i ++)
    {
        ini_write_real(string(arry_enemy_id[i]), "arry_enemy_id" , arry_enemy_id[i]);
    }
        for(var i = 0; i  <  enemy_save_temp; i ++)
    {
        ini_write_real(string(arry_enemy_id[i]), "player_prox", player_prox[i]);
    }
ini_write_real( "save1", "enemy_save_temp", enemy_save_temp );
ini_write_real( "enemy_count", "enemy_count", enemy_count );

ini_close();

gml_pragma("global", "scriptInitvariables()")

global.global_player_fuel_init = 10000;
global.player_recharge_init = 1;
global.player_money_init = 10000;
global.plasma_damage_init = 3

global.player_recharge = global.player_recharge_init;
global.global_player_fuel = global.global_player_fuel_init;
global.player_money = global.player_money_init;
global.plasma_damage = global.plasma_damage_init;

globalvar enemy_save;
enemy_save = 0;
globalvar enemy_x;
globalvar enemy_y;

globalvar enemy_count;
ini_open("save_mem_dump.ini");
   if !ini_section_exists("enemy_count")
   {
    enemy_count = 0;
    ini_write_real( "enemy_count", "enemy_count", enemy_count );
   }
    enemy_count = ini_read_real( "enemy_count", "enemy_count", 0 );

ini_close();

globalvar arry_enemy_hp;
globalvar arry_enemy_spd;
globalvar arry_enemy_id;
globalvar enemy_save_temp;

enemy_save_temp = 0;
The code seems to sortof write some of what I'm hoping for (sortof), but doesn't seem to restore any enemies at all. Any advice is welcome (including neatness tips on my wretched excuse for coding). I've never worked with anyone, clearly.

Again, I'm sorry to put anyone through this nightmare...
 

Phil Strahl

Member
I am being a completely ignorant dickhead and instead of answering your question, I am questioning it in true StackOverflow style: "Why do you want to do that?" Or better: What problem are you trying to solve by doing this? If you are worried that you are running out of memory, it's easier to deactivate objects and re-activate them at a certain radius around the view/player.

But when you want to serialize your data out onto a file, doing this via ini_write and ini_read might not be the best choice as reading and writing it can become a bit unwieldy, as you've noticed. Have you looked into writing ini files via ds_maps_write()? This might be better suited. Or you can read and write a ds_map or a ds_list as JSON (json_Decode() and json_encode()). Lastly, ds_map_secure_save() would allow you to encrypt the map before saveing, so player can't easily tamper with the data.

Again, sorry for being an ignorant, especially if you already went through the manual and know all this stuff. In that case, apologies for not helping you at all.
 

TheouAegis

Member
Using external files is a lot slower than just filling up the RAM using data structures.

You may be better off just deactivating instances. This will cause them to linger in RAM, but at least you're not constantly running Create and Destroy parameters.

If you are trying to have an auto-save, you should save everything at intervals, not in the midst of an expensive create-destroy loop.

Where's the restoration code? Is it in that script? All I noticed were the ini_writes, not ini_reads.
 
B

Brandon Gale

Guest
I am being a completely ignorant dickhead and instead of answering your question, I am questioning it in true StackOverflow style: "Why do you want to do that?" Or better: What problem are you trying to solve by doing this? If you are worried that you are running out of memory, it's easier to deactivate objects and re-activate them at a certain radius around the view/player.

But when you want to serialize your data out onto a file, doing this via ini_write and ini_read might not be the best choice as reading and writing it can become a bit unwieldy, as you've noticed. Have you looked into writing ini files via ds_maps_write()? This might be better suited. Or you can read and write a ds_map or a ds_list as JSON (json_Decode() and json_encode()). Lastly, ds_map_secure_save() would allow you to encrypt the map before saveing, so player can't easily tamper with the data.

Again, sorry for being an ignorant, especially if you already went through the manual and know all this stuff. In that case, apologies for not helping you at all.
You are not being a dickhead (but the StackOverflow peeps definitely are). The length at which you've responded is generous considering the heap-o-trash I've offered up. I am happy to glean from a more advanced perspective.

I appreciate your starting with that question ('what am I trying to accomplish'). I ought to have led with that as the title (my title was getting a bit long as it was, albeit).

I am indeed trying to come up with a system for alleviating memory. Unfortunately, I am at lunch atm and have ran out of time to respond further. At any rate, I will likely scrap my silly plans for ini and probably study Json instead. I have been considering the other various structures (ds_grids is one I've looked at most).

Thank you for your suggestions. It helps to get out of my own head. I will post again, when I am home, in more detail.
 
B

Brandon Gale

Guest
Using external files is a lot slower than just filling up the RAM using data structures.

You may be better off just deactivating instances. This will cause them to linger in RAM, but at least you're not constantly running Create and Destroy parameters.

If you are trying to have an auto-save, you should save everything at intervals, not in the midst of an expensive create-destroy loop.

Where's the restoration code? Is it in that script? All I noticed were the ini_writes, not ini_reads.
Yeah, I have been attacking that code so much, I must have finally given it one too many axe hits right before pasting it here. Please forgive me for even subjecting anyone to it.

With that behind us, any recommendations for helping a small-timer learn how to put things out of memory and into a file of some sort, would be greatly appreciated. I'm am quite new at this, but am looking forward to learning all sorts of various methods. So, any tips or tutorial links would be awesome!

(Btw, I did create a save feature that seems to work lovely using an array and a precise numbering system. It puts everything to ini and then returns it all. This method didn't seem to work during gameplay though, only for saving and restoring while everything was static). Any great tutorials on Json specific to GameMaker memory handling?
 
I'm glad I came across this topic right now, I just started trying to implement something similar and it's a bit confusing trying to figure it out for the first time.

My idea was to basically store an array of enemy coordinates and such in the room and have the game create enemy objects from them when the points saved are within the screen; ini files are great for storing level and save data but they seem like they'd be a lot slower to read/write to constantly. But I don't know the quickest way to pull this off sadly!
 

TheouAegis

Member
Personally if I'm going to have level data stored in a file, I save all my data in raw values in .dat files using a hex editor, then open the file and dump the contents into a buffer, then just read from the buffer.
 
B

Brandon Gale

Guest
I'm glad I came across this topic right now, I just started trying to implement something similar and it's a bit confusing trying to figure it out for the first time.

My idea was to basically store an array of enemy coordinates and such in the room and have the game create enemy objects from them when the points saved are within the screen; ini files are great for storing level and save data but they seem like they'd be a lot slower to read/write to constantly. But I don't know the quickest way to pull this off sadly!
This is my updated code. It is getting really close to working, but I am still making a slight math error somewhere I think. It has to do with the position changes of the enemies (required to keep everything away from the edge of the room -- infinite).

I fully believe that Json is going to be better for what I'm doing, but I enjoy trying to trudge through the rough terrain anyways. (In fact, I programmed games using nothing but Excel VBA for 2 years... as a hobby).

Code:
globalvar enemy_x;
globalvar enemy_y;
globalvar arry_enemy_hp;
globalvar arry_enemy_spd;
globalvar enemy_save_temp;
globalvar player_prox;
globalvar playerprox_temp;
playerprox_temp = 0;

var i;
for (i = 0; i < instance_number(obj_enemy); i += 1)
   {
    enemy[i] = instance_find(obj_enemy,i);
        if enemy[i].playerprox > (room_width/2)
        {
            var enemy_map_x = enemy[i].x //+ global.ply_map_x;
            var enemy_map_y = enemy[i].y //+ global.ply_map_y;
            ini_open( "save_mem_dump.ini" );
                ini_write_real(enemy[i].enemy_id, "enemy_x" , enemy_map_x);
                ini_write_real(enemy[i].enemy_id, "enemy_y", enemy_map_y);
                ini_write_real(enemy[i].enemy_id, "arry_enemy_hp", enemy[i].enemy_hp);
                ini_write_real(enemy[i].enemy_id, "arry_enemy_spd" , enemy[i].spd);
                ini_write_string(enemy[i].enemy_id, "arry_enemy_id" , enemy[i].enemy_id);
                ini_write_real(enemy[i].enemy_id, "player_prox", enemy[i].playerprox);
            ini_close();
              with enemy[i]
              {
                instance_destroy()
              }
          enemy_save_temp = i + 1;
        }
   }
   enemy = 0;

player_prox_temp = 0;
enemy_x_temp = 0;
enemy_y_temp = 0;
enemy_instance = 0;
ply_x = obj_player.x + -global.ply_map_x;
ply_y = obj_player.y + -global.ply_map_y;

ini_open("save_mem_dump.ini");
for(var i = 0; i  <  99; i ++)
{
    if ini_section_exists(string(i))
    {
        //player_prox_temp = ini_read_real(string(i), "player_prox", 0);
            enemy_x_temp = ini_read_real(string(i), "enemy_x", 0 );
            enemy_y_temp = ini_read_real(string(i), "enemy_y", 0 );
            //enemy_x_temp += global.ply_map_x;
            //enemy_y_temp += global.ply_map_y;
                if point_distance(ply_x, ply_y, enemy_x_temp, enemy_y_temp) < (room_width/2)
                {        
                enemy_instance = instance_create_layer(enemy_x_temp, enemy_y_temp,"layer_enemy", obj_enemy);
                with enemy_instance
                {
                    enemy_hp = ini_read_real(string(i), "arry_enemy_hp", 0 );
                    spd = ini_read_real(string(i), "arry_enemy_spd", 0 );
                    enemy_id = ini_read_string(string(i), "arry_enemy_id", 0 );
                } 
            ini_section_delete(string(i));
            }
    }
}
ini_close();

enemy_x = 0;
enemy_y = 0;
 arry_enemy_hp = 0;
 arry_enemy_spd = 0;
 enemy_save_temp = 0;
 player_prox = 0;
 arry_enemy_id = 0;
 player_prox_temp = 0;
enemy_x_temp = 0;
enemy_y_temp = 0;
enemy_instance = 0;
Personally if I'm going to have level data stored in a file, I save all my data in raw values in .dat files using a hex editor, then open the file and dump the contents into a buffer, then just read from the buffer.
Hmm, I am not familiar with .dat and I've never seen a hex editor either. I'll have to look into buffers also. :p

Like I said, I'm green. However, I am having a blast learning everything. It is way smoother than making games in VBA :p
 
I know this is a year old topic, I wanted to bump it because I'm pretty close to figuring out how to properly do this myself, but I need some help.

I basically store all the enemies in the level in a grid along with their starting x and y positions and their index in the grid, like so:

Code:
grid[# objectX, i] = x;
grid[# objectY, i] = y;
grid[# objectGridIndex, i] = i;
grid[# objectSpawnBit, i] = true;
grid[# object, i] = object_index;
The enemy is only spawned if its spawn bit is set to true; if an enemy walks offscreen or something it will set it's own spawn bit to true before destroying itself so it can be spawned later when the camera scrolls back to it's spawn point. If the player destroys an enemy on the other hand, it will not set it's spawn bit in the grid to true, so it won't be respawned again.

What I'm not sure of is exactly how to spawn the enemies. I haven't been able to find much information, but it basically seems like the best way to do so is to sort the grid indexes by their x position (which I have a ds_list that are the indexes sorted by x position) and to basically loop through the list spawning enemies (provided they haven't been spawned yet) until you reach the edge of the screen.

The M.C. Kids programming breakdown at https://games.greggman.com/game/programming_m_c__kids/ suggests this:
Objects are introduced into the object system through various means. The most common are objects that appear as the player moves through the level. For each level in the game there is an object introduction table. This table describes where various objects appear in the level such as gophers, fish, crabs and moving platforms.

Each entry in the table has 3 data fields: X position, Y position and object type. The object table is sorted in ascending X position order. The object introduction routines keep an index for the current position in the table.

When the player moves to the right, the current screen position is compared with the current object in the table. If the object is inside the screen it is introduced to the active list of objects and the index is incremented. If the object is not inside the screen then all objects further along in the table are also not inside the screen since they are farther to the right.

The routines also handle object introduction when scrolling left, up and down in a similar manner. For moving up and down there is a table of indices sorted in ascending Y position.

There is also an active flag table in RAM with one flag for each entry in the object introduction table. When an object is introduced from the object introduction table its corresponding active flag is set. When the object introduction routines are about to introduce a new object they first check its active flag to see if the object has already been introduced and if it is they don't introduce the object again.

I also found a breakdown of how to port Sonic 3's object table manager to Sonic 2's and while I can't really read assembly code, it seems to operate on a similar basis: https://info.sonicretro.org/SCHG_How-to:Port_S3K_Object_Manager_into_Sonic_2

Seems like you need to store the leftmost grid position and the rightmost grid positions on the screen, and when the camera moves loop through any objects on the screen that weren't there before and run the spawning routine if they're on the screen. Would anyone know how to best do this?
 
Top