Gunnar the Clovis
Member
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:
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
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.
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.
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:
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:
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.
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.
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.
- With 53 subimages (52 + card back):
- Four of Diamonds draws as Three of Diamonds
- Six of Hearts draws as Five of Hearts
- With 54 subimages (52 + card back + joker):
- Five of Hearts draws as Four of Hearts
- With 55 subimages (52 + card back + joker + extra for testing):
- Five of Diamonds draws as Four of Diamonds
- Five of Hearts draws as Four of Hearts
- Six of Clubs draws as Five of Clubs
- Eight of Hearts draws as Seven of Hearts
- Ten of Clubs draws as Nine of Clubs
- Three of Clubs draws as Two of Clubs
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
}
}
}
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");
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;
}
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.