1
\$\begingroup\$

I'm learning Unity2D by trying to make a Mega Man clone. The first thing I'm trying to do is to replicate the original series physics. It differs from game to game, but I'm leaning towards something like Mega Man 3. I found this post (#3 and #8) where the game physics is described in detail and started to "translate" it to Unity. So, for instance, it says that Mega Man moves at 1.3 pixels per frame in MM3. Since I'm working with 60FPS, I multiply it by 60 and get 78 pixels per second, and use it as my character's walking speed, i.e., its horizontal velocity. Is that correct?

My real question, however, is about the following: "Rock jumps at 4.875 pixels per frame with a gravity of 0.25 pixels per frame per frame.". As I understand it, Mega Man's initial jump speed should be 4.875 pixels per frame, i.e., 292.5 pixels per second. Considering that I'm using a Rigidbody2D of mass=1, does it mean that I should use addForce(0, 292.5)? Also, how can I "translate" the 0.25 pixels per frame² gravity? My first thought would be to multiply 0.25 by 60*60 to get pixels per s², but it gives me 900 and the character does not even get off the ground. How do I convert gravity from pixels per frame² into Unity's Rigidbody2D gravity scale parameter?

Edit: this is a learning attempt, I'm not building a MM engine. So, for now, I want to understand this specific issue. I'm not new to MM gamedev and already did this before without a physics engine. I'm new to Unity and want to learn how to do it this way.

\$\endgroup\$
2
\$\begingroup\$

There's going to be some variation in how you approach this, depending on whether you're going for "pixel-perfect retro re-creation" or "playable prototype to learn about making platformers." You don't need perfect precision to make something fun, so there's nothing wrong with taking a simpler route, especially in your early work. As I go through the steps below I'll call out extra things you'll want to think about if you want to push toward pixel-perfection, but you can also safely skip these and still get something that feels good to play.

Step 1: Decide on your unit scale

Mega Man 3 used 16x16 tiles, so you'll probably find it convenient to consider this "one unit" for the purposes of laying out your levels. You could also halve this value if you want both tile corners and tile centers to sit on integer coordinates - this is really a matter of taste.

(I'd recommend against using 1 pixel = 1 unit though - generally you want your unit to be in the ballpark of the size of the player avatar, since a lot of engine tools & systems assume you're working in this kind of scale range)

If you want your game to render pixel-perfect across multiple resolutions, you may want to use two or more unit scales for your graphics, selecting the best fit for the current resolution as described in this article on pixel-perfect rendering in Unity. To keep gameplay consistent, you'll still want to pick one canonical unit scale for calculating your metrics & movement in Step 4.

Step 2: Configure your Pixels Per Unit import settings

This is found in the Inspector when selecting any image you've imported into your project with TextureType: Sprite (2D and UI)

Image of Unity Inspector highlighting Pixels Per Unit parameter in Sprite import settings

Set this to your unit scale value (eg. 16 if using 16-pixel tiles, or the appropriate variant for each asset if you're using multiple sizes as touched on above)

Once you do this for each Sprite asset you're using, each tile will automatically size itself to 1 unit when you add it to your scene (and other shapes/sizes of sprites like characters will size themselves proportionately)

Step 3: Configure your camera size

Using the formula:

Orthographic camera size = (screen height in source pixels) / (pixels per unit * 2)

So, if you want to emulate a standard NES resolution of 240 pixels vertically (including overscan), with 16 pixels per unit, you'd set your camera size to (240)/(16*2)= 7.5

If you want to crop in further to mimic the fact that old CRTs only displayed part of the image, you could use 224/(16*2) = 7

To get a pixel perfect look you need to be careful how this is scaled to the player's screen, which typically has way more pixels than a retro-style game. If you notice rippling artifacts in your sprites it's because your viewport size isn't an integer multiple of your "retro" resolution. Again, the pixel-perfect rendering article linked above has tips on how to crop or pad your display to get this lined up neatly, if you want to go all the way with it.

Step 4: Tune your gameplay metrics & speeds

Now we can use this value to translate our measurements into gameplay units:

Jump speed in pixels per frame    =   4.875   p/f
...times 60 frames per second     = 292.5     p/s
...divided by 16 pixels per unit  =  18.28125 u/s

Acceleration in pixels per frame² =   0.25    p/f²
...times (60 frames per second)²  = 900.0     p/s²
...divided by 16 pixels per unit  =  56.25    u/s²

Step 5: Using Unity Physics

Unity's Box2D physics system is a lot more complex than the logic used in classic platformers, so as others have pointed out above, if you want to truly duplicate older mechanics then the way to do it is to write your own custom physics handling - that's beyond the scope of this answer.

But if purity of reproduction isn't your goal and you just want to get something playable and fun, using Unity's built-in physics may help you get up and running faster, and can be tuned to give a similar feel, so that's what I'll describe here.

You'll find there's a lot of negativity out there about these physics engines, but most of it comes from the fact that they're very easy to use wrong. With a little care we can tame them to get the kind of results we want.

There are three main parts to this:

A) FixedUpdate

If your reference game's physics update at 60 fps, then you'll want to go into Project Settings -> Time and set Fixed Timestep to 1/60 = 0.01666...

This will ensure that things like gravity and collisions are integrated at the expected time resolution. Note that if your rendering framerate is very close to 60, this can cause a beat frequency between the physics rate and the display rate, but for a 2D game you'll typically be running well above 60 fps anyway. There are ways to address this if you find noticeable artifacts.

Get in the habit of putting any movement logic in FixedUpdate() methods. This will ensure they run as often as the physics step, no matter what happens to the rendering framerate, helping you get consistent gameplay across different devices and levels of load.

B) Applying your forces and velocities

Applying the gravity acceleration is straightforward. If everything is using the same gravity, you can go to Project Settings -> Physics 2D and put the value we calculated above into Y, negated. (ie. X: 0 Y: -56.25). Then you can leave the gravityScale on each object at its default value of 1.

Some old games used different gravity values per character/object, or for different phases of the jump. In a case like that you'd probably want to zero-out the gravity globally or by setting rigidbody2D.gravityScale = 0 on the objects that need custom gravity, then apply it yourself at the end of every FixedUpdate with:

rigidbody2D.velocity += Vector2.down * gravity * Time.deltaTime;

To kick your character into the air with a given initial velocity, you'd use something like this:

Vector2 velocity = rigidbody2D.velocity;
// You can also add to the current vertical velocity instead of replacing it, 
// if you want the jump to adapt to running up/downhill or jumping off a moving platform.
velocity.y = jumpVelocity;
ridigbody2D.velocity = velocity;

That should give you a good starting point. Test it out and see how it works.You may find the jump arc in Unity deviates slightly from your target, due to differences in when gravity is applied (eg. between steps or continuously over the course of each step) - if you find that's the case, comment below with the metrics you're seeing and we can work out the appropriate compensating factors.

C) Disable unwanted physics effects

Create a new Physics2D Material from the Assets menu and set its Friction to 0. Use this on any player colliders so contact with the ground doesn't sap a portion of the velocity value you want.

On the player Rigidbody2D, check FreezeRotation Z under Constraints so your character doesn't roll around. Keep Linear Drag at its default of zero for the same reason as the friction - it'll be up to your control script to stop the player when you want them stopped. ;)

\$\endgroup\$
  • \$\begingroup\$ This is exactly what I was looking for (and much beyond that), it seems the pixel-per-unit ratio was the trick I was missing. Thank you very much! Just a minor issue maybe you can help: ForceMode can't be used with RigidBody2D, it asks me to use ForceMode2D. But it doesn't have the VelocityChange mode. How to fix it? Tha falling speed seems already ok, I just can't make it jump yet. \$\endgroup\$ – rcpinto Jul 9 '16 at 3:19
  • \$\begingroup\$ Whoops, I'd forgotten that inconsistency. It's no big deal, we can just modify the velocity directly, I just prefer using AddForce with an appropriate ForceMode when available because it communicates intent clearly. I'll make a quick edit to replace those lines... \$\endgroup\$ – DMGregory Jul 9 '16 at 3:27
  • \$\begingroup\$ DMGregory, this is fuc*ing AWESOME! Thank you very much !! \$\endgroup\$ – Ismoh Aug 9 '16 at 17:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.