How to program "Little Nightmares"-esque player movement?

I’m wanting to know how I could program player movement that functions similarly to the player movement in the game series “Little Nightmares”.

For anyone who doesn’t know, Little Nightmares is a 3D game with a camera in the 3rd person. For the most part, the camera is in a fixed position that is independent from the player. The camera moves occasionally, but that’s besides the point.

The problem I’m running into right now is when the character the player controls moves to the far right or far left side of the camera, the character appears to be moving in a diagonal direction when moving on the z axis (towards/away from the camera) because of the way camera’s perspective makes those directions “warp” on the outer edges of its view.

Ideally, I’d like for the character to move in the direction that “up” and “down” are perceived to be rather than just moving on the z axis; when I press whatever button I’m using for down and up (the W and S keys in this example) I’d like for the character to move in the directions that look like they should be up and down instead of moving on the z axis at all times. To clarify, I’m using “up” to refer to the direction where positive z axis coordinates would usually lie, and “down” is the direction where negative z axis coordinates would usually lie.

I watched a video going over some code for “camera-relative movement” but this isn’t the solution I’m looking for; the camera is in a fixed position and doesn’t turn (yet) so all that did was make the character move along the camera’s z axis, but the camera’s z axis is the same as the actual z axis.

The following is the current code for the character’s movement if that helps; “Speed” is just a variable I’m using so that I can fine-tune the character’s speed if I want to, and “HorizontalInput” and “VerticalInput” are self-explanatory. Also note that this has nothing to do with the “camera-relative movement” video I had tried the code for:

private void Movement()
{
    HorizontalInput = Input.GetAxis("Horizontal");
    VerticalInput = Input.GetAxis("Vertical");

    transform.Translate(Vector3.forward * VerticalInput * Time.deltaTime * Speed);
    transform.Translate(Vector3.right * HorizontalInput * Time.deltaTime * Speed);
}

I’m very new to Unity and coding as a whole so I’d greatly appreciate any suggestions for how to accomplish something like what I’m asking for. Also if there’s a better term for this than “Little Nightmares-esque” that would also be nice to know.

Thank you!

1 Like

Sounds like you want to convert a vector from screen space to world space. There’s no builtin method for that. If you’re comfortable with linear algebras, you can derive your own from the camera matrices. If not, you can use Unity - Scripting API: Camera.ScreenToWorldPoint on 2 point and subtract them to do the same.

Something like this (untested):

var playerPosScreen = cam.WolrdToScreenPoint(playerPosWorld);
var playerPosNextScreen  = playerPosScreen  + inputVec;
var playerPosNextWorld = cam.ScreenToWorldPoint(playerPosNextScreen  );

transform.Translate(Vector3.Normalize(playerPosNextWorld - playerPosWorld) * Time.deltaTime * Speed );

Thanks so much for replying!

I tried the code you gave; it’s really close to what I’m looking for (I think). It looks like now when the character moves up and down it always moves toward the camera, but not quite the directions that appear to be up and down. When the character is on the far left or far right side of the camera, they move slightly inwards when moving down, and slightly outward when moving up. I’m assuming these new directions are because the new vector is pointed towards the camera at all times. I don’t really understand the code but that’s what it looks like is happening. This is definitely and improvement, but still not what I’m looking for.

I might be doing something wrong with the code; like I said, I don’t really understand it.

Here’s the code I ended up using:

var PlayerPosWorld = GameObject.FindGameObjectWithTag("Player").transform.position;
var PlayerPosScreen = Camera.main.WorldToScreenPoint(PlayerPosWorld);
var PlayerPosNextScreen = PlayerPosScreen + Vector3.forward;
var PlayerPosNextWorld = Camera.main.ScreenToWorldPoint(PlayerPosNextScreen);
Vector3 CameraZ = PlayerPosNextWorld - PlayerPosWorld;
CameraZ.y = 0; 

transform.Translate(CameraZ * VerticalInput * Time.deltaTime * Speed);

I assumed “playerPosWorld” was supposed to be the player’s world position, but I wasn’t really sure what the variable “inputVec” you gave was supposed to be so I guessed. Labeling the new vector as “CameraZ” was just a personal preference thing but it doesn’t affect the code in any way.

Let me know if you figure out anything new or if I just did the code wrong or something.
Thanks again!

Seems like you’re looking for transform.TransformDirection and transform.InverseTransformDirection These methods take a direction from local space to world space and vice versa. So you basically take the input direction from the keyboard, turn it into a screenSpace direction, then turn that screenSpace direction into a woldSpace movement for the character. This way, holding down the right arrow key will make the player walk forward if they’re facing to the right in relation to the camera. I haven’t tested this code, but something like it should work.

using UnityEngine;

public class PlayerController : MonoBehaviour
{
    public float Speed = 3;

    void Update()
    {
        var horizontal = Input.GetAxis("Horizontal");
        var vertical = Input.GetAxis("Vertical");

        var inputRaw = new Vector3 (horizontal, 0, vertical);
        var screenSpaceInput = Camera.main.transform.TransformDirection(inputRaw);
        var characterSpaceInput = transform.InverseTransformDirection(screenSpaceInput);

        transform.Translate(characterSpaceInput * Speed * Time.deltaTime);
    }
}

Inputvec is a 2d vector (xy) representing the direction you want to move in screen space. Keep in mind screen vectors are 2d xy vectors, so adding the forward vector which is in z will not work.

I’m also assuming your movement plane is perpendicular to the screen. If not, instead of screentoworldpoint, you need screenpointtoray, then cast that ray against your movement plane.

The keywords you want to search to learn more is: project a screen vector to world.

transform.TransformDirection and co do not take the projection matrix into account and will not work.

I see, thank you for the explanation. I’ll look into that