Help me with this Quaternion LOGIC and you'll be in my game credit!

Hi,
I’ve been struggling with this game mechanic for 7 hours for 3 freaking DAYS, i wanna d*!*e :frowning:
anyways I have a first-person shooter game and the player later will be on the wheelchair, I’m planning to make the wheelchair Gameplay similar to the “Wolfenstein 2: The New Colossus” when the player plays on the wheelchair, so I went ahead and added the wheelchair movement and made the camera controls.
Then I wanted to add a general game mechanic which is when the player want to move side ways they’ll press A/D and the wheelchair should strafe that way but to make it realistic I wanted to make the chair rotate that way before strafing, so the chair should rotate -90 and 90 degrees before moving, everything cool until now, but I don’t want that limit to be constant, but rather relative to the camera rotation, in other words if the camera have rotated 93 degrees the limits will be 3 to 183. sounds easy huh? NO it’s not… no matter what I do it never works, I milked ChatGPT to the last drop and I didn’t achieve anything. the problem is mostly because of the Quaternion and the weird rotation of the Camera and it’s difference from the wheelchair rotation.

Please Help me with this and it would be much appreciated if you tried it before sending a simple solution and calling me an idiot, here’s my code and I highlighted with comments the section that needs attention.
ow and first one who manages to solve my puzzle, I’ll put there name in my upcoming game credits :smiley:

private PlayerInput playerinput;
    private PlayerInput.BasicActions input;
    private Rigidbody rb;
    public Transform wheelchair;
    public Transform orientation;
    public Transform bigWheelR;
    public Transform bigWheelL;
    public Transform viewStart;
    public Transform viewEnd;
    public FirstPersonCam fps;
    public Transform Cam;
    //public float pushRate;
    //private float pushTimer;
    public float speed;
    public float rotationSpeed;
    public float rotationSmoothness;
    private float decelerationForce;
    public float decayRate = 0.05f; // Decay for deceleration
    private float yRotation;
    private float currentForce;
    private bool braking;
    public bool canMove = true;
    private int steeringDirMultiplier = 1;
    public bool isMoving;
    [SerializeField] private bool isSteering;
    private float lastDir;
    private float lastRot;
    private readonly float wheelRadius = 0.47f;

    private void Awake()
    {
        playerinput = InputManager.playerinput;
        input = playerinput.Basic;
        rb = GetComponent<Rigidbody>();
    }

    private void FixedUpdate()
    {
        // Apply rotation for wheels
        float rot = ((rb.velocity.magnitude * Time.fixedDeltaTime) / wheelRadius) * Mathf.Rad2Deg * lastDir;
        bigWheelR.Rotate(rot, 0, 0);
        bigWheelL.Rotate(rot, 0, 0);

        if (!isMoving && isSteering)
        {
            float circularRot = ((rotationSpeed * Time.fixedDeltaTime) / wheelRadius) * Mathf.Rad2Deg * lastRot * 0.5f;
            bigWheelR.Rotate(circularRot, 0, 0);
            bigWheelL.Rotate(-circularRot, 0, 0);
        }

        if (braking) return;

        // Move player
        if (!canMove) return;
        //here's the function VVVVVVVVVVVVVVVVVVVVVVVVVVVV
        Vector2 moveDir = input.Move.ReadValue<Vector2>().normalized;
        float X = moveDir.x * rotationSpeed * Time.deltaTime;
        float CamYRot = Cam.eulerAngles.y;

        float minRot = Mathf.Min(0, CamYRot - 100);
        float maxRot = Mathf.Max(0, CamYRot + 100);


        if(wheelchair.eulerAngles.y >= minRot && wheelchair.eulerAngles.y <= maxRot)
            yRotation += X * steeringDirMultiplier;
        //else if (wheelchair.eulerAngles.y < minRot)
        //{
        //    yRotation = minRot;
        //}
        //else if (wheelchair.eulerAngles.y > maxRot)
        //{
        //    yRotation = maxRot;
        //}
        wheelchair.localRotation = Quaternion.Lerp(wheelchair.localRotation, Quaternion.Euler(0, wheelchair.localRotation.y + yRotation, 0), rotationSmoothness);
        Debug.Log($"minRot:{minRot}, maxRot:{maxRot}, chairRot:{wheelchair.eulerAngles.y}, yrot:{yRotation}");
        //Ends here.XXXXXXXXXXXXXXXXXXX

        currentForce = speed * -moveDir.y;
        if (moveDir.y == 0 && rb.velocity.magnitude != 0) // Input is released => Apply deceleration
        {
            //pushTimer = 0f;
            if (decelerationForce == 0)
            {
                decelerationForce = rb.velocity.magnitude * speed * lastDir;
            }
            decelerationForce *= Mathf.Pow(1f - decayRate, Time.fixedDeltaTime); // Apply exponential decay
            rb.AddForce(decelerationForce * orientation.forward, ForceMode.Force);
        }
        else decelerationForce = 0;

        // Check current stats
        if (moveDir.y < 0)
            steeringDirMultiplier = -1;
        else
            steeringDirMultiplier = 1;

        if (moveDir.x != 0)
        {
            isSteering = true;
            lastRot = moveDir.x;
        }
        else isSteering = false;
        if (moveDir.y != 0)
        {
            isMoving = true;
            lastDir = -moveDir.y;
        }
        else
            isMoving = false;


        //if(moveDir.y != 0)
        //{
        //    // Add Push to the camera periodically for wheelchair effect
        //    print(pushTimer);
        //    pushTimer += Time.fixedDeltaTime;
        //    if (pushTimer >= pushRate)
        //    {
        //        fps.LeanForward();
        //        pushTimer = 0f;
        //    }
        //}

        rb.AddForce(10f * currentForce * orientation.forward, ForceMode.Force);
    }

    private void Brakes(bool activate)
    {
        braking = activate;
        decelerationForce = 0;
    }

    private void OnEnable()
    {
        input.Brake.started += _ => Brakes(true);
        input.Brake.canceled += _ => Brakes(false);
    }

    private void OnDisable()
    {
        input.Brake.started -= _ => Brakes(true);
        input.Brake.canceled -= _ => Brakes(false);
    }

Hm, don’t think you really even need to think in terms of Quaternions here…

It is two things that take and X/Y offset (or X/Z if you are flat on ground) and produce a heading from 0 to 360.

One heading turns the the wheelchair, one heading is for the turret or dude sitting in it.

The latter could also in your case drive the camera look direction, perhaps even a little “sloppily” to allow you to sway the turret back and forth a bit without turning the view direction.

It’s kinda the same exact situation as a twin stick tank game… and I got one of those controllers handy in my proximity_buttons package!

This is the code:

https://bitbucket.org/kurtdekker/proximity_buttons/src/master/proximity_buttons/Assets/DemoTwinStickTanker/TwinStickTankController.cs

Update_Moving(); drives the tank body and Update_Shooting(); drives the top turret direction.

proximity_buttons is presently hosted at these locations:

https://bitbucket.org/kurtdekker/proximity_buttons

well you didn’t address my problem, certainly I do have a similar movement to the twin stick tank logic, but I have this already working, my problem isn’t with the controls but rather the wheelchair(tank) lock rotation, if the main camera rotation is 0, the limits that the wheelchair can rotate is -90 and 90 and if the camera is 90 then the limits are 0 and 180, so the limits are -90, +90 relative to the camera rotation but (surely for quaternion reasons) when I do a full rotation the wheelchair gets reversed like it get a negative sign, it’s probably because of my clamp function but what other than that to achieve the same effect? (A/D buttons for now it rotates the wheelchair in their sides)
here’s a solution that I was using but there’s the same issue:

Vector2 moveDir = input.Move.ReadValue<Vector2>().normalized;
float X = moveDir.x * rotationSpeed * Time.deltaTime;

// Step 2: Get the camera's current Y rotation (normalized to -180 to 180)
float camYRot = Cam.eulerAngles.y;

// Step 3: Calculate yRotation update
yRotation += X * steeringDirMultiplier;  // Increment yRotation based on input

// Step 4: Define the range relative to the camera's rotation
float minRotation = camYRot - 90f;  // Left limit relative to camera
float maxRotation = camYRot + 90f;  // Right limit relative to camera

// Step 5: Apply smooth clamping to yRotation based on min/max range
// Ensure that the yRotation stays within the clamped limits (minRotation and maxRotation)
yRotation = Mathf.Clamp(yRotation, minRotation, maxRotation);

// Step 6: Apply the final rotation to the wheelchair smoothly
Quaternion targetRotation = Quaternion.Euler(0, yRotation, 0);
wheelchair.localRotation = Quaternion.Lerp(wheelchair.localRotation, targetRotation, rotationSmoothness * Time.deltaTime);

I’ve DONE IT GUYS I"VE DONE IT!!!
I resolved the problem by myself, I’m going to put my name in my game credit : )
here’s my code if anyone got the same problem:

// Move player
        if (!canMove) return;
        Vector2 moveDir = input.Move.ReadValue<Vector2>();
        Vector3 movement = new(moveDir.x, 0f, moveDir.y);
        Vector3 desiredMovement = orientation.forward * moveDir.y;
        if(moveDir.y == 0)
        {
            desiredMovement = orientation.forward * Mathf.Abs(moveDir.x);
        }

        Quaternion camRot = Quaternion.Euler(0, Cam.eulerAngles.y, 0);
        Quaternion localRotation = Quaternion.LookRotation(-movement * (moveDir.y < 0 ? -1 : 1));
        Quaternion targetRotation = camRot * localRotation;

        if (movement.magnitude > 0)
            wheelchair.rotation = Quaternion.Slerp(wheelchair.rotation, targetRotation, rotationSpeed * Time.deltaTime);

        rb.AddForce(-10f * steadySpeed * Time.deltaTime * desiredMovement, ForceMode.Force);
1 Like