Ranger Aiming System

Hello again fellas. So… today I’m trying to develop a Ranger Aiming Camera System, something to get the players to more accurately aim free look projectiles if they so desire (because aiming arrows from the back of a horse won’t work, because I see no sense in creating a targeting state there. So, I decided I’ll develop it in freeroam first, and then try it for the state on the back of a horse). My question for now is, which camera is better for this one, a FreeLook or Virtual Camera? I know VCams are static, hence the question, but I also want a FreeLook cam that can not only rotate as the mouse rotates (I believe I can dig up my code to find that), but also get the player to rotate along with it accordingly (because he’s aiming his arrow at wherever the mouse is, so he has to rotate too)

So… Virtual or FreeLook?

Virtual Camera, it is… I’m already halfway through implementing the freelook version

I already got nearly everything to work, the only exception that I can’t get to work, is firing the arrow at the point wherever the mouse lands on the screen at. I’m trying to use a ‘ScreenToWorldPoint’ function for that, in ‘Fighter.Shoot()’, but… Yeah I’m better off deleting that and trying again

As for firing and switching between states and the Virtual Camera, yeah that’s all nearly taken care of

Here’s what I came up with, so far:

using RPG.States.Player;
using UnityEngine;

public class PlayerRangerAimingState : PlayerBaseState
{
    private float mouseX;
    private float mouseY;
    private float sensitivity = 100f;
    private float maxYRotation = 80f;
    private float minYRotation = -80f;

    private readonly int RangerStateBowLoadHash = Animator.StringToHash("RangerStateBowLoad");

    public PlayerRangerAimingState(PlayerStateMachine stateMachine) : base(stateMachine) {}

    public override void Enter()
    {
        Debug.Log($"Player has entered Ranger Aiming State");
        stateMachine.InputReader.RangerAimCancelEvent += CancelAim;
        stateMachine.rangerAimingCamera.m_Priority = 11;
        stateMachine.Animator.CrossFadeInFixedTime(RangerStateBowLoadHash, stateMachine.CrossFadeDuration);
    }

    public override void Tick(float deltaTime)
    {
        if (stateMachine.InputReader.IsAttacking) 
        {
            stateMachine.SwitchState(new PlayerRangerFiringState(stateMachine));
            return;
        }

        HandleRotation(deltaTime);
        Move(deltaTime);
    }

    public override void Exit()
    {
        Debug.Log($"Player has exited Ranger Aiming State");
        stateMachine.InputReader.RangerAimCancelEvent -= CancelAim;
        stateMachine.rangerAimingCamera.m_Priority = 9;
    }

    private void HandleRotation(float deltaTime) 
    {
        mouseX += Input.GetAxis("Mouse X") * sensitivity * deltaTime;
        mouseY -= Input.GetAxis("Mouse Y") * sensitivity * deltaTime;
        mouseY = Mathf.Clamp(mouseY, minYRotation, maxYRotation);

        stateMachine.transform.rotation = Quaternion.Euler(mouseY, mouseX, 0);
    }

    private void CancelAim()
    {
        SetLocomotionState();

        // Reset the y-axis rotation to zero:
        Quaternion resetRotation = Quaternion.Euler(0, stateMachine.transform.eulerAngles.y, 0);
        stateMachine.transform.rotation = resetRotation;
    }
}
using RPG.States.Player;
using UnityEngine;

public class PlayerRangerFiringState : PlayerBaseState
{
    private readonly int RangerStateBowFireHash = Animator.StringToHash("RangerStateBowFire");
    private bool hasFired;

    public PlayerRangerFiringState(PlayerStateMachine stateMachine) : base(stateMachine)
    {
        hasFired = false;
    }

    public override void Enter()
    {
        Debug.Log($"Player Entered Ranger Firing State");
        stateMachine.Animator.CrossFadeInFixedTime(RangerStateBowFireHash, stateMachine.CrossFadeDuration);
        stateMachine.rangerAimingCamera.m_Priority = 11;
        hasFired = false;
    }

    public override void Tick(float deltaTime)
    {
        Move(deltaTime);

        if (!stateMachine.InputReader.IsRangerAiming) 
        {
            ReturnToFreeLookState();
            return;
        }

        if (!hasFired && stateMachine.Animator.GetCurrentAnimatorStateInfo(0).IsName("RangerStateBowFire") && stateMachine.Animator.GetCurrentAnimatorStateInfo(0).normalizedTime >= 1.0f) 
        {
            hasFired = true;
            ReturnToRangerAimingState();
        }
    }

    public override void Exit()
    {
        if (!stateMachine.InputReader.IsRangerAiming) // returning to freelook from firing
        {
            stateMachine.rangerAimingCamera.m_Priority = 9;
        }
        else stateMachine.rangerAimingCamera.m_Priority = 11; // returning to aiming state
    }

    private void ReturnToRangerAimingState()
    {
        stateMachine.SwitchState(new PlayerRangerAimingState(stateMachine));
    }

    private void ReturnToFreeLookState() 
    {
        stateMachine.SwitchState(new PlayerFreeLookState(stateMachine));

        // Reset the y-axis rotation to zero:
        Quaternion resetRotation = Quaternion.Euler(0, stateMachine.transform.eulerAngles.y, 0);
        stateMachine.transform.rotation = resetRotation;
    }
}

Right now, I got three problems I can’t figure out:

  1. Whenever I return to ‘PlayerRangerAimingState’, the rotation always points front for some reason. It does not fit with the game, because it doesn’t point where it’s expected to point. Long story short, rotate all you want in free look or firing or aiming state, and try getting into aiming state again, and you’ll be looking front… (I tried installing a second parameter into the constructor of the ‘PlayerRangerAimingState.cs’ script, but that failed too…!)

[For the first problem, as it turns out, the ‘HandleRotation’ was forcing the rotation to start in the front direction. I eliminated that line, but I still can’t think of a single way to get the rotation to be whatever the freelook camera was initially pointing at, without placing that line in]

  1. I want my arrows to fire, in this state, at wherever the mouse is pointing at. At the very least, which script had the Mouse Cursor UI again from the courses? Maybe I can just get that mouse cursor to show up and call that one a day

  2. How does the InputReader, somehow, switch between my state machines when I hit the aiming button? In simple terms, it re-activated my state machine (when it wasn’t supposed to), and introduced a ton of bugs that were simply not supposed to be there… I’m guessing my best approach is to create an alternative input reader script for that?

Later on I’ll see how I can integrate movement into that state. For now, I just want to fix the two problems above, especially the first one, because this is the most awkward one (and I can’t think of a single solution to even figure out the source of the problem. If it helps, the camera is a Cinemachine Virtual Camera with no follow or lookat parameters attached to it)

Any help is appreciated :slight_smile:

sigh… I tried everything I know, but I really can’t get that Ranger Aiming State to work, so I’ll probably keep it aside for the time being, until I get a better idea or something (the goal of that system was, quite literally, just to make freelook range fights happen). If anyone wants to help me fix it, here’s everything I did so far:

  1. Create a ‘RangerAimingVirtualCamera’ in the hierarchy, assign it all it needs (but no ‘Follow’ or ‘LookAt’, because it’s under the player on the hierarchy), and give it a tag called ‘RangerAimingCamera’

  2. Assign this into my Cinemachine Brain’s ‘Custom Blends’, between the freelook camera and the new ‘RangerAimingVirtualCamera’, with a timer of about 0.3 seconds

  3. Create an Input for it, which will be the right mouse button, with all the procedures in ‘InputReader.cs’ (which will have one boolean, ‘IsRangerAiming’ and 2 events: one for entering the ranger state, and one for exiting it), and a new boolean in ‘WeaponConfig.cs’ called ‘isRangerAimWeapon’. Because this and the blocking state share the same button, this boolean was necessary, to make sure ranged weapons can’t go into blocking state, only that new state

  4. Reference it in ‘PlayerFreeLookState’ and subscribe and unsubscribe to it as well, as follows:

// Subscribe in 'Enter()'
stateMachine.InputReader.RangerAimEvent += InputReader_HandleRangerAimingEvent;

// Usubscribe in 'Exit()'
stateMachine.InputReader.RangerAimEvent -= InputReader_HandleRangerAimingEvent;

// the 'InputReader_HandleRangerAimingEvent' function
// TEST - Player Ranger Aiming State:
        private void InputReader_HandleRangerAimingEvent()
        {
            if (stateMachine.Fighter.GetCurrentWeaponConfig().GetIsRangerAimWeapon())
            {
                stateMachine.SwitchState(new PlayerRangerAimingState(stateMachine));
            }
        } 
  1. The function itself, ‘PlayerRangerAimingState.cs’:
using System.Collections;
using RPG.States.Player;
using UnityEngine;

public class PlayerRangerAimingState : PlayerBaseState
{
    private float mouseX;
    private float mouseY;
    private float sensitivity = 100f;
    private float maxYRotation = 80f;
    private float minYRotation = -80f;

    private readonly int RangerStateBowLoadHash = Animator.StringToHash("RangerStateBowLoad");


    public PlayerRangerAimingState(PlayerStateMachine stateMachine) : base(stateMachine) {}

    public override void Enter()
    {
        Debug.Log($"Player has entered Ranger Aiming State");
        stateMachine.InputReader.RangerAimCancelEvent += CancelAim;
        stateMachine.rangerAimingCamera.m_Priority = 11;
        stateMachine.Animator.CrossFadeInFixedTime(RangerStateBowLoadHash, stateMachine.CrossFadeDuration);

        // Capture the initial camera rotation
        mouseX = stateMachine.transform.eulerAngles.y;
        mouseY = stateMachine.transform.eulerAngles.x;
    }

    public override void Tick(float deltaTime)
    {
        if (stateMachine.InputReader.IsAttacking) 
        {
            stateMachine.SwitchState(new PlayerRangerFiringState(stateMachine));
            return;
        }

        HandleRotation(deltaTime);
        Move(deltaTime);
    }

    public override void Exit()
    {
        Debug.Log($"IsAttacking: {stateMachine.InputReader.IsAttacking}");
        Debug.Log($"Player has exited Ranger Aiming State");
        stateMachine.InputReader.RangerAimCancelEvent -= CancelAim;
        stateMachine.rangerAimingCamera.m_Priority = 9;

        if (stateMachine.InputReader.IsAttacking) { return; }
        else // if you're not attacking, reset the rotation 
        // (because you're going back to FreeLook State, from 'CancelAim')
        {
        // Reset the y-axis rotation to zero:
        Quaternion resetYRotation = Quaternion.Euler(0, stateMachine.transform.eulerAngles.y, 0);
        stateMachine.transform.rotation = resetYRotation;
        }
    }

    private void HandleRotation(float deltaTime)
    {
        float mouseXDelta = Input.GetAxis("Mouse X") * sensitivity * deltaTime;
        float mouseYDelta = Input.GetAxis("Mouse Y") * sensitivity * deltaTime;

        mouseX += mouseXDelta;
        mouseY -= mouseYDelta;
        mouseY = Mathf.Clamp(mouseY, minYRotation, maxYRotation);

        // Quaternion targetRotation = initialCameraRotation * Quaternion.Euler(mouseY, mouseX, 0);
        Quaternion targetRotation = Quaternion.Euler(mouseY, mouseX, 0);
        stateMachine.transform.rotation = targetRotation;
    }

    private void CancelAim()
    {
        SetLocomotionState();
    }
}
  1. When you’re firing, there’s a ‘PlayerRangerFiringState.cs’ for that:
using System.Collections;
using RPG.States.Player;
using UnityEngine;

public class PlayerRangerFiringState : PlayerBaseState
{
    private readonly int RangerStateBowFireHash = Animator.StringToHash("RangerStateBowFire");
    private bool hasFired;

    public PlayerRangerFiringState(PlayerStateMachine stateMachine) : base(stateMachine)
    {
        hasFired = false;
    }

    public override void Enter()
    {
        Debug.Log($"Player Entered Ranger Firing State");
        stateMachine.Animator.CrossFadeInFixedTime(RangerStateBowFireHash, stateMachine.CrossFadeDuration);
        stateMachine.rangerAimingCamera.m_Priority = 11;
        hasFired = false;
        stateMachine.InputReader.RangerAimCancelEvent += CancelAim;
    }

    public override void Tick(float deltaTime)
    {
        Move(deltaTime);

        if (!stateMachine.InputReader.IsRangerAiming) 
        {
            ReturnToFreeLookState();
            return;
        }

        if (!hasFired && stateMachine.Animator.GetCurrentAnimatorStateInfo(0).IsName("RangerStateBowFire") && stateMachine.Animator.GetCurrentAnimatorStateInfo(0).normalizedTime >= 1.0f) 
        {
            // animation in layer 0 (i.e: the animation layer this animation is getting its info from), has finished playing
            // (i.e: normalized time > 1.0f)
            hasFired = true;
            ReturnToRangerAimingState();
        }
    }

    public override void Exit()
    {
        if (!stateMachine.InputReader.IsRangerAiming) // returning to freelook from firing
        {
            stateMachine.rangerAimingCamera.m_Priority = 9;
        }
        else 
        {
            stateMachine.rangerAimingCamera.m_Priority = 11; // returning to aiming state
        }

        // Reset the y-axis rotation to zero:
        Quaternion resetYRotation = Quaternion.Euler(0, stateMachine.transform.eulerAngles.y, 0);
        stateMachine.transform.rotation = resetYRotation;
        stateMachine.InputReader.RangerAimCancelEvent -= CancelAim;
    }

    private void ReturnToRangerAimingState()
    {
        stateMachine.SwitchState(new PlayerRangerAimingState(stateMachine));
    }

    private void ReturnToFreeLookState()
    {
        stateMachine.SwitchState(new PlayerFreeLookState(stateMachine));

        /* // Reset the y-axis rotation to zero:
        Quaternion resetRotation = Quaternion.Euler(0, stateMachine.transform.eulerAngles.y, 0);
        stateMachine.transform.rotation = resetRotation; */
    }

    private void CancelAim() 
    {
        ReturnToFreeLookState();
    }
}

I got stuck because I was unable to get the correct rotation when exiting the firing state to the aiming state again, and I am absolutely terrible when it comes to rotations… They genuinely confuse me… So I’m seeking help, please, or I’ll just wait until Brian returns and tries to help me with this :slight_smile: (and honestly, it was introducing so many bugs I can’t even get to fix because I can’t even fix the rotation issue…!)

and somehow, it was also colliding with my horse riding system… I don’t know what miracle was going on there, but with the Virtual camera in the scene, sometimes it will, quite literally, work on its own the moment I ride the horse (i.e: When I deactivate my player state machine to activate the state machine for mounting animals)

(It’s really sad for me to quit on this one tbh, because I really wanted this system into my project!)

I just hope we can fix this system up, because frankly speaking, it is an important one


Random errors:

There’s also this error that sometimes happens when I launch my game, ever since I introduced my Cinemachine Virtual Camera:

NullReferenceException: Object reference not set to an instance of an object
Cinemachine.Editor.CinemachineSceneToolUtility+<>c.<.cctor>b__21_0 () (at Library/PackageCache/com.unity.cinemachine@2.9.7/Editor/Utility/CinemachineSceneTools.cs:272)
UnityEditor.EditorApplication.Internal_CallUpdateFunctions () (at <57a8ad0d1492436d8cfee9ba8e6618f8>:0)

I have absolutely no clue where it’s coming from

Just a bit of an update here, I fixed the error with part 1, regarding the return of the camera to a position irrelevant to its own. However, I did that at the cost of removing the clamping constraint on the camera, because the error was related to the clamping somehow, and that needs to be fixed. It was all in ‘PlayerRangerAimingState.HandleRotation()’. If you take a closer look, there was a line of ‘mouseY = Mathf.Clamp(…)’ that got erased (that was the troubling line), and here’s the updated ‘PlayerRangerAimingState’ state machine:

using RPG.States.Player;
using UnityEngine;

public class PlayerRangerAimingState : PlayerBaseState
{
    // MOUSE MOVEMENT AND SENSITIVITY
    private float mouseX;
    private float mouseY;
    private float sensitivity = 100f;

    // CLAMP VALUES
    private float maxXRotation = 80f;
    private float minXRotation = -80f;

    private readonly int RangerStateBowLoadHash = Animator.StringToHash("RangerStateBowLoad");

    public PlayerRangerAimingState(PlayerStateMachine stateMachine) : base(stateMachine) {}

    public override void Enter()
    {
        stateMachine.Health.OnTakenHit += OnTakenHit;

        Debug.Log($"Player has entered Ranger Aiming State");
        stateMachine.InputReader.RangerAimCancelEvent += CancelAim;
        stateMachine.rangerAimingCamera.m_Priority = 11;
        stateMachine.Animator.CrossFadeInFixedTime(RangerStateBowLoadHash, stateMachine.CrossFadeDuration);

        // Capture the initial camera rotation
        mouseX = stateMachine.transform.eulerAngles.y;
        mouseY = stateMachine.transform.eulerAngles.x;
    }

    public override void Tick(float deltaTime)
    {
        if (stateMachine.Fighter.GetCurrentWeaponConfig().HasProjectile() && !stateMachine.Fighter.CheckForProjectileAndSufficientAmmo())
        {
            // (DEBATABLE IDEA) needs ammo, but no ammo in the Quiver. Switch to freelook and block aiming
            stateMachine.SwitchState(new PlayerFreeLookState(stateMachine));
            return;
        }

        if (stateMachine.InputReader.IsAttacking && !stateMachine.CooldownTokenManager.HasCooldown("RangerFiringCooldown"))
        {
            stateMachine.SwitchState(new PlayerRangerFiringState(stateMachine));
            return;
        }

        HandleRotation(deltaTime);
        Move(deltaTime);
    }

    public override void Exit()
    {
        stateMachine.Health.OnTakenHit -= OnTakenHit;

        Debug.Log($"IsAttacking: {stateMachine.InputReader.IsAttacking}");
        Debug.Log($"Player has exited Ranger Aiming State");
        stateMachine.InputReader.RangerAimCancelEvent -= CancelAim;
        stateMachine.rangerAimingCamera.m_Priority = 9;

        if (stateMachine.InputReader.IsAttacking) 
        { 
            // No rotation modifications will be done here, that'll just ruin everything
            return;
        }
        else
        {
            // if you're not attacking, reset the y-axis euler rotation
            // (because you're going back to FreeLook State, from 'CancelAim')

            // Reset the y-axis rotation to zero:
            Quaternion resetYRotation = Quaternion.Euler(0, stateMachine.transform.eulerAngles.y, 0);
            stateMachine.transform.rotation = resetYRotation;
        }
    }

    private void HandleRotation(float deltaTime)
    {
        float mouseXDelta = Input.GetAxis("Mouse X") * sensitivity * deltaTime;
        float mouseYDelta = Input.GetAxis("Mouse Y") * sensitivity * deltaTime;

        mouseX += mouseXDelta;
        mouseY -= mouseYDelta;

        Quaternion targetRotation = Quaternion.Euler(mouseY, mouseX, 0);
        stateMachine.transform.rotation = targetRotation;
    }

    private void CancelAim()
    {
        SetLocomotionState();
    }

    void OnTakenHit(GameObject instigator)
    {
        // Makes sure that when the player gets hit, he goes to the right state and not mess this up
        // (Bug Fix)
        if (stateMachine.InputReader.IsRangerAiming) 
        {
            stateMachine.SwitchState(new PlayerRangerAimingState(stateMachine));
        }
        else stateMachine.SwitchState(new PlayerFreeLookState(stateMachine));
    }
}

along with a few other mini fixes.


Tomorrow, I want to see how in the world is this script related to causing some serious bugs when I ride Malbers’ horse, because there’s something extremely interesting, and extremely wrong as well, going on there, and I am extremely confused to say the least, because these two are not supposed to have a connection at all (not yet, at least!)

I mean… I could deactivate and reactivate the RangerAim camera when needed, but that would leave an unresolved bug (and a loophole) in the system, so I want to figure out the source of the bug instead, because I don’t want any sort of problems with my code in the future, but this became really hard when the priority of the camera didn’t even change to begin with… (and the reason why aiming and unaiming a bow fixes the problem, is because it’s the only way to bring the priority back down to 9, lower than the freelook camera’s priority)

I’m guessing it automatically happens because when switching the state machines, the other one has no cinemachine camera, so it’s holding on to whatever camera it can find in the scene?

For the time being, I think it’s safe to say that I figured the entire thing out, with god’s miracle and the generous help of a good Unity Forums official Unity Tech programmer. Here’s what I came up with, although it’s not finalized yet, but for 99% of use cases, it should work just fine

(I’m assuming you’ve followed along with @Brian_Trotter 's Merge between the third person and the RPG Courses. If not, this State Machine won’t make much sense to you):

using RPG.States.Player;
using UnityEngine;

public class PlayerRangerAimingState : PlayerBaseState
{
    // MOUSE MOVEMENT AND SENSITIVITY
    private float mouseX;
    private float mouseY;
    private float sensitivity = 100f;

    // CLAMP VALUES
    private float maxXRotation = 80f;
    private float minXRotation = -80f;

    private readonly int RangerStateBowLoadHash = Animator.StringToHash("RangerStateBowLoad");

    public PlayerRangerAimingState(PlayerStateMachine stateMachine) : base(stateMachine) {}

    public override void Enter()
    {
        stateMachine.Health.OnTakenHit += OnTakenHit; // Bug fix, when the player takes a hit

        Debug.Log($"Player has entered Ranger Aiming State");
        stateMachine.InputReader.RangerAimCancelEvent += CancelAim;
        stateMachine.rangerAimingCamera.m_Priority = 11;
        stateMachine.Animator.CrossFadeInFixedTime(RangerStateBowLoadHash, stateMachine.CrossFadeDuration);

        // Capture the initial camera rotation
        mouseX = stateMachine.transform.eulerAngles.y;
        mouseY = stateMachine.transform.eulerAngles.x;
    }

    public override void Tick(float deltaTime)
    {
        /* if (stateMachine.Fighter.GetCurrentWeaponConfig().HasProjectile() && !stateMachine.Fighter.CheckForProjectileAndSufficientAmmo())
        {
            // (DEBATABLE IDEA) needs ammo, but no ammo in the Quiver? Switch to freelook and block aiming
            stateMachine.SwitchState(new PlayerFreeLookState(stateMachine));
            return;
        } */

        if (stateMachine.InputReader.IsAttacking && !stateMachine.CooldownTokenManager.HasCooldown("RangerFiringCooldown"))
        {
            // Cooldown is over, and player is attempting to attack? Go back to firing arrows:
            stateMachine.SwitchState(new PlayerRangerFiringState(stateMachine));
            return;
        }

        HandleRotation(deltaTime);
        Move(deltaTime);
    }

    public override void Exit()
    {
        stateMachine.Health.OnTakenHit -= OnTakenHit; // Bug fix, when the player takes a hit

        Debug.Log($"IsAttacking: {stateMachine.InputReader.IsAttacking}");
        Debug.Log($"Player has exited Ranger Aiming State");
        stateMachine.InputReader.RangerAimCancelEvent -= CancelAim;
        stateMachine.rangerAimingCamera.m_Priority = 9;

        if (stateMachine.InputReader.IsAttacking) 
        { 
            // No rotation modifications will be done here, that'll just ruin everything
            return;
        }
        else
        {
            // if you're not attacking, reset the y-axis euler rotation
            // (because you're going back to FreeLook State, from 'CancelAim')

            // Reset the y-axis rotation to zero:
            Quaternion resetYRotation = Quaternion.Euler(0, stateMachine.transform.eulerAngles.y, 0);
            stateMachine.transform.rotation = resetYRotation;
        }
    }

    private void HandleRotation(float deltaTime)
    {
        float mouseXDelta = Input.GetAxis("Mouse X") * sensitivity * deltaTime;
        float mouseYDelta = Input.GetAxis("Mouse Y") * sensitivity * deltaTime;

        mouseX += mouseXDelta;
        mouseY -= mouseYDelta;

        mouseY = NormalizeAngle(mouseY);
        mouseY = Mathf.Clamp(mouseY, minXRotation, maxXRotation);

        Quaternion targetRotation = Quaternion.Euler(mouseY, mouseX, 0);
        stateMachine.transform.rotation = targetRotation;
    }

    private void CancelAim()
    {
        SetLocomotionState();
    }

    private float NormalizeAngle(float angle)
    {
        if (angle > 180) angle -= 360;
        if (angle < -180) angle += 360;
        return angle;
    }

    void OnTakenHit(GameObject instigator)
    {
        // Makes sure that when the player gets hit, he goes to the right state and not mess this up
        // (Bug Fix)
        if (stateMachine.InputReader.IsRangerAiming)
        {
            stateMachine.SwitchState(new PlayerRangerAimingState(stateMachine));
        }
        else stateMachine.SwitchState(new PlayerFreeLookState(stateMachine));
    }
}

I was told on the Unity forums page though, to reduce the sensitivity and eliminate the ‘deltaTime’ multiplication in ‘HandleRotation’, otherwise it’ll work frame-dependent and cause bad results, so I’ll work on that next

For now, enjoy your new algorithm :slight_smile:

My next step will be to investigate a way to get the correct UI Cursor to show up at the right time, at the strike spot, because right now it’s still a little frustrating

OK umm… how do you setup a 2D UI Aim to show us where the arrow is supposed to be going again? I found this tutorial, but it wasn’t of much help (because to begin with, that part was done :sweat_smile:)

@bixarrio any ideas on how to setup a 2D Aim UI in the game scene?

Edit: Ahh nevermind, I’m halfway through the process

New day, new problem:

Hello all. I’m trying to develop a Ranger Aiming System in my game, and I’m currently on the final step of trying to get the player to rotate, before he enters the Ranger Aiming State (my architecture follows the State Machine model, for context), to wherever the Cinemachine FreeLook camera is pointing at. The goal is to get the player to rotate in direction of wherever the Cinemachine FreeLook Camera is pointing at in my game scene, when we are about to access the Ranger Aiming State, but right now whilst it’s not random, it’s not working as expected at all, and I’m guessing it’s a mathematical issue…

I asked ChatGPT for a little bit of help, and whilst we got somewhat better results, I feel like sometimes it’s still random, so we developed 2 checks, one where the camera finds something by hitting a Raycast (where the player is expected to rotate to whatever the camera hit), and one where the camera can’t find anything 100 meters from where it started (in this case, the player points at wherever the camera’s forward is), but still… it’s a little random (again, it’s a Cinemachine FreeLook Camera)

Can someone kindly guide me through this problem? Here’s what I developed so far:

// TEST - Player Ranger Aiming State:
        private void InputReader_HandleRangerAimingEvent()
        {
            if (stateMachine.Fighter.GetCurrentWeaponConfig().GetIsRangerAimWeapon())
            {
                RotatePlayerToCameraDirection();
                stateMachine.SwitchState(new PlayerRangerAimingState(stateMachine));
            }
        }

        // TEST - Rotating the Player before entering the Ranger Aiming Phase:
        private void RotatePlayerToCameraDirection()
        {
            if (stateMachine.MainFreeLookCamera != null)
            {
                Vector3 rayOrigin = stateMachine.MainFreeLookCamera.transform.position;
                Vector3 rayDirection = stateMachine.MainFreeLookCamera.transform.forward;

                Ray ray = new Ray(rayOrigin, rayDirection);
                if (Physics.Raycast(ray, out RaycastHit hit)) // if the camera fired a raycast that hit something, point the player to it
                {
                    Vector3 targetDirection = (hit.point - stateMachine.transform.position).normalized; // (Player) --> (hit.point), literally
                    targetDirection.y = 0;

                    targetDirection = -targetDirection;
                
                    Quaternion targetRotation = Quaternion.LookRotation(targetDirection, Vector3.up);
                    targetRotation = NormalizeRotation(targetRotation);
                    stateMachine.transform.rotation = targetRotation;
                    Debug.Log($"Rotating player to match the Raycast Direction: {targetDirection}");
                }
                else // if the camera fired a raycast that hit nothing, set the player's rotation to match that of the camera
                {
                    Vector3 fallbackDirection = stateMachine.MainFreeLookCamera.transform.forward; // direction of the camera, forward
                    fallbackDirection.y = 0;

                    fallbackDirection = -fallbackDirection;
                
                    Quaternion fallbackRotation = Quaternion.LookRotation(fallbackDirection, Vector3.up);
                    fallbackRotation = NormalizeRotation(fallbackRotation);
                    stateMachine.transform.rotation = fallbackRotation;

                    Debug.Log($"Rotating player to match the fallback camera direction: {fallbackDirection}");
                }
            }
            else Debug.Log($"MainFreeLookCamera is not assigned in PlayerStateMachine");
        }

        private Quaternion NormalizeRotation(Quaternion rotation)
        {
            Vector3 euler = rotation.eulerAngles;
            euler.x = NormalizeAngle(euler.x);
            euler.y = NormalizeAngle(euler.y);
            euler.z = NormalizeAngle(euler.z);
            return Quaternion.Euler(euler);
        }

        private float NormalizeAngle(float angle)
        {
            if (angle > 180) angle -= 360;
            if (angle < -180) angle += 360;
            return angle;
        }

I also introduced a Normalizer to deal with Unity’s Internal workings with Euler Angles, but that wasn’t enough. Please send help

(and yes, the logic can be wrong. I’m not sure at this point in time, to be honest)

Edit: I figured this one out. It was somewhat easier than this one, but mathematically challenging to figure out…

Ahh, nevermind, I figured this out. That system is one absolute (forgive my hype) banger. I’m keeping this and the Targeting system. Let’s just hope I don’t forget the targeting system is there, xD

But this one REALLY, REALLY, REALLY catches the action!

But now, I’m dealing with animation issues, because on the back of a horse, for god knows what reason, the bow gets in the way, and the arrow hits the bow :sweat_smile:

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms