lockon, targetting and rolls control improvements
This commit is contained in:
@@ -105638,6 +105638,8 @@ MonoBehaviour:
|
||||
strafeRollBackwardAnim: Roll_Backward
|
||||
strafeRollLeftAnim: Roll_Left
|
||||
strafeRollRightAnim: Roll_Right
|
||||
strafeRollLeftCorrectionAngle: 45
|
||||
strafeRollRightCorrectionAngle: 70
|
||||
--- !u!114 &9202663235077955828
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@@ -18993,6 +18993,7 @@ MonoBehaviour:
|
||||
highlightFadeDuration: 0.3
|
||||
preferSkinnedMeshRenderer: 1
|
||||
autoLockSelectedTarget: 1
|
||||
alwaysLockOnInCombat: 1
|
||||
targetLockSystem: {fileID: 0}
|
||||
manualSwitchCooldownDuration: 0.75
|
||||
--- !u!4 &6425420852750441961 stripped
|
||||
|
||||
@@ -16212,6 +16212,36 @@ PrefabInstance:
|
||||
propertyPath: groundLayer.m_Bits
|
||||
value: 268435457
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4704300330561169846, guid: 851e8e61247888340bdec90fc8aa37f5,
|
||||
type: 3}
|
||||
propertyPath: strafeRollDuration
|
||||
value: 2
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4704300330561169846, guid: 851e8e61247888340bdec90fc8aa37f5,
|
||||
type: 3}
|
||||
propertyPath: strafeRollLeftAngle
|
||||
value: -100
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4704300330561169846, guid: 851e8e61247888340bdec90fc8aa37f5,
|
||||
type: 3}
|
||||
propertyPath: strafeRollRightAngle
|
||||
value: 100
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4704300330561169846, guid: 851e8e61247888340bdec90fc8aa37f5,
|
||||
type: 3}
|
||||
propertyPath: blockFreeModeForwardRoll
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4704300330561169846, guid: 851e8e61247888340bdec90fc8aa37f5,
|
||||
type: 3}
|
||||
propertyPath: strafeRollCorrectionAngle
|
||||
value: 71.9
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4704300330561169846, guid: 851e8e61247888340bdec90fc8aa37f5,
|
||||
type: 3}
|
||||
propertyPath: strafeRollLeftCorrectionAngle
|
||||
value: 70.6
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4704300330561169846, guid: 851e8e61247888340bdec90fc8aa37f5,
|
||||
type: 3}
|
||||
propertyPath: _onReceiveDamage.m_PersistentCalls.m_Calls.Array.size
|
||||
@@ -16297,6 +16327,11 @@ PrefabInstance:
|
||||
propertyPath: m_IsActive
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5054440825960491160, guid: 851e8e61247888340bdec90fc8aa37f5,
|
||||
type: 3}
|
||||
propertyPath: targetingAngleThreshold
|
||||
value: 120
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5083365752064418721, guid: 851e8e61247888340bdec90fc8aa37f5,
|
||||
type: 3}
|
||||
propertyPath: m_AnchorMax.x
|
||||
@@ -17938,6 +17973,10 @@ PrefabInstance:
|
||||
insertIndex: -1
|
||||
addedObject: {fileID: 1681403708}
|
||||
m_AddedComponents:
|
||||
- targetCorrespondingSourceObject: {fileID: 5935883485110830931, guid: 851e8e61247888340bdec90fc8aa37f5,
|
||||
type: 3}
|
||||
insertIndex: -1
|
||||
addedObject: {fileID: 1516777741}
|
||||
- targetCorrespondingSourceObject: {fileID: 7577249534690070221, guid: 851e8e61247888340bdec90fc8aa37f5,
|
||||
type: 3}
|
||||
insertIndex: -1
|
||||
@@ -19764,6 +19803,12 @@ Material:
|
||||
- _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
--- !u!1 &1447701206 stripped
|
||||
GameObject:
|
||||
m_CorrespondingSourceObject: {fileID: 5935883485110830931, guid: 851e8e61247888340bdec90fc8aa37f5,
|
||||
type: 3}
|
||||
m_PrefabInstance: {fileID: 1004473948}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!21 &1461622385
|
||||
Material:
|
||||
serializedVersion: 8
|
||||
@@ -20136,7 +20181,7 @@ MonoBehaviour:
|
||||
type: 3}
|
||||
m_PrefabInstance: {fileID: 1004473948}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_GameObject: {fileID: 1447701206}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 38b9f160099a9484b936b7192dd82968, type: 3}
|
||||
@@ -20148,12 +20193,24 @@ MonoBehaviour:
|
||||
type: 3}
|
||||
m_PrefabInstance: {fileID: 1004473948}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_GameObject: {fileID: 1447701206}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 90fb639067d504fc69e8ac3a3fa2283e, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
--- !u!114 &1516777741
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1447701206}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: a069af71c57a846598551f9d5bf36cef, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
--- !u!21 &1523257492
|
||||
Material:
|
||||
serializedVersion: 8
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -165,7 +165,7 @@ namespace Beyond
|
||||
}
|
||||
else if (base.currentTarget == null && isLockingOn) // Target became null while we thought we were locked
|
||||
{
|
||||
UnlockTarget(false); // Silently unlock if target disappeared
|
||||
UnlockTarget(false); // Silently unlock if target disappeared
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,5 +394,35 @@ namespace Beyond
|
||||
if (Player.Instance != null) _playerTransform = Player.Instance.transform;
|
||||
else _playerTransform = transform; // Fallback if bLockOn is on player
|
||||
}
|
||||
|
||||
// --- ADD THIS ENTIRE METHOD TO YOUR bLockOn.cs SCRIPT ---
|
||||
|
||||
/// <summary>
|
||||
/// Externally sets the lock-on state.
|
||||
/// </summary>
|
||||
/// <param name="value">True to turn lock-on ON, False to turn it OFF.</param>
|
||||
public void SetLockOn(bool value)
|
||||
{
|
||||
// If the state is already what we want, do nothing.
|
||||
if (isLockingOn == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// We don't set isLockingOn directly here, as the methods below handle the state change.
|
||||
|
||||
if (value)
|
||||
{
|
||||
// If we are turning lock-on ON, find the best target immediately.
|
||||
// This prevents a delay before the first lock.
|
||||
// The `null` argument tells it to find any valid target.
|
||||
AttemptLockOn(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we are turning lock-on OFF, clear the current target.
|
||||
UnlockTarget();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,16 @@ namespace Beyond
|
||||
[Tooltip("The name of the animation state to play when rolling right while strafing.")]
|
||||
public string strafeRollRightAnim = "Roll_Right";
|
||||
|
||||
// --- UPDATED FIELDS FOR INDEPENDENT CONTROL ---
|
||||
[Header("Beyond's Strafe Roll Correction")]
|
||||
[Tooltip("The fixed angle to rotate the character before a LEFT strafe roll.")]
|
||||
[Range(0f, 90f)]
|
||||
public float strafeRollLeftCorrectionAngle = 45f;
|
||||
|
||||
[Tooltip("The fixed angle to rotate the character before a RIGHT strafe roll.")]
|
||||
[Range(0f, 90f)]
|
||||
public float strafeRollRightCorrectionAngle = 70f;
|
||||
// --- END OF UPDATED FIELDS ---
|
||||
|
||||
public bool GodMode
|
||||
{
|
||||
@@ -51,7 +61,7 @@ namespace Beyond
|
||||
{
|
||||
base.Start();
|
||||
}
|
||||
|
||||
|
||||
protected override void RollBehavior()
|
||||
{
|
||||
// If we are not strafing, use the default Invector roll behavior.
|
||||
@@ -61,17 +71,15 @@ namespace Beyond
|
||||
return;
|
||||
}
|
||||
|
||||
// --- Custom Strafe Roll with Root Motion ---
|
||||
// Custom Strafe Roll with Root Motion (no rotation logic needed here anymore)
|
||||
if (!isRolling)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// We apply the root motion position change directly.
|
||||
Vector3 deltaPosition = new Vector3(animator.deltaPosition.x, 0f, animator.deltaPosition.z);
|
||||
Vector3 v = (deltaPosition / Time.deltaTime) * (1f - stopMoveWeight);
|
||||
|
||||
// Apply gravity to the roll if enabled
|
||||
if (rollUseGravity && animator.GetNormalizedTime(baseLayer) >= rollUseGravityTime)
|
||||
{
|
||||
v.y = _rigidbody.linearVelocity.y;
|
||||
@@ -82,12 +90,11 @@ namespace Beyond
|
||||
|
||||
public override void Roll()
|
||||
{
|
||||
// If we are strafing, use our custom directional logic.
|
||||
if (isStrafing)
|
||||
{
|
||||
TriggerStrafeRoll(strafeRollForwardAnim, strafeRollBackwardAnim, strafeRollLeftAnim, strafeRollRightAnim);
|
||||
}
|
||||
else // Otherwise, use the default free-locomotion roll.
|
||||
else
|
||||
{
|
||||
OnRoll.Invoke();
|
||||
isRolling = true;
|
||||
@@ -99,7 +106,6 @@ namespace Beyond
|
||||
|
||||
public virtual void Dash()
|
||||
{
|
||||
// The Dash logic now mirrors the Roll logic.
|
||||
if (isStrafing)
|
||||
{
|
||||
TriggerStrafeRoll(strafeRollForwardAnim, strafeRollBackwardAnim, strafeRollLeftAnim, strafeRollRightAnim);
|
||||
@@ -114,7 +120,7 @@ namespace Beyond
|
||||
}
|
||||
}
|
||||
|
||||
// This is the private helper method that contains the core logic for this feature.
|
||||
// --- MODIFIED METHOD WITH TWO-ANGLE LOGIC ---
|
||||
private void TriggerStrafeRoll(string forwardAnim, string backwardAnim, string leftAnim, string rightAnim)
|
||||
{
|
||||
OnRoll.Invoke();
|
||||
@@ -127,10 +133,30 @@ namespace Beyond
|
||||
// Prioritize side rolls based on horizontal input.
|
||||
if (Mathf.Abs(horizontalSpeed) > strafeRollInputThreshold)
|
||||
{
|
||||
animToPlay = horizontalSpeed > 0 ? rightAnim : leftAnim;
|
||||
float correction = 0f;
|
||||
|
||||
// Check if rolling right (positive horizontal speed)
|
||||
if (horizontalSpeed > 0)
|
||||
{
|
||||
animToPlay = rightAnim;
|
||||
// For a right roll, we apply a negative rotation (turn left) to angle the trajectory forward.
|
||||
correction = -strafeRollRightCorrectionAngle;
|
||||
}
|
||||
// Otherwise, rolling left
|
||||
else
|
||||
{
|
||||
animToPlay = leftAnim;
|
||||
// For a left roll, we apply a positive rotation (turn right) to angle the trajectory forward.
|
||||
correction = strafeRollLeftCorrectionAngle;
|
||||
}
|
||||
|
||||
// Apply the calculated rotation instantly around the Y-axis.
|
||||
if (correction != 0)
|
||||
{
|
||||
transform.Rotate(0, correction, 0);
|
||||
}
|
||||
}
|
||||
// If horizontal input is not met, always default to the backward roll.
|
||||
// This effectively blocks the forward roll.
|
||||
// If horizontal input is not met, default to the backward roll.
|
||||
else
|
||||
{
|
||||
animToPlay = backwardAnim;
|
||||
@@ -138,6 +164,7 @@ namespace Beyond
|
||||
|
||||
animator.CrossFadeInFixedTime(animToPlay, rollTransition, baseLayer);
|
||||
}
|
||||
// --- END OF MODIFIED METHOD ---
|
||||
|
||||
public void OnEvadeStart() { if (!m_GodMode) isImmortal = true; }
|
||||
public void OnEvadeEnd() { if (!m_GodMode) isImmortal = false; }
|
||||
|
||||
Reference in New Issue
Block a user