- Home /
Unity 8-Directional Locked Movement (Super Mario 3D World Style)
Hey everyone. I've been struggling for the past few days on something I didn't think would be too difficult but has turned out to be anyway. I know exactly what I want to make and I have a lot of experience in Unity, but I cannot figure out the solution to this problem at all. Here are the details:
I am trying to create a top-down character controller where the player can only move in 8 directions using WASD in the input manager to control.
The player always faces the direction they are going, like Diablo or Super Mario.
However, I still want to lerp, or blend, between the 8 different directions to make everything look smooth.
The main problem I keep having is if I slightly tap a key to move diagonally, the player doesn't complete the rotation towards the diagonal angle and stops between 2 of the 8 directions. Instead of being at a 90 or 45 degrees angle, if you slightly touch the key, the player doesn't finish rotating into that new position and will get stuck in obscure angles like 72 or whatever.
I guess what I am trying to say is I need some sort of way to move the player smoothly in 8 directions, but still is certain to end up on a solid 45, 90, 135, etc. angle. I have tried everything I can think of but I still have no idea how to go about doing this.
TL;DR: Here is a video that shows the game Super Mario 3D World's movement, which is EXACTLY what I'm looking for. Notice how Mario always is locked to facing 8 directions but also smoothly rotates between those directions. Thanks a lot for reading this! I hope someone knows a way to do this.
Answer by colinmbo · May 08, 2016 at 01:02 AM
Vector3 input = new Vector3 (horizontal, 0f, vertical);
Vector3 inputRaw = new Vector3 (horizontalRaw, 0f, verticalRaw);
if (input.sqrMagnitude > 1f)
input.Normalize ();
if (inputRaw.sqrMagnitude > 1f)
inputRaw.Normalize ();
if (inputRaw != Vector3.zero)
targetRotation = Quaternion.LookRotation (input).eulerAngles;
rigidbod.rotation = Quaternion.Slerp (transform.rotation, Quaternion.Euler (targetRotation.x, Mathf.Round (targetRotation.y / 45) * 45, targetRotation.z), Time.deltaTime * rotationSpeed);
//Movement Code Goes Here...
I had been working on this and it works really well! No problems with it at all whatsoever. It works identical to how it does in Super Mario 3D World. I hope this helps out anyone else trying to achieve this!
When I paste this the editor tells me that some words (like vertical and horizontalRaw does not exist in the context, how can I do to avoid this? I'm very new to Unity and my c# skills are basically 0, sorry if my question is stupid.
Those are variables I created myself, so it wouldn't work on your end. Basically, Unity has a built-in "Input $$anonymous$$anager" that takes key presses and lets you access them easily. You can edit the keys from Edit>Project Settings>Input $$anonymous$$anager. For example, I could assign the W and S keys on your keyboard to a variable called "Vertical" and make them interact with each other so that "Vertical" equals 1 when W is pressed and -1 when S is pressed. This makes it very easy to program movement because you can pretty much just say: make the character's Y-axis speed equal to "Vertical". This way the character will move up 1 when you press W and down 1 when you press S, because his Y-position is adding 1 or subtracting 1 every frame. After these variables are assigned in the Input $$anonymous$$anager, you can refer to these variables from your code. This is where you program the movement or whatever you are doing (In this case, rotation). The Input $$anonymous$$anager is just for receiving inputs. So in your code, you can get these values from the Input $$anonymous$$anager by typing: Input.GetAxis("name of the variable here") This is if the value you are trying to grab is an axis, which in this case it is. You don't have to go in and edit anything though if you do not want to-- Unity already has keys in the Input $$anonymous$$anager by default! And my code uses the default axis names so you do not have to change those.
So, that was just exposition because it is really important that you understand why something works and you aren't just blindly copying code. So make sure you read and understand everything above because it makes everything make a lot more sense. But anyways, here's what you have to do:
Replace where it says horizontal with Input.GetAxis("Horizontal")
Replace where it says vertical with Input.GetAxis("Vertical")
Replace where it says horizontalRaw with Input.GetAxisRaw("Horizontal")
Replace where it says verticalRaw with Input.GetAxisRaw("Vertical")
This pretty much gets the inputs from the Input $$anonymous$$anager and puts it in your code in that spot-- meaning if in the Input $$anonymous$$anager, "Horizontal" equals 1, that whole "Input.GetAxis..." equals 1. It is the code interpretation of that Input $$anonymous$$anager axis. You can use this if you want to program movement in your game as well.
Do that word for word and see if that works. I don't use Unity much anymore, but the problem is very basic and that solution should work. I wrote all that stuff at the beginning to make sure that you understood exactly what you were doing. I didn't want to just give you the answer without context, that way you would never learn. Hope this helps man, and remember it's okay that you didn't know what to do, I was in your shoes once too when I started using Unity!
Thanks a lot for the fast reply, in the meantime I solved my problem, searching in the Unity documentation I found some ways to manage the movement with the Blend Tree in the Animator window, so I added an x and z parameter and I wrote in the script if (input_x == -1) //sx transform.eulerAngles = new Vector3 (0, 270, 0);
for the rotation and float input_x = Input.GetAxisRaw ("Horizontal"); float input_z = Input.GetAxisRaw ("Vertical");
for the movement.
Now my character moves 8 ways and also rotate in the right direction thanks to eulerAngles! Usually I don't copy-paste but the guy who write scripts for me wasn't available atm so I was searching for a quick way to go on with my project, but thanks a lot for the kind words, have a nice day!
Hey, guys! I could use some help! @colinmbo @Bunny83 @$$anonymous$$aitocs
I've put each code to test, but the rotation doesn't work properly. When i type left, my character faces left, like in the Super $$anonymous$$ario video posted here, but while facing that new direction, my character moves towards the camera (that is, sideways). Same happens when pressing right.
Also, when pressing down arrow, he turns until he's looking to the camera and moves to the direction his back is facing.
Same diagonals problem too!
On the console, it says: "Look rotation viewing vector is zero UnityEngine.Quaternion:LookRotation(Vector3)"
I hope what i just wrote seems clear to you! Thanks, in advance, for the help given!
Answer by Bunny83 · May 07, 2016 at 03:41 AM
Well, it would be interesting how you actually implemented the rotation. If you create a Vector3 out of the two key axis the vector you just have to clamp it to one of those 8 directions.
Vector3 targetDir;
void Update()
{
Vector3 inputDir = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
Vector3 v = inputDir.normalized;
v.x = Mathf.Round(v.x);
v.z = Mathf.Round(v.z);
if (v.sqrMagnitude > 0.1f)
targetDir = v.normalized;
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(targetDir), Time.time*5f);
// your movement code
}
The trick here is we first normalize the direction so it's length is 1.0. That means the components of "v" are in the range [-1, 1]
Simply rounding each component seperately will ensure their values are either -1, 0 or 1. This represents exactly the 8 direction thing you want. If the input vector is too small (i.e. the user doesn't press any keys) we have to prevent that the "targetDir" is set to Vector3.zero, that's why there's the magnitude check
So at any time targetDir represents a vector that always points in one of those 8 directions. Independent from that we do the rotation smoothing each frame in Update.
There were a few problems with the code that I went ahead and fixed, but the one problem that is still prevalent is when trying to move the player diagonally. When pressing two keys at the same time to move diagonally, the player moves correctly. However, when you release those two keys, unless the player releases those keys on the same exact frame (which isn't likely), the player turns to the direction of the last pressed key. I was having this same problem last time I attempted this as well. I need to have some sort of buffer time to detect if two buttons are released at the same time. Not sure how to go about doing that. Any suggestions? I'm trying to make it look as much like Super $$anonymous$$ario 3D World as I can, which isn't really turning out so well. Thanks for your help!
Answer by dKd · Oct 16, 2016 at 04:02 PM
Hello @Colinmbo,
I have the exact same problem : "However, when you release those two keys, unless the player releases those keys on the same exact frame (which isn't likely), the player turns to the direction of the last pressed key".
Have you found a solution for this? I've looked around the forums and anwsers for over 3 hours now. Nothing so far. Doing this in 2D actually.
Thanks in advance.
I had the same issue too when I was working on this. I've kind of scrapped my own game concept that used this and I haven't used Unity in months, but I still know the solution (sorry for the somewhat late reply by the way). The idea is you want to round the direction of the inputs to the nearest direction (of the 8 directions) and only rotate the player to the new direction once the rounded input equals that direction. Seems kinda confusing, but heres an example: If your input is going from pressing right to pressing right&up (at the same time-- diagonal), there is a transition between those two inputs, so that the value given actually transitions from right to the right&up diagonal. So what you want to do is program it so that the player's rotation is actually the rounded version of that number, so that the player only rotates when the input is mostly facing that direction. Now, I don't have the code anymore, and that sounds complicated, but please read it over and over and try to visualize what I'm saying and you can probably figure out the logic behind it and code it yourself. If you are still confused just tell me and I'll try to come up with a different example for how it all works. The main issue is that the player rotates to the last key press, meaning diagonal movement causes issues unless releasing at the exact same frame. So you will probably want to round those numbers in a way so that you only turn to the new direction when your input is mostly in that direction (So if you tap it there would be no effect). Hope this helps!