Mega Man Style Camera Movement - C#

How can I get the camera to follow the player like in the NES Mega Man games? My current camera only follows the player left to right and not up or down, but I want it to “pause” like it does in the Mega Man games when you go to a different area. Here is my current Camera Controller script in C#

 using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraController : MonoBehaviour {
     public GameObject target;
     public float followAhead;
     private Vector3 targetPosition;
     public float smoothing;
     public bool followTarget;
     // Use this for initialization
     void Start () {
         followTarget = true;
     }
   
     // Update is called once per frame
     void Update () {
         if(followTarget)
         {
             targetPosition = new Vector3(target.transform.position.x, transform.position.y, transform.position.z);
             if (target.transform.localScale.x > 0f)
             {
                 targetPosition = new Vector3(targetPosition.x + followAhead, targetPosition.y, targetPosition.z);
             }
             else
             {
                 targetPosition = new Vector3(targetPosition.x - followAhead, targetPosition.y, targetPosition.z);
             }
             //transform.position = targetPosition;
             transform.position = Vector3.Lerp(transform.position, targetPosition, smoothing * Time.deltaTime);
         }     
     }
}

OK, so I should point out that on the original NES you could only really get one directional scrolling. Either horizontal or vertical. Basically you’re allowed enough space on the PPU (picture processing unit) for 2 screens worth of data, organized horizontally or vertically, and you can choose what portion of those 2 screens worth of data is visible at any given time by setting a scroll position.

(fun fact, this is actually how the ‘secret path’ puzzles in SMB1 work in the dungeons. It would stop writing the forward moving register to the PPU, so it’d effectively just loop around the same 2 screens)

There is technically a trick (which games like SMB3 use to trick multi-directional scrolling) by doing a weird mirroring trick, that fakes horizontal scrolling while using actual vertical scrolling. This is why there is weird glitched graphics at the edge of SMB3 scenes (usually covered by overscan on old tvs).

With that said…

Mega Man does normal horizontal scrolling, but fakes vertical scrolling. This is why the game sort of freezes when you change vertically. The game basically:

pauses
dumps the offscreen data for horizontal scroll
flips to vertical scroll
fills it with the new screens graphics
performs a vertical scroll
dumps the old screens graphics
flips to horizontal scroll
fills it with new horizontal scroll information
unpauses

You’ll notice that vertical scrolls always seem to happen at ends of corridors. It’s because they’re waiting for the horizontal scroll to reach either end of the PPU scroll region… that way they don’t shear the scene data in half.

This is actually one of the reasons that Mega Man, and Mega Man X are fundamentally different games. Mega Man does not full motion scrolling, X does. I really like ego-raptors discussion on Mega Man X, why it’s such a good game, and how it is a fundamentally different game from Mega Man:

Anyways… this has nothing to do with how you’d fake this behaviour. Since we don’t have the small memory constraints of the NES PPU.

Just wanted to clarify why the Mega Man scrolling behaviour is the way it is.

Also… I wanted to break down the behaviour… since that’s what we’re going to emulate.

Now what triggers the NES to perform the scroll is that Mega Man reaches a specific point on the screen vertically that triggers the screen to perform a scroll. Basically if it reaches the top of the screen, or the bottom.

This is a simple value in NES terms since it has a fixed resolution and all positions are really just pixel positions relative to that resolution. If Mega Man is at 0, he’s at the bottom of the screen. If he’s at 240, he’s at the top of the screen.

Unfortunately Unity doesn’t work in this manner. Your position relative to the camera meaning top or bottom of the screen depends on the several variables including resolution/aspect ratio, projection matrix, variables that go to define that projection matrix (like fieldOfView for perspective projection), and even distance from the camera.

Determining that top or bottom really hinges on these things. Orthographic project of course can make this easier since it creates a 2d like projection that reduces the number of variables impacting this.

And of course… you could always just use WorldToScreenPoint.

OR, you could say screw all that and use special trigger boxes that tell it to scroll up or down. You’ll notice that in Mega Man it always scroll in very controlled spaces. Usually you’re like on a ladder or something. Sure you can fall and scroll downward… but you can still fake that with larger trigger boxes.

Benefit to this is that if the player happens to leap from a high up platform and graze the top of the screen… you don’t scroll because they happened to pass the pixel that tells it to scroll. Instead you rely on specially placed trigger boxes to signal that.

So presuming you want to truly mimic the Mega Man style horizontal scroll with vertical transitions.

Basically you define your camera horizontal scroll rails.

rail 0 = y of 0
rail 1 = y of 100
rail 2 = y of 200
etc

When the event occurs to transition (enter trigger box, or in Update you notice the player cross the vertical threshold).

You determine to which rail that transition should move to.

You pause the game (if you want to fake that).

Tween the position of the camera from its current y to the new y.

Done.

2 Likes

I appreciate you taking the time to respond, but I don’t understand what you are saying. I don’t want to copy exact like it was done then, just for the camera to move like it does in Mega Man.

If anyone can post a script on this and explain the actions, it would be appreciated.

Well, I liked your post, lordofduct :smile:

OK to address the question of “pausing” – I assume you’re moving everything relative to Time.deltaTime.

Of course, the camera also depends on Time.deltaTime, so it’s not like you can change the time scale to 0, which would also pause the camera.

So, roll your own version of Time.deltaTime (just stick a static member in some GameManager object somewhere), then you’re free to pause everything by setting “myDeltaTime” to 0, while the camera is still able to move because it’s still using Time.deltaTime.

Does that make sense?

1 Like

You don’t need to roll your own deltaTime.

You’d just update the camera using ‘unscaledDeltaTime’ instead:

Hm, I keep forgetting about unscaledDeltaTime.

I can’t help feeling that there may be unwanted side effects of setting the time scale to 0, though. If I recall correctly, that’d stop Invokes from happening, and I believe WaitForSeconds would be affected as well (at least I’m pretty sure that’s why WaitForSecondsRealTime exists).

I’m not sure I’d want to mess around with the time scale due to these unwanted side effects. At least keeping your own deltaTime mitigates that risk.

Yep, Invokes and WaitForSeconds are effected.

And I agree about keeping your own timescale, I do a similar thing:

And I wrote custom WaitForDuration yield instruction to support it:

The ITimeSupplier was written to easily control which time you were using for anims/tweens. But also the big reason I created it was time scaling which I describe in the readme at the top of the class. You could do:

SPTime.Normal.SetScale("slowmo_powerup", 0.5f);
yield return WaitForDuration.Seconds(60f, SPTime.Real);
SPTime.Normal.RemoveScale("slowmo_powerup");

Elsewhere:

SPTime.Normal.SetScale("swordswing_slowmo", 0.5f);
yield return WaitForDuration.Seconds(1f, SPTime.Real);
SPTime.Normal.RemoveScale("swordswing_slowmo");

No matter when each occurred, if they both happened to occur at an overlapping moment. Normal deltaTime would be occurring at 0.25 rate.

BUT, this might be a bit over OP’s head at the moment.

And if they want the game to pause during that moment. Most everything should probably pause anyways. And you’d use unscaledDeltaTime for only those things that shouldn’t be paused… i.e. the camera moving vertically.

And of course it means you don’t need to write all this extra code… AND having your own custom time supplier breaks WaitForSeconds anyways, since you’ll now need to go and write your own WaitFor… to support your time supplier for those things you want to scale. Hence my ‘WaitForDuration’ (though technically my method allows WaitForSeconds to still work for ‘Normal’ time, and WaitForDuration is just used to support custom time suppliers).