Author Topic: Edge-of-the-map bug, Exit bug  (Read 4988 times)

durnurd

  • Lead Lemming
  • Expert
  • Fanatic
  • *****
  • Posts: 1234
  • Games completed so far: 0
    • MSN Messenger - durnurd@hotmail.com
    • View Profile
    • Find My Ed
Edge-of-the-map bug, Exit bug
« on: 2007-01-21, 09:59:34 AM »
1:

When the player attempts to leave the map, he is pushed back a bit too far.  The most noticeable way to see this is in the sample project, standing on the structure in the upper-left corner of the map.  If you are standing on the roof that slopes down towards the left edge of the map and attempt to jump off to the edge of the map, you would expect to fall down the one-tile-wide hole there.  However, the sprite it pushed back one pixel, so you end up standing on the edge of the roof instead.

It's also noticeable if you just start the map and press left until you hit the edge of the map and let go.  You will notice that the sprite is pushed back and faces to the right and there is a one-pixel gap between the edge of the map and the sprite.

2:

Selecting "Exit" from the File menu while playing the game does nothing.
Edward Dassmesser

bluemonkmn

  • SGDK Author
  • Administrator
  • Fanatic
  • *****
  • Posts: 2761
    • ICQ Messenger - 2678251
    • MSN Messenger - BlueMonkMN@gmail.com
    • View Profile
    • http://sgdk2.sf.net/
    • Email
Re: Edge-of-the-map bug, Exit bug
« Reply #1 on: 2007-02-03, 10:24:58 AM »
Problem number 1 exists because negative numbers are rounded differently than positive numbers.  When the left side of the player sprite is at 0, and the velocity is -.75 pixels, the planned position will be three quarters of a pixel off the left edge, that position rounds to zero, so the player is still actually on the map in terms of whole pixels.  The player is allowed to move its planned velocity as long is it thinks it's not hitting a solid, and since its planned position is still on the map, it hasn't hit anything yet.  So then the player's position is at -.75.  Its next position (continuing this velocity) will put it beyond -1.  This is clearly off the edge of the map, so the velocity gets set to some positive number that will get it back to position 0 in one frame.  Of course if the inertia is more than 0%, the player will move a little extra after getting to 0.  That's what causes the player to "bounce" back a bit from the edge.

Compare that to when the player hits a normal wall on it's left: assume the position of the player is 32, and theres a solid pixel at position 31.  If the player's velocity is -.75 again, this time it will end up at 31.25.  But since this is a positive number, that rounds to 31 instead of 32.  So immediately the player can see that it will be on the solid pixel at position 31 and bounce back to 32.  Why doesn't inertia cause it to bounce back farther?  Because the player is at position 32 and it's new planned position is at 32.  There is no velocity required to move the player back to the position it needs to be at.  It's already there.

I found that I can work around this problem by changing the PixelX and ProposedPixelX properties of SpriteBase (which are responsible for rounding the sprite's position to a whole number) to add some arbitrary number before rounding, and then subtract it afterwards, but this seems like a lot of overhead to impose on every sprite at every position for such a small problem that can be ignored or worked around by having borders on your maps.  I could use Math.Floor instead of casting the position to an integer to work around the problem too (that rounds toward negative infinity instead of rounding toward zero), but after doing performance testing on that, I can see that it's more than twice as slow as casting to an integer.  Adding and subtracting is about 7% slower than a straight typecast, and using Math.Floor is 123% slower.  Typecasting the position to an int seems to be the most efficient way to round the position of the sprite to a whole pixel.

So I added comments to SpriteBase to explain how to eliminate that behavior if it's really worth the overhead, but the default code will still behave the same.

durnurd

  • Lead Lemming
  • Expert
  • Fanatic
  • *****
  • Posts: 1234
  • Games completed so far: 0
    • MSN Messenger - durnurd@hotmail.com
    • View Profile
    • Find My Ed
Re: Edge-of-the-map bug, Exit bug
« Reply #2 on: 2007-02-14, 12:38:57 PM »
Could there be a flag or option of some sort so that the first column and row of the map are automatically marked solid and the display starts at the second column and row instead?  I know this seems like a hack solution at first, but since one proposed solution is to have a solid border around the edge of the map, perhaps a built-in method, just like a boolean property in a layer's properties dialog would be helpful.

Or perhaps an infinitely better solution would be to just use a 1-based system instead of 0-based.  Again, a bit of reprogramming would certainly be involved here, and it would get confusing for people used to a 0-based system.

I also found an interesting solution from another game engine.  ID software's fake-3D engine they used for Doom used fixed-point values rather than floats, due to the fact that floats get less accurate as they get larger.  So basically, the lower order 16 bits represent the mantissa while the high-order 16 bits represent the integral.  Adding and subtracting fixed values works like normal, but you'd have to program a special function if you wanted to ever multiply or divide them.  The upshot of all this is that to get the integer value, you just have to left-shift by 16 bits, and it always rounds down.

Well, it's an interesting solution anyway.  Probably not worth all the trouble though.
Edward Dassmesser

bluemonkmn

  • SGDK Author
  • Administrator
  • Fanatic
  • *****
  • Posts: 2761
    • ICQ Messenger - 2678251
    • MSN Messenger - BlueMonkMN@gmail.com
    • View Profile
    • http://sgdk2.sf.net/
    • Email
Re: Edge-of-the-map bug, Exit bug
« Reply #3 on: 2007-02-14, 07:01:10 PM »
Interesting.  So the mantissa would add up to 65535 (representing 65535/65536ths) and then cycle to 0 and the high order word would increment by 1?  In effect, it's one number which you conceptually divide by 65536 to get the actual value.  Hmmm.  I don't think I'll worry about this issue for the first release except to provide that commented bit of code in SpriteBase.  All these other solutions seem like a lot of hacking for little benefit.  But using these special fixed point values to represent sprite positions and velocities would make sense for multiple reasons -- I might have to play around with that after the initial release.  Multiplication and division probably wouldn't be that complicated.  I suspect that to multiply, I would just perform the operation in a 64-bit integer and then right-shift the result by 16 bits, and to divide I would just left-shift the dividend by 16 bits (in a 64-bit integer) before dividing.  Or if one of my multiplicands or the divisor is a plain integer instead of a fixed-point value (which it usually is) I don't need to do anything special.  Seems really easy.  I should try making a "fixed" class with overloaded operators to try this out... but later... another release perhaps.  I wonder if it could all be accomplished in user-editable code without having to make any modifications to the code generator.

durnurd

  • Lead Lemming
  • Expert
  • Fanatic
  • *****
  • Posts: 1234
  • Games completed so far: 0
    • MSN Messenger - durnurd@hotmail.com
    • View Profile
    • Find My Ed
Re: Edge-of-the-map bug, Exit bug
« Reply #4 on: 2008-08-24, 01:37:19 PM »
Wow, old post.  But this came up on the project I am working on at work.  I'm porting a game from J2ME, which doesn't have support for floating decimal values, so the implementation framework contained a Fixed class, much as you described.  The only difference is that it was a static class.  It just had functions to convert to and from ints, but the data was always just stored as an int.  They were just prefixed with "fix" as part of the variable name.  Also, it didn't have overloaded operators, because Java doesn't support overloaded operators.  I'm not sure if it would be faster to use a static class in the cases its needed and just store the data as an int otherwise?
Edward Dassmesser