SOLVED Need Help Understanding/Approximating NES-era Pixel Movement

yarrow

Member
EDIT: Solved my problem and wrote a tutorial explaining the solution. The TLDR is that I needed to store the fractional amount of my movement speed in a container, rather than simply re-adding the fractional amount to movement speed at the end of the collision check (or at the beginning of the next collision check). Once the container reaches a whole number, the extra pixel gets added to the move speed for that step's collision check, then the extra pixel is subtracted from move speed after movement. It's a little more complicated but the tutorial explains everything.

***

Hi all,

I'm having quite the time trying to wrap my little head around how NES games handled player movement and would be eternally indebted to anyone who could clarify a few things for me!

A bit of background: I'm working on a game with a resolution of 240 x 180, composed of 16-pixel tiles, with a player sprite of 16 x 32 pixels. It's running at 60 fps, with a tile-based collision system that's currently working perfectly. In order to handle subpixel remainders and move/check collisions at whole pixels, I'm storing the fractional portion of speed in a variable and re-adding it after movement has occurred.

I'm currently trying to approximate the physics of SMB for NES, and found an incredibly detailed breakdown online that I converted from hexadecimal into standard decimal (rounding to the thousandths place). But here's my question:

According to this guide, the player's minimum walk speed is .074, their acceleration is .037, and their max walk speed is 1.563. If the player can only move at increments of one pixel, and we're adding acceleration to the minimum walk speed at every step, then the player would not begin to move until the 28th frame (which is indeed what's happening to me currently). But the original game's physics are incredibly smooth, and even tapping the control pad shifts the player a little to the left or right. So is my math wrong? Or are the numbers off? Something else?

I normally like figuring things out on my own but am feeling completely out of my depth here. When I comment out the code that's storing/re-adding the fractional portions of the horizontal movement speed, the motion is incredibly smooth and looks like what I'd expect, but of course the collisions don't work anymore and I'm not sure how pixel integrity would be affected by scaling. Can anyone can help me understand what I'm missing here? Thank you in advance!
 
Last edited:

rytan451

Member
Maybe draw the objects with the fractional parts added in, but leave the x and y coordinates as integers for the collision logic?
 

TheouAegis

Member
You first need to decide if you want true NES 8bit mechanics, or if you want floating point mechanics with an NES gloss. 8bit movement has some ugly inherent flaws which usually don't rear their heads until you start delving deep into complex movement patterns. (rf. http://gmvania.blogspot.com/2019/05/simple-ellipse-based-on-medusa-head.html)

If you want to keep floating point's flexibility, floor coordinates when drawing and when performing collision checks.

Always move before collision checks, not after like people usually do in Game Maker nowadays.

Negative coordinates are not allowed. Biteise AND coordinates by 255, 65535, or 1677215 at all times; or modulo 256, 65536, or 1677216 if you want to keep floating point benefits.

If you want strict NES motion, you will need many variables: x_fraction, y_fraction, hsp_fraction, hsp_integer, vsp_fraction, vsp_integer. If you want variable acceleration (e.g., friction, gravity), those would need to be applied to hsp_fraction and vsp_fraction. If hsp_fraction is greater than 256, step hsp_integer in the proper direction. AND or modulo both hsp_fraction and hsp_integed. Do the same with vsp_fraction and vsp_integer. Add hsp_fraction to x_fraction, then add hsp_integer to x. Then if x_fraction is greater than 256, step x once more in the proper direction. Then AND or modulo both x_fraction and x. Do the same for y_fraction and y. It's more complex than that, but that's the gist. But if you want the full mechanics, 8bit falling is:
Code:
var c = 0;
vsp_fraction += grv + c;
c = vsp_fraction > 255;
vsp_fraction &= 255;
vsp_integer += c;
vsp_integer &= 255;
c = 0;
y_fraction += vsp_fraction + c;
c = y_fraction > 255;
y_fraction &= 255;
y += vsp_integer + c;
y &= 255; //or 65535
Subtracting is nearly identical, except c gets set to 1, not 0. Subtractive movement is a thing...
 
Last edited:

yarrow

Member
@rytan451 Thanks for the suggestion! Will play around with this and see if it solves my problem.

@TheouAegis Good question. I'm definitely not trying to achieve an authentic translation of NES-era code, and I'm fairly new to coding so bitwise operations are well above my paygrade. I'd ideally like a movement system that feels true to SMB movement, which is why I'm using the speed amounts from the guide linked above. I'm already flooring coordinates before drawing/collision checks but will try moving before collision and see if that helps.

It's my understanding that sprite position on the NES could only be represented at pixel increments, and subpixel amounts were stored, floored and re-added (so some frames the player would move one pixel, other frames two). Am I wrong about this? Because if that's the case, I don't see how you could have a move speed less than 1 and still be able to shift the player incrementally by simply tapping the control pad (a la SMB). But I fear I'm missing something...

Anyway, thank you for your thoughts.
 

TheouAegis

Member
NES games used 16bit values for speeds and coordinates. One byte was the fractional, one byte was the integer. NES games had subpixel movement, but they only rendered at whole numbers and only processed collisions at whole numbers.
 

yarrow

Member
Thanks @TheouAegis. I'm starting to think the issue might be shoddy hex-to-decimal conversion of the original values (since all of my other systems are pretty much working as intended). Will check my maths and see if that fixes things. Appreciate your time and input.
 
Top