improvements to magic attacks, full arena logic, improvements to NPCs fresnel rendering for targetting

This commit is contained in:
2025-05-23 09:54:57 +02:00
parent 2705b6c4cb
commit abc5ef0332
6 changed files with 673 additions and 237 deletions

View File

@@ -1,6 +1,7 @@
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq; // Required for List.AddRange and ToArray
using Invector.vCharacterController.AI.FSMBehaviour; // For vFSMBehaviourController
using Beyond; // For GameStateManager and Player (ensure Player.cs is in this namespace or adjust)
using System; // For Action
@@ -16,8 +17,8 @@ public class AutoTargetting : MonoBehaviour
public float targetingAngleThreshold = 90f;
[Header("Rotation Parameters")]
[Tooltip("Speed at which the player rotates towards the current target when rotation is explicitly called.")]
public float playerRotationSpeed = 10f;
[Tooltip("Speed at which the player rotates towards the current target when rotation is explicitly called (e.g., by MagicAttacks).")]
public float playerRotationSpeed = 10f; // This will be used by MagicAttacks
[Header("Visuals")]
[Tooltip("Name of the material color property to animate for Fresnel effect.")]
@@ -30,8 +31,8 @@ public class AutoTargetting : MonoBehaviour
public Color deselectHighlightColor = Color.black;
[Tooltip("Duration of the fade in/out animation for the highlight.")]
public float highlightFadeDuration = 0.3f;
[Tooltip("If true, will try to find a SkinnedMeshRenderer first, then MeshRenderer. If false, affects all renderers.")]
public bool preferSkinnedMeshRenderer = true;
[Tooltip("If true, was previously used to prefer SkinnedMeshRenderer. Now GetTargetRenderers collects both SkinnedMeshRenderers and MeshRenderers regardless of this flag. This flag might be repurposed or removed in the future.")]
public bool preferSkinnedMeshRenderer = true; // Note: Its effect on GetTargetRenderers is changed.
public vFSMBehaviourController CurrentTarget { get; private set; }
public event Action<vFSMBehaviourController> OnTargetSelected;
@@ -45,7 +46,7 @@ public class AutoTargetting : MonoBehaviour
void Start()
{
if (Player.Instance == null) // Player.Instance should be set by its own Awake
if (Player.Instance == null)
{
Debug.LogError("AutoTargetting: Player.Instance is not available at Start! Ensure Player script with static Instance exists and runs before AutoTargetting.");
enabled = false;
@@ -57,7 +58,7 @@ public class AutoTargetting : MonoBehaviour
if (_gameStateManager != null)
{
_gameStateManager.m_OnStateChanged.AddListener(HandleGameStateChanged);
HandleGameStateChanged(_gameStateManager.CurrentState); // Initialize based on current state
HandleGameStateChanged(_gameStateManager.CurrentState);
}
else
{
@@ -98,17 +99,16 @@ public class AutoTargetting : MonoBehaviour
_targetingLoopCoroutine = StartCoroutine(TargetingLoop());
}
}
else // State is NORMAL or other non-combat
else
{
if (_targetingLoopCoroutine != null)
{
StopCoroutine(_targetingLoopCoroutine);
_targetingLoopCoroutine = null;
}
if (CurrentTarget != null) // If there was a target, deselect it
if (CurrentTarget != null)
{
vFSMBehaviourController oldTarget = CurrentTarget;
SetNewTarget(null); // This will handle fade out and event
SetNewTarget(null);
}
}
}
@@ -117,12 +117,12 @@ public class AutoTargetting : MonoBehaviour
{
while (true)
{
if (_playerTransform == null && Player.Instance != null) // Defensive: re-cache if player was respawned or similar
if (_playerTransform == null && Player.Instance != null)
{
_playerTransform = Player.Instance.transform;
}
if (_playerTransform != null) // Only proceed if we have a valid player transform
if (_playerTransform != null)
{
UpdateTarget();
}
@@ -130,9 +130,6 @@ public class AutoTargetting : MonoBehaviour
}
}
/// <summary>
/// Checks if the given AI target is within the specified angle from the source transform's forward direction.
/// </summary>
public bool IsTargetInAngle(Transform sourceTransform, vFSMBehaviourController targetAI, float angleThreshold)
{
if (targetAI == null || sourceTransform == null)
@@ -141,9 +138,8 @@ public class AutoTargetting : MonoBehaviour
}
Vector3 directionToTarget = (targetAI.transform.position - sourceTransform.position);
directionToTarget.y = 0; // Consider only horizontal angle
directionToTarget.y = 0;
// If target is effectively at the same horizontal position, consider it in angle.
if (directionToTarget.sqrMagnitude < 0.0001f) return true;
directionToTarget.Normalize();
@@ -162,7 +158,7 @@ public class AutoTargetting : MonoBehaviour
if (combatControllers == null || combatControllers.Count == 0)
{
if (CurrentTarget != null) SetNewTarget(null); // No enemies, clear current
if (CurrentTarget != null) SetNewTarget(null);
return;
}
@@ -173,20 +169,14 @@ public class AutoTargetting : MonoBehaviour
continue;
}
// Check 1: Is target within selection angle?
if (!IsTargetInAngle(_playerTransform, controller, targetingAngleThreshold))
{
continue;
}
// Check 2: Is target within selection distance?
float distSqr = (controller.transform.position - _playerTransform.position).sqrMagnitude;
if (distSqr <= minDistanceSqr) // distSqr must also be <= maxTargetingDistance^2 due to minDistanceSqr initialization
if (distSqr <= minDistanceSqr)
{
// If multiple targets are at similar very close distances, this might pick the "last one" processed.
// For more refined "closest", ensure this is strictly '<' for new candidates,
// or add a secondary sort criterion if multiple are at exact same minDistanceSqr.
// Current logic is fine for most cases.
minDistanceSqr = distSqr;
bestCandidate = controller;
}
@@ -196,27 +186,22 @@ public class AutoTargetting : MonoBehaviour
{
SetNewTarget(bestCandidate);
}
else if (CurrentTarget != null && !IsTargetValid(CurrentTarget)) // Current target became invalid (e.g. died, moved out of range/angle)
else if (CurrentTarget != null && !IsTargetValid(CurrentTarget))
{
SetNewTarget(null);
}
}
/// <summary>
/// Checks if a given target is still valid according to AutoTargetting's rules.
/// </summary>
private bool IsTargetValid(vFSMBehaviourController target)
{
if (target == null || !target.gameObject.activeInHierarchy || target.aiController.currentHealth <= 0)
return false;
if (_playerTransform == null) return false; // Should not happen if script is active
if (_playerTransform == null) return false;
// Check 1: Angle (using AutoTargetting's own targetingAngleThreshold)
if (!IsTargetInAngle(_playerTransform, target, targetingAngleThreshold))
return false;
// Check 2: Distance (using AutoTargetting's own maxTargetingDistance)
float distSqr = (target.transform.position - _playerTransform.position).sqrMagnitude;
return distSqr <= maxTargetingDistance * maxTargetingDistance;
}
@@ -225,26 +210,21 @@ public class AutoTargetting : MonoBehaviour
{
if (CurrentTarget == newTarget) return;
// Deselect previous target
if (CurrentTarget != null)
{
ApplyHighlight(CurrentTarget, false); // Fade out
ApplyHighlight(CurrentTarget, false);
OnTargetDeselected?.Invoke(CurrentTarget);
}
CurrentTarget = newTarget;
// Select new target
if (CurrentTarget != null)
{
ApplyHighlight(CurrentTarget, true); // Fade in
ApplyHighlight(CurrentTarget, true);
OnTargetSelected?.Invoke(CurrentTarget);
}
}
/// <summary>
/// Smoothly rotates the player character towards the CurrentTarget on the horizontal plane.
/// </summary>
public void ExecuteRotationTowardsCurrentTarget(float deltaTime)
{
if (CurrentTarget == null || _playerTransform == null)
@@ -255,17 +235,13 @@ public class AutoTargetting : MonoBehaviour
Vector3 directionToTarget = CurrentTarget.transform.position - _playerTransform.position;
directionToTarget.y = 0f;
if (directionToTarget.sqrMagnitude < 0.0001f) return; // Already at target or too close to get a direction
if (directionToTarget.sqrMagnitude < 0.0001f) return;
directionToTarget.Normalize();
Quaternion targetRotation = Quaternion.LookRotation(directionToTarget);
_playerTransform.rotation = Quaternion.Lerp(_playerTransform.rotation, targetRotation, deltaTime * playerRotationSpeed);
}
/// <summary>
/// Gets the health of the CurrentTarget.
/// </summary>
/// <returns>The health of the CurrentTarget, or -1f if no target or target has no health component.</returns>
public float GetCurrentTargetHealth()
{
if (CurrentTarget != null && CurrentTarget.aiController != null)
@@ -280,26 +256,51 @@ public class AutoTargetting : MonoBehaviour
SetNewTarget(null);
if (findNewOneImmediately && _gameStateManager != null && _gameStateManager.CurrentState == GameStateManager.State.COMBAT)
{
UpdateTarget(); // Attempt to find a new one if in combat
UpdateTarget();
}
}
// *** MODIFIED GetTargetRenderers METHOD ***
private Renderer[] GetTargetRenderers(vFSMBehaviourController targetController)
{
if (targetController == null) return new Renderer[0];
if (preferSkinnedMeshRenderer)
List<Renderer> collectedRenderers = new List<Renderer>();
// Collect all SkinnedMeshRenderers
SkinnedMeshRenderer[] smrs = targetController.GetComponentsInChildren<SkinnedMeshRenderer>(true);
if (smrs != null && smrs.Length > 0)
{
SkinnedMeshRenderer smr = targetController.GetComponentInChildren<SkinnedMeshRenderer>();
if (smr != null) return new Renderer[] { smr };
MeshRenderer mr = targetController.GetComponentInChildren<MeshRenderer>();
if (mr != null) return new Renderer[] { mr };
collectedRenderers.AddRange(smrs);
}
// Collect all MeshRenderers
MeshRenderer[] mrs = targetController.GetComponentsInChildren<MeshRenderer>(true);
if (mrs != null && mrs.Length > 0)
{
collectedRenderers.AddRange(mrs);
}
return targetController.GetComponentsInChildren<Renderer>(true); // Include inactive renderers if any
// If no specific SMRs or MRs were found, fall back to all Renderers (optional, but good for safety)
// Or, if the goal is *only* SMRs and MRs, this fallback can be removed.
// For now, let's assume we want to highlight *something* if possible.
// If you strictly want ONLY SMRs and MRs, and nothing else, remove this 'if' block.
if (collectedRenderers.Count == 0)
{
Renderer[] allRenderers = targetController.GetComponentsInChildren<Renderer>(true);
if (allRenderers != null && allRenderers.Length > 0)
{
collectedRenderers.AddRange(allRenderers);
}
}
// The preferSkinnedMeshRenderer flag is not directly used here for filtering types anymore.
// It could potentially be used for ordering or other logic if needed in the future.
return collectedRenderers.Distinct().ToArray(); // Distinct() to avoid duplicates if an object somehow has multiple relevant renderer types
}
private void ApplyHighlight(vFSMBehaviourController targetController, bool fadeIn)
{
if (targetController == null || string.IsNullOrEmpty(materialHighlightPropertyName)) return;
@@ -310,12 +311,10 @@ public class AutoTargetting : MonoBehaviour
{
if (rend == null) continue;
// Use rend.materials to get instances for modification
foreach (Material mat in rend.materials)
{
if (mat == null || !mat.HasProperty(materialHighlightPropertyName)) continue;
// Stop any existing fade coroutine for this specific material
if (_materialToFadeCoroutineMap.TryGetValue(mat, out Coroutine existingCoroutine) && existingCoroutine != null)
{
StopCoroutine(existingCoroutine);
@@ -324,7 +323,6 @@ public class AutoTargetting : MonoBehaviour
Color currentColor = mat.GetColor(materialHighlightPropertyName);
Color targetColor = fadeIn ? highlightColor : deselectHighlightColor;
// Smartly store original color only if not already a highlight/deselect color.
if (fadeIn)
{
if (!_originalMaterialColors.ContainsKey(mat) ||
@@ -333,15 +331,6 @@ public class AutoTargetting : MonoBehaviour
_originalMaterialColors[mat] = currentColor;
}
}
// When fading out, if an original was stored, we could potentially use it instead of always deselectHighlightColor.
// However, for a consistent "off" state, deselectHighlightColor (e.g., black) is usually desired.
// If fading out and original exists and isn't black:
// else if (_originalMaterialColors.TryGetValue(mat, out Color original) && original != deselectHighlightColor)
// {
// targetColor = original; // Fade back to true original
// }
Coroutine newFadeCoroutine = StartCoroutine(FadeMaterialPropertyCoroutine(mat, currentColor, targetColor, highlightFadeDuration));
_materialToFadeCoroutineMap[mat] = newFadeCoroutine;
}
@@ -351,11 +340,10 @@ public class AutoTargetting : MonoBehaviour
private IEnumerator FadeMaterialPropertyCoroutine(Material material, Color fromValue, Color toValue, float duration)
{
float timer = 0f;
// yield return null; // Not strictly necessary here as StopCoroutine handles overlaps.
while (timer < duration)
{
if (material == null) yield break; // Material might have been destroyed
if (material == null) yield break;
timer += Time.deltaTime;
float progress = Mathf.Clamp01(timer / duration);
@@ -363,15 +351,9 @@ public class AutoTargetting : MonoBehaviour
yield return null;
}
if (material != null) // Final set
if (material != null)
{
material.SetColor(materialHighlightPropertyName, toValue);
}
// Optional: Remove from map if coroutine completed naturally and is still the one in the map.
// if (_materialToFadeCoroutineMap.TryGetValue(material, out Coroutine currentMappedCoroutine) && currentMappedCoroutine == /*this specific instance, tricky to get*/)
// {
// _materialToFadeCoroutineMap.Remove(material);
// }
}
}