• Hey! Guest! The 36th GMC Jam will take place between February 27th, 12:00 UTC - March 2nd, 12:00 UTC. Why not join in! Click here to find out more!

Help with understanding Arrays

Halas

Member
So I am trying to learn Arrays, and it definitely is something that is hard to wrap my mind around.

What I am trying to do is create an array for an RPG character to handle his levels and stats.
So each level, the player will get one ability point to put into stats.

What I have so far, is not much at all. haha, but please let me know if I am doing this correct and what my next steps would be.

I have an object that if the player picks up, gets experience.

if not instance_exists(other) exit;
audio_play_sound(a_expr, 1, false);
with (other)
{
experience += 1;
if (experience >= max_experience)
{
level += 1;
experience = experience - max_experience;
max_experience += max_experience;
Cabilitypt += 1;
}

now, for the array. Where and how should I set this up?

I'd like it to be something like.

If (Cabilitypt <= 1)
{
Levelup_script;
}


Levelup_script

Code:
/// Create Choices

/// Character Stats 2D Array

/// 0-Levels (50)
/// 1-Vitality (Health, Stamina)
/// 2-Intelligence (Mana, Spell damage)
/// 3-Strenght (Melee Damage)
/// 4-Dexterity (Ranged Damage)
/// 5-Defence (Reduced Damage taken)

/// Initialize

for (var i = 50; i < array_height_2d(Player_Stats); ++i;)
   {
   for (var j = 6; j < array_length_2d(Player_Stats, i); ++j;)
      {
      Player_Stats[i, j] = -1;
      }
   }

Now I need to add in, I think strings to draw the choices and for the player to select the choice. Am I heading in the correct direction?
 

TheouAegis

Member
Is this array the BASE stats at every level? Or is it how many points leveling up grants at each level gain? If it's for neither of those, then you need to rethink your strategy. Even then, I'd still reconsider this and use an algorithm instead.

Also your first for loop i think is wrong.
 
So what am I gathering is:
1.) Every level up, the player gets 1 ability point to spend.
2.) The player can spend that 1 ability point on 1 of 6 abilities.

You want to keep track of how many ability points each ability has, correct?
Can the abilities have an unlimited amount of points, or do they have a max limit?
What are the initial point values for those abilities, 0?

What I would do is initialize the array with all the initial point values for each ability. Let's have "a" be the ability itself and "ability_selected" be the ability selected to add a point to. Let's also say you start off with no points in any of them. You don't need a 2d array. The amount of points will be the VALUE of the 1d array, which references the ability.
Code:
//initialize the array

for(a=0;a<6;a++){
Player_Stats[a] = 0; //zero points
}
Now when you want to add a point to an ability...
Code:
Player_Stats[ability_selected] += 1;
Now you just need to set ability_selected to the one you've picked, which should be a number.
 

Halas

Member
Can the abilities have an unlimited amount of points, or do they have a max limit?

Max level being 50, the player would have 50 points to put wherever they chose. So upto 50 in one ability, 0 in the others.

What are the initial point values for those abilities, 0?
Yes ideally it would start at 0.

For example, base health would have 10 points.
At 100 health. For every point, it increases by 10.

So initialize 6 spots.
Then declare them?
Player_stats[0] = Vitality
Player_stats[1] = intelligence
Etc?

Then on each level.
Create buttons on the character stats screen that activate this (how would this be done)

Player_Stats[ability_selected] += 1;
Once selected delete other buttons?

?
 
So initialize 6 spots.
Then declare them?
Player_stats[0] = Vitality
Player_stats[1] = intelligence
Etc?

Then on each level.
Create buttons on the character stats screen that activate this (how would this be done)

Player_Stats[ability_selected] += 1;
Once selected delete other buttons?

?
No. The array will NEVER have the actual name of the ability as its value... that's what the chart you provided is for (to be used as a reference). Each ability will be identified by a number, with 0 being vitality, 1 being intelligence, etc.

The actual value for the array is the point amount, which starts at 0. Declare the array exactly as I did.

Selecting a button can be done in several different ways, but I don't know if you want to use the mouse to select and click the button or to select it with the keyboard.

Once the button is selected, you'll do Player_Stats[ability_selected] += 1 and then destroy ALL the buttons, including the one you just pressed.

EDIT: I just realized you only have 5 abilities, so that for loop should be this instead:
Code:
for(a=0;a<5;a++){
Player_Stats[a] = 0; //zero points
}
And you'll want to move each ability down a number and get rid of the first one you had, as the current level is NOT an ability:
Code:
/// 0-Vitality (Health, Stamina)
/// 1-Intelligence (Mana, Spell damage)
/// 2-Strength (Melee Damage)
/// 3-Dexterity (Ranged Damage)
/// 4-Defense (Reduced Damage taken)
 
Last edited:

Halas

Member
Okay so ability selected is = to 0-6 depending on the choice.

I'd like it to be a button press.

For example the attached image. A + sign shows up beside each of the abilities when CharAbility <= 1. For the player to hover over and click.

Okay I will remove the level. I had assumed it was needed for a 2D array , but I guess this is a 1D array that should be fine.
I would draw a "+" Sprite to the stats gui screen that's accessed when the player hits "C"

I'm not sure how to implement the choice aspect.
Is it more, if mouse click,
Player_stats[0] += 1
Instance_destroy();

(should I be destroying buttons, for instance if CharAbility = 2 it should be more
CharAbility -= 1 since we have in the beginning
If CharAbility <= 1
Do all this)

Where would I store this function? In a persistent game object? Or is this a script?
Or should I use global variables for the stats since it will be called by the player, level objects and eventually an inventory system.
 

Attachments

ability_selected will be from 0-5.

I will try my best with the button press. I've never done this in my game before, so this may not work.

For the button press, all the buttons need to be unique, so I'd suggest creating five objects with the button sprite set for each. Call them obj_button1, obj_button2, obj_button3, obj_button4, and obj_button5.

Then, in each of those objects, have a Mouse Left event. In that event is where you'll want to update ability_selected and add the point to the correct ability. I don't know which of your objects initialized/contains the array, so just change obj_levelup to that object.

So, for the first button (obj_button1):
Code:
with(obj_levelup){
ability_selected = 0;
if global.level <= 50{
Player_Stats[ability_selected] += 1;
instance_destroy(obj_button1);
instance_destroy(obj_button2);
instance_destroy(obj_button3);
instance_destroy(obj_button4);
instance_destroy(obj_button5);
}
}
Second button (obj_button2):
Code:
with(obj_levelup){
ability_selected = 1;
if global.level <= 50{
Player_Stats[ability_selected] += 1;
instance_destroy(obj_button1);
instance_destroy(obj_button2);
instance_destroy(obj_button3);
instance_destroy(obj_button4);
instance_destroy(obj_button5);
}
}
Third button (obj_button3):
Code:
with(obj_levelup){
ability_selected = 2
if global.level <= 50{
Player_Stats[ability_selected] += 1;
instance_destroy(obj_button1);
instance_destroy(obj_button2);
instance_destroy(obj_button3);
instance_destroy(obj_button4);
instance_destroy(obj_button5);
}
}
Fourth button (obj_button4):
Code:
with(obj_levelup){
ability_selected = 3;
if global.level <= 50{
Player_Stats[ability_selected] += 1;
instance_destroy(obj_button1);
instance_destroy(obj_button2);
instance_destroy(obj_button3);
instance_destroy(obj_button4);
instance_destroy(obj_button5);
}
}
Fifth button (obj_button5):
Code:
with(obj_levelup){
ability_selected = 4;
if global.level <= 50{
Player_Stats[ability_selected] += 1;
instance_destroy(obj_button1);
instance_destroy(obj_button2);
instance_destroy(obj_button3);
instance_destroy(obj_button4);
instance_destroy(obj_button5);
}
}
In that first box in your picture, you'll just want to display the level... I'd make it global.level. Don't forget to initialize that as well... global.level = 1;
 
Last edited:
I recommend you try using enums for your stat types. Then when you are referencing the array it is easy to read the code, plus you can leverage them for looping through the array as well as writing code that can handle additional stats without much modification.

Code:
enum Stats
{
 Vitality,
 Intelligence,
  Strength,
  Dexterity,
  Defense,
  MAX
}

ability_selected = Stats.Vitality;
enums are just a way to put human readable labels on numbers, by default the values in an enum start at 0 and increase by 1 for each value.

The computer still sees them as numbers, but makes it easier for coding for us humans.

To loop through the stats array, you can just start from 0 and go to Stats.MAX

Code:
for ( var i =  0; i < Stats.MAX ; ++i)
{
    // Do stuff
    Player_Stats[i] = 100;
}
 
I recommend you try using enums for your stat types. Then when you are referencing the array it is easy to read the code, plus you can leverage them for looping through the array as well as writing code that can handle additional stats without much modification.

Code:
enum Stats
{
 Vitality,
 Intelligence,
  Strength,
  Dexterity,
  Defense,
  MAX
}

ability_selected = Stats.Vitality;
enums are just a way to put human readable labels on numbers, by default the values in an enum start at 0 and increase by 1 for each value.

The computer still sees them as numbers, but makes it easier for coding for us humans.

To loop through the stats array, you can just start from 0 and go to Stats.MAX

Code:
for ( var i =  0; i < Stats.MAX ; ++i)
{
    // Do stuff
    Player_Stats[i] = 100;
}
I've never used enums. Good to know!
 

Halas

Member
I am a bit confused by this part:

{
// Do stuff
Player_Stats = 100;
}

Could you please elaborate on this part? We are initializing the array, I wasn't sure what else you would more like, could do during this stage.

Also we referenced Player_stats - do you mean Stats? (enum Stats)
 

Rob

Member
You can set an entry in an array to whatever you want whilst initialising it at the same time.

You don't have to set every entry in an array to -1 before you can use it.

If i put:

Code:
a_stats[4] = "Hi";
An array will be created called "a_stats" containing 5 entries and the fifth entry (a_stats[4]) will equal "Hi". The other 4 entries will automatically be set to 0.

I definitely recommend using an enumerator, even if you only have 6 stats. You could also use a 2D array to display the NAME of the stat as well as its value eg:

Code:
enum Stats
{
 Vitality,
 Intelligence,
  Strength,
  Dexterity,
  Defense,
  MAX
}

enum Stats_Info{
   Value,
   Name,
}

a_stats[Stats_Info.Value, Stats.Vitality] = 3;
a_stats[Stats_Info.Name, Stats.Vitality] = "Vitality";
I would just use one object and have 6 instances of it as buttons too - each button having its own individual variable that lets it know what entry in the array it is (actually I'd use a for loop and not have the extra object at all but its all personal preference)
 

Halas

Member
I would just use one object and have 6 instances of it as buttons too - each button having its own individual variable that lets it know what entry in the array it is (actually I'd use a for loop and not have the extra object at all but its all personal preference)
How exactly would that work?

I have this currently inside my Draw GUI Event

Code:
#region Stats Screen
    if stat_screen
        {
            ///Draw Stats Sprite
        draw_sprite(spr_stats_screen, 0, 0, 0)      
            ///Draw Text
        draw_set_font(Font_MMenu);
        draw_set_color(c_yellow);
        draw_set_halign(fa_center);
        draw_set_valign(fa_middle);      
            ///Draw Title
        draw_text(guiWidth-950, guiHeight-700, "Stats Screen");      
            ///Draw Stats
            _tw = 490
        draw_text(_tw, 125, + string(level));    /// Character Level
        draw_text(_tw, 265, + string(Obj_Player.max_hp));    /// Character Vitality
        draw_text(_tw, 325, "10");    /// Character Intelligence
        draw_text(_tw, 375, "10");    /// Character Strenght
        draw_text(_tw, 435, "10");    /// Character Dexterity
        draw_text(_tw, 495, "10");    /// Character Defence
        draw_text(_tw, 575, + string(CharAbility));    /// Character Ability points
       
            if Obj_PlayerStats.Stats_Buttons = true
                {
                    _twb = 548
                    draw_sprite(spr_stats_button, 0, _twb, 265)
                    draw_sprite(spr_stats_button, 0, _twb, 325)
                    draw_sprite(spr_stats_button, 0, _twb, 375)
                    draw_sprite(spr_stats_button, 0, _twb, 435)
                    draw_sprite(spr_stats_button, 0, _twb, 495)      
                }
   
       
       
        }
#endregion


Do I need to add additional code by the Draw_sprite(Spr_stats_button) to make them into buttons that are selectable?


Or do I have to Instance create a unique object button per ability?
 
Last edited:

Rob

Member
How exactly would that work?

Do I need to add additional code by the Draw_sprite(Spr_stats_button) to make them into buttons that are selectable?


Or do I have to Instance create a unique object button per ability?
There's nothing wrong with using the draw_gui and in fact I prefer it rather than using an extra object - here's how I would update your draw_gui (I missed some things out like Character Level but the objective is to show you how to increase
an array's value from within the for loop and you'll need to set up the 2D array for this to work (fill it with entries) - make sure the array and variables are named properly if you're using different names than I have. Also I can miss out brackets when typing code like this so look for any red marks on the left and they'll tell you whats wrong)

Code:
#region Stats Screen
    if stat_screen{
       draw_sprite(spr_stats_screen, 0, 0, 0);

       ///Draw Text
       draw_set_font(Font_MMenu);
       draw_set_color(c_yellow);
       draw_set_halign(fa_center);
       draw_set_valign(fa_middle);

        ///Draw Title
        draw_text(guiWidth-950, guiHeight-700, "Stats Screen");
        ///Draw Stats

        var startX = 490;
        var startY = 265;

        //This for loop will run 5 times EVERY step, each time it runs, all of the variables in the loop will be updated
        for (var display_stats = 0; display_stats < Stats.MAX; display_stats ++){
           var drawX = startX;
           var drawY = startY + (display_stats * 60);

           var stat_value = a_stats[Stats_Info.Value, display_stats ]; //display_stats will first equal 0 then 1/2/3/4 before the for loop is done (this runs every step)
           var stat_text = a_stats[Stats_Info.Name, display_stats ];
           var half_text_width = string_width(stat_text) / 2;
           var half_text_height = string_height(stat_text) / 2;
 
           /*
             First I'm going to check to see if the mouse is over the stat
             The text is being drawn from the center and I want to know if the mouse is less than half the width and less than half the height from this origin point - the abs function is great for this
             What should happen is when the mouse is over the stat text like "Vitality" Vitality should turn green. We now know the mouse is over it and with 1 click we can add a point to the vitality stat
           */
           if ( abs(device_mouse_x_to_gui(0) - drawX) <= half_text_width && abs(device_mouse_y_to_gui(0) - drawY) <= half_text_height ){
               draw_set_colour(c_green);

               //Add a point to vitality with a mouse click
               if mouse_check_button_pressed(mb_left) a_stats[Stats_Info.Value, display_stats ] += 1; //This doesn't remove an ability point or even check if there are ability points to use but hopefully it's not a big leap for you from here
           }else draw_set_colour(c_white);

            //Now we have the value of the stat and the name of the current stat to be drawn, draw it:
           draw_text(drawX, drawY, stat_text + ": " + string(stat_value));
        }
    }
#endregion


[EDIT] I didn't actually answer your question because I was trying to avoid overloading you with too much stuff. If you still want to know how to make 6 instances from the same object it would look like this:

Code:
//In the step event of a controller object

if (keyboard_check_pressed(ord("C"))){
    stat_screen = !stat_screen;

   //Check to see if we need to create or destroy stat objects

   if (stat_screen == true){
      //Create 5 instances

      var startX = 490;
      var startY = 265;

      for (var i = 0; i < Stats.MAX; i ++){
        var instanceX = startX;
        var instanceY = startY + (i* 60);

        new_instance = instance_create_layer(instanceX, instanceY, layer, obj_stat);
        new_instance.stats_index = i; //First instance will have stats_index equalling 0, next will equal 1 etc
      }
   }else with obj_stat instance_destroy(); //if stat_screen is false, destroy any instances of obj_stat
}

Now inside obj_stat:

Code:
//Draw Gui
var stat_value = a_stats[Stats_Info.Value, stats_index ]; //if a_stats is not global, this will cause an error so it needs to be either obj_controller.a_stats[Stats_Info.Value, stats_index ] OR global.a_stats[Stats_Info.Value, stats_index ]
var stat_text = a_stats[Stats_Info.Name, stats_index ];

//Now each instance should be drawing a different stat
draw_text(x, y, stat_text + ": " + string(stat_value));
I didn't put any code in to check for the mouse over the stat objects but if they dont have a sprite it wouldnt work anyway. If you did give them a sprite(whatever needs to be pressed to increase the value) then you can just use similar code to the mouse_check_button_pressed block that I put in the draw_gui code earlier in the post
 
Last edited:

Halas

Member
Is this valid?


Code:
enum Stats
{
    Vitality,
    Intelligence,
    Strength,
    Dexterity,
    Defense,
    MAX
}

for ( var i =  0; i < Stats.MAX ; ++i)
{
    Stats.Vitality = 0;
    Stats.Intelligence = 0;
    Stats.Strength = 0;
    Stats.Dexterity = 0;
    Stats.Defense = 0; 
}
or is it:

Code:
enum Stats
    {
        Vitality,
        Intelligence,
        Strength,
        Dexterity,
        Defense,
        MAX
    }
for ( var i =  0; i < Stats.MAX ; ++i)
    {
        Stats = 0;
    }
 
Last edited:

Rob

Member
Is this valid?


Code:
enum Stats
{
    Vitality,
    Intelligence,
    Strength,
    Dexterity,
    Defense,
    MAX
}

for ( var i =  0; i < Stats.MAX ; ++i)
{
    Stats.Vitality = 0;
    Stats.Intelligence = 0;
    Stats.Strength = 0;
    Stats.Dexterity = 0;
    Stats.Defense = 0;
}
or is it:

Code:
enum Stats
    {
        Vitality,
        Intelligence,
        Strength,
        Dexterity,
        Defense,
        MAX
    }
for ( var i =  0; i < Stats.MAX ; ++i)
    {
        Stats = 0;
    }
no it has to be something similar to what I posted eg

Code:
Stats[0] = 0;
Stats[1] = 1;
//array[enumerator.Strength]  = 0
Stats[Stats.Strength] = 0;
And now you can see why using a prefix is a good idea for enums and arrays as we have an enumerator AND an array BOTH called Stats. You can't reference an array like "Stats.strength" but it IS how you reference an enumerator. Forget about the enumerator for now if it's confusing you more.

When you set up an enumerator, every entry is automatically assigned a number so

Code:
enum e_stats
{
    Vitality,
    Intelligence,
    Strength,
    Dexterity,
    Defense,
    MAX
}
is the same as

Code:
enum e_stats
{
    Vitality = 0,
    Intelligence = 1,
    Strength = 2,
    Dexterity = 3,
    Defense = 4,
    MAX = 5,
}
I would normally just use a for loop to initialise the array:

Code:
//Fill an array with 5 entries
for (var i = 0; i < e_stats.MAX; i ++){
   global.a_stats[i] = 0;
}
 
Last edited:

Halas

Member
Okay, I think I misunderstood how Enums work, looks like it is storing the position in the code:

Code:
enum e_stats
{
   Vitality = 0,
   Intelligence = 1,
   Strength = 2,
   Dexterity = 3,
   Defense = 4,
   MAX = 5,
}

But, how would I manipulate the values that they represent?

For example, if the player has 10 points into Vitality
Vitality = 10.

Is this where I need the 2D array then?

Code:
enum Stats_Info{
   Value,
   Name,
}

a_stats[Stats_Info.Value, Stats.Vitality] = 3;
a_stats[Stats_Info.Name, Stats.Vitality] = "Vitality";

If that is the case, then is this the more appropriate code?

Code:
//Fill an array with 5 entries
for (var i = 0; i < Stats.MAX; i ++)
{
   Stats[i] = 0;
}

enum Stats
    {
        Vitality,
        Intelligence,
        Strength,
        Dexterity,
        Defense,
        MAX
    }
   
enum Stats_Info
    {
       Value,
       Name,
    }

Stats[Stats_Info.Value, Stats.Vitality] = 0;
Stats[Stats_Info.Name, Stats.Vitality] = "Vitality";
Or would Stats.Vitality = 10 work?
 

NightFrost

Member
If that is the case, then is this the more appropriate code?
Your code is assigning the same name (Stats) to both the array and the enumerator. Call the enumerator as e_stats like Rob suggested, or something else that doesn't mix it up with the Stats array. The array is a row of boxes (or grid of boxes, in case of 2D array), the enumerators are helpers that can be used to select the correct box.
 

Halas

Member
Ah, I thought it was just sorting one array, not two arrays acting separately.

So in essence, this is 3 Arrays working together?
a_stats
e_stats
Stats_info

So is this correct?

Code:
a_stats[5] = 0;

    enum e_stats
        {
            Vitality,
            Intelligence,
            Strength,
            Dexterity,
            Defense,
            MAX
        }
 
    enum Stats_Info
        {
           Value,
           Name,
        }

a_stats[Stats_Info.Value, e_stats.Vitality] = 0;
a_stats[Stats_Info.Name, e_stats.Vitality] = "Vitality";

So I got it working this way:

Code:
globalvar Stats

for(var a=0; a<5; a++)
    {
    Stats[a] = 0; //zero points
    }
using 0-5 as the stat marks.

Now I am working on the button press and I have this.

var PosX = argument0;
var PosY = argument1;
var StatChoice = argument2;
var redrawX = argument3;


if (abs(device_mouse_x_to_gui(0) - PosX) <= 5 && abs(device_mouse_y_to_gui(0) - PosY) <= 5)
{
//Add a point to vitality with a mouse click
if mouse_check_button_pressed(mb_left) && CharAbility >= 1
{
draw_sprite(spr_stats_button, 1, PosX, PosY) ///Green on click
Stats[StatChoice] += 1; ///Add Stats
CharAbility -= 1;
}
//This doesn't remove an ability point or even check if there are
///ability points to use but hopefully it's not a big leap for you from here
} else exit;
//Now we have the value of the stat and the name of the current stat to be drawn, draw it:
draw_text(redrawX, PosY, + string(Stats[StatChoice])); /// Character stat;

My issue now, is the mouse press, how do I get it on a specific marker, for this, it just registers a click anywhere rather than on the sprite.

Update: got it working, reflecting in the above code.

I am now going to test all the stats and see how it work.
 
Last edited:

NightFrost

Member
So in essence, this is 3 Arrays working together?
One array and two enumerators. Enumerators are, essentially, stand-ins for numbers as already mentioned. Because when you might have instead written:
Code:
a_stats[1, 2] = 10;
and when a month later look at the code again, you say "what am I setting here? I have no idea! Something about stats, judging by the array name."

But since with enumerators you instead have:
Code:
a_stats[Stats_Info.Value, e_stats.Strength] = 10;
you'll instead nod "Hm, so I am setting value of Strength to ten here, I see." In other words, you don't have to memorize the meaning of all those numbers as you have clear labels to use instead. You don't even have to guess them thanks to autocomplete; just write the enumerator's root and the dot, and the autocomplete gives you a dropdown of suggestions with all the value labels.
 

samspade

Member
Unless you really need the name stored in the array, I wouldn't have one. I would keep it simple:

Code:
//set up enums
enum e_stats {
   Vitality,
   Intelligence,
   Strength,
   Dexterity,
   Defense,
   MAX
}

//create a blank array
global.stats_array = array_create(e_stats.MAX, 0);
if you want the strings for some reason I would probably just make a static array for that. It would be extra work to create and maintain, but could then be referenced with the same enums and would be a lot easier to use, save, and reference than a 2D array.

Code:
global.stats_array_names = ["Vitality", "Intelligence", "Strength", "Dexterity", Defense"];

//now you can reference things like this individually:
global.stats_array[e_stats.Vitality];
global.stats_array_names[e_stats.Vitality];
The other problem I see in many of your examples is that while enums are great, especially for naming numbers like NightFrost mentioned, if you are looping through an array you wouldn't use them, you would instead use your loop variable (generally i):

Code:
var _stat, _stat_name;
for (var i = 0; i < e_stats.MAX; i++) {
    _stat = global.stats_array[i];
    _stat_name= global.stats_array_names[i];
    ///use stat and stat name for example to draw them
}
 

Halas

Member
One array and two enumerators. Enumerators are, essentially, stand-ins for numbers as already mentioned. Because when you might have instead written:
Code:
a_stats[Stats_Info.Value, e_stats.Strength] = 10;
I am not sure why the Enums arent working.

I have it currently as:

globalvar Stats
for(var a=0; a<5; a++)
{
Stats[a] = 0; //zero points
}

If I were to do:

enum e_stats {
Vitality,
Intelligence,
Strength,
Dexterity,
Defense,
MAX
}

To add an ability point would be:
Stats[e_stats.Vitality] += 1;

???




Unless you really need the name stored in the array, I wouldn't have one. I would keep it simple:

The other problem I see in many of your examples is that while enums are great, especially for naming numbers like NightFrost mentioned, if you are looping through an array you wouldn't use them, you would instead use your loop variable (generally i):
Code:
var _stat, _stat_name;
for (var i = 0; i < e_stats.MAX; i++) {
    _stat = global.stats_array[i];
    _stat_name= global.stats_array_names[i];
    ///use stat and stat name for example to draw them
}


What do you mean loop through my array? I don't understand the problem.




In other notes; as I was working through the code to add to the player's base stats, I received this error.

Code:
FATAL ERROR in
action number 1
of Create Event
for object Obj_lifeform:

trying to index a variable which is not an array
 at gml_Object_Obj_lifeform_Create_0 (line 1) - hp = 50 *(Stats[0]+0.15); ///  I want to increase HP by 15% per ability point
############################################################################################
--------------------------------------------------------------------------------------------
stack frame is
gml_Object_Obj_lifeform_Create_0 (line 1)
called from - gml_Object_Obj_Player_Create_0 (line 1) - event_inherited()
 

TsukaYuriko

Q&A Spawn Camper
Forum Staff
Moderator
You're calling Obj_lifeform's Create event on line 1 (via event_inherited(). In it, you're referencing Stats. Stats does not exist at this point in time at all or is not an array.

I am not sure why the Enums arent working.
Please define "doesn't work" - what is your code supposed to do and what does it do instead?

PS:
globalvar Stats
globalvar is deprecated. Global variables are prefixed with "global.". in every place they are referenced - you should update your code accordingly.
 

Halas

Member
You're calling Obj_lifeform's Create event on line 1 (via event_inherited(). In it, you're referencing Stats. Stats does not exist at this point in time at all or is not an array.
How do I make sure the array runs before these checks then?

Doesn't this initialize the Stats?
globalvar Stats
for(var a=0; a<5; a++)
{
Stats[a] = 0; //zero points
}


Please define "doesn't work" - what is your code supposed to do and what does it do instead?
It just doesn't work, gives me errors. I will try it again and post the error statements. After I get this current error working first though.

PS:

globalvar is deprecated. Global variables are prefixed with "global.". in every place they are referenced - you should update your code accordingly.
I do not understand, doing global.Stats gives me an error.
"unnecessary expression used as a statement."





I am coming across an issue with this array.

For example if I would like to Increase player hp by 10% per Vitality point its getting blown up.

If I do this:
max_hp *= (Stats[0]+.10)
it doesn't work. Gets the error posted above
If I do this when the player puts a point into vitality:

draw_sprite(spr_stats_button, 1, PosX, PosY) ///Green on click
Stats[StatChoice] += 1; ///Add Stats
CharAbility -= 1;
Obj_Player.max_hp += 10

It adds 1020 HP to the player.
 

Attachments

Last edited:

TsukaYuriko

Q&A Spawn Camper
Forum Staff
Moderator
How do I make sure the array runs before these checks then?
Initialize it in a separate room, preferably the first room of the game, before any other code has any chance to run.

It just doesn't work, gives me errors. I will try it again and post the error statements. After I get this current error working first though.



I do not understand, doing global.Stats gives me an error.
"unnecessary expression used as a statement."
Please post full error messages and all involved code. A fragment of an error message does not provide us with the full context, which is required for us to be able to help you accurately.

<Screenshot of an error>
The statement "global.level;" on its own has no meaning. Therefore, an error is raised. Remove this statement.
If you were trying to initialize this variable, you need to assign a value to it, like this:
Code:
global.level = 1;
 

Nidoking

Member
I feel the need to jump in here, in the middle of the troubleshooting, to get back to the original topic of learning how to use arrays, because there's one gigantic lesson that I feel needs to be added: This is not a good use for arrays. If you're just trying to learn how arrays work, that's fine, but these values are just linear values. In reality, you'd just set Player.Vitality = 10; and move on. Arrays would be used when you have a collection of values that need to be associated with indices and all serve the same purpose. For a better example, in my opinion, let's say that the player's vitality determines maximum health, but non-linearly. Perhaps, Vitality 1 gives max HP of 30, Vit 2 gives max HP 40, Vit 3 gives max HP 48, and so on. You don't want to have to write a formula to calculate that. In this case, you have a Vitality stat that is a single value, but you have a collection of max HP values that correspond to each Vitality value. You might use an array for this:
MaxHPArray[1] = 30;
MaxHPArray[2] = 40;
MaxHPArray[3] = 48;

and so on. Then, when you increase Vitality, you might do this:

Player.MaxHP = MaxHPArray[Player.Vitality];

The new Vitality value is your index into the array that tells the program what the max HP value should be. Every value in the array serves the same purpose, and corresponds to the index value.
 

Halas

Member
I feel the need to jump in here, in the middle of the troubleshooting, to get back to the original topic of learning how to use arrays, because there's one gigantic lesson that I feel needs to be added: This is not a good use for arrays. If you're just trying to learn how arrays work, that's fine, but these values are just linear values. In reality, you'd just set Player.Vitality = 10; and move on. Arrays would be used when you have a collection of values that need to be associated with indices and all serve the same purpose. For a better example, in my opinion, let's say that the player's vitality determines maximum health, but non-linearly. Perhaps, Vitality 1 gives max HP of 30, Vit 2 gives max HP 40, Vit 3 gives max HP 48, and so on. You don't want to have to write a formula to calculate that. In this case, you have a Vitality stat that is a single value, but you have a collection of max HP values that correspond to each Vitality value. You might use an array for this:
MaxHPArray[1] = 30;
MaxHPArray[2] = 40;
MaxHPArray[3] = 48;

and so on. Then, when you increase Vitality, you might do this:

Player.MaxHP = MaxHPArray[Player.Vitality];

The new Vitality value is your index into the array that tells the program what the max HP value should be. Every value in the array serves the same purpose and corresponds to the index value.

I am glad you posted it. I do think now this is the underlying issue of why the system won't work.
As it stands, the feature works like this:
Once a player levels, they get 1 stat point to put into their 1 of 5 stats.
Now trying to get the Stats to influence the player stats is proving to be difficult.
For example. 1 Stat in Vitality would increase health(10%), & stamina(15%) by a given percent.

How I planned on doing it:

With one stat point into vitality (Stat[0] should = 1)

max_hp = 50 * (Stats[0]+0.1);
hp = max_hp;


But this is not working, giving this error:

___________________________________________
############################################################################################
FATAL ERROR in
action number 1
of Create Event
for object Obj_Player:

trying to index a variable which is not an array
at gml_Object_Obj_Player_Create_0 (line 30) - max_hp = 50 * (Stats[0]+0.1);
############################################################################################
--------------------------------------------------------------------------------------------
stack frame is
gml_Object_Obj_Player_Create_0 (line 30)
 

Attachments

Yal

GMC Memer
GMC Elder
With one stat point into vitality (Stat[0] should = 1)
Judging from the error message, it isn't set to 1 - it doesn't even exist! ("Trying to index a variable that isn't an array" errors also show up when the variable doesn't exist, regrettably, and usually that case is more likely than mixing array and non-array uses on the same variable.)

From the look of it, you want Stats to be global, but it isn't (since there's no global. in front of its name). So it looks for a variable in the player object named Stats, which doesn't exist.
 

Rob

Member
[Edited as Yal already responded to the error msg - I'm also gonna bow out of this thread because I feel like I'm just confusing the guy now!]

Stats[e_stats.Vitality] += 1; is the exact way to increase your vitality stat by 1, I would recommend using some kind of prefix before "Stats" like "a_stats" or "global.a_stats" so that you don't make mistakes trying to reference the wrong thing. "a" being short hand for "array" and "e" being shorthand for "enumerator".
 

Yal

GMC Memer
GMC Elder
Stats[e_stats.Vitality] += 1; is the exact way to increase your vitality stat by 1, I would recommend using some kind of prefix before "Stats" like "a_stats" or "global.a_stats" so that you don't make mistakes trying to reference the wrong thing. "a" being short hand for "array" and "e" being shorthand for "enumerator".
This is known as Hungarian Notation, and pretty much universally disliked because it's redundant (the compiler knows the type, anyone that can find an assignment to the variable knows the type, and a good variable name conveys the type) and makes code harder to read, maintain and refactor. It's also very popular with Microsoft developers. There is probably some sort of connection.

If you're gonna use prefixes, it's better to prefix by logical divisor instead - as in, some group name that clearly defines which things belong together. It also helps you easily find things, if you just know what subsystem they belong to - just search for that and pick the one you were looking for.
 

Halas

Member
I was using globarvar. But I changed it to global.stats and it still is the same


___________________________________________
############################################################################################
FATAL ERROR in
action number 1
of Create Event
for object Obj_Player:

trying to index a variable which is not an array
at gml_Object_Obj_Player_Create_0 (line 30) - max_hp = 50 * (global.Stats[0]+0.1);
############################################################################################
--------------------------------------------------------------------------------------------
stack frame is
gml_Object_Obj_Player_Create_0 (line 30)


So it took a bit of maneuvering to complete.
Basically had to move the array into the Obj_player to initialize instead of having its own object.
I also got the stats system to work.
In the script that updates the Abilitypoints, I have it update the stats as well.
I have to test more than one stat to see if there are any issues.
I have a feeling there will be issues in the future. For example, if the player dies and respawns, what will happen when the create event runs again and re-initializes the array.
 
Last edited:

Halas

Member
If you're gonna use prefixes, it's better to prefix by logical divisor instead - as in, some group name that clearly defines which things belong together. It also helps you easily find things, if you just know what subsystem they belong to - just search for that and pick the one you were looking for.
Could you elaborate a little on this?
 

TailBit

Member
Basically had to move the array into the Obj_player to initialize instead of having its own object.
the reason for that is the order of operations, it tried to increase a number in the array before the array had been created ..

I guess you had both objects in the room, but the order they were put in the room does matter .. the room editor should have a list that you can re-arrange their creation order so the one that defines the array comes before the player.

usually I have my control object take care of player creation, with variables for room and the position to place him at.
 

TsukaYuriko

Q&A Spawn Camper
Forum Staff
Moderator
To add to that - such problems regarding which code executes first when it's about initialization of variables can be easily avoided if you...
Initialize it in a separate room, preferably the first room of the game, before any other code has any chance to run.
;)
 

Halas

Member
.. the room editor should have a list that you can re-arrange their creation order
How do you do this?

To add to that - such problems regarding which code executes first when it's about initialization of variables can be easily avoided if you...

;)
This actually introduced similar errors haha, I guess it will be a learning curve figuring out how to find which code should be initialized first. But knowing how to do this above(rearranging in editor) should help.
 

TsukaYuriko

Q&A Spawn Camper
Forum Staff
Moderator
You can find the instance creation order under the layer properties tab when you have an instance layer selected. By default, this is docked to the left side of the room editor, the tab is titled "Properties", and the creation order tab is accessible via a button labeled "Instance Creation Order".
 

Joe Ellis

Member
I was using globarvar. But I changed it to global.stats and it still is the same


___________________________________________
############################################################################################
FATAL ERROR in
action number 1
of Create Event
for object Obj_Player:

trying to index a variable which is not an array
at gml_Object_Obj_Player_Create_0 (line 30) - max_hp = 50 * (global.Stats[0]+0.1);
############################################################################################
--------------------------------------------------------------------------------------------
stack frame is
gml_Object_Obj_Player_Create_0 (line 30)


So it took a bit of maneuvering to complete.
Basically had to move the array into the Obj_player to initialize instead of having its own object.
I also got the stats system to work.
In the script that updates the Abilitypoints, I have it update the stats as well.
I have to test more than one stat to see if there are any issues.
I have a feeling there will be issues in the future. For example, if the player dies and respawns, what will happen when the create event runs again and re-initializes the array.
For the problem: "what will happen when the create event runs again and re-initializes the array"
You need to change it so this doesn't happen when the player is created. If you're using arrays for building the stat upgrades and things, you need to make this structure when the game first starts ideally, and give every instance easy access to it by making it a global variable.

Also I wanted to point out, for initializing an array of any length, you can just do: array[length] = 0, and if the array with a certain name already exists and you want to wipe it, just set "array" to 0, then do: array[length] = 0 and it will create a brand new array and assign it to the variable name
 

Halas

Member
I think I have everything working. I put it in a game initializing the object, the stats work and influence the player stats when selected.
I have to try to implement a respawn and damage floating to see how much damage I'm doing and I think I should be good.
 

Yal

GMC Memer
GMC Elder
Could you elaborate a little on this?
I thought the code screenshot was clear enough, but to explain it even more
Controller State - ctst_SETUP, ctst_ANNOUNCE, etc.
Battle action - battleaction_ATTACK, battleaction_ITEM, etc.

I usually use shorter prefixes with mnemonics and abbreviations for stuff that's used in more places (since having those names be too long just wastes space and makes it harder to type out the constants) and longer, more descriptive names for stuff that's only used in one or two places (so that the user don't need to chase it down just to understand what it does, but can figure it out from the name alone).
 

Joe Ellis

Member
Question, when you are trying to save a game, how do you save an array?
You have to save the contents of it to a file and load it back into an array when loading, either with a text\ini file or a buffer. It's simple if all the values are numbers, you can just save each one as a separate entry, but if there are strings aswell it gets more complicated, you'd have to ask if the value "is_string(value)", before writing and also something that indicates which type it is before reading the value from the file.
If you're using a text file you can get around this problem by loading each line\entry as a string, then you can simply convert strings holding numbers into real numbers by asking if the first character of the string is a digit or "-" followed by a digit
 
Last edited:

Yal

GMC Memer
GMC Elder
You have to save the contents of it to a buffer and load the buffer back into an array when loading. This is simple if all the values are numbers, you can just save each one as buffer_f32 or buffer_f64, or if they're all integers above -1 you can use buffer_u16. But if there are strings aswell it gets more complicated, you'd have to ask if the value "is_string(value)", before writing and also write a byte before it specifying which data type it is.
You don't have to save it to a buffer - I usually just loop through it and save every number on a separate line in the file (I use text files for everything). Depending on how serious I am, I either save the array size first, or just use a constant for both reading and writing.
 

Joe Ellis

Member
You don't have to save it to a buffer - I usually just loop through it and save every number on a separate line in the file (I use text files for everything). Depending on how serious I am, I either save the array size first, or just use a constant for both reading and writing.
Sorry yes I should've said either a buffer or text file. I'm gonna edit what I put
 

Halas

Member
You don't have to save it to a buffer - I usually just loop through it and save every number on a separate line in the file (I use text files for everything). Depending on how serious I am, I either save the array size first, or just use a constant for both reading and writing.
What would the code look like to loop through an array collecting all its values?
 

Joe Ellis

Member
Or a repeat loop:

Code:
var i = -1, val;
repeat array_length_1D(array)
{
val = array[++i]
//do something with val
}
Repeat loops are a bit faster than for & while loops, plus getting the length of the array every cycle of the loop will add extra time.
If you still want to use a for or while loop it's better to get the length of the array once, put it in a var then compare with this, it increases the speed by about 10%, which can be a big difference with large arrays. eg.
Code:
//For loop:
var l = array_length_1d(array), val;
for (var i = 0; i < l; ++i)
{val = array[i]}

//While loop:
var i = -1, l = array_length_1d(array) - 1, val;
while (i < l)
{val = array[++i]}

//Repeat loop:
var i = -1, val;
repeat array_length_1d(array)
{val = array[++i]}
 

Yal

GMC Memer
GMC Elder
Repeat loops are a bit faster than for & while loops, plus getting the length of the array every cycle of the loop will add extra time.
AFAIK there's no evidence suggesting repeat loops are faster, but it's a plausible guess (since the for-loop counter is compiled instead of interpreted). Usually the loop body takes the vast majority of the execution time, though :p
Also, there's a lot of times you want access to the loop counter, repeat loops won't give you that.

Instead of executing an expensive operation (e.g. array_length_1d()) every iteration, execute it once and store the result in a variable. Bam, now you only need to do it once. (It obviously doesn't work if you expect the result to change over time, but then again, then you should be using a while/do-until loop instead of a for loop anyway!)
 

Joe Ellis

Member
AFAIK there's no evidence suggesting repeat loops are faster, but it's a plausible guess (since the for-loop counter is compiled instead of interpreted). Usually the loop body takes the vast majority of the execution time, though :p
Also, there's a lot of times you want access to the loop counter, repeat loops won't give you that.

Instead of executing an expensive operation (e.g. array_length_1d()) every iteration, execute it once and store the result in a variable. Bam, now you only need to do it once. (It obviously doesn't work if you expect the result to change over time, but then again, then you should be using a while/do-until loop instead of a for loop anyway!)
It's not a guess, I tested it just before I wrote that, and you can access the loop counter just as easily with a repeat loop cus "i" is the counter.
There's been a few other tests I've seen on here that all say repeat loops are the fastest. They are only very slightly faster though, they seem to be able to do around 1500 more reps in one frame than a for loop, but when both can do around 41000-43000 there isn't much difference relatively.

-EDIT
I ran two tests:

Test1:
Code:
if ++num_steps < 3600
{
var start_time = get_timer(), reps = 0, val, l = 100;
while get_timer() - start_time < 15000
{
//For:
for (var i = 0; i < l; ++i)
{val = 0}
//Repeat:
var i = 0;
repeat l
{
++i;
val = 0;
}
++reps
}
total_reps += reps
}
This measured how many times each loop could be called per step for 1 minute.
(within 15000 microseconds, a full step is 16000 but you can't use up the entire frame time
cus it's needed for drawing and things, so I use 15000 and it makes the fps_real just above 60)

Results:

10000 cycles in loop:

Total in 1 minute:
repeat: 24251 reps
for: 22187 reps
difference: 2064

per step average:
repeat: 6.73 reps
for: 6.16 reps
difference: 0.57 reps (5700 cycles more)

1000 cycles:

Total in 1 minute:
repeat: 223089 reps
for: 209360 reps
difference: 13729

per step average:
repeat: 61.96 reps
for: 58.15 reps
difference: 3.81 reps (3810 cycles more)

100 cycles:

Total in 1 minute:
repeat: 2202243 reps
for: 2049085 reps
difference: 153158

per step average:
repeat: 611.73 reps
for: 569.19 reps
difference: 42.54 reps (4254 cycles more)

10 cycles:

Total in 1 minute:
repeat: 18161396 reps
for: 17540926 reps
difference: 620470

per step average:
repeat: 5044.83 reps
for: 4872.47 reps
difference: 172.36 reps (1724 cycles more)

Test2:

Code:
if ++num_steps <= 3600
{
var l = 100, val, start_time, end_time;
start_time = get_timer()

//For loop:
for (var i = 0; i < l; ++i)
{val = 0}

//Repeat loop:
var i = 0;
repeat l
{
++i;
val = 0;
}

end_time = get_timer()
time_taken += end_time - start_time
}
This measured how much time in microseconds each loop took to run.

Results:

10000 cycles:

Total in 1 minute:
repeat: 12064541 ms
for: 12322877 ms
difference: 258336 ms

per step average:
repeat: 3351 ms
for: 3423 ms
difference: 72 ms

1000 cycles:

Total in 1 minute:
repeat: 1441925 ms
for: 1553714 ms
difference: 10209 ms

per step average:
repeat: 401 ms
for: 432 ms
difference: 31 ms

100 cycles:

Total in 1 minute:
repeat: 164447 ms
for: 174656 ms
difference: 10209 ms

per step average:
repeat: 46 ms
for: 49 ms
difference: 3 ms

10 cycles:

Total in 1 minute:
repeat: 31536 ms
for: 26820 ms
difference: 10209 ms

per step average:
repeat: 8.76 ms
for: 7.45 ms
difference: 1.31 ms

It shows that for loops actually take less time to complete if the length of the loop is low (10)
So I need to test this further and see where the crossover point is where repeat becomes faster.
It might be because repeat loops take longer to initialize, or that they cause less strain on the cpu,
making the function take longer because the cpu is running slower.
However I feel that test1 is a better test, because it measures how many times the loops can be run per step, and the more times results in a task being completed faster.
But in summary, the difference is that miniscule that even a very long task would only take about quarter of a second longer if you use for loops, so you may aswell use whichever one you prefer.
 
Last edited:
Top