Hi,
I’ve finished all the lectures for 3rd person combat and traversal and now I’m trying to implement target switching whilst in the targeting state. I’ve managed to get the basic functionality working by following this topic Toggle between different lock on targets but have hit a couple of issues:
The first one is with using the right stick, I’ve done this by adding a new CycleTargetEvent event to the onlook method I’ve managed to get it to switch targets but it starts jumping back and forth and the camera flickers alot, I think this is due to the right stick also being used for looking around in the free look state and the fact that the analogue input in a vector2 rather than a button press on the action map. I’ve proved that the code works when on a new button input (e.g. North gamepad button). Do I need to alter how the right analogue behaves while targeting?
Input Reader:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class InputReader : MonoBehaviour, Controls.IPlayerActions
{
public bool IsAttacking {get; private set; }
public bool IsBlocking { get; private set; }
public bool IsAiming {get; private set; }
public Vector2 MovementValue {get; private set; }
public Vector2 MousePosition;
public Vector2 look;
public event Action JumpEvent;
public event Action DodgeEvent;
public event Action TargetEvent;
public event Action CycleTargetEvent;
private Controls controls;
private void Start()
{
controls = new Controls();
controls.Player.SetCallbacks(this);
controls.Player.Enable();
}
private void onDestroy()
{
controls.Player.Disable();
}
public void OnJump(InputAction.CallbackContext context)
{
if(!context.performed) { return; }
JumpEvent?.Invoke();
}
public void OnDodge(InputAction.CallbackContext context)
{
if(!context.performed) { return; }
DodgeEvent?.Invoke();
}
public void OnMove(InputAction.CallbackContext context)
{
MovementValue = context.ReadValue<Vector2>();
}
public void OnLook(InputAction.CallbackContext context)
{
look = context.ReadValue<Vector2>();
look.y = (look.y * -1);
CycleTargetEvent?.Invoke();
}
public void OnTarget(InputAction.CallbackContext context)
{
if(!context.performed) { return; }
//if(!stateMachine.PlayerTargetingState())
TargetEvent?.Invoke();
}
public void OnAttack(InputAction.CallbackContext context)
{
if(context.performed)
{
IsAttacking = true;
}
else if(context.canceled)
{
IsAttacking = false;
}
}
public void OnAim(InputAction.CallbackContext context)
{
if(context.performed)
{
IsAiming = true;
}
else if(context.canceled)
{
IsAiming = false;
}
}
public void OnBlock(InputAction.CallbackContext context)
{
if (context.performed)
{
IsBlocking = true;
}
else if (context.canceled)
{
IsBlocking = false;
}
}
}
PlayerTargetingState:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerTargetingState : PlayerBaseState
{
private readonly int TargetingBlendTreeHash = Animator.StringToHash("TargetingBlendTree");
private readonly int TargetingForwardHash = Animator.StringToHash("TargetingForward");
private readonly int TargetingRightHash = Animator.StringToHash("TargetingRight");
private const float CrossFadeDuration = 0.1f;
public PlayerTargetingState(PlayerStateMachine stateMachine) : base(stateMachine) { }
public override void Enter()
{
stateMachine.InputReader.TargetEvent += OnTarget;
stateMachine.InputReader.DodgeEvent += OnDodge;
stateMachine.InputReader.JumpEvent += OnJump;
stateMachine.InputReader.CycleTargetEvent += OnCycleTarget;
stateMachine.Animator.CrossFadeInFixedTime(TargetingBlendTreeHash, CrossFadeDuration);
}
public override void Tick(float deltaTime)
{
if(stateMachine.InputReader.IsAttacking)
{
stateMachine.SwitchState(new PlayerAttackingState(stateMachine, 0));
return;
}
if (stateMachine.InputReader.IsBlocking)
{
stateMachine.SwitchState(new PlayerBlockingState(stateMachine));
return;
}
if (stateMachine.Targeter.CurrentTarget == null)
{
stateMachine.SwitchState(new PlayerFreeLookState(stateMachine));
return;
}
Vector3 movement = CalculateMovement(deltaTime);
Move(movement * stateMachine.TargetingMovementSpeed, deltaTime);
UpdateAnimator(deltaTime);
FaceTarget();
}
public override void Exit()
{
stateMachine.InputReader.TargetEvent -= OnTarget;
stateMachine.InputReader.DodgeEvent -= OnDodge;
stateMachine.InputReader.JumpEvent -= OnJump;
stateMachine.InputReader.CycleTargetEvent -= OnCycleTarget;
}
private void OnCycleTarget()
{
stateMachine.Targeter.SelectNextTarget();
}
private void OnTarget()
{
stateMachine.Targeter.Cancel();
stateMachine.SwitchState(new PlayerFreeLookState(stateMachine));
}
private void OnDodge()
{
stateMachine.SwitchState(new PlayerDodgingState(stateMachine, stateMachine.InputReader.MovementValue));
}
private void OnJump()
{
stateMachine.SwitchState(new PlayerJumpingState(stateMachine));
}
private Vector3 CalculateMovement(float deltaTime)
{
Vector3 movement = new Vector3();
movement += stateMachine.transform.right * stateMachine.InputReader.MovementValue.x;
movement += stateMachine.transform.forward * stateMachine.InputReader.MovementValue.y;
return movement;
}
private void UpdateAnimator(float deltaTime)
{
if(stateMachine.InputReader.MovementValue.y == 0)
{
stateMachine.Animator.SetFloat(TargetingForwardHash, 0, 0.1f, deltaTime);
}
else
{
float value = stateMachine.InputReader.MovementValue.y > 0 ? 1f : -1f;
stateMachine.Animator.SetFloat(TargetingForwardHash, value, 0.1f, deltaTime);
}
if(stateMachine.InputReader.MovementValue.x == 0)
{
stateMachine.Animator.SetFloat(TargetingRightHash, 0, 0.1f, deltaTime);
}
else
{
float value = stateMachine.InputReader.MovementValue.x > 0 ? 1f : -1f;
stateMachine.Animator.SetFloat(TargetingRightHash, value, 0.1f, deltaTime);
}
}
}
Targeter:
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Cinemachine;
using UnityEngine;
public class Targeter : MonoBehaviour
{
[SerializeField] private CinemachineTargetGroup cineTargetGroup;
private Camera mainCamera;
public List<Target> targets = new List<Target>();
public Target CurrentTarget { get; private set; }
private void Start()
{
mainCamera = Camera.main;
}
private void OnTriggerEnter(Collider other)
{
if (!other.TryGetComponent<Target>(out Target target)){ return; }
targets.Add(target);
target.OnDestroyed += RemoveTarget;
}
private void OnTriggerExit(Collider other)
{
if (!other.TryGetComponent<Target>(out Target target)){ return; }
RemoveTarget(target);
}
public bool SelectTarget()
{
if (targets.Count == 0) { return false; }
Target closestTarget = null;
float closestTargetDistance = Mathf.Infinity;
foreach (Target target in targets)
{
Vector2 viewPos = mainCamera.WorldToViewportPoint(target.transform.position);
if(!target.GetComponentInChildren<Renderer>().isVisible)
{
continue;
}
Vector2 toCenter = viewPos - new Vector2(0.5f, 0.5f);
if(toCenter.sqrMagnitude < closestTargetDistance)
{
closestTarget = target;
closestTargetDistance = toCenter.sqrMagnitude;
}
}
if(closestTarget == null) { return false; }
CurrentTarget = closestTarget;
cineTargetGroup.AddMember(CurrentTarget.transform, 1f, 2f);
return true;
}
public void SelectNextTarget()
{
if (targets.Count == 0) return;
// Find the current target's index
var currentIndex = targets.IndexOf(CurrentTarget);
if (currentIndex == -1)
{
// The current target is no longer in the list. Just select the closest target
SelectTarget();
return;
}
// move to the next index, wrapping around if we're at the end
var nextIndex = (currentIndex + 1) % targets.Count;
SetCurrentTarget(targets[nextIndex]);
}
public void Cancel()
{
if (CurrentTarget == null) { return; }
cineTargetGroup.RemoveMember(CurrentTarget.transform);
CurrentTarget = null;
}
private void SetCurrentTarget(Target newCurrent)
{
// Remove current target from cm target group
cineTargetGroup.RemoveMember(CurrentTarget.transform);
// Set the new current target
CurrentTarget = newCurrent;
// Add new target to cm target group
cineTargetGroup.AddMember(CurrentTarget.transform, 1f, 2f);
}
private void RemoveTarget(Target target)
{
if(CurrentTarget == target)
{
cineTargetGroup.RemoveMember(CurrentTarget.transform);
CurrentTarget = null;
}
target.OnDestroyed -= RemoveTarget;
targets.Remove(target);
}
}
The second is that ideally what I want is to be able to switch targets based on left right analogue inputs, so right will find the closest next target on the right and left will find the next closest target on the left, so far all I’ve manged is to get it switching to the next closest.
I’ve tried a few things to get this working, all of them required a lot of googling, but I’ve ended up with a similar result every time. In the end I removed all the code and went with the method described in the mentioned topic as it was the cleanest, but I’m still unsure how to handle the behaviour I want.