Tracking the float remainder of an int-locked object

I would like to have an elegant solution to an admittedly trivial problem, if possible. I currently have a solution, but it uses If/Else and I feel, in this case, it may be unnecessary. It's not so much that I'm concerned about the cycles, it's more about the principal of writing good code, and I'm unable to think of how to write this a better way.

-

I have a retro game where objects' positions are ints, rather than floats, so they can be rendered at exact pixels (rather than float locations), and, so that two objects narrowly scraping by each other do not collide. (i.e. if I were to use floats, and if the bottom of one object is at 6.98, and the top of another object is at 7.03, there would be a collision, which I don't want.) The distance an object travels per frame is still: m_speed * deltaTime.

I have considered storing the x,y as floats, and then simply casting back to an int before using them in rendering/collision, like:

1
2
float m_x;
int GetX() { return (int)(m_x + 0.5f); }


(No negative x,y vals are possible in the game, so adding the 0.5f to round it is ok. i.e. 2.66 should round to 3, and adding +0.5f before casting to int accomplishes that.) This would work, but I feel like that's a little sloppy as well-- casting/rounding every single time I need any objects position.

Also, consider an object moving on a track (predefined behavior). At a defined speed, it shall move down 64 pixels until it's in line with a precise tile row, and then start moving right 64 pixels at the same speed. I don't want it to move down "64.353 pixels" and, as it hits the proper row and begins to move right, it still has that extra ".353" on the Y. Instead, ideally, that remainder of ".353" would've carried over to the next frame, as part of the (now) right-heading movement. The cast method wouldn't behave this way.

And what I mean by carry over is that the remainder should build up, which can ultimately determine whether or not I add +1 or subtract -1 to the overall distance.

For example:
If a float-based object in some game is moving at a rate of 3.1pix per frame, then, on the 10th frame, it will have moved 31 total pixels.

In my game, where the positions are ints, it needs to behave as such:
1
2
3
Movement amount:  +3.1 +3.1 +3.1 +3.1 +3.1 +3.1 +3.1 +3.1 +3.1 +3.1
Remainder:        +0.1 +0.2 +0.3 +0.4 +0.5 -0.4 -0.3 -0.2 -0.1 -0.0
Int position:     3    6    9    12   16   19   22   25   28   31   


Once the remainder goes >= 0.5, I want to say, Alright, we've stored enough remainder to be able to round up +1, so round up one, and minus -1.0 to the saved remainder. The purpose of this is so I'm not "losing" any accuracy/movement due to rounding up/down. (could happen both ways)

What I have works, but I can't help but think that this is a poor implementation of this functionality. I feel as if there's probably a way to do this without conditionals, but I can't think of how.

Here is an Update example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
int Object::Update(float deltaTime)
{
	// Get movement distance
	float distance	= deltaTime * m_speed;              // Float distance
	int rounded	= (int)(distance + 0.5f);           // Round it up/dn
	float leftover	= -1.0f * (rounded - distance);     // Save remainder
	int finaldist	= rounded;                 // Set to rounded at first

	// Accumulate the remainder
	m_remainder += leftover;
	
	// Should we +1/-1 this frame?
	if(m_remainder <= -0.5f)
	{
		m_remainder += 1.0f;
		--finaldist;
	}
	else if(m_remainder >= 0.5f)
	{
		m_remainder -= 1.0f;
		++finaldist;
	}

	// Iterate movement per pixel
	for(int i = 0; i < finaldist; ++i)
	{
		if(1 == AttemptMove())
			return 1;
	}

	return 0;
}


fyi, AttemptMove tries to move, and checks for collisions, which happens every pixel of movement. i.e. if the object was meant to move 5 pixels that frame, it runs that 5 times.

If there really isn't a more efficient/better way to write this, then, this does work, but I'm not as experienced as some people here so if there is a better way, guidance would be appreciated. Thank you
Last edited on
In terms of individual pixels, I have to question whether this rounding error actually matters. On a modern screen, one pixel is almost unnoticeable.

But let's assume that it is (maybe your math is in units of chunky texels or something). You can just do
 
move_pixels = std::round(fpos += m_speed * deltaTime);

Where fpos is a floating-point position - the "real" position of your image. You can accumulate into that - where the object actually is, but move it on screen only move_pixels pixels.

Edit: You'll need a static_cast <> around the std::round call - it returns a float.
Last edited on
But let's assume that it is (maybe your math is in units of chunky texels or something).

You're right it's something like that. The resolution is zoomed in quite a bit so the pixels are more visible.

move_pixels = std::round(fpos += m_speed * deltaTime);

Hm, forgive me if I'm wrong but isn't that the same thing as not worrying about a remainder at all, adding into m_x, m_y (floats), and rounding them when I need to use them?
1
2
float m_x;
int GetX() { return (int)(m_x + 0.5f); }

(Although std::round undoubtedly supports negative numbers too, I don't really need to because my x,y vals never go negative)

The reason I don't prefer this solution is there would be two separate "remainders". Not actual remainder variables, but since the x and y would be independent, as in- Let's say an object moves down until he's at Y=100. BUT his Y isn't 100, he's at 100.499. Then, he moves right for 3 minutes. He still remembers that .499 from 3 minutes ago on the Y axis, so, the next time he moves down, he's very likely to be pushed an extra pixel down right when he starts moving down, which is odd, since, he's been at Y=100 visibly for so long, if that makes sense. The remainders could be shaved off when an object switches moving left/right, to up/down, but that's not perfect either. I feel that when he's moving down to 100.499, then starts moving right, that .499 should now contribute to the right-heading movement rather than be thrown out, or saved for a future Y-movement.

I'm curious if my "if >= 0.5 else if <= -0.5" (and the contents of the branches) could've been done without if/else, or more efficient etc?
Topic archived. No new replies allowed.