[GameMaker Tutorial] Simple Embedded database using Grid Data Structure

on October 2, 2017 - 1685 Views

GameMakerBlog Tutorials

Create A Simple Database with DS_GRID

Games require a lot of information, everything from player hp, mana, level, skills, attack power, sprites, and even quest lists. Storing that data is a common problem that has many different solutions, though when you want to make something simple, portable and only use the base language you might want to investigate using a data structure called a Grid. A Grid is very similar to both a classical RDBMS and even an HTML table. You have what can be considered rows and columns that you will store information into and then retrieve that information later on, you can even use built-in functions to write the grid to disc and store it away to read it again later. And since it will be an embedded system, you will not need to make any calls to an external database which reduces the failure points and complexity of your program. Of course, there are limits and you should think twice about using a simple grid to store hundreds of players data for an MMO or network based game but for games of small data sizes, especially those of the HTML5 target types, a grid could be a very quick solution to a complex problem.

Grids or virtual graphing paper

A Grid is defined as vertical and horizontal lines that cross each other forming a series of rows and columns made up of squares. While that may or may not be helpful, this great visual from the YoYo Games documentation certainly will be:

haya mix

So right away this may start to look either like a bad game of minesweeper or maybe just a giant bingo sheet but it really is a visual representation of a relational database. A database is simply a structured set of data that can be accessed in definable and repeatable ways. A relational database structures the sets of data in a way that recognizes the relationships between the data. All of this will begin to become clear after seeing a more fitting visual example. First let’s setup a GRID and define what we will be using it for:

  1. objMain -> Create Event
    • global.skillDB = ds_grid_create(7,3);
      • This creates a new ds (data structure) of type “grid”. The first number is the Width, or how many boxes across there will. In this case, I made it 7 boxes across. The second number is the Height, or how many boxes going down, I made it 3 boxes going down.
        • The Width is the “columns” and the Height is the number of “rows”. This example is 7 columns and 3 rows.
      • I am assigning the “grid” id to a global variable as the “skillDB” will be used throughout the game called by various scripts and objects. While it is generally a bad idea to use global variables, in the case of a central data structure that will be used throughout the game there should be no scoping issues and it is better than constantly recreating the grid.
  2. Create Script -> PopulateSkillDB
    ///@description Populates the Skill Grid Database
     
    #region Skill DB Legend
    /*
    0 = The Sprite Index for which Sprite represents the Skill
    1 = A human readable name for the Skill
    2 = The base Cooldown in seconds for the Skill
    3 = The base Damage of the Skill
    4 = The base Mana of the Skill
    5 = Whether the skill is used on the Enemy (False) or Player (True)
    6 = The Current Skill Level
    */
    var SPRITE = 0;
    var NAME = 1;
    var COOLDOWN = 2;
    var DMG = 3;
    var MANA = 4;
    var HEAL = 5;
    var LEVEL = 6;
    #endregion
     
    for (var counter = 0;counter<ds_grid_height(global.skillDB);counter++)
    {
    	if (counter == 0)
    	{
    		ds_grid_set(global.skillDB,SPRITE,counter,sprTripleShot);
    		ds_grid_set(global.skillDB,NAME,counter,"Triple Shot");
    		ds_grid_set(global.skillDB,COOLDOWN,counter,5);
    		ds_grid_set(global.skillDB,DMG,counter,7);
    		ds_grid_set(global.skillDB,MANA,counter,8);
    		ds_grid_set(global.skillDB,HEAL,counter,false);
    		ds_grid_set(global.skillDB,LEVEL,counter,5);
    		continue;
    	}
    	if (counter == 1)
    	{
    		ds_grid_set(global.skillDB,SPRITE,counter,sprHeal);
    		ds_grid_set(global.skillDB,NAME,counter,"Complete Heal");
    		ds_grid_set(global.skillDB,COOLDOWN,counter,50);
    		ds_grid_set(global.skillDB,DMG,counter,8);
    		ds_grid_set(global.skillDB,MANA,counter,25);
    		ds_grid_set(global.skillDB,HEAL,counter,true);
    		ds_grid_set(global.skillDB,LEVEL,counter,9);
    		continue;
    	}
    	if (counter == 2)
    	{
    		ds_grid_set(global.skillDB,SPRITE,counter,sprRoot);
    		ds_grid_set(global.skillDB,NAME,counter,"Root");
    		ds_grid_set(global.skillDB,COOLDOWN,counter,16);
    		ds_grid_set(global.skillDB,DMG,counter,2);
    		ds_grid_set(global.skillDB,MANA,counter,10);
    		ds_grid_set(global.skillDB,HEAL,counter,false);
    		ds_grid_set(global.skillDB,LEVEL,counter,0);
    		continue;
    	}
    }

      • This script populates the empty grid we created: global.skillDB
      • The for loop will loop through as many times as the number of “rows” or height of the grid
      • The IF statements determine the next skill to add to the grid, we could have used a switch statement here as well but I find the IF statement is cleaner to read through in this situation
  3. [Optional] Create a Helper Script to Get values from the ScriptDB
    1. /// GetValueFromSkillDB(valueType,valuePosition)
      /// @description Get a value from the Skill Grid DB
      /// @param valueType What value to look up (ie. SKILL NAME)
      /// @param valuePosition What database row is the specified skill in
       
      var SPRITE = 0;
      var NAME = 1;
      var COOLDOWN = 2;
      var DMG = 3;
      var MANA = 4;
      var HEAL = 5;
      var LEVEL = 6;
       
      var valueType = argument0;
      var valuePosition = argument1;
       
      if (valueType == "SPRITE")
      {
      	return ds_grid_get(global.skillDB,SPRITE,valuePosition);
      }
       
      if (valueType == "NAME")
      {
      	return string_replace(ds_grid_get(global.skillDB,NAME,valuePosition), "0", "");
      }
       
      if (valueType == "COOLDOWN")
      {
      	return ds_grid_get(global.skillDB,COOLDOWN,valuePosition);
      }
       
      if (valueType == "DMG")
      {
      	return ds_grid_get(global.skillDB,DMG,valuePosition);
      }
       
      if (valueType == "MANA")
      {
      	return ds_grid_get(global.skillDB,MANA,valuePosition);
      }
       
      if (valueType == "HEAL")
      {
      	return ds_grid_get(global.skillDB,HEAL,valuePosition);
      }
       
      if (valueType == "LEVEL")
      {
      	return ds_grid_get(global.skillDB,LEVEL,valuePosition);
      }

      • The important thing to remember when retrieving a value from the grid is you want to get the correct “value” from the right “row”. Another way to picture the GRID is like this:
      •  SPRITE INDEX[0]SKILL NAME[1]COOLDOWN[2]DAMAGE[3]MANA[4]HEAL[5]LEVEL[6]
        ROW[0]sprTripleShotTriple Shot578false5
      • So in the above table, to get the SKILL_NAME for sprTripleShot we call:
        • ds_grid_get(global.skillDB,1,0)
          • Where 1 refers to COLUMN [1] or : SKILL NAME[1] from the above table
          • Where 0 refers to the first ROW of data or ROW[0] from the above table
        • This may run counter-intuitive to those familiar with databases like MySQL where you may be used to accessing the ROW of data and then the COLUMN in the data: give me data from ROW1::COLUMN2, this is why I use a helper script so I can just call what I need by name: “LEVEL” instead of remembering that column 6 is the Level data
      • NOTE: You may have noticed there is a string_replace function when getting the NAME of the skill, this is to fix an issue with the HTML5 target and getting a string, there is a 0 (zero) prepended to the string name. I am not sure why this happens so I just work around it.
  4. Create Test Object: objSkills
    1. In Draw Event ->
      • draw_sprite(GetValueFromSkillDB("SPRITE",0),0,10,25);
        draw_sprite(GetValueFromSkillDB("SPRITE",1),0,10+64+10,25);
        draw_sprite(GetValueFromSkillDB("SPRITE",2),0,20+128+10,25);
         
        draw_text(10,25+64,GetValueFromSkillDB("NAME",0));
        draw_text(10+64+50,25+64,GetValueFromSkillDB("NAME",1));
        draw_text(70+128+50,25+64,GetValueFromSkillDB("NAME",2));

        • Here we are just drawing some basic information about the skills but you can do things like
          • Get the DAMAGE of a skill when the player attacks an enemy
          • Get the MANA amount of a skill when the player casts and subtract it from the total mana

What next?

Now that we have a working database we can add in as many skills as we need and easily adjust the base values without messing with a lot of different files. You can also write the contents of the grid to a string (not human-readable) and then load it again; in this way you create a very easy Save/Load system.

  1. ds_grid_write() will actually turn the grid structure into a  string, it does not actually “write” it anywhere
  2. ds_grid_read() reads the string structure and parses back into a grid structure
  3. Take precautions when working with HTML5 as there are some pitfalls that sneak up on you
    1. If there are any strings in the grid (like skill name) you might get a json parse error or grid read error
      1. Encode the strings first, then Decode after the read
      2. There are other alternatives
    2. You can save the grid to an ini file in local storage but remember that there are size limits to local storage
      1. Grids should not be used for storing millions of rows of information as stated previously

Hey guys…check this out

Here is a very short HTML5 example of the grid system working, along with saving/loading/restarting and saved “passwords”. Left Click on the Skill Icon to raise the level of the skill. Click Save to save the current skill level (if this is your first time saving it might take a few seconds to create the save data on your browser). Start Over resets the grid db back to defaults.

If anyone would like to know more details on anything, just leave a comment below. There are a lot of different functions and techniques that go off topic like: saving/loading, string encoding, especially when it comes to HTML5.

Good Luck in all things,
Hikati Games
Hikati Games LLC Logo

 

 

 

Recent Posts

Leave a Reply

Your email address will not be published. Required fields are marked *

UA-103187421-1