• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

SOLVED [SOLVED] Engine Bug / Objects drawing WRONG image_index in odd but reproducible ways based on image_number

Hello, I sincerely plead that someone helps me with this, as I am going insane pulling my hair out trying to resolve this issue... This is a critical problem for me, and if I can't resolve it, I'll have to scrap months of professional work and move to different game engine. All code is posted below after the in-depth explanation, but you can also just download the YYZ file here: https://drive.google.com/file/d/1dqdZ9xFc6jaGR8nfbcxeTIo7-Sh2DOKW/view?usp=sharing
The project has been simplified to include only things necessary to reproduce and highlight the problem. When running the game, you can use spacebar to recalculate and reset the image_indexes, printing to console, and use enter to cycle through different sprites with image_numbers 53, 54, and 55.

Short Story: Under specific but simple conditions, objects are drawing the incorrect image_index, contrary to what they claim to be storing upon init and real-time printing.



See above, where 52 playing card images are drawn in-order, 2 to Ace by suit. Each draws in real-time in the Draw Event their internal held instance variable information about their Rank and Suit, and below that draws their image_index. There is a spr_cards sprite with 53 subimages, 2 to Ace by suit in Club, Diamond, Heart, Spade order, and then a 53rd subimage that is the card back. So image_number = 53, with indexes 0 to 52, with 51 being Ace of Spades and 52 being the card back (not seen in this draw exercise).

Notice how most of the cards correctly draw their correct subimage corresponding to their Rank and Suit and appropriate image_index, and except two strange cases:
  • Four of Diamonds draws as the Three of Diamonds image, even though its image_index is 15.00, and the correct image_index for Four of Diamonds is 15.
  • Six of Hearts draws as the Five of Hearst image, even though its image_index is 30.00, and the correct image_index for Six of Hearts is 30.
Also note that Seven of Hearts strangely has trailing 0s after its decimal point as well with 31.00, but it draws correctly as the Seven of Hearts. I have no idea why these seemingly random specific image_indexes have a decimal point and trailing 0s. The 15 and 30 might imply some strange relation to multiples of 15 (for whatever reason), but the Eight of Spades with image_index 45 is unaffected, and the deck doesn't go as high as image_index 60 to see if there's an issue there.

How is image_index being calculated?

It's very simple: each obj_card card object has a Card structure. Each Card struct contains only a Rank enum and a Suit enum, which iterate from Two = 2 to Ace = 14, and Club = 0 to Spade = 3 respectively. Then these two Rank and Suit enum data points can be easily converted into the appropriate image_index via the equation return (suit * 13) + (rank - 2);. On paper this works perfect, and even within GMS 2.3 if I output the image_index calculations to the console every step of the way: it's correct. They never calculate wrongly with weird floating point maths or anything. In fact, even those 15.00 and 30.00 weird image_indexes are calculated, returned, and set as 15 and 30, without a random decimal point. It's only after a frame of drawing that they magically become 15.00 and 30.00 instead.

The weird thing is is that I had this exact same problem with GameMaker Studio 2 (and 2.3... on different machines) and an entirely different setup. There I didn't use enums and (obviously) no structs. Instead all card data was simply stored as the image_index. To make all cards in the deck, I'd create each card in a 52-loop for-loop, incrementing image_index from 0 to 51. Then, I had another reverse formula to either get the suit of the card from the image_index (certain ranges) or get the rank of the card from the image_index. Both also worked perfect 99% of the time... except in these odd cases, where they returned the right image_index, but the Four of Diamonds specifically displayed incorrectly as the Three of Diamonds.

I thought it might be some weird BS floating-point maths issue within GM, so I switched to enums with this version for additional safety (or so I thought), rewritten from scratch. No code was transferred.

But the really weird thing is is that I changed the image_index calculation function from rank and suit again to be extra extra safe, trying to resolve the issue... Instead of doing that logical suit * 13 return method, I made nested switch-statements, where I return the exact image_index appropriate for each rank and suit. No multiplication, no trailing 0s after a decimal point, just the exact integer return value needed. No bizarre floating-point maths errors could possibly intervene... right?

So with that my comprehension of the issue was completely thrown out the window. It apparently doesn't have to do with any math; GM is just deciding that the Four of Diamonds should just really be the Three of Diamonds. Again, only visually; internally the image_index is always correct, every step of the way from initialization to setting, even recalculating to reset with testing. And my project has much greater functions doing poker-style hand evaluations and comparisons and the like, and these logic functions are always correct, treating the Four of Diamonds as the Four of Diamonds. The only error I can possibly detect is the obvious misdraw.

I tried recreating my card sprite, seeing is somehow the problem was with some hidden glitch snuck into the sprite itself or something. The subimages were all correct, nothing out of order or missing or duplicated. I checked that early on, and if something like that was there it would throw off a lot more than just the Four of Diamonds and Six of Hearts. However, when I recreated my sprite, it solved the issue! ...for Four of Diamonds. Similar errors cropped out in several other places around the deck, shifting which cards drew incorrectly. After some testing, I figured out that it was because of image_number. I originally was using 53 images for the 52 cards plus the card back, but in this new project I added the joker card in its early stages, making 54 subimages.

If I change the number of subimages in the card sprite, the cards that draw incorrectly change.
  1. With 53 subimages (52 + card back):
    1. Four of Diamonds draws as Three of Diamonds
    2. Six of Hearts draws as Five of Hearts
  2. With 54 subimages (52 + card back + joker):
    1. Five of Hearts draws as Four of Hearts
  3. With 55 subimages (52 + card back + joker + extra for testing):
    1. Five of Diamonds draws as Four of Diamonds
    2. Five of Hearts draws as Four of Hearts
    3. Six of Clubs draws as Five of Clubs
    4. Eight of Hearts draws as Seven of Hearts
    5. Ten of Clubs draws as Nine of Clubs
    6. Three of Clubs draws as Two of Clubs
There is not any rhyme or reason I can see. Here is the draw result for the 54 image_number sprite:

And here's the draw result for the 55 image_number sprite:


Here's the code for the card structure located in a script resource, but again this problem was present even if I didn't use enums or structs at all in both GMS 2 and 2.3, only setting image_index, in an entirely different project.
GML:
enum Suit
{
    Club        = 0, // In Ricochet Poker, suits are ranked Clubs, Diamonds, Hearts, Spades
    Diamond        = 1,
    Heart        = 2,
    Spade        = 3,
    Error        = 4,
    
    Joker        = 5,
}

enum Rank
{
    Two        = 2,
    Three    = 3,
    Four    = 4,
    Five    = 5,
    Six        = 6,
    Seven    = 7,
    Eight    = 8,
    Nine    = 9,
    Ten        = 10,
    Jack    = 11,
    Queen    = 12,
    King    = 13,
    Ace        = 14,
    Error    = 15,
    
    Joker    = 16,
}


#macro IMAGE_INDEX_CARD_BACK    52        // The card sprite image_index for the card back
#macro IMAGE_INDEX_CARD_JOKER    53        // The card sprite image_index for the card joker

/// @function Card(_s, _r)
/// @param    {Suit}        _s        The suit of the card
/// @param    {Rank}        _r        The rank of the card
/// @return    {Card}                A card structure
function Card(_s, _r) constructor
{
    // Card Structure:
    // Usage: var _card = new Card(Suit.Diamonds, Rank.Two);
    
    suit = _s;
    rank = _r;
    
    /////////////////////////////////////////////////
    // Card Methods:
    // Usage: _card.GetString();
    
    /// @function GetString()
    /// @return    {string}    The card as a string
    static GetString = function()
    {
        return rank_to_string(rank) + " of " + suit_to_string(suit) + "s";
    }
    
    /// @function GetImageIndex()
    /// @return {image_index}        Card sprite image_index from card
    static GetImageIndex = function()
    {
        if(suit == Suit.Joker || rank == Rank.Joker || suit == Suit.Error || rank == Rank.Error)
        {
            if(suit == Suit.Joker && rank == Rank.Joker)
            {
                return IMAGE_INDEX_CARD_JOKER;
            }
            else // Unknown card
            {
                show_debug_message("Unknown card image_index, Card = " + GetString());
                return IMAGE_INDEX_CARD_BACK;
            }
        }
        else
        {
            //return (suit * 13) + (rank - 2); // Does the math here cause the image_index glitch? It really shouldn't...
            
            #region image_index Hard-Coded Return Process (ignored if above line is uncommented)
            switch(suit)
            {
                case Suit.Club:
                {
                    switch(rank)
                    {
                        case Rank.Two:        return 0;
                        case Rank.Three:    return 1;
                        case Rank.Four:        return 2;
                        case Rank.Five:        return 3;
                        case Rank.Six:        return 4;
                        case Rank.Seven:    return 5;
                        case Rank.Eight:    return 6;
                        case Rank.Nine:        return 7;
                        case Rank.Ten:        return 8;
                        case Rank.Jack:        return 9;
                        case Rank.Queen:    return 10;
                        case Rank.King:        return 11;
                        case Rank.Ace:        return 12;
                        default:            return IMAGE_INDEX_CARD_BACK;
                    }
                }
                case Suit.Diamond:
                {
                    switch(rank)
                    {
                        case Rank.Two:        return 13;
                        case Rank.Three:    return 14;
                        case Rank.Four:        return 15;
                        case Rank.Five:        return 16;
                        case Rank.Six:        return 17;
                        case Rank.Seven:    return 18;
                        case Rank.Eight:    return 19;
                        case Rank.Nine:        return 20;
                        case Rank.Ten:        return 21;
                        case Rank.Jack:        return 22;
                        case Rank.Queen:    return 23;
                        case Rank.King:        return 24;
                        case Rank.Ace:        return 25;
                        default:            return IMAGE_INDEX_CARD_BACK;
                    }
                }
                case Suit.Heart:
                {
                    switch(rank)
                    {
                        case Rank.Two:        return 26;
                        case Rank.Three:    return 27;
                        case Rank.Four:        return 28;
                        case Rank.Five:        return 29;
                        case Rank.Six:        return 30;
                        case Rank.Seven:    return 31;
                        case Rank.Eight:    return 32;
                        case Rank.Nine:        return 33;
                        case Rank.Ten:        return 34;
                        case Rank.Jack:        return 35;
                        case Rank.Queen:    return 36;
                        case Rank.King:        return 37;
                        case Rank.Ace:        return 38;
                        default:            return IMAGE_INDEX_CARD_BACK;
                    }
                }
                case Suit.Spade:
                {
                    switch(rank)
                    {
                        case Rank.Two:        return 39;
                        case Rank.Three:    return 40;
                        case Rank.Four:        return 41;
                        case Rank.Five:        return 42;
                        case Rank.Six:        return 43;
                        case Rank.Seven:    return 44;
                        case Rank.Eight:    return 45;
                        case Rank.Nine:        return 46;
                        case Rank.Ten:        return 47;
                        case Rank.Jack:        return 48;
                        case Rank.Queen:    return 49;
                        case Rank.King:        return 50;
                        case Rank.Ace:        return 51;
                        default:            return IMAGE_INDEX_CARD_BACK;
                    }
                }
                default:                    return IMAGE_INDEX_CARD_BACK;
            }
            #endregion
        }
    }
}
That's the only code segment I really feel is relevant, but for clarity / in case I'm wrong, all obj_card instances in the room are created in this obj_game Create Event:

GML:
/// @description Init / Debug Testing
/////////////////////////////////////////////////////
// Written by Gunnar Clovis (2021)                 //
//  [email protected]                       //
//   https://www.linkedin.com/in/gunnarclovis/     //
//    Copyright Gunnar Clovis Galaxy Gaming 2021   //
/////////////////////////////////////////////////////

// Center game window if not fullscreen
if(!window_get_fullscreen())
    window_center();
    
//////////////////////////////////////////////////////////////////////////////

show_debug_message("\n------------------------------------------------\nGame Debug Testing Start:\n------------------------------------------------\n");

if(room == rm_cards53)
{
    for(var s = Suit.Club; s <= Suit.Spade; s++)
    {
        for(var r = Rank.Two; r <= Rank.Ace; r++)
        {
            instance_create_layer(-250 + 150 * r, 100 + 200 * s, "Cards", obj_card).SetCardByElements(s, r);
        }
    }
}
else if(room == rm_cards54)
{
    for(var s = Suit.Club; s <= Suit.Spade; s++)
    {
        for(var r = Rank.Two; r <= Rank.Ace; r++)
        {
            instance_create_layer(-250 + 150 * r, 100 + 200 * s, "Cards", obj_card54).SetCardByElements(s, r);
        }
    }
}
else if(room == rm_cards55)
{
    for(var s = Suit.Club; s <= Suit.Spade; s++)
    {
        for(var r = Rank.Two; r <= Rank.Ace; r++)
        {
            instance_create_layer(-250 + 150 * r, 100 + 200 * s, "Cards", obj_card55).SetCardByElements(s, r);
        }
    }
}

show_debug_message("\n------------------------------------------------\nGame Debug Testing End:\n------------------------------------------------\n");
Which calls a little helper method on obj_card. This drawing issue with present even without any of these 2.3 methods, but for clarity, this is the obj_card Create Event:

GML:
/// @description 
/////////////////////////////////////////////////////
// Written by Gunnar Clovis (2021)                 //
//  [email protected]                       //
//   https://www.linkedin.com/in/gunnarclovis/     //
//    Copyright Gunnar Clovis Galaxy Gaming 2021   //
/////////////////////////////////////////////////////

image_speed = 0;
image_index = IMAGE_INDEX_CARD_BACK;

card = 0; // Card structure - obj_card.card.suit, obj_card.card.rank

/////////////////////////////////////////////////
// Card Object Methods:
// Usage: oCard.SetCard(_card);

/// @function SetCard(_card)
/// @param    {Card}        _card    The card enum to set the card object as representing
/// @return {obj_card}            The card object being operated on (for method chaining)
SetCard = function(_card)
{
    card = _card;
    
    image_index = card.GetImageIndex();
    
    show_debug_message("SetCard image_index = " + string(image_index));
    
    return id;
}

/// @function SetCardByElements(_suit, _rank)
/// @param    {Suit}        _suit    The card suit for the card object to represent
/// @param    {Rank}        _rank    The card rank for the card object to represent
/// @return {obj_card}            The card object being operated on (for method chaining)
SetCardByElements = function(_suit, _rank)
{
    card = new Card(_suit, _rank);
    
    image_index = card.GetImageIndex();
    
    show_debug_message("SetCardByElements image_index = " + string(image_index));
    
    return id;
}
All other code in the project is extraneous debug / printout testing code in the Step Events, added after this issue with noticed in efforts to understand it. You can see that in the project itself if you download the YYZ file here: https://drive.google.com/file/d/1dqdZ9xFc6jaGR8nfbcxeTIo7-Sh2DOKW/view?usp=sharing

If you've read this far, I sincerely thank you.

I know this is a very long write-up, but I'm just trying to be as thorough as possible. I am completely at my wit's end with this problem, and it's not something I can ignore. This is a critical, game-breaking error for me directly impacting my job, and I have no clue why it's happening or how to fix it. At this point, the only real explanation I can fathom is that it's some bizarre engine bug, but I sincerely hope that's not the case and it's something wrong with my code and what I'm doing that can be fixed. Thank you for any help you can provide.
 

chamaeleon

Member
Use string_format(rank, 0, 16) or something like that include more decimal digits (my guess is you'll see 14.99999... or something and not 15.0000...). Although, for numbers so small like this, using only addition and multiplication, I can't off hand think of why you would not get an accurate number as the floating point behind the scenes should be able to accurately represent numbers in this range without issue.
 

Roldy

Member
I tried opening you project. It fails to convert it to version (crashes the ide when trying to update the project). What version (runtime and IDE) is this made with?

I made a sample project and could not reproduce:

Sprite with 53 and 54 frames:
Calculated image_index = currentSuit * rankCount + currentRank;
And using your Card.GetImageIndex function.

Quick question... what is your room speed set too. I know there is a weird bug with image_index, animation_end, and certain room speeds.

The sprite i made is just a number on each frame and the back ground color changes every 20 frames:

Left is image_index = currentSuit * rankCount + currentRank;
Right is myCard.GetImageIndex(); your struct function.

The three text numbers along the top are the calucated index, the actual image_index, and the index using your GetImageIndex function.

None of them every show the wierd (x.00) decimal places you show. How are you printing those?

They always produce the same index and the frame is always shown correctly.

Animation.gif

Try cleaning your project. Could be something wrong with it,because I can't import it (crashes IDE). Tried with 2.2.5 and 2.3.2.560 (2.3.2436)

Possibly look at the generated texture pages and see if the cards are all there.
 
Last edited:
Thank you for your reply, and good suggestion, chamaeleon.

Yeah, I figured as much as well and that is the case. See below where the image_index is being printed with draw_text_transformed(x, y + 16, string_format(image_index, 0, 8), 1, 1, 0); instead of draw_text_transformed(x, y + 16, string(image_index), 1, 1, 0);



But yes, I had the same thought, that even in this scenario, the floating-point rounding should make it a non-issue, however we can see that the Four of Diamonds' 14.99999905 image_index is being floored into 14 when drawing instead of rounded to 15.

(This is the full Draw Event for obj_card:)

GML:
draw_self();

draw_set_color(c_black);
draw_rectangle(x - 64, y - 32, x + 64, y + 32, false);

draw_set_color(c_white);
draw_set_valign(fa_middle);
draw_set_halign(fa_center);
draw_text_transformed(x, y - 16, card.GetString(), 1, 1, 0);
draw_text_transformed(x, y + 16, string_format(image_index, 0, 8), 1, 1, 0);
However, that is not my main issue with this bug. Even if the image_index consistently floored instead of rounded; fine, I could happily accept and deal with that. My issue is how is it becoming 14.99999905 in the first place???
Why is it tied to image_number???


As said up in my big wall-of-text thorough explanation, the image_index is being set via a nested switch statement based on the Suit and Rank enums to return the exact image_index required without any addition, multiplication, or maths at all to avoid weird floating-point problems like this. See the switch statement here:

GML:
switch(suit)
{
    case Suit.Club:
    {
        switch(rank)
        {
            case Rank.Two:        return 0;
            case Rank.Three:    return 1;
            case Rank.Four:        return 2;
            case Rank.Five:        return 3;
            case Rank.Six:        return 4;
            case Rank.Seven:    return 5;
            case Rank.Eight:    return 6;
            case Rank.Nine:        return 7;
            case Rank.Ten:        return 8;
            case Rank.Jack:        return 9;
            case Rank.Queen:    return 10;
            case Rank.King:        return 11;
            case Rank.Ace:        return 12;
            default:            return IMAGE_INDEX_CARD_BACK;
        }
    }
    case Suit.Diamond:
    {
        switch(rank)
        {
            case Rank.Two:        return 13;
            case Rank.Three:    return 14;
            case Rank.Four:        return 15;
            case Rank.Five:        return 16;
            case Rank.Six:        return 17;
            case Rank.Seven:    return 18;
            case Rank.Eight:    return 19;
            case Rank.Nine:        return 20;
            case Rank.Ten:        return 21;
            case Rank.Jack:        return 22;
            case Rank.Queen:    return 23;
            case Rank.King:        return 24;
            case Rank.Ace:        return 25;
            default:            return IMAGE_INDEX_CARD_BACK;
        }
    }
    case Suit.Heart:
    {
        switch(rank)
        {
            case Rank.Two:        return 26;
            case Rank.Three:    return 27;
            case Rank.Four:        return 28;
            case Rank.Five:        return 29;
            case Rank.Six:        return 30;
            case Rank.Seven:    return 31;
            case Rank.Eight:    return 32;
            case Rank.Nine:        return 33;
            case Rank.Ten:        return 34;
            case Rank.Jack:        return 35;
            case Rank.Queen:    return 36;
            case Rank.King:        return 37;
            case Rank.Ace:        return 38;
            default:            return IMAGE_INDEX_CARD_BACK;
        }
    }
    case Suit.Spade:
    {
        switch(rank)
        {
            case Rank.Two:        return 39;
            case Rank.Three:    return 40;
            case Rank.Four:        return 41;
            case Rank.Five:        return 42;
            case Rank.Six:        return 43;
            case Rank.Seven:    return 44;
            case Rank.Eight:    return 45;
            case Rank.Nine:        return 46;
            case Rank.Ten:        return 47;
            case Rank.Jack:        return 48;
            case Rank.Queen:    return 49;
            case Rank.King:        return 50;
            case Rank.Ace:        return 51;
            default:            return IMAGE_INDEX_CARD_BACK;
        }
    }
    default:                    return IMAGE_INDEX_CARD_BACK;
}
This is of course reacting to the Suit and Rank enums here:

GML:
enum Suit
{
    Club        = 0, // In Ricochet Poker, suits are ranked Clubs, Diamonds, Hearts, Spades
    Diamond        = 1,
    Heart        = 2,
    Spade        = 3,
    Error        = 4,
    
    Joker        = 5,
}

enum Rank
{
    Two        = 2,
    Three    = 3,
    Four    = 4,
    Five    = 5,
    Six        = 6,
    Seven    = 7,
    Eight    = 8,
    Nine    = 9,
    Ten        = 10,
    Jack    = 11,
    Queen    = 12,
    King    = 13,
    Ace        = 14,
    Error    = 15,
    
    Joker    = 16,
}
Really straightforward, and the Error and Joker cases are never even coming into the equation for these tests. I just iterate over each suit from Club to Spade and rank Two to Ace to construct each card in the 52-card deck.

I did set the image_index using maths before with the mathematically-correct return (suit * 13) + (rank - 2); equation, but that gave the exact same results as using these nested switch-statements.

This crap is mind-bottling; y'know, when things are so crazy that it gets your thoughts all trapped, like in a bottle?
 

devKathy

Member
Thoughts (take with a grain of salt as I do not use GM much these days):

1) You're not touching image_index anywhere else in the code we haven't seen, correct? No division?
2) If it still works the same way, a decimal image speed can cause the image_index to go to decimal: https://forum.yoyogames.com/index.p...e_index-gives-decimal-values.9690/#post-66759
3) This may be relevant, depending on if that decimal is being stubborn for whatever reason, and depending on what you want to do: https://docs.yoyogames.com/source/dadiospice/002_reference/maths/real valued functions/math_set_epsilon.html
4) I second @Roldy 's question about runtime and IDE.
 
I tried opening you project. It fails to convert it to version (crashes the ide when trying to update the project). What version (runtime and IDE) is this made with?

I made a sample project and could not reproduce:

Sprite with 53 and 54 frames:
Calculated image_index = currentSuit * rankCount + currentRank;
And using your Card.GetImageIndex function.

Quick question... what is your room speed set too. I know there is a weird bug with image_index, animation_end, and certain room speeds.

The sprite i made is just a number on each frame and the back ground color changes every 20 frames:

Left is image_index = currentSuit * rankCount + currentRank;
Right is myCard.GetImageIndex(); your struct function.

The three text numbers along the top are the calucated index, the actual image_index, and the index using your GetImageIndex function.

None of them every show the wierd (x.00) decimal places you show. How are you printing those?

They always produce the same index and the frame is always shown correctly.

View attachment 40625

Try cleaning your project. Could be something wrong with it,because I can't import it (crashes IDE). Tried with 2.2.5 and 2.3.2.560 (2.3.2436)

Possibly look at the generated texture pages and see if the cards are all there.
Thank you so much for the reply, Roldy. Good thoughts all-around.

I'm using IDE v2.3.2.560 and Runtime v2.3.2.426, so that's really bizarre that the project failed to convert from the YYZ... I just tried importing the YYZ fresh and it ran fine, but that's very curious / troubling...
Here's the project again but as a Zip instead of a YYZ (also includes my little screenshots in the extra "assets" folder). Hopefully that works, but if still not then you should be able to open / view my sprites and code via the folder no problem:
https://drive.google.com/file/d/1hTPg-wUbkdnIRi-hQy9o8ILfSRZEq1Pu/view?usp=sharing

The weird x.00 decimal places print was with draw_text_transformed(x, y + 16, string_format(image_index, 0, 8), 1, 1, 0); but if you look at my reply to Chamaeleon just now you can see how I changed that to string_format and the results there, showing 14.99999905 for Four of Diamonds instead of 15.00.

And cleaning the project gifts no avail. I did it again just to be sure, but yeah that's always my first goto (HTML5 horrors have taught me to Clean almost every compile now). And this exact error has been present in about 20 other projects of mine... I just didn't realize it for a long-time... Most of those games dealt x-number of random cards from a deck or infinite shoe, etc., so when a Four of Diamonds was displaying as a Three of Diamonds, me and my players just thought it was a Three of Diamonds dealt normally. It was only if we were really paying attention on these edge cases where we'd notice that the hand wasn't evaluating properly. And all those previous games used image_index as the only data point to track the card's suit and rank instead of these safe enums I switched to for this project. But yeah, this error has been present for dozens of projects across GMS 2 and 2.3 with different image_index / card implementations, no shared code, on many different machines and platforms. I'm building / testing on a Windows 10 machine primarily, but this bug was also present for Android and Mac and Amazon Fire and HTML5 whenever I search for it in a previous game. Right now I'm testing on two different Windows 10 machines to identical results.

And the room speed is 60. Very good question, you might be on to something there, that sounds like it could be it—a weird image_index + room speed engine bug—but I don't want that to be the case since I want the game to run at 60 FPS haha... If it's necessary, I can lower / change the room speed, and I haven't tried that, that might very well fix it. I have a feeling that might be it; I'll try it now, thank you.
 
I tried opening you project. It fails to convert it to version (crashes the ide when trying to update the project). What version (runtime and IDE) is this made with?

I made a sample project and could not reproduce:

Sprite with 53 and 54 frames:
Calculated image_index = currentSuit * rankCount + currentRank;
And using your Card.GetImageIndex function.

Quick question... what is your room speed set too. I know there is a weird bug with image_index, animation_end, and certain room speeds.

The sprite i made is just a number on each frame and the back ground color changes every 20 frames:

Left is image_index = currentSuit * rankCount + currentRank;
Right is myCard.GetImageIndex(); your struct function.

The three text numbers along the top are the calucated index, the actual image_index, and the index using your GetImageIndex function.

None of them every show the wierd (x.00) decimal places you show. How are you printing those?

They always produce the same index and the frame is always shown correctly.

View attachment 40625

Try cleaning your project. Could be something wrong with it,because I can't import it (crashes IDE). Tried with 2.2.5 and 2.3.2.560 (2.3.2436)

Possibly look at the generated texture pages and see if the cards are all there.
Just tried running it at different room speeds (with Cleans in-between each run) and it gave the exact same results regardless...
I tried with room speed 60, 30, 10, 45, 59, and 69 (I'm a child)—all gave the exact same output.

IDE v2.3.2.560 (v2.3.2.426) on a modern updated Windows 10 machine, and it seems to be the exact same on every machine I have access to.
 

Roldy

Member
Thank you so much for the reply, Roldy. Good thoughts all-around.

I'm using IDE v2.3.2.560 and Runtime v2.3.2.426, so that's really bizarre that the project failed to convert from the YYZ... I just tried importing the YYZ fresh and it ran fine, but that's very curious / troubling...
Here's the project again but as a Zip instead of a YYZ (also includes my little screenshots in the extra "assets" folder). Hopefully that works, but if still not then you should be able to open / view my sprites and code via the folder no problem:
https://drive.google.com/file/d/1hTPg-wUbkdnIRi-hQy9o8ILfSRZEq1Pu/view?usp=sharing
I have to go now, be back later. But I was able to download the zip and run your program. It did show the weird rounding errors.

However I quickly did the following to get rid of the errors:

GML:
//obj_Card Create Event

once = true;

// At the top of obj_Card Draw Event

if (once ) {
    //once = false; //  With this commented out we will SetCard before drawing
                    // every frame. And there are NO errors.  The cards draw correctly.
                    // However, if we uncomment this and only do it one time
                    // then the cards draw incorrectly with rounding errors.
    SetCard(card);
}

Consider the above. Somewhere between the create event SetCard and your drawing the image_index is being changed, resulting in the float errors.
 
Thoughts (take with a grain of salt as I do not use GM much these days):

1) You're not touching image_index anywhere else in the code we haven't seen, correct? No division?
2) If it still works the same way, a decimal image speed can cause the image_index to go to decimal: https://forum.yoyogames.com/index.p...e_index-gives-decimal-values.9690/#post-66759
3) This may be relevant, depending on if that decimal is being stubborn for whatever reason, and depending on what you want to do: https://docs.yoyogames.com/source/dadiospice/002_reference/maths/real valued functions/math_set_epsilon.html
4) I second @Roldy 's question about runtime and IDE.
Thanks so much for the reply / attention, devKathy.
  1. Nope, image_index is only set / written to once when it's initialized in the obj_game Create Event via the SetCardByElements obj_card method (both seen above in the OP). The only exception to this is an added debug test, where I call the method SetCard if the spacebar is pressed, which is irrelevant. Originally the image_index was calculated with a simple math expression upon the suit and rank enums, but now they're just set via a nested switch-statement to the same results.
  2. The first line in the obj_card Create Event is image_speed = 0; and image_speed is never referenced in the code again.
  3. Yeah, I tried set the epsilon to something more aggressive early on, but it didn't help at all and (obviously) made all my other math less accurate, so I stopped messing with epsilon.
  4. IDE is v2.3.2.560 and Runtime is v2.3.2.426
1623701062123.png

obj_game Step Event with extra code added later for testing the bug; the issue was present before they were introduced:
GML:
if(keyboard_check_pressed(vk_space))
{
    with(obj_card)
    {
        show_debug_message("");
        show_debug_message(card.GetString());
        show_debug_message("pre-SetCard image_index = " + string(image_index));
        SetCard(card);
    }
}

if(keyboard_check_pressed(vk_enter))
{
    if(room == rm_cards55)    room_goto(rm_cards53);
    else                    room_goto_next();
}
 
I have to go now, be back later. But I was able to download the zip and run your program. It did show the weird rounding errors.

However I quickly did the following to get rid of the errors:

GML:
//obj_Card Create Event

once = true;

// At the top of obj_Card Draw Event

if (once ) {
    //once = false; //  With this commented out we will SetCard before drawing
                    // every frame. And there are NO errors.  The cards draw correctly.
                    // However, if we uncomment this and only do it one time
                    // then the cards draw incorrectly with rounding errors.
    SetCard(card);
}

Consider the above. Somewhere between the create event SetCard and your drawing the image_index is being changed, resulting in the float errors.
THANK YOU!

I tried the exact same thing so many ways, including still with the obj_game Step Event spacebar debug effort, to no avail—except I never did it in the Draw Event. I was always resetting the image_index in Step or Step End or Step Begin or something. If I run SetCard(card) at the top of the obj_card Draw Event, it fixes my problem; however, I can't make it one-and-done as you say with a state flag once = false;

If I uncomment that line, the error still appears, the same way my obj_game spacebar debug thing was calling SetCard(card) to reset every obj_card's image_index yet only fixed the issue for a single frame.

Even if I remove every single Step Event in the game (which is just the obj_game two if-tests on keypress), something is happening within the engine between frame start and Draw to alter the image_indexes.

Your force-reset image_index at the start of Draw (instead of Step) successfully fixes my problem, though of course it's not a great fix as it requires setting state members and running a function on every card instance every frame that should only need to be called once. I'm happy with it for now and can accept it as a temporary band-aid measure, however of course it's a minor performance issue (and eventually I need this to run on HTML5 so I need every perf boost I can get haha) and I'm still concerned about the underlying problem that is causing the image_indexes to glitch out and change to 14.99999905 every Step anyway.

It sounds more and more to me like this is a really weird edge case engine bug, but hopefully not and we can figure out that I'm just stupid.

Thank you so much again, I really appreciate your time and help. You're a real one.
 

Roldy

Member
THANK YOU!

I tried the exact same thing so many ways, including still with the obj_game Step Event spacebar debug effort, to no avail—except I never did it in the Draw Event. I was always resetting the image_index in Step or Step End or Step Begin or something. If I run SetCard(card) at the top of the obj_card Draw Event, it fixes my problem; however, I can't make it one-and-done as you say with a state flag once = false;

If I uncomment that line, the error still appears, the same way my obj_game spacebar debug thing was calling SetCard(card) to reset every obj_card's image_index yet only fixed the issue for a single frame.

Even if I remove every single Step Event in the game (which is just the obj_game two if-tests on keypress), something is happening within the engine between frame start and Draw to alter the image_indexes.

Your force-reset image_index at the start of Draw (instead of Step) successfully fixes my problem, though of course it's not a great fix as it requires setting state members and running a function on every card instance every frame that should only need to be called once. I'm happy with it for now and can accept it as a temporary band-aid measure, however of course it's a minor performance issue (and eventually I need this to run on HTML5 so I need every perf boost I can get haha) and I'm still concerned about the underlying problem that is causing the image_indexes to glitch out and change to 14.99999905 every Step anyway.

It sounds more and more to me like this is a really weird edge case engine bug, but hopefully not and we can figure out that I'm just stupid.

Thank you so much again, I really appreciate your time and help. You're a real one.

I verified it in a separate test project. My first test project did not show the problem because I was slamming image_index every frame.

But if you set image_index once then it will be 'good' for the first frame... and then at some point between the GUI Draw END of the first frame and Begin Step of the second frame image index will acquire some rounding errors. Which frames will display incorrect is dependent on the total frame count of the sprite. Just like you said.

I put show_debug_messages in every single object event and found no place to get control over it.

I'll submit my test program as a bug, if you don't submit yours.

The work around is to set image_index every frame.


EDIT: I havn't downloaded the current beta but I think it will. I know they made ANOTHER change to image_index in this beta.. So it may have fixed it out even changed the bug behaviour.

A side note on your expressed desire to switch engines. Just know every engine (software in general) is going to have bugs. It is not ideal but just a fact. The goal is to know about them and to work around them. If you switch engines you will most likely run into a bug that you will also have to work around. IMO it is better to stick with what you have and get to know it well enough that the bugs don't surprise you.
 
Last edited:
I verified it in a separate test project. My first test project did not show the problem because I was slamming image_index every frame.

But if you set image_index once then it will be 'good' for the first frame... and then at some point between the GUI Draw END of the first frame and Begin Step of the second frame image index will acquire some rounding errors. Which frames will display incorrect is dependent on the total frame count of the sprite. Just like you said.

I put show_debug_messages in every single object event and found no place to get control over it.

I'll submit my test program as a bug, if you don't submit yours.

The work around is to set image_index every frame.


EDIT: I havn't downloaded the current beta but I think it will. I know they made ANOTHER change to image_index in this beta.. So it may have fixed it out even changed the bug behaviour.

A side note on your expressed desire to switch engines. Just know every engine (software in general) is going to have bugs. It is not ideal but just a fact. The goal is to know about them and to work around them. If you switch engines you will most likely run into a bug that you will also have to work around. IMO it is better to stick with what you have and get to know it well enough that the bugs don't surprise you.
Yeah, I was packaging together another side test project to submit as an engine bug.
See it here: https://drive.google.com/file/d/1RbpHXimT5_LehPcvr0GdzcMXQKlQW1zN/view?usp=sharing

And yeah, bugs normally don't concern me (this is only the second GM Forum thread I've ever made), but this was an extremely critical, game-breaking bug for me, and I had been hitting my head against for a week (at a full-time job) trying to find any fix or even band-aid solution, like you have graciously given me haha! I tried changing how the image_indexes were calculated to a completely different, safer format (twice), switched from a math-based method to those hard-coded values from switch-statements, did it with and without enums, with and without structs and methods, tried calculating image_index once on the first frame after all the create / game start / room start events, tried hard-setting the image_index in Step or Step End, cleaned infinity times, made a new clean project, removed all other code, tried recreating the sprite, put debug console print messages everywhere to try and isolate the issue, everything I could think of. Of course the real band-aid fix was the one thing I hadn't thought of to try, exactly as I was hoping someone could provide with this thread haha 😅

But this bug was a severe game-breaking barrier for my work / job, and if I didn't find some fix I would have to move to a different game engine, as I have a schedule to keep, time-tables to meet, you know the drill. Switching engines is not something I want to do, as I adore GameMaker (I love GM so much I've taught it to kids / high-schoolers for years, and was restarting GM YT tutorials last year), but I could swap engines if necessary. I've worked in dozens in game engines over the years, and even made a few back in my school days, and I consider myself language and engine-independent... but I just really love and swear by GMS2 for 99% of 2D projects. I'm using GMS2 for my job purely because I wanted to and I had the choice to use whatever I pleased as long as the games got out. Unity is definitely the standard for my industry (and I get a few of the Unity reps knocking on my door trying to get me to use Unity at my company every while), but I just think GameMaker is a better engine for my purposes.

I've dealt with (and reported) several GameMaker engine bugs in my day (as well as with Unity and other engines), but this is the first game-breaking engine bug I've dealt with (at least without finding a work-around on my own within a few days).

Again, I really appreciate your time, attention, and help, Roldy (and the others in this thread!). I'm fortunate enough to be able to work in GameMaker as my full-time job now after several years of GM contract work, but I'm the only developer working on games / in GameMaker at my company, so I don't have another head to bounce ideas and GM-specific programming problems off on.

Hopefully I can return the favor sometime; we all just need another pair of eyes / a new perspective with coding issues occasionally. Let me know if there's anything I can ever do to help you—whether that's providing another pair of eyes on your own GM problem / thread, or supporting some games of yours, watching some YT videos of yours, etc.

I'll be submitting this as a bug to YoYo Games now that we've pretty conclusively narrowed it down to being an engine bug. Cheers!

Sincerely,
Gunnar Clovis
 

FrostyCat

Redemption Seeker
Try using a new variable other than image_index, and use draw_sprite manually to draw the correct card face. There is additional engine-level logic outside of developer reach that operates on image_index, and breaking your dependence on that for this specific case looks like a reasonable immediate step.
 

Roldy

Member
Try using a new variable other than image_index, and use draw_sprite manually to draw the correct card face. There is additional engine-level logic outside of developer reach that operates on image_index, and breaking your dependence on that for this specific case looks like a reasonable immediate step.

This is a solution. However, there are two bugs here and your solution only address one.

Consider this code:

GML:
if (image_index == 30) {   // image_index actually equals ~29.99998 and the  GML  comparison it will 'round' (or epsilon) to 30
                                         // This is not dependent on the '==' operator and effect other comparisons as well

    draw_sprite(sprite_index, 30, x + 100, y);
    draw_self(); // image_index actually equals 29.99998 but the drawing code floors it to 29 instead of rounding to 30 like GML
}
This will draw the same sprite twice, but with different frames. Even though GML logic tells you image_index is 30 the underlying code treats it as 29.

The fact that image_index is manipulated (for no reason in OPs case) and gets rounding errors is ONE bug, that your suggestion solves.

The fact that the GML math and the underlying engine math do not match is a SECOND bug that your suggestion does not address.
 
Last edited:
Try using a new variable other than image_index, and use draw_sprite manually to draw the correct card face. There is additional engine-level logic outside of developer reach that operates on image_index, and breaking your dependence on that for this specific case looks like a reasonable immediate step.
Very good idea! Thanks, FrostyCat!

That's a much more performant solution for my purposes that will slot in fairly naturally to this greater overall render management system I'm building. The idea with this render manager is to have one object handle all draw calls in layered batches to different surfaces, and then those surfaces are rendered to application_surface instead of individual draw calls. Hopefully with this I can eliminate vertex buffer breaks for better performance, and of course a surface only needs to be re-drawn when one of its constituents updates, so background surface(s) (comprised of any number of layers) whose visual elements only change once every few minutes (if ever) only need to drawn once / upon changes. I'm trying to optimize for performance as much as possible here as my end target is distributing dozens of HTML5 games that involve a lot of intensive CPU-calculations in bursts that need to run as fast as possible even on very crappy, non-gaming PCs being used by a very non-tech savvy, non-gamer demographic.

Thank you for your time and attention, FrostyCat. I thank you for your service to the GM community; your posts on many threads in the past have been very helpful for me! I'm also a fan of your extensions, though admittedly I rarely use them as they don't really apply to my work. But you're a real one, mad respect.

Stay Frosty,
 
I assume you don't actually necessarily need another variable, but can use the value of GetImageIndex() as the frame to use in a draw_sprite() call.
Yes, though I would be saving that result in a new variable so as to minimize how many times the GetImageIndex() function was called. I presume that would be more performant, slightly more memory technically with a new real variable, but that should be better than calling a method (that calls a function) every frame for every relevant object, unless I'm thinking about it wrong.

Thanks again for the reply!
 
I've reported this as an engine bug to YoYo with the following information:

Wrong subimage draws due to image_index being corrupted every frame by rounding errors based on image_number

IDE 2.3.2.560
Runtime 2.3.2.426

Platforms:

Windows

Area(s) this problem affects:
Sprites

Description:
On at least Windows platforms (VM & YYC), sprites will draw the incorrect subimage at consistent, reproducible rates due to rounding errors inserted into the image_index by the engine every frame, and the engine flooring to render the subimage. The image_number of the sprite used will alter which image_indexes are corrupted with rounding errors and thusly which subimages incorrectly render as their neighboring subimage.

For example, a sprite with 84 subimages (and image_speed set to 0) will incorrectly draw subimage 57 as subimage 56, and subimage 61 as subimage 60.
Whereas a sprite with 53 subimages will instead incorrectly draw subimage 15 as 14, and 30 as 29.

You can see the GM Forum Thread below for more details on what has been tested and another sample project.

Sample Project Instructions:

Sample is automated. The only code in the project is three short events: obj_manager Create, obj_thing Create, and obj_thing Draw.
The project could be simplified to only include the obj_manager Create code (or even no code at all) using GUI Variable Definitions or setting image_speed for each obj_thing to 0 within the obj_manager's Create, etc. And this simplified project would still demonstrate the engine bug, however all three of these Events were included for maximum clarity, and to print the image_index of each instance in real-time for better debug understanding.
Run the game in VM or YYC mode. 84 instances will be created using the same 84-frame sprite, with image_speed set to 0, incrementing through image_index 0 to 83. Each instance draws itself and then draws its image_index twice, once raw and then with string_format. Notice how the instance with image_index 57 draws its image_index as 56.999996 and draws the previous subimage for 56.
Instance 61 does the same, drawing as subimage 60.
Between game compilations, Edit obj_thing and change the sprite from spr_frames84 to spr_frames53 or spr_frames54. See how the difference in sprite image_number affects which subimages acquire rounding errors every frame.

GM Forum Thread:


Platforms:

Tested and found as present on multiple Windows 10 machines in both VM and YYC.
Not present in HTML5 (at least as same period / image_number to image_index corruption).
Unknown presence on other platforms.
With the same sample project here: https://drive.google.com/file/d/1RbpHXimT5_LehPcvr0GdzcMXQKlQW1zN/view?usp=sharing

The ui.log file failed to upload during the submission process, but I dropped this link in an email reply to the ticket: https://drive.google.com/file/d/13RsaAXDiBXVkya0ciPwrrSeM6g0V37O5/view?usp=sharing

Please let me know if you think I goofed up at any part along that process, and thank you all again for you assistance and support. I do really appreciate it. Seriously, if there's ever any way I can return the favor (helping on your own thread, making a video about your game, tweeting out your game, buying your game on Steam, etc.) just holler.
 

Neoclod

Member
have you tried to round() it? (so you don't get decimals?)

I mean isn't index 15 different to 15.00?? It's odd but there are 4 cards in there with a decimal point.

It could be that the value is like 15.00000000001 on the ones that work so game maker actually puts a correct value on it. while 15.009999999999 could be a different index to index??
Honestly I'm not sure if this is the correct answer but it never hurts to try.
 

Roldy

Member
have you tried to round() it? (so you don't get decimals?)

I mean isn't index 15 different to 15.00?? It's odd but there are 4 cards in there with a decimal point.

It could be that the value is like 15.00000000001 on the ones that work so game maker actually puts a correct value on it. while 15.009999999999 could be a different index to index??
Honestly I'm not sure if this is the correct answer but it never hurts to try.
This topic is a couple months old and has since been resolved in subsequent GMS2 updates.
 
have you tried to round() it? (so you don't get decimals?)

I mean isn't index 15 different to 15.00?? It's odd but there are 4 cards in there with a decimal point.

It could be that the value is like 15.00000000001 on the ones that work so game maker actually puts a correct value on it. while 15.009999999999 could be a different index to index??
Honestly I'm not sure if this is the correct answer but it never hurts to try.
Yes, rounding (and excessive rounding) was the first / main thing I tried; I tried rounding at just about every stage of the process, every Event it seemed halfway logical to do so in, messed with epsilon, had rounding baked into my card value methods, etc. to no avail.

It turned out that my error was in fact an engine bug tied to the image_index variable inherently, so the only solution ended up being replacing image_index with an alternate variable I had full control over.

Please read through the following thread above for details on the experimentation process and my final solution and bug submission.

Thank you for trying to help anyway! I appreciate the thought 😊
 
Top