Found the issue. This isn't a problem in atan2, this is a problem in how the GPU finds which MIP level to use in the texture. If you connect the MIP input of the Texture 2D node, the artifact goes away:). SUBSCRIBE for More Unity Tutorials! Discord this unity tutor.
-->- If (object A is at (5,0) and object B is at (10,0) then (assuming that 0 degrees is (0,1)) then object B is at 90 degrees to object A. They way I was taught to do this was to work out the vector (C) between them A-B=C and then do atan2 (c.y,c.x). The way I was taught vector subtraction is as follows.
- Returns the arctangent of two values (x,y). The arctangent of (y,x). The signs of the x and y parameters are used to determine the quadrant of the return values within the range of -π to π. The atan2 HLSL intrinsic function is well-defined for every point other than the origin, even if y equals 0 and x does not equal 0. Type Description.
Returns the arctangent of two values (x,y).
ret atan2(y, x) |
---|
Parameters
Item | Description |
---|---|
y | [in] The y value. |
x | [in] The x value. |
Return Value
The arctangent of (y,x).
Remarks
The signs of the x and y parameters are used to determine the quadrant of the return values within the range of -π to π. The atan2 HLSL intrinsic function is well-defined for every point other than the origin, even if y equals 0 and x does not equal 0.
Type Description
Name | Template Type | Component Type | Size |
---|---|---|---|
y | same as input x | float | same dimension(s) as input x |
x | scalar, vector, or matrix | float | any |
ret | same as input x | float | same dimension(s) as input x |
Minimum Shader Model
This function is supported in the following shader models.
Shader Model | Supported |
---|---|
Shader Model 2 (DirectX HLSL) and higher shader models | yes |
Shader Model 1 (DirectX HLSL) | vs_1_1 |
Requirements
Requirement | Value |
---|---|
Header |
|
See also
In this post we'll be creating a third person character controller complete with animations, a collision-detecting freelook camera, and input using Unity's new input system.
Note From Tildey: This tutorial was created using Unity 2020.2.1f1. Since it uses the most up to date practices and packages, it should work on any foreseeable Unity update, but if you encounter any issues or unexpected behavior following these steps—or even if you'd just like to drop in and say hello—please don't hesitate to get in touch by clicking the 'Contact' button at the top of this site.
Let's get started.
Initial Setup
Installing Cinemachine & Input System Packages
First we'll install our necessary packages with the package manager.
Click Window -> Package Manager then dropdown and select Unity Registry. From here we'll need to install Cinemachine and Input System. Installing the new input system will prompt a warning from Unity. Click yes to continue and allow the editor to restart.
I'll also be installing the Synty Nature Pack. This is a paid asset I'm only using to pretty things up a bit, so it isn't required of course, but if you do like the look of this environment click here to view it on the Unity asset store.
Importing A Mixamo Character Into Unity
Next, we'll need to import a model for our character. I'll be choosing a character from mixamo.com, which offers free characters and animations, but any character model that is rigged as a humanoid will work.
If you're getting a character from Mixamo, find a model you like, select it, and click download. Then check that FBX for Unity and T-Pose are selected before clicking download once more.
Now that the model is on your computer, create a folder in Assets called 'Models', then drag the model from the downloads folder into your Unity project, and drop it in the Assets/Models folder.
You'll need an object to act as the floor that your character will move on. I'll be using this scene from the asset I mentioned earlier, but you can also just drag in a cube or plane, as long as it has a collider.
Drag the character model into the scene.
You may be greeted with some strange visual bugs. This odd behavior happens with Mixamo imports and has an easy fix. Click on the model in the assets folder, then navigate to the Materials tab in the inspector. Click 'Extract Textures...', create a Textures folder, then extract into the newly created folder. If there's a Unity pop-up, just click 'Fix Now'.
Repeat this process, except this time click 'Extract Materials' and create a Materials folder.
Select the model in the scene and rename it to 'Player'. We're now all setup and ready to get started with the camera for our character controller!
Cinemachine Camera Setup
Creating A Cinemachine FreeLook Camera
Select 'Cinemachine' at the top of your screen, then 'Create FreeLook Camera'.
This will do two things:
- Create an object named 'CM FreeLook1'. This is the Cinemachine camera that will be used for our character.
- Add a 'CinemachineBrain' component to our Main Camera. This is how Cinemachine interfaces between the Main Camera and its own cameras.
Unity Calculate Angle
The first properties we'll be changing on the freelook camera (CM FreeLook1) are 'Follow' and 'Look At'. These are self-explanatory, serving as the target transform that the camera will be following and pointing at.
We could set the player itself as the target, but the base of the player is at their feet, which puts us in a position to do some awkward repositioning. A simpler solution is to create an object as a child of the player, which has a sole purpose of serving as the target for the freelook camera.
Create an empty game object called 'Camera Target' as a child of the player object, then position it in the scene view until it is at shoulder height and horizontally centered on the player model. Once it's correctly positioned, drag it into the 'Follow' and 'Look At' fields of the freelook camera.
Using Unity's New Input System With A Cinemachine FreeLook Camera
If you've hit play out of curiosity, you'll notice our FreeLook camera is throwing a couple of errors related to the new input system.
If we look at the inspector panel with the FreeLook camera selected, we'll see the reason for these errors are the references to the Mouse Y and Mouse X input axes. Let's create a reference to these mouse axes using the new input system to fix these errors.
We'll start by creating an Input Master file.
- Create a 'Scripts' folder in assets.
- Create an 'Input' folder in Scripts.
- While in your new Input folder, right click -> Create -> Input Actions (at the very bottom)
- Name this file 'Input Master'
Double click this new file to open the Input Actions panel. I find it easier to work with if I drag the panel next to game.
You'll also notice a 'Generate C# Class' checkbox in your inspector panel. Click this, then click apply. This creates the C# file we'll need to communicate with Input Master in the future.
Now, back in the Input Actions panel, we can create the references to the Mouse X and Mouse Y axes.
- Create a new action map called 'Player'.
- Rename the new action it created to 'Mouse Look'.
- Change the action type from Button to 'Pass Through'.
- Change the control type from Button to 'Vector 2'.
We need a Vector 2 because we'll be grabbing two coordinates from the mouse input, the x and y coordinate.
5. Select the binding underneath the Mouse Look action called '<No Binding>' and rename it to 'Mouse Look'.
6. Change the binding's path to 'Delta [Mouse]'.
7. Click 'Save Asset' at the top of the Input Actions panel.
Now that we have the input action setup, we need to wire these actions to the FreeLook camera itself.
Select the FreeLook camera in the hierarchy, then in the inspector panel, click 'Add Component', and add a Cinemachine Input Provider.
Click the icon on the far right of the 'XY Axis' property, then select our newly created 'Player/Mouse Look' action. Do the same for 'Z Axis'.
Lastly, on the Cinemachine FreeLook Camera, select 'Invert' in the Y Axis, and deselect it in the X Axis. You can change these in the future if you'd prefer, but I find this to be the most comfortable setting.
Positioning The Cinemachine FreeLook Camera
Now if you hit play, you'll see you can orbit your player by moving your mouse. If you're like me though, you'll also find it doesn't feel perfect quite yet.
This may be a sensitivity issue—you can easily change sensitivity by altering the 'Speed' value in the Y Axis and X Axis sections of the inspector panel with the FreeLook camera selected. I'll be keeping mine at the default values, but if this feels too fast feel free to adjust these values.
Sensitivity won't completely alleviate our issues though, we'll also have to take a look at the FreeLook camera orbits.
Go to scene view and select the FreeLook camera in the hierarchy. See the three red rings around your player? Those are orbits. They are what your camera moves on as you rotate around your player. As you move your mouse, the Cinemachine FreeLook camera automatically chooses the optimal orbit and moves the camera around it.
To adjust the height and radius of the orbits just go to the orbits section of the inspector panel. Play around with these values using both the scene view and testing in game view, until your camera feels comfortable.
Feel free to adjust these values until you're happy with them, but for reference, here are the values I settled on:
- Top Rig Height: 5 — Top Rig Radius: 1.75
- Middle Rig Height: 1 — Middle Rig Radius: 5
- Bottom Rig Height: -1.25 — Middle Rig Radius: 1.25
Also, while we're here, change the 'Binding Mode' setting in the Orbits section from 'Simple Follow With World Up' to 'World Space'. This is necessary for when we implement player movement, which is the next step!
Player Movement For A Third Person Controller
Creating WASD Input Actions
First we'll setup the input actions for player movement.
- Go to the Input Actions panel, and create a new action called 'Movement'.
- Change the action type to Value, and the control type to Vector 2.
- Delete the binding it creates by default.
- Click the plus sign to the right of movement, then 'Add 2D Vector Composite', and name it 'WASD'.
As you may expect, these will be the inputs for moving with the WASD keys. Click on each individual binding, then set the path to the corresponding keyboard key.
Be sure to click save asset in the input action panel, this is an easy step to forget in my experience.
Creating An Input Manager For Unity's New Input System
Now that we've setup the input actions, it's time to make them accessible by code.
In Assets/Scripts/Input, create a C# script called 'InputManager'.
Erase the Start and Update functions, along with all using statements other than 'using UnityEngine' (we won't be using any of the features added by the other statements), then add the following functions:
- private void Awake()
- private void OnEnable()
- private void OnDisable()
First we'll create a reference to InputMaster, set it to a new instance of the class, then use OnEnable and OnDisable to enable and disable the instance. At this point your InputManager class should look like this:
Next, we need to create two delegates: one will be called when the player is moving, the other will be called when the player stops moving.
First create the delegate for when the player is moving. We'll call it PlayerMovementPerformed, and it needs to accept a Vector2 so we know the direction the player is moving.
On the next line, create a static event called OnPlayerMovementPerformed of type PlayerMovementPerformed. This event will trigger when the player is moving, causing the delegate function to be called.
We'll repeat this process for PlayerMovementCanceled, resulting in these 4 lines at the top of your class:
If you haven't worked with delegates and events before, Sebastian Lague has an excellent two-part series covering the topic.
Now that we've created the delegates and events, we just need to hook them up to the Input Master in Awake().
This is all we have to do in the Input Manager. Your final file should look like this:
Now that we have a functioning input manager, we just need to add it to the scene. Create an empty game object called 'Game Manager' and add the Input Manager script to it.
Making Our Third Person Character Move
Now that we access to player input, we can finally get our character moving.
Start by adding a Character Controller component to the player object. Adjust the collider by tweaking the radius, height, and center properties of the controller until it appears to fit your model.
Next, add a new script to the player object called ThirdPersonCharacterController, then drag it into the Scripts folder to keep a nice, tidy project.
Inside the script, our first job is to get the movement input we just set up, so we know which way to move our player.
To do this we'll add four functions:
- private void OnEnable()
- private void OnDisable()
- private void OnPlayerMovementPerformed(Vector2 direction)
- private void OnPlayerMovementCanceled()
Those last two are compatible with the delegate we created in InputManager. We'll set the movement direction in those, but first we need to tell InputManager that they exist.
These lines add our functions to the list to call when the events are triggered, and removes them when the script is disabled.
Now we can get input data from the manager to the ThirdPersonCharacterController.
Define a private Vector2 called inputDirection that is set to Vector2.zero by default. Then set this variable to the direction sent from the movement event when performed, and back to zero when it is canceled.
Your script should look like this:
This gives us access to the direction the player wishes to move, now all we need to do is move them in that direction.
Start by defining a private CharacterController named controller at the top of the class, then setting it in start as seen below:
We'll use this soon to move the controller, but first we need to know which direction to move them in.
Right now we have a Vector2 representing the player input, but since the character lives in a 3d world, we need a Vector3 in order to move it.
This is simple enough: we know in Unity that the y coordinate is up and down, and we don't want to use WASD to move our character up and down, so using process of elimination, we must want to move our character on the x and z axes.
Create a new private Vector3 at the top of the class called moveAngle, and set it to Vector3.zero by default.
Next, in Update(), set moveAngle to a new Vector3 using the input direction as needed, and move the character controller in that direction:
If we click play after adding this code, we'll see we finally have movement! Very slow movement, but we'll be fixing that momentarily.
If for some reason you don't have movement, I'd start the debug process by printing debug logs from the OnPlayerMovementPerformed and Canceled functions in ThirdPersonCharacterController. If the logs don't print, something went wrong in connecting the delegates and events.
There are a few problems with the current system. Obviously we're going much too slow, but you'll also notice if you rotate the camera, this code doesn't take your new camera rotation into account like you may expect.
Let's fix both of these issues.
To increase movement speed, create a private float called playerSpeed at the top of your ThirdPersonCharacterController class, and set it to a default of 4. Above this variable, add [SerializeField]. This allows us to adjust the variable in the editor, without the drawbacks of making it public. Then just add '* playerSpeed' to your controller.Move parameters.
To take camera rotation into account, set moveAngle to the following:
This looks odd at first, but consider the case when you are holding W to move forward. In this case, moveAngle.z will be equal to 1, since 1 means we are moving forward. If we multiply 1 and the forward angle of the camera, we just end up with the forward angle of the camera. This is perfect, since when we're holding W, we want to move in the direction the camera is facing. This logic extends to moveAngle.z and the right angle of the camera as well.
The line at the end setting y to zero is simply a precaution put in place to avoid y getting set to anything else in the sometimes-weird process of vector multiplication.
If you test out your build now, you'll find your character moves relative to where the camera is pointing, just as you'd expect!
If your character starts at a y greater than 0, you may have noticed their lack of following the laws of physics—that is, gravity doesn't exist.
Let's fix that by adding a gravityValue and playerVelocity property, then moving the character with these in mind.
Our character controller is nearly complete now. The last step is to make our character rotate to face the direction they're moving.
First add a private float rotationSpeed with a default value of 8 at the top of the class. As we did with playerSpeed, add [SerializeField] above this declaration.
Then, at the bottom of the Update function, add this:
First, we check that there is in fact a non-zero inputDirection, because otherwise we wouldn't have to rotate our character.
Assuming we do have an inputDirection, we need to determine the angle needed to point in the correct direction. This will, to the dismay of some readers I'm sure, require a bit of trigonometry. Considering this post has already passed three-thousand words, I'm not sure it'd be wise to include a trigonometry course now that we're near the end.
Just know that the targetAngle is being set to the angle we need to rotate our player to, given the player's current movement.
That angle is simply the y rotation needed (rotate your player on the y axis a bit to see why we're focusing on y if it's not clear), so before we can assign it to our player's rotation, we need to turn it into a full set of euler angles.
Once we have those angles, we use Quaternion.Lerp to smoothly transition from our current rotation to the new target rotation; the speed of this rotation is dependent on the rotationSpeed variable.
Your ThirdPersonCharacterController script should now look like this:
Upon testing our game we'll find a character that smoothly rotates to face the direction they are moving, and moves at a much nicer speed!
Adding Collision To Our Cinemachine Camera
Now that our player can move, you may find yourself getting in positions where an object is blocking the cameras view of the player. This is simple to fix with Cinemachine.
First, check that your player is on the 'Player' layer.
Next, on the CinemachineFreeLook component of your freelook camera, click Add Extension and add a cinemachine collider.
On the new collider, set the Ignore Tag to Player, and the Strategy to Pull Camera Forward.
Now, in play mode, if you rotate your camera into an object that could obfuscate the view of your player, the camera will smoothly avoid it, keeping your player in view!
Animating A Third Person Character Controller
Now let's animate our character.
Add an Animator component to your player, then create a new folder called 'Animation' in Assets. In this folder, right click -> Create -> Animator Controller, and name it Player Animator.
Assign your new Player Animator to the 'Controller' component of the Animator on your Player. You'll notice there's also an empty 'Avatar' property we need to fill.
To create an avatar, go to Assets/Models and select your model. In the inspector panel, go to the Rig tab, change the animation type to 'Humanoid', then change the avatar definition to 'Create From This Model'. Double-check your properties, then click apply.
Now go back to your player object in the scene hierarchy and assign the Avatar property of the Animator component to this newly created avatar.
Unity Atan2
Getting Animations From Mixamo For Unity
Just like we used Mixamo to get a character model, we'll now download a couple of necessary animations from the site.
First, go to animations on Mixamo, and search jogging. Select the animation titled 'Jogging', check the 'In Place' box, then download the animation.
For the download settings, be sure your format is 'FBX For Unity' and you've selected without skin. Check the image below for clarification.
Next, search 'idle', browse until you've found an idle animation you enjoy, then download it with the same settings.
Drag both of the downloaded animations into Assets/Animation.
Now we're set to begin animating.
Creating Animation States
In Assets/Animation, double-click the Player Animator to bring up the Animator tab.
The green entry box represents the state your character will begin in.
Right click to the right of the box -> create state -> empty.
This will create an orange arrow emerging from the entry box and entering a box called 'New State'. Click on the new state box and rename it to 'Idle'.
This new Idle state has a Motion property; set it to the Idle animation we downloaded from Mixamo.
Hitting play right now will result in some very odd behavior, far from an idle animation. This is because we need to tweak some settings on the animations we imported.
Starting with the idle animation (located in Assets/Animation), go to the Rig tab and change the animation type to Humanoid. For avatar definition, select 'Copy From Other Avatar', then for the Source avatar, select the player avatar we generated earlier. Lastly, in the Animation tab, check 'Loop Time' to ensure the animation continues looping instead of playing only once. Don't forget to apply these settings in the inspector as you make the changes.
Repeat this same process with the jogging animation as well, but in Jogging, also select 'Bake Into Pose' under Root Transform Position (Y). This will allow some bouncing movements from the animation to show in the game.
Entering play mode should now result in your player properly showing an idle animation!
Our final step is transitioning from idle to jogging animations.
In the animator, switch to the Parameters tab and create a new bool parameter called 'Jogging'.
Next, create a new animation state, also called jogging, then set the Motion property of this state to the jogging animation.
The animation state is perfect now, but we need to handle the transitions.
Right click the Idle state, select Make Transition, then drag the arrow to the Jogging state. Click on the transition, and add a new Condition so that the transition occurs when Jogging is true. Also, uncheck 'Has Exit Time', so there won't be a delay when transitioning between Idle and Jogging, and under 'settings', change the Transition Duration to 0.1 for snappier transitions.
Repeat this process, except drag the transition arrow from Jogging to Idle (for when we stop jogging), and set the condition to when jogging is false instead of true.
Now all that's left is to set the animator parameter from our script.
In ThirdPersonCharacterController, add a new private Animator animator at the top of the class.
Unity Atan2 角度
In Start, set controller to GetComponent<Animator>(). We can use GetComponent since this script and the animator live on the same object.
In OnPlayerMovementPerformed, use the animator to set jogging to true with animator.SetBool('Jogging', true). Set Jogging to false using the same strategy in OnPlayerMovementCanceled.
Your final ThirdPersonCharacterController script should look like this:
If you run a test in play mode, you should now have a fully-functioning third person character!
You Have A Third-Person Character Controller! What Now?
The third-person character controller we build throughout this post is perfectly built to be expanded however you please.
Want to add sprinting? Simply add a new state in the animator, and increase the speed in the controller script.
Jumping? Add a new state in the animator, and increase the Y velocity in the controller script.
The skills you learned and the foundation you've built with this post have put you in a position to do whatever you please with your character, so go crazy! If you have any questions, don't hesitate to reach out by using the content link at the top of this site—I'm happy to help!
If you enjoyed this tutorial, consider subscribing to my newsletter, where you'll be updated any time I post new content.
Mathf Atan2
If you'd like to see similar content in video form, check out my Youtube channel!