continuing with combat, sword combos working, not finished, blockign working, need to build blockpush and improving bow combat
This commit is contained in:
@@ -22,6 +22,12 @@ public class NPCController : MonoBehaviour
|
||||
[Tooltip("The socket on the character model where the weapon should spawn (e.g., Prop_R). Leave blank to auto-search.")]
|
||||
public Transform weaponSocket;
|
||||
|
||||
[Header("Ragdoll Settings")]
|
||||
[Tooltip("If true, the NPC will collapse into a ragdoll on death rather than playing an animation.")]
|
||||
public bool useRagdoll = true;
|
||||
|
||||
private Rigidbody[] _boneRigidbodies;
|
||||
|
||||
[Header("Patrol Testing")]
|
||||
[Tooltip("If true, the NPC will automatically walk between the patrol points below.")]
|
||||
public bool isPatrolling = false;
|
||||
@@ -42,6 +48,8 @@ public class NPCController : MonoBehaviour
|
||||
private float _lastAttackTime = 0f;
|
||||
private float _stunTimer = 0f;
|
||||
private bool _isDead = false;
|
||||
private bool _isBlocking = false;
|
||||
private float _lastBlockAttemptTime = 0f;
|
||||
private GameObject _equippedWeaponInstance;
|
||||
|
||||
// Animator Hashes specifically for Synty's AC_Sidekick and AC_Polygon controllers
|
||||
@@ -59,6 +67,7 @@ public class NPCController : MonoBehaviour
|
||||
private readonly int _attackHash = Animator.StringToHash("Attack");
|
||||
private readonly int _takeDamageHash = Animator.StringToHash("TakeDamage");
|
||||
private readonly int _deathHash = Animator.StringToHash("Death"); // Optional
|
||||
private readonly int _isBlockingHash = Animator.StringToHash("IsBlocking");
|
||||
|
||||
private void Start()
|
||||
{
|
||||
@@ -69,6 +78,10 @@ public class NPCController : MonoBehaviour
|
||||
_animator = GetComponentInChildren<Animator>();
|
||||
}
|
||||
|
||||
// Gather all rigidbodies that belong to the ragdoll
|
||||
_boneRigidbodies = GetComponentsInChildren<Rigidbody>();
|
||||
SetRagdollState(false); // Make them kinematic while alive
|
||||
|
||||
// Auto-assign player as combat target if empty (great for quick testing!)
|
||||
if (combatTarget == null)
|
||||
{
|
||||
@@ -135,6 +148,19 @@ public class NPCController : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
private void SetRagdollState(bool isActive)
|
||||
{
|
||||
if (_boneRigidbodies == null) return;
|
||||
|
||||
foreach (Rigidbody rb in _boneRigidbodies)
|
||||
{
|
||||
// We ignore the root GameObject's Rigidbody if it has one
|
||||
if (rb.gameObject == gameObject) continue;
|
||||
|
||||
rb.isKinematic = !isActive;
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_isDead) return;
|
||||
@@ -177,11 +203,16 @@ public class NPCController : MonoBehaviour
|
||||
|
||||
float distToTarget = Vector3.Distance(transform.position, combatTarget.position);
|
||||
|
||||
PlayerStats playerStats = combatTarget.GetComponent<PlayerStats>();
|
||||
MeleeCombatLayerController playerCombat = combatTarget.GetComponent<MeleeCombatLayerController>();
|
||||
|
||||
bool isPlayerBlocking = playerStats != null && playerStats.isBlocking;
|
||||
bool isPlayerAttacking = playerCombat != null && playerCombat.IsAttacking;
|
||||
|
||||
if (distToTarget <= npcData.attackRadius)
|
||||
{
|
||||
// We are close enough to attack! Stop moving and face them.
|
||||
// Stop and face them
|
||||
_agent.isStopped = true;
|
||||
|
||||
Vector3 direction = (combatTarget.position - transform.position).normalized;
|
||||
direction.y = 0;
|
||||
if (direction != Vector3.zero)
|
||||
@@ -189,19 +220,51 @@ public class NPCController : MonoBehaviour
|
||||
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(direction), Time.deltaTime * 5f);
|
||||
}
|
||||
|
||||
// Attack if cooldown is ready
|
||||
if (Time.time >= _lastAttackTime + npcData.attackCooldown)
|
||||
// Blocking Logic: Try to block if the player is attacking
|
||||
if (isPlayerAttacking && !_isBlocking && Time.time >= _lastBlockAttemptTime + 1.5f)
|
||||
{
|
||||
PerformAttack();
|
||||
_lastBlockAttemptTime = Time.time;
|
||||
if (Random.value <= npcData.blockChance)
|
||||
{
|
||||
SetBlockState(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Stop blocking if the player stops attacking (or after a small delay)
|
||||
if (!isPlayerAttacking && _isBlocking && Time.time >= _lastBlockAttemptTime + 1.0f)
|
||||
{
|
||||
SetBlockState(false);
|
||||
}
|
||||
|
||||
// Attacking Logic: Only attack if we are NOT currently blocking
|
||||
if (!_isBlocking)
|
||||
{
|
||||
// If the player is turtling (blocking), attack twice as fast to try and break their guard!
|
||||
float currentCooldown = isPlayerBlocking ? npcData.attackCooldown * 0.5f : npcData.attackCooldown;
|
||||
|
||||
if (Time.time >= _lastAttackTime + currentCooldown)
|
||||
{
|
||||
PerformAttack();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Target is too far, chase them!
|
||||
// Target is too far, chase them! (And stop blocking if we were)
|
||||
if (_isBlocking) SetBlockState(false);
|
||||
RunTo(combatTarget.position);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetBlockState(bool block)
|
||||
{
|
||||
_isBlocking = block;
|
||||
if (_animator != null)
|
||||
{
|
||||
_animator.SetBool(_isBlockingHash, block);
|
||||
}
|
||||
}
|
||||
|
||||
private void PerformAttack()
|
||||
{
|
||||
_lastAttackTime = Time.time;
|
||||
@@ -211,6 +274,14 @@ public class NPCController : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Some Synty animations use "DealDamage" instead of "Hit" as the event name.
|
||||
/// </summary>
|
||||
public void DealDamage()
|
||||
{
|
||||
Hit();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call this from the "Hit" Animation Event on the NPC's attack animation
|
||||
/// </summary>
|
||||
@@ -221,12 +292,19 @@ public class NPCController : MonoBehaviour
|
||||
// If the target is still within attack range when the animation hits, deal damage
|
||||
if (Vector3.Distance(transform.position, combatTarget.position) <= npcData.attackRadius + 0.5f)
|
||||
{
|
||||
PlayerStats playerStats = combatTarget.GetComponent<PlayerStats>();
|
||||
if (playerStats != null)
|
||||
float damageToDeal = npcData.weaponData != null ? npcData.weaponData.damage : npcData.baseDamage;
|
||||
Debug.Log($"NPC landed a hit on the Player for {damageToDeal} damage!");
|
||||
|
||||
// Try to hit the combat controller first so it can process blocks/stamina/animations
|
||||
MeleeCombatLayerController playerCombat = combatTarget.GetComponent<MeleeCombatLayerController>();
|
||||
if (playerCombat != null)
|
||||
{
|
||||
float damageToDeal = npcData.weaponData != null ? npcData.weaponData.damage : npcData.baseDamage;
|
||||
Debug.Log($"NPC landed a hit on the Player for {damageToDeal} damage!");
|
||||
playerStats.TakeDamage(damageToDeal);
|
||||
playerCombat.TakeDamage(damageToDeal);
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayerStats playerStats = combatTarget.GetComponent<PlayerStats>();
|
||||
if (playerStats != null) playerStats.TakeDamage(damageToDeal);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -235,6 +313,16 @@ public class NPCController : MonoBehaviour
|
||||
{
|
||||
if (_isDead) return;
|
||||
|
||||
if (_isBlocking)
|
||||
{
|
||||
float blockedAmount = amount * npcData.blockReductionPercentage;
|
||||
amount -= blockedAmount;
|
||||
Debug.Log($"NPC Blocked {blockedAmount} damage! Taking only {amount} damage.");
|
||||
|
||||
// Optionally: Break guard if hit hard?
|
||||
// SetBlockState(false);
|
||||
}
|
||||
|
||||
currentHealth -= amount;
|
||||
Debug.Log($"NPC took {amount} damage! Health remaining: {currentHealth}");
|
||||
|
||||
@@ -267,25 +355,53 @@ public class NPCController : MonoBehaviour
|
||||
if (_agent != null && _agent.isOnNavMesh)
|
||||
{
|
||||
_agent.isStopped = true;
|
||||
_agent.enabled = false; // Turn off the agent completely so physics don't fight it
|
||||
}
|
||||
|
||||
if (_animator != null)
|
||||
if (useRagdoll)
|
||||
{
|
||||
_animator.SetTrigger(_deathHash);
|
||||
if (_animator != null) _animator.enabled = false; // Turn off animator so physics take over
|
||||
SetRagdollState(true); // Let the limbs fall!
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_animator != null)
|
||||
{
|
||||
_animator.SetTrigger(_deathHash);
|
||||
}
|
||||
}
|
||||
|
||||
// Disable ALL colliders on this object so it doesn't block the player or stay lock-on-able
|
||||
// Disable ALL root colliders on this object (capsules) so it doesn't block the player
|
||||
Collider[] cols = GetComponents<Collider>();
|
||||
foreach(Collider col in cols)
|
||||
{
|
||||
col.enabled = false;
|
||||
}
|
||||
|
||||
// Specifically disable the lock-on script if it exists
|
||||
MonoBehaviour lockOnScript = GetComponent("SampleObjectLockOn") as MonoBehaviour;
|
||||
// Find and disable the Lock-On script even if it's on a child object
|
||||
MonoBehaviour lockOnScript = null;
|
||||
foreach (MonoBehaviour mb in GetComponentsInChildren<MonoBehaviour>())
|
||||
{
|
||||
if (mb != null && mb.GetType().Name == "SampleObjectLockOn")
|
||||
{
|
||||
lockOnScript = mb;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (lockOnScript != null)
|
||||
{
|
||||
lockOnScript.enabled = false;
|
||||
// Also disable its collider so we can't lock on anymore!
|
||||
Collider lockOnCol = lockOnScript.GetComponent<Collider>();
|
||||
if (lockOnCol != null) lockOnCol.enabled = false;
|
||||
}
|
||||
|
||||
// Just to be absolutely safe, let's disable ALL trigger colliders in the children
|
||||
// (This catches the giant lock-on sphere without disabling the physical ragdoll bone colliders!)
|
||||
Collider[] allCols = GetComponentsInChildren<Collider>();
|
||||
foreach (Collider c in allCols)
|
||||
{
|
||||
if (c.isTrigger) c.enabled = false;
|
||||
}
|
||||
|
||||
Debug.Log("NPC has died!");
|
||||
@@ -381,4 +497,19 @@ public class NPCController : MonoBehaviour
|
||||
_agent.isStopped = false;
|
||||
_agent.SetDestination(destination);
|
||||
}
|
||||
|
||||
#region Animation Event Dummies
|
||||
// These methods are here purely to absorb the Animation Events you added for the Player's Combo System.
|
||||
// Since NPCs use the exact same animation clips, they will fire the events too!
|
||||
// This stops Unity from throwing "has no receiver" errors.
|
||||
public void OpenComboWindow()
|
||||
{
|
||||
// NPCs don't have a UI combo system, so they just ignore this!
|
||||
}
|
||||
|
||||
public void CloseComboWindow()
|
||||
{
|
||||
// Ignored!
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user