• 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!

image_xscale=-1 doesn't work properly on sprites with odd widths

BoB3k

Member
[before writing all this out, I DID go and upgrade to the newest build to ensure this is still happening - IDE:2.2.5.481 - RT:2.2.5.378]

So, I'm using GMS2 to create a side-scroll-y game with small sprites, and so I'm trying to keep interactions working well down to the pixel. Because my sprites are small, I've gone with odd width sprites as my guys are often as small as 3 or 5 pixels in width.

I am doing the common trick of using xscale=-1 to flip sprites for left/right movement/animation. And, that's where I've run into a problem. It looks like GMS2 has been optimized for even width sprites. Which makes sense, as most people use stuff like 16,32,etc. But, flipping should still work for odd sprites.

Essentially, what happens is that the odd width sprite is not flipped properly around the origin as defined in the sprite. It is off by a pixel. My guess is that this is an artifact of how the GMS code handles even width sprite flipping. It makes sense that in an even width sprite, there is no middle pixel, w/2 is the left of the pair of center pixels, and that when you flip you have to take into account for that. But in an odd width sprite, there IS a middle pixel that will be the middle pixel both ways, and that's not happening.

I have some pics below to illustrate this, the first set shows a guy, his origin at the bottom middle (drawn in code), and his bounding box (also drawn in code) around him. The two pics show him in the same X/Y spot, but with his xscale flipped. Notice that he literally moves a pixel. This is no good.

gms_pix_flip_bug.png

In the second set, you will see where I've tried to account for this by doing my own drawing using
draw_sprite_ext() and doing this trick to the X=>ceil(x-image_xscale*.5), which does fix the problem by moving the x over when xscale is negative. But this DOESN'T FIX MY PROBLEM. Firstly, this now messes up even width drawing as it now makes even width sprites draw offset (I think I'm essentially undoing the GMS even width tricks with my trick). I could fix this with a ( ? : ) to check width odd/even, but then this STILL DOESN'T FIX MY PROBLEM because the collision mask also gets flipped with xoffset flip (which it should), so it still has the broken xoffset flip, so even if I fix the draw, my collision mask is now off from my sprite. See this second pic:

gms_pix_flip_bug_2.png

This, I don't know if I can fix as I don't think we have control of moving collision masks. Not to mention, I shouldn't have to be doing this. Defining sprites and their origins and collision masks is one of the cool features that GMS gives you.

This is really bumming me out as I don't see a way around this other than going to all even sprites, which I shouldn't have to do, and don't want to do, it changes my art style, or redrawing everything bigger or something, or even making multiple sets of sprites so I don't have to flip them. All of which are more work and more organization that I shouldn't have to do.

It would be great if someone knew a fix to this. But, it would be more great if the yoyo guys agreed and fixed this issue. Essentially, if I have an odd width sprite drawn to the screen, and i flip xscale, the sprite shouldn't move.

(BTW, I'm sure this is all true for yscale, I just focused on xscale here)

Thanks for listening.

BoB


Edit : I have since come up with a workaround for this, posted below -- https://forum.yoyogames.com/index.php?threads/image_xscale-1-doesnt-work-properly-on-sprites-with-odd-widths.71734/#post-423622
 
Last edited:

Nocturne

Friendly Tyrant
Forum Staff
Admin
The origin isn't in the middle of a pixel, it's in the top left corner of the pixel, so flipping the sprite gives an off-by-one error. You can't have half-a-pixel so this makes sense...
 

BoB3k

Member
The origin isn't in the middle of a pixel, it's in the top left corner of the pixel, so flipping the sprite gives an off-by-one error. You can't have half-a-pixel so this makes sense...
I disagree. Regardless of the underlying implementation, if you flip a sprite that has a centered origin, it should draw in the same place, but with its pixels flipped no matter what its width.

Here, you don't even have to use any code, just take an odd width sprite, select centre in its Origin pulldown, drop it in a layer, and then flip it. It shouldn't move, but it does--see pic. This could either be fixed by having smarter scaling/flipping code, or by allowing fractional origin points. But, I think a code change is the best. GMS should be smart enough to flip an odd length without moving it. And remember this happens to the collision mask as well, which is just as big an issue.


Notice the X is the same for both, but the flipped one is not drawn in the same position. even though 'centre' is picked for the sprite.
gms_pix_flip_bug_3.png
 

TheouAegis

Member
So you want an already slow engine to be even slower?

What I want is for sprites to be defined by their dimensions with the origin always at the same point and image_xscale simply dictates in which order sprites pixels are rendered, just like in the good old days. But instead, I learned to work with a system that is logical geometrically. I do wish fractional origins could be applied to upscaling, though...
 

Yal

šŸ§ *penguin noises*
GMC Elder
You always have the option to have a symmetrical collision mask and draw the sprite yourself, using your own variables to flip/rotate it... it's a lot of extra work, but for stuff like player characters that can be squished/stretched for emphasis in animations, can switch between dozens of sprites etc it usually saves more headaches than they create. (I usually use the built-in systems for stock enemies and effects, but override them for bosses and player-controlled objects)
 

BoB3k

Member
So you want an already slow engine to be even slower?
That's a silly statement. Of course I don't want it slower. but really, there's no simple math fix that takes into account the 'center' being properly calculated for both even and odd sprite dimensions?
 

BoB3k

Member
So, I did figure a workaround for this, but I don't really care for its level of complexity. --

You can change a sprites xoffset and yoffset dynamically in code. So, if you check to see if a sprite is odd width and xscale flipped, you can adjust its xoffset to compensate--i.e. keep in drawn (and mask) in the same spot only flipped. This works just fine except for one snag--you need to adjust it back when xscale is not flipped, and to adjust back and forth without too much complexity, you really just need to save the original xoffset and each time through decide whether to adjust or not.

I only have a smaller group of objects I care about this level of detail, so it was easy to put this adjustment code only in their parent object. I check for odd sprite width only once during creation. And, I have a ds_map that I use to keep track of sprites' original widths in (that is keyed directly to sprite id, so lookups should be fast), and then in Draw Begin, if I'm odd width, I lookup original offset and adjust current sprite offset based on current xscale +/-.

Not too ugly, it works and doesn't seem to slow things noticeably. It might if you started doing it to every drawn object I guess, I haven't tested.

One note, asset changes are NOT reset when a game_restart() is called, so if you're like me and have a restart shortcut for dev/test, in your code where you call the game_restart() you need to loop through the ds_map of sprites and set them all back to their original offsets, or else you'll get drift with each restart.
 
R

robproctor83

Guest
The problem is you can't have sub pixels and so when you start transforming the sprite and your trying to use a sub pixel like that it gets lost. Unfortunately it's not able to be fixed like you want, but there is a solution it just requires that you do things differently.
 

MIchael PS

Member
Well. I am kind of late, but I hope it helps.
Here is what I did:
a) I had a simple rectangle sprite that I used as the sprite of the player's object in order to calculate the collisions without messing with its scale!
b) I draw on top of that the sprite of my player. Take note that I didn't want the player sprite to move when it was x scaling, so I worked around that by drawing the sprite one horizontal pixel to the left, when the image_xscale was -1, keeping manually the object at the same position.
 

Rob

Member
If it was me I would have just used an even width Sprite and saved the hours of aggravation.
 

BoB3k

Member
If it was me I would have just used an even width Sprite and saved the hours of aggravation.
My entire game was based around a 3x11 stick-man, with a 3x3 head on a 1x1 neck and a 3x7 body (when standing), but, yeah, I'll just redo my entire graphical theme instead of expecting my game engine to handle literally half of all possible sprite widths when negative scaling. That definitely sounds like the simpler answer.
 

saffeine

Member
i don't know why this thread keeps popping up every 5 seconds several months on but if draw_sprite_ext( sprite_index, image_index, x + ( image_xscale < 0 ), y, image_xscale, image_yscale, 0, c_white, 1 ); is too much for people then i really don't know if game development is the way to go. yes, it'd be nice if gamemaker did it for you, no, it isn't difficult to solve. that's really kind of it, no discussion is needed. it's a simple +/- 1 if the sprite is flipped, maybe you need to add an empty column to the side of a sprite for it, but ??
 

BoB3k

Member
Here is what I did ...
a) ... I used as the sprite of the player's object in order to calculate the collisions without messing with its scale!
b) ... drawing the sprite one horizontal pixel to the left, when the image_xscale was -1, keeping manually the object at the same position.
This is a good answer, pretty much what I was doing but from a different angle --you used a different mask, which the engine handles better than changing the mask like I did. I like it and might use it if I was doing this today. The reason I didn't do it back then was because my little guy had multiple sprites for different states and they had different hit boxes, so I would have had to switch around the multiple hit boxes along with states instead of just using the hitbox defined in each sprite like the engine is built for.
 

BoB3k

Member
i don't know why this thread keeps popping up every 5 seconds but if draw_sprite_ext( sprite_index, image_index, x + ( image_xscale < 0 ), y, image_xscale, image_yscale, 0, c_white, 1 ); is too much for people then i really don't know if game development is ever the way to go. yes, it'd be nice if gamemaker did it for you, no, it isn't difficult to solve.
Threads pop up when they get a reply. This thread had replies in February, and then a few today. I wouldn't really call that every 5 seconds.

If that draw_sprite_ext trick works, that's cool, I might try it.

As to your laments of game development, my original thread was half a problem/complaint, and half reporting what I still feel is a deficiency in the engine. The game engine gives you a way to define a hitbox, and allows you to scale negatively. It should work consistently within it's own system; the hitbox shouldn't adjust differently for negative scaling.
 

Rob

Member
My entire game was based around a 3x11 stick-man, with a 3x3 head on a 1x1 neck and a 3x7 body (when standing), but, yeah, I'll just redo my entire graphical theme instead of expecting my game engine to handle literally half of all possible sprite widths when negative scaling. That definitely sounds like the simpler answer.
I don't want to argue with you man but wouldn't one of the first things that you do in a game be to make a player/actor and have them move around? It's usually my approach anyway.
Designing a whole game/spending hours on the code etc and THEN finding out that you have to mess about because of something like this seems like a waste of time.

I didn't even realise this thread is from Feb sorry - I wouldn't even have replied if i'd noticed :D
 

TheouAegis

Member
i don't know why this thread keeps popping up every 5 seconds several months on but if draw_sprite_ext( sprite_index, image_index, x + ( image_xscale < 0 ), y, image_xscale, image_yscale, 0, c_white, 1 ); is too much for people then i really don't know if game development is the way to go. yes, it'd be nice if gamemaker did it for you, no, it isn't difficult to solve. that's really kind of it, no discussion is needed. it's a simple +/- 1 if the sprite is flipped, maybe you need to add an empty column to the side of a sprite for it, but ??
the issue isn't where the sprite is drawn, the issue is where the collision box is placed. When you negate the scale, you actually change the bounding box as well, which causes collision issues. Back in GM8 and GMS1.0 when that was a very, very serious game-breaking issue with all sprite sizes, it wasn't as simple as just shifting coordinates, either. However, I do agree shifting x (or y) when changing scale causes the bounds to logically shift should be the simple solution people turn to in these situations. But as one of my old bosses told me, common sense isn't common. And not only that, people go into Game Maker expecting the program to function logically insofar as what they themselves perceive as logical, so when simply flipping a Sprite causes the game to break, they panic.
 

MIchael PS

Member
This is a good answer, pretty much what I was doing but from a different angle --you used a different mask, which the engine handles better than changing the mask like I did. I like it and might use it if I was doing this today. The reason I didn't do it back then was because my little guy had multiple sprites for different states and they had different hit boxes, so I would have had to switch around the multiple hit boxes along with states instead of just using the hitbox defined in each sprite like the engine is built for.
Yeah... the method I used only works if the hitbox has the same size during the whole game
 
Top