Genshin Impact Movement in Unity | #8 - Rotating the Player

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
With our camera working  like Genshin Impact Camera, we can finally use it to rotate our character  and move it towards the right direction. Because we'll need to know our  Camera rotation to do that, we'll need a reference to our Camera. So, open up the "Player" script. In here, create a new public  property of type "Transform" to which I'll name "MainCameraTransform". Then, in the "Awake" method, type in  "MainCameraTransform = Camera.main.transform;". We're getting the main camera  and not the Cinemachine camera because Cinemachine controls the Main Camera, so we are safe to get our data from there. Now, "Camera.main" was something that wasn't very performant before and that's the  reason why we're caching it here. This is because it used to search for all  objects with tags and add it to a temporary list, which would then be searched again for an  object with the Camera Component enabled. However, seems like in Unity  2020.2, that was optimized and now it stores a dedicated list of  objects with the "MainCamera" tag instead and then searches on that list only. And as it says there, they've found the new  way of doing things to be way more performant. However, we'll still cache it ourselves not  only because we're also getting the transform, but also caching it means we can  assign any value to this variable whenever we want and it's changed everywhere, and likely makes the code a  bit cleaner and organized. With that done, lets head  back to our "Movement State". We'll be adding our Player Rotation here. Just under our "Move" method declaration,  create another one by typing in "private float Rotate()". We'll accept a parameter of type  "Vector3" named "direction", which is simply the direction that  our Player will rotate towards. Before we start coding it though lets first  take a look at how it works in Genshin Impact. Lets start by pressing "W". As one might expect, this  will move the player forwards. However, the moment we rotate the camera  around, the player will start rotating with it and move forwards towards  where the camera is looking at. If we instead start pressing  "S" to move backwards, it will keep on doing the same but  towards the opposite direction. The same happens when we move sideways. What's happening here is that our  player is moving towards the sum of our "Input" Direction plus the Rotation of the Camera. So if we press "W" and move the camera "45º", we want to go 0º (Up) from our forward  direction plus 45 degrees from our camera. If we instead press "S", we  want to go 180º (Down) degrees from our backwards direction  plus 45 degrees from our camera. We'll understand more in a bit on why  "W" and "S" are "0" and "180" degrees. Lets then start by getting that sum. The first thing we'll need will be our  movement input angle, or its direction angle. We can get that through a  "Math" method named "Atan2". "Atan2" receives two parameters: the "y"  and "x" of a coordinate, in that order. Lets say we were moving forward, which  translates to the coordinate of "(0, 1)". Remember that our "y" axis here is  the equivalent of the 3D "z" axis. If we pass in "1, 0" to the "Atan2" method, it will be the same as having  the "(0, 1)" coordinate. The angle this returns is "90"  degrees, which should be correct. However, Unity forward axis is the "z" axis. Contrary to the normal degree circle,  it increments its angles clockwise, starting at the top. This means that our "(0, 1)" coordinate should've  been "0" degrees instead of "90" degrees, as "90" degrees in Unity would be  towards the right and not forwards. We can actually make it so that "Atan2"  returns relative to the "z" axis by simply swapping the order  of the parameter values. If we now pass in "0, 1" to  our "Atan2" method instead, which is the same order as the forward coordinate, the coordinate will now become "(1, 0)". If we take a look at the degree circle now, that coordinate is at "0" degrees,  which is exactly what we need. Now, I don't completely understand  the reason why this happens, but I guess that's just how math works. It's like we're saying "give us the  angle relative to the 'z' axis instead", or "relative to the second parameter axis". Lets also take a look at a few others  coordinates: "(0, -1) - Backwards", "(-1, 0) - Left", "(-0.7, 0.7) - Left Forward". With that in mind, lets  get that angle by typing in "float directionAngle =  Mathf.Atan2(direction.x, direction.z);". This method however returns the value in Radians. To transform it into degrees we just  need to multiply it by a certain value, which is by "Mathf.Rad2Deg". This works because we know that "1 Radian"  is the equivalent of an amount of degrees. That amount is stored in the  "Mathf Rad2Deg" variable, so by multiplying it with the amount of  radians, we get the angle in degrees. We however have yet another problem:  Atan2 can return negative values, as it ranges from "-180" to "180". If I remember correctly, there were some  problems with rotations that I needed to make sure that negative angles  were converted into positive angles. I believe it was because a rotation  wouldn't always follow the shortest path to a certain angle, so it could rotate wrongly. Thankfully, it's quite easy to turn  negative angles into positive angles. To do that, simply type in  "if (directionAngle < 0f)", then we sum to the  "directionAngle += 360f" degrees. This means that "-90" will equal to  "270", "-180" will equal to "180", etc.... With that, we have our input direction angle  and simply need to add our camera angle to it. Now, if any of you is wondering why  do we need to add our camera angle if we already have the input direction in degrees, that's because the movement input is always the  same regardless of where the player is looking at. That's of course because that's how  it is defined in the Input Actions, "W" is always "Up", "S" is always "Down", etc... Not only that, but no matter  how much the player rotates, "90" degrees in Unity is always to the same  direction (right), as it is in world space. That means that if we tell  our player to go "90" degrees, it will always go to the right in World Space and not to the Right relative to  where the Player is facing. And that's when our camera angle comes in. Lets say we move "90" degrees,  or to the right by pressing "D". Then, we also move our Camera  90 degrees to the right. Currently, our "Player" would keep  on moving towards the same direction as it's going to the World  Space "90 degrees" direction. However, lets also add the  "90" degrees of our camera to make a total of "180" degrees. Our player will now move "90"  degrees to the right of the camera, which is considered "Down" or "Backwards" in  World Space, the equivalent of "180 degrees", which is exactly what we want. So, to add our camera angle, simply type in "directionAngle += stateMachine.Player.MainCameraTransform.eulerAngles.y;". Note that we need to use "eulerAngles" and not  "rotation", as "rotation" returns a Quaternion. The "y" axis of a Camera  is the horizontal rotation. Of course, when adding the camera  angle to our direction angle, it will be possible to go over 360 degrees. To fix that problem, we simply  check "if (directionAngle > 360f)". If that's the case, we subtract to  the "directionAngle -= 360f" degrees. So, "600" degrees would lead to "240" degrees  or "380" degrees would lead to "20" degrees. With that done, we have our final direction angle. We can now return it by typing  in "return directionAngle;". To keep things organized, lets do two things: Select the first if statement  and assignment and extract it to a new method named "GetDirectionAngle();". Then, select the second if statement  and assignment and extract it to a new method named "AddCameraRotationToAngle();". I'll rename the last method  parameter to be "angle". We now have the direction angle we  want to rotate our player towards. But if we take a look at Genshin  Impact, whenever the player rotates, either by pressing one of the Movement  Keys or by rotating the Camera, it takes some time to get to the target rotation. You might not notice it too much when pressing  the Movement Keys because that reach time is quite low, but you can see that  it isn't null as it isn't snappy. If you do rotate the camera, then you can see  that the player is a bit delayed on its rotation. We'll be smoothing our player rotation much like  they do using a method named "SmoothDampAngle". This method requires a few things from us: The "current angle", which we'll be  able to obtain from our Player Rotation, the "target angle", which is our "directionAngle", a "velocity", which the method  takes care of updating its value and the "amount of time" it should  take to reach the target angle. We'll create one variable for each of  these, including for our "directionAngle", as we'll need it somewhere  else further into this series. So, above, create a few new variables: "protected Vector3 currentTargetRotation;", "protected Vector3 timeToReachTargetRotation;", "protected Vector3  dampedTargetRotationCurrentVelocity;" and "protected Vector3  dampedTargetRotationPassedTime;". We are creating "Vector3"'s because in our "Gliding System" we'll need a  value for the "x" and "z" axis, so we might as well just make  it a "Vector3" right away. We now need to initialize our reach time  to a certain value so in our constructor call in a new method named "InitializeData();". Inside, we'll type in  "timeToReachTargetRotation.y = 0.14f;". We'll later on understand how I  got to this value but just know that it is the time it takes for  the rotation to happen in Genshin. When that's done, back to our "Rotate" method, we'll call in a new method named  "RotateTowardsTargetRotation();". I'll make this method protected and move  it to the "Reusable Methods" region. We'll start by getting the  current angle by typing in "float currentYAngle = stateMachine.Player.Rigidbody.rotation.eulerAngles.y;". We can now start smoothing our player rotation but  only if we aren't already at our target rotation, so, type in "if (currentYAngle == currentTargetRotation.y)" If that's the case, we "return;". Otherwise, we'll smooth our angle by typing in "float smoothedYAngle = Mathf.SmoothDampAngle();". When that's done, pass in "currentYAngle"  and "currentTargetRotation.y". Then, we'll need to pass in the velocity  variable but with the "ref" keyword, as Unity automatically updates and uses this  variable as it wishes inside of the method, so pass in "ref dampedTargetRotationCurrentVelocity.y". For the amount of time it should  take to reach the target rotation, we'll pass in "timeToReachTargetRotation.y  - dampedTargetRotationPassedTime.y". If we were to pass in  "timeToReachTargetRotation" alone here, it would always take "0.14"  seconds for each smooth method call instead of "0.14" seconds for the whole rotation. Of course, we need to increment something to the passed time variable  as otherwise it will keep on being "0", so type in "dampedTargetRotationPassedTime.y  += Time.deltaTime;". Note that because this method is being  called in the "FixedUpdate" method, Unity will automatically return "fixedDeltaTime"  when we use the "deltaTime" variable. We now have our smoothed value, so  we can rotate our player with it. To do that, we'll create a new "Quaternion",  to which I'll name "targetRotation". Then, we'll set it to be "Quaternion.Euler()", which accepts euler angles and  transforms them into a quaternion, and pass in "0f" for the "x", "smoothedYAngle"  for the "y" and "0f" for the "z". When that's done, we'll set the  player rotation by typing in "stateMachine.Player.Rigidbody.MoveRotation(targetRotation);". That takes care of rotating our Player. However, we need to remember that the moment  our direction angle changes to something else, we need to reset the passed time and also  set the target rotation to our new angle, as we're not yet doing that. So, back in our "Rotate" method,  between the last 2 methods, type in "if (directionAngle != currentTargetRotation.y)", we'll call in a new method named  "UpdateTargetRotationData();" with "directionAngle" as an argument. I'll update the name of the parameter  to be "targetAngle" instead. In here, we'll set the "currentTargetRotation.y"  to be the "targetAngle". Then, we'll reset the passed time by typing in "dampedTargetRotationPassedTime.y = 0f;". We don't really need to reset anything else. We don't need to reset our velocity either, as the "SmoothDampAngle" method  automatically takes care of that for us. Back in our "Rotate" method, I'll select the code of the if statement  up until the "GetDirectionAngle" method and extract it to another method  named "UpdateTargetRotation();". Then, I'll make it "protected" and add  it to the "Reusable Methods" region. I'll also add a new parameter of type "bool" named "shouldConsiderCameraRotation" defaulted to "true". Then, before we add our camera angle, type in "if (shouldConsiderCameraRotation)" and  put the line inside of this if statement. We'll need this later for our "Dashing State". We now have everything  needed to rotate our Player. Of course, we aren't yet calling  the "Rotate" method anywhere. If we take a look at Genshin Impact, if we  move the camera around while Idling or Jumping, our player won't rotate with it. This simply means that our rotation  only happens when we are moving. The Idling and Jumping are not considered "Moving"  because we set their "Speed Modifiers" to "0". So, in our "Move" method, right after  we get the movement direction, type in "float targetRotationYAngle = Rotate(movementDirection);". We now need to transform this angle into a  direction that the player can move towards, as right now we're moving  towards the input direction, which will always be the same  regardless of the camera angle. To do that, type in "Vector3 targetRotationDirection = GetTargetRotationDirection();", passing in "targetRotationYAngle" as an argument. I'll rename the method parameter  name to be "targetAngle" instead. I'll also make it be "protected" and  add it to the "Reusable Methods" region. In here, we'll "return Quaternion.Euler(0f,  targetAngle, 0f) * Vector3.forward;". Now, if I'm being completely honest, I'm  not too sure what actually happens here. If I understood something, it's that we have  a point, which is our forward direction. And then, we want to know what point would  we get if we rotated that forward direction by an amount of degrees, lets say "45 degrees". With this multiplication of a Quaternion, which  represents our "45 degrees angle", by a Vector, some black magic happens and we get our normalized point, which is the direction we want to go to. We pass in "Vector3.forward" here because  we always want a rotation from the "z" axis, which is the player forward axis. Do note, that it's apparently important for the  multiplication to be a Quaternion by a Vector and not the opposite, so order matters here. With that done, lets go back to our "Move" method. In the "AddForce" method, we  now swap the "movementDirection" with our new "targetRotationDirection",  which already comes normalized. If we save and go back to Unity, entering Play Mode should allow us to move  and rotate our Player around correctly. We even get a thumbs up from our shadow.
Info
Channel: Indie Wafflus
Views: 6,125
Rating: undefined out of 5
Keywords: genshin impact movement in unity, genshin impact movement, genshin impact in unity, genshin impact unity tutorial, unity player rotation, genshin in unity, genshin movement in unity, genshin gliding in unity, unity 3d movement, unity gliding, unity physics based movement, unity rigidbody movement, unity swimming, unity genshin movement tutorial, unity 3d movement tutorial, unity third person movement, unity genshin impact, genshin camera unity, unity quaternions
Id: K9xmUeGB8sc
Channel Id: undefined
Length: 21min 23sec (1283 seconds)
Published: Tue Mar 01 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.