lockon, targetting and rolls control improvements

This commit is contained in:
2025-07-25 16:47:19 +02:00
parent f5c1c37d32
commit 630b9a7b53
6 changed files with 249 additions and 235 deletions

View File

@@ -10,37 +10,28 @@ namespace Beyond
{
public class AutoTargetting : MonoBehaviour
{
// ... (other headers and variables)
#region Fields & Properties
[Header("Targeting Parameters")]
[Tooltip("Maximum distance AutoTargetting will consider an enemy for selection.")]
public float maxTargetingDistance = 20f;
[Tooltip("How often (in seconds) to re-evaluate for a new target during combat.")]
public float targetingInterval = 0.25f;
[Tooltip("Maximum angle (in degrees from player's forward) within which an enemy can be auto-targeted.")]
public float targetingAngleThreshold = 90f;
[Header("Rotation Parameters")]
[Tooltip("Speed at which the player rotates towards the current target when rotation is explicitly called (e.g., by MagicAttacks).")]
public float playerRotationSpeed = 10f;
[Header("Visuals")]
// ... (visual parameters)
public string materialHighlightPropertyName = "_FresnelColor";
[ColorUsage(true, true)]
public Color highlightColor = Color.white;
[ColorUsage(true, true)]
public Color deselectHighlightColor = Color.black;
[ColorUsage(true, true)] public Color highlightColor = Color.white;
[ColorUsage(true, true)] public Color deselectHighlightColor = Color.black;
public float highlightFadeDuration = 0.3f;
public bool preferSkinnedMeshRenderer = true;
[Header("Lock-On Integration")]
[Tooltip("If true, automatically locks onto the target selected by AutoTargetting.")]
public bool autoLockSelectedTarget = false;
[Tooltip("Reference to the bLockOn script, usually on the player. Will try to find if not set.")]
public bool alwaysLockOnInCombat = true;
public bLockOn targetLockSystem;
[Tooltip("How long (in seconds) AutoTargetting will pause after a manual target switch before re-evaluating.")]
public float manualSwitchCooldownDuration = 0.75f; // Cooldown duration
public float manualSwitchCooldownDuration = 0.75f;
public vFSMBehaviourController CurrentTarget { get; private set; }
public event Action<vFSMBehaviourController> OnTargetSelected;
@@ -51,14 +42,16 @@ namespace Beyond
private Dictionary<Material, Color> _originalMaterialColors = new Dictionary<Material, Color>();
private Dictionary<Material, Coroutine> _materialToFadeCoroutineMap = new Dictionary<Material, Coroutine>();
private Transform _playerTransform;
// Cooldown variables
private bThirdPersonController _playerController;
private bool _manualSwitchCooldownActive = false;
private float _manualSwitchCooldownTimer = 0f;
#endregion
#region Unity Methods
void Start()
{
// ... (Start logic mostly the same)
if (Player.Instance == null)
{
Debug.LogError("AutoTargetting: Player.Instance is not available at Start!");
@@ -66,6 +59,12 @@ namespace Beyond
}
_playerTransform = Player.Instance.transform;
_playerController = Player.Instance.GetComponent<bThirdPersonController>();
if (_playerController == null)
{
Debug.LogError("AutoTargetting: Could not find bThirdPersonController on Player.Instance! Custom roll rotation may not work correctly.");
}
_gameStateManager = GameStateManager.Instance;
if (_gameStateManager != null)
{
@@ -80,11 +79,10 @@ namespace Beyond
if (targetLockSystem == null)
{
if (Player.Instance != null) targetLockSystem = Player.Instance.GetComponentInChildren<bLockOn>(true);
if (targetLockSystem == null) targetLockSystem = GetComponent<bLockOn>(); // Fallback
targetLockSystem = Player.Instance.GetComponentInChildren<bLockOn>(true);
if (targetLockSystem == null)
{
Debug.LogWarning("AutoTargetting: bLockOn system not found. Auto-lock and target sync will be disabled.");
Debug.LogWarning("AutoTargetting: bLockOn system not found. Auto-lock will be disabled.");
autoLockSelectedTarget = false;
}
}
@@ -98,42 +96,42 @@ namespace Beyond
void OnDestroy()
{
// ... (Cleanup logic)
if (_gameStateManager != null)
{
_gameStateManager.m_OnStateChanged.RemoveListener(HandleGameStateChanged);
}
StopAndClearAllFadeCoroutines();
if (_targetingLoopCoroutine != null)
{
StopCoroutine(_targetingLoopCoroutine);
_targetingLoopCoroutine = null;
}
if (_gameStateManager != null) _gameStateManager.m_OnStateChanged.RemoveListener(HandleGameStateChanged);
StopAndClearAllFadeCoroutines(); // This call is correct
if (_targetingLoopCoroutine != null) StopCoroutine(_targetingLoopCoroutine);
if (targetLockSystem != null)
{
targetLockSystem.onLockOnTarget.RemoveListener(HandleLockOnSystemTargetChanged);
targetLockSystem.onUnLockOnTarget.RemoveListener(HandleLockOnSystemTargetUnlocked);
}
}
#endregion
#region Core Logic
// --- THIS IS THE MISSING METHOD THAT HAS BEEN RESTORED ---
private void StopAndClearAllFadeCoroutines()
{
foreach (var pair in _materialToFadeCoroutineMap)
{
if (pair.Value != null) StopCoroutine(pair.Value);
if (pair.Value != null)
{
StopCoroutine(pair.Value);
}
}
_materialToFadeCoroutineMap.Clear();
}
// --- END OF RESTORED METHOD ---
private void HandleGameStateChanged(GameStateManager.State newState)
{
if (newState == GameStateManager.State.COMBAT)
{
if (_targetingLoopCoroutine == null)
{
_targetingLoopCoroutine = StartCoroutine(TargetingLoop());
}
if (_targetingLoopCoroutine == null) _targetingLoopCoroutine = StartCoroutine(TargetingLoop());
}
else
{
@@ -142,11 +140,8 @@ namespace Beyond
StopCoroutine(_targetingLoopCoroutine);
_targetingLoopCoroutine = null;
}
if (CurrentTarget != null)
{
SetNewTarget(null, true); // Force immediate update if leaving combat
}
_manualSwitchCooldownActive = false; // Reset cooldown when leaving combat
if (CurrentTarget != null) SetNewTarget(null, true);
_manualSwitchCooldownActive = false;
}
}
@@ -156,48 +151,22 @@ namespace Beyond
{
if (_manualSwitchCooldownActive)
{
_manualSwitchCooldownTimer -= targetingInterval; // Or Time.deltaTime if loop is faster
if (_manualSwitchCooldownTimer <= 0)
{
_manualSwitchCooldownActive = false;
// Debug.Log("AutoTargetting: Manual switch cooldown ended.");
}
_manualSwitchCooldownTimer -= targetingInterval;
if (_manualSwitchCooldownTimer <= 0) _manualSwitchCooldownActive = false;
}
if (!_manualSwitchCooldownActive) // Only update target if cooldown is not active
if (!_manualSwitchCooldownActive)
{
if (_playerTransform == null && Player.Instance != null)
{
_playerTransform = Player.Instance.transform;
}
if (_playerTransform != null)
{
UpdateTarget();
}
if (_playerTransform == null && Player.Instance != null) _playerTransform = Player.Instance.transform;
if (_playerTransform != null) UpdateTarget();
}
yield return new WaitForSeconds(targetingInterval);
}
}
public bool IsTargetInAngle(Transform sourceTransform, vFSMBehaviourController targetAI, float angleThreshold)
{
// ... (IsTargetInAngle logic - unchanged)
if (targetAI == null || sourceTransform == null) return false;
Vector3 directionToTarget = (targetAI.transform.position - sourceTransform.position);
directionToTarget.y = 0;
if (directionToTarget.sqrMagnitude < 0.0001f) return true;
directionToTarget.Normalize();
float angle = Vector3.Angle(sourceTransform.forward, directionToTarget);
return angle <= angleThreshold;
}
private void UpdateTarget()
{
if (_playerTransform == null || _gameStateManager == null) return;
if (_manualSwitchCooldownActive) return; // Double check, though TargetingLoop should handle it
if (_playerTransform == null || _gameStateManager == null || _manualSwitchCooldownActive) return;
vFSMBehaviourController bestCandidate = null;
float minDistanceSqr = maxTargetingDistance * maxTargetingDistance;
@@ -222,161 +191,107 @@ namespace Beyond
}
}
if (CurrentTarget != bestCandidate)
{
SetNewTarget(bestCandidate);
}
else if (CurrentTarget != null && !IsTargetValid(CurrentTarget))
{
SetNewTarget(null);
}
if (CurrentTarget != bestCandidate) SetNewTarget(bestCandidate);
else if (CurrentTarget != null && !IsTargetValid(CurrentTarget)) SetNewTarget(null);
}
private bool IsTargetValid(vFSMBehaviourController target)
{
// ... (IsTargetValid logic - unchanged)
if (target == null || !target.gameObject.activeInHierarchy || target.aiController.currentHealth <= 0) return false;
if (_playerTransform == null) return false;
if (!IsTargetInAngle(_playerTransform, target, targetingAngleThreshold)) return false;
float distSqr = (target.transform.position - _playerTransform.position).sqrMagnitude;
return distSqr <= (maxTargetingDistance * maxTargetingDistance);
}
/// <summary>
/// Sets a new target for AutoTargetting.
/// </summary>
/// <param name="newTarget">The new target. Can be null.</param>
/// <param name="forceLockSystemUpdate">If true, ManuallySetLockOnTarget will be called even if newTarget is same as CurrentTarget (used for re-assertion).</param>
private void SetNewTarget(vFSMBehaviourController newTarget, bool forceLockSystemUpdate = false)
{
if (_manualSwitchCooldownActive && !forceLockSystemUpdate) // Don't change target if cooldown is active, unless forced
{
// If cooldown is active and this SetNewTarget call is NOT from a HandleLockOnSystem... event,
// it means AutoTargeting's own loop tried to change target. We prevent this.
// If it IS from HandleLockOnSystem... it means bLockOn initiated, and we need to sync.
// The 'isManualSwitch' parameter in HandleLockOnSystemTargetChanged handles activating cooldown.
return;
}
if (CurrentTarget == newTarget && !forceLockSystemUpdate)
{
// If auto-lock is enabled, ensure the bLockOn system is actually locked on this target.
if (autoLockSelectedTarget && targetLockSystem != null && CurrentTarget != null)
{
if (targetLockSystem.GetCurrentLockOnTarget() != CurrentTarget.transform)
{
targetLockSystem.ManuallySetLockOnTarget(CurrentTarget.transform, true);
}
}
return;
}
if (_manualSwitchCooldownActive && !forceLockSystemUpdate) return;
if (CurrentTarget == newTarget && !forceLockSystemUpdate) return;
if (CurrentTarget != null)
{
ApplyHighlight(CurrentTarget, false);
OnTargetDeselected?.Invoke(CurrentTarget);
if (autoLockSelectedTarget && targetLockSystem != null &&
targetLockSystem.GetCurrentLockOnTarget() == CurrentTarget.transform &&
(newTarget == null || newTarget.transform != CurrentTarget.transform))
{
targetLockSystem.ManuallySetLockOnTarget(null, false);
}
}
CurrentTarget = newTarget;
if (targetLockSystem != null)
{
bool shouldLock = (autoLockSelectedTarget || alwaysLockOnInCombat) && CurrentTarget != null;
if (alwaysLockOnInCombat && CurrentTarget != null && !targetLockSystem.isLockingOn)
{
targetLockSystem.SetLockOn(true);
}
targetLockSystem.ManuallySetLockOnTarget(shouldLock ? CurrentTarget.transform : null, true);
}
if (CurrentTarget != null)
{
ApplyHighlight(CurrentTarget, true);
OnTargetSelected?.Invoke(CurrentTarget);
if (autoLockSelectedTarget && targetLockSystem != null)
{
targetLockSystem.ManuallySetLockOnTarget(CurrentTarget.transform, true);
}
}
else // CurrentTarget is now null
{
// Deselection logic above handles unlocking if autoLockSelectedTarget was true
// and if bLockOn was on the CurrentTarget that just became null.
}
}
public void ExecuteRotationTowardsCurrentTarget(float deltaTime)
{
if (_playerController != null && !_playerController.enabled)
{
return;
}
if (CurrentTarget == null || _playerTransform == null) return;
Vector3 directionToTarget = CurrentTarget.transform.position - _playerTransform.position;
directionToTarget.y = 0f;
if (directionToTarget.sqrMagnitude < 0.0001f) return;
Quaternion targetRotation = Quaternion.LookRotation(directionToTarget.normalized);
_playerTransform.rotation = Quaternion.Slerp(_playerTransform.rotation, targetRotation, deltaTime * playerRotationSpeed);
}
#endregion
#region Lock-On System Sync
private void HandleLockOnSystemTargetChanged(Transform lockedTargetTransform)
{
if (lockedTargetTransform == null || targetLockSystem == null) return;
// This event means bLockOn changed target (e.g., player pressed Next/Previous)
// Activate the cooldown for AutoTargetting.
_manualSwitchCooldownActive = true;
_manualSwitchCooldownTimer = manualSwitchCooldownDuration;
// Debug.Log($"AutoTargetting: Manual switch detected via bLockOn. Cooldown activated for {manualSwitchCooldownDuration}s.");
vFSMBehaviourController fsmTarget = lockedTargetTransform.GetComponentInParent<vFSMBehaviourController>();
if (fsmTarget == null) fsmTarget = lockedTargetTransform.GetComponentInChildren<vFSMBehaviourController>(true);
if (CurrentTarget != null && CurrentTarget.transform == lockedTargetTransform)
{
// AutoTargetting was already on this target, but bLockOn re-confirmed.
// Cooldown is still active due to bLockOn's event.
return;
}
if (fsmTarget != null)
{
// Debug.Log($"AutoTargeting: Syncing to bLockOn's new target: {fsmTarget.name}.");
// We need to update AutoTargetting's CurrentTarget to match bLockOn,
// even during cooldown, because bLockOn IS the source of truth now.
// The 'forceLockSystemUpdate = true' ensures highlights are updated correctly.
SetNewTarget(fsmTarget, true);
}
else
{
// bLockOn locked something without an FSM, or unlocked.
// If AutoTargeting had a target, it should now clear it to sync.
// Debug.LogWarning($"AutoTargeting: bLockOn locked {lockedTargetTransform.name} (no FSM) or unlocked. Clearing AutoTarget.");
SetNewTarget(null, true);
}
if (CurrentTarget != null && CurrentTarget.transform == lockedTargetTransform) return;
if (fsmTarget != null) SetNewTarget(fsmTarget, true);
else SetNewTarget(null, true);
}
private void HandleLockOnSystemTargetUnlocked(Transform previouslyLockedTargetTransform)
{
if (targetLockSystem == null) return;
// This event means bLockOn explicitly unlocked (e.g., player pressed Tab again).
// If player manually unlocks, we should respect that and also stop AutoTargetting's autoLock behavior for a bit.
// Or, if autoLockSelectedTarget is false, this is just a notification.
_manualSwitchCooldownActive = true; // Treat manual unlock like a manual switch
_manualSwitchCooldownActive = true;
_manualSwitchCooldownTimer = manualSwitchCooldownDuration;
// Debug.Log($"AutoTargeting: Manual unlock detected via bLockOn. Cooldown activated.");
string targetName = previouslyLockedTargetTransform ? previouslyLockedTargetTransform.name : "an unknown target";
// Debug.Log($"AutoTargeting: bLockOn unlocked from '{targetName}'.");
if (CurrentTarget != null && previouslyLockedTargetTransform == CurrentTarget.transform)
{
// AutoTargeting's current target was the one that bLockOn just unlocked.
// We should clear AutoTargeting's target to be in sync.
// The cooldown will prevent immediate re-auto-targeting.
SetNewTarget(null, true);
}
// If previouslyLockedTargetTransform was something else, AutoTargeting might already be on null or a different target.
// The cooldown ensures AutoTargeting doesn't fight this unlock.
}
#endregion
// ... (ExecuteRotationTowardsCurrentTarget, GetCurrentTargetHealth, ClearTarget, Renderer methods, Fade coroutine remain the same)
public void ExecuteRotationTowardsCurrentTarget(float deltaTime)
#region Helper Methods
public bool IsTargetInAngle(Transform sourceTransform, vFSMBehaviourController targetAI, float angleThreshold)
{
if (CurrentTarget == null || _playerTransform == null) return;
Vector3 directionToTarget = CurrentTarget.transform.position - _playerTransform.position;
directionToTarget.y = 0f;
if (directionToTarget.sqrMagnitude < 0.0001f) return;
directionToTarget.Normalize();
Quaternion targetRotation = Quaternion.LookRotation(directionToTarget);
_playerTransform.rotation = Quaternion.Lerp(_playerTransform.rotation, targetRotation, deltaTime * playerRotationSpeed);
if (targetAI == null || sourceTransform == null) return false;
Vector3 directionToTarget = (targetAI.transform.position - sourceTransform.position);
directionToTarget.y = 0;
if (directionToTarget.sqrMagnitude < 0.0001f) return true;
return Vector3.Angle(sourceTransform.forward, directionToTarget.normalized) <= angleThreshold;
}
private bool IsTargetValid(vFSMBehaviourController target)
{
if (target == null || !target.gameObject.activeInHierarchy || target.aiController.currentHealth <= 0) return false;
if (_playerTransform == null) return false;
if (!IsTargetInAngle(_playerTransform, target, targetingAngleThreshold)) return false;
float distSqr = (target.transform.position - _playerTransform.position).sqrMagnitude;
return distSqr <= (maxTargetingDistance * maxTargetingDistance);
}
public float GetCurrentTargetHealth()
@@ -390,33 +305,26 @@ namespace Beyond
public void ClearTarget(bool findNewOneImmediately)
{
SetNewTarget(null, true); // Force update if clearing target
SetNewTarget(null, true);
if (findNewOneImmediately && _gameStateManager != null && _gameStateManager.CurrentState == GameStateManager.State.COMBAT)
{
// UpdateTarget will respect cooldown, so immediate find might be delayed
// If you need it to bypass cooldown here, more logic is needed.
// For now, let's assume standard behavior.
if (!_manualSwitchCooldownActive) UpdateTarget();
if (!_manualSwitchCooldownActive)
{
UpdateTarget();
}
}
}
#endregion
#region Visuals
private Renderer[] GetTargetRenderers(vFSMBehaviourController targetController)
{
if (targetController == null) return new Renderer[0];
List<Renderer> collectedRenderers = new List<Renderer>();
SkinnedMeshRenderer[] smrs = targetController.GetComponentsInChildren<SkinnedMeshRenderer>(true);
if (smrs != null && smrs.Length > 0) collectedRenderers.AddRange(smrs);
MeshRenderer[] mrs = targetController.GetComponentsInChildren<MeshRenderer>(true);
if (mrs != null && mrs.Length > 0) collectedRenderers.AddRange(mrs);
if (collectedRenderers.Count == 0)
{
Renderer[] allRenderers = targetController.GetComponentsInChildren<Renderer>(true);
if (allRenderers != null && allRenderers.Length > 0) collectedRenderers.AddRange(allRenderers);
}
return collectedRenderers.Distinct().ToArray();
return targetController.GetComponentsInChildren<Renderer>(true).Distinct().ToArray();
}
private void ApplyHighlight(vFSMBehaviourController targetController, bool fadeIn)
{
if (targetController == null || string.IsNullOrEmpty(materialHighlightPropertyName)) return;
@@ -427,20 +335,11 @@ namespace Beyond
foreach (Material mat in rend.materials)
{
if (mat == null || !mat.HasProperty(materialHighlightPropertyName)) continue;
if (_materialToFadeCoroutineMap.TryGetValue(mat, out Coroutine existingCoroutine) && existingCoroutine != null)
{
StopCoroutine(existingCoroutine);
}
if (_materialToFadeCoroutineMap.TryGetValue(mat, out Coroutine existingCoroutine) && existingCoroutine != null) StopCoroutine(existingCoroutine);
Color currentColor = mat.GetColor(materialHighlightPropertyName);
Color targetColor = fadeIn ? highlightColor : deselectHighlightColor;
if (fadeIn)
{
if (!_originalMaterialColors.ContainsKey(mat) ||
(_originalMaterialColors[mat] != currentColor && currentColor != deselectHighlightColor && currentColor != highlightColor))
{
_originalMaterialColors[mat] = currentColor;
}
}
Coroutine newFadeCoroutine = StartCoroutine(FadeMaterialPropertyCoroutine(mat, currentColor, targetColor, highlightFadeDuration));
_materialToFadeCoroutineMap[mat] = newFadeCoroutine;
}
@@ -454,14 +353,12 @@ namespace Beyond
{
if (material == null) yield break;
timer += Time.deltaTime;
float progress = Mathf.Clamp01(timer / duration);
material.SetColor(materialHighlightPropertyName, Color.Lerp(fromValue, toValue, progress));
material.SetColor(materialHighlightPropertyName, Color.Lerp(fromValue, toValue, Mathf.Clamp01(timer / duration)));
yield return null;
}
if (material != null)
{
material.SetColor(materialHighlightPropertyName, toValue);
}
if (material != null) material.SetColor(materialHighlightPropertyName, toValue);
}
#endregion
}
}