I’m facing some issue setting up a viable character controller.
I’m working on a fixed camera angle prototype-game (like old Resident Evil, Silent Hill, Kuon etc…), I would like have two controls scheme available anytime on the fly anytime when playing, one classic tank control available on the D-pad, and another more modern camera relative orientation (still in fixed camera) using the thumb stick (similar to what we can find in the Resident Evil Remaster, Silent Hill 2 (2D Control) etc…)
For now, i’ve got both working as intended with cinemachine ClearShot for the fixed camera handling, except for one thing :
For the more modern scheme, what I’m trying to achieve is that the player direction carries over from the last virtual camera angle to any number of camera changes as long as the player is still holding the last direction input on the thumb stick before the camera change and then updates to the new camera angle once the player let’s go or changes the input direction on the thumb stick.
Here is the code snippet that is responsible for the movement and camera relativeness of the player direction
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerMovementCamRel : MonoBehaviour
{
public CharacterController controller;
public Transform cam;
public Vector3 direction;
public bool canMovePlayer;
public float speed = 1.0f;
public float turnSmoothTime = 0.1f;
float turnSmoothVelocity;
void Start()
{
controller = GetComponent<CharacterController>();
void FixedUpdate()
{
if (!inDialogue())
{
if (canMovePlayer)
{
float horizontal = Input.GetAxisRaw("Horizontal");
float vertical = Input.GetAxisRaw("Vertical");
bool hasHorizontalInput = !Mathf.Approximately(horizontal, 0f);
bool hasVerticalInput = !Mathf.Approximately(vertical, 0f);
bool isWalking = hasHorizontalInput || hasVerticalInput;
direction = new Vector3(horizontal, 0f, vertical).normalized;
m_Animator.SetBool("IsWalking", isWalking);
if (isWalking)
{
if (direction.magnitude >= 0.1f)
{
float targetAngle = Mathf.Atan2(direction.x, direction.z) * Mathf.Rad2Deg + cam.eulerAngles.y;
float angle = Mathf.SmoothDampAngle(transform.eulerAngles.y, targetAngle, ref turnSmoothVelocity, turnSmoothTime);
transform.rotation = Quaternion.Euler(0f, angle, 0f);
Vector3 moveDir = Quaternion.Euler(0f, targetAngle, 0f) * Vector3.forward;
//moveDir.y -= gravityValue ;
controller.Move(moveDir * speed * Time.deltaTime);
}
}
}
From what I understand the issue could come from cam.eulerAngles.y, that takes any new camera for the new relative direction of the player. And since i’m using cinemachine I don’t know how I could keep the last camera direction as direction relative until the current input control in released.
If you need any more details or clarification feel free.
Thanks in Advance !
You can probably leave most of what you have in place, except “plumb in” the following bypass.
Instead of directly taking the eulerAngles.y value “live” from the camera, keep your own notion of that value in a variable, and use that for relative controls.
Only when the player meets your specific conditions to consider a new camera angle (ie., thumb stick released, or “changed enough”, whatever that means), then copy the new current camera’s eulerAngles.y out to your variable.
I totally get your second point, the logic behind this would be that when the player stop moving ( when direction is equal to vector3.zero for example ) the cam.eulerAngles.y is set to the actual camera angle targeting the player.
But I don’t get in your first point, how i could store the value of the cam.eulerAngles.y and reuse it ? I’ve been looking around the unity documentation and some forums but I found nothing that made sense for this situation.
I’m still learning c# and the unity logic around it so maybe that’s a noob problem but it seem’s i can’t wrap my head around this problem since a few days ahah. I’m not asking for litteral code because i wan’t to solve it by myself but if you could give me some examples of use of the logic you are mentionning it would be really nice.
Now I have the cam.eulerAngles values also updating in editor view, which allowed to grasp a bit more what I was doing. But I still can’t figure out a way to check when these values changes to implement the system that I want. The last clue was effective but I think there something not ticking in my head, however I feel that i’m close to the original goal.
I could think of a few ways to do this, but I feel the easiest way would be not change the active camera input to the next camera until the horizontal and vertical axis values are close to zero. That way if the player is currently moving, it will stay relative to the previous camera.
Another method would be to use a coroutine of some kind to blend into the new camera transform before switching the next active camera to be the current.
Well, I finally found a solution that works as I wanted, what a relief after a few days of figuring this out. Here is the solution :
I created a variable storing the last euler.Angle.y of the cam, then I simply updated it when the player wasn’t moving, keeping him in the last direction wanted. I put my code here in case somebody stumble upon the same issue :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerMovementCamRel : MonoBehaviour
{
public CharacterController controller;
public Transform cam;
public Vector3 direction;
Vector3 lastCamAngle;
float camEulerAngX;
float camEulerAngY;
float camEulerAngZ;
public bool canMovePlayer;
public float speed = 1.0f;
public float turnSmoothTime = 0.1f;
float turnSmoothVelocity;
void Start()
{
controller = GetComponent<CharacterController>();
void FixedUpdate()
{
camEulerAngX = cam.localEulerAngles.x;
camEulerAngY = cam.localEulerAngles.y;
camEulerAngZ = cam.localEulerAngles.z;
if (!isMoving) // insert here your own check for player movement
{
lastCamAngle = new Vector3(camEulerAngX, camEulerAngY, camEulerAngZ);
}
if (!inDialogue())
{
if (canMovePlayer)
{
float horizontal = Input.GetAxisRaw("Horizontal");
float vertical = Input.GetAxisRaw("Vertical");
bool hasHorizontalInput = !Mathf.Approximately(horizontal, 0f);
bool hasVerticalInput = !Mathf.Approximately(vertical, 0f);
bool isWalking = hasHorizontalInput || hasVerticalInput;
direction = new Vector3(horizontal, 0f, vertical).normalized;
m_Animator.SetBool("IsWalking", isWalking);
if (isWalking)
{
if (direction.magnitude >= 0.1f)
{
float targetAngle = Mathf.Atan2(direction.x, direction.z) * Mathf.Rad2Deg + lastCamAngle.y;
float angle = Mathf.SmoothDampAngle(transform.eulerAngles.y, targetAngle, ref turnSmoothVelocity, turnSmoothTime);
transform.rotation = Quaternion.Euler(0f, angle, 0f);
Vector3 moveDir = Quaternion.Euler(0f, targetAngle, 0f) * Vector3.forward;
controller.Move(moveDir * speed * Time.deltaTime);
}
}
}
The solution was simple but I took time for it to tick in my head, thanks to both of you for guiding me !