started building NPC combat

This commit is contained in:
2026-09-29 16:11:54 +01:00
parent be2e7a5824
commit 7486c8d310
4952 changed files with 1093899 additions and 745 deletions

View File

@@ -14,6 +14,9 @@ public class MeleeCombatLayerController : MonoBehaviour
[Tooltip("Your main camera's transform - used to know where you're looking")]
[SerializeField] private Transform _cameraTransform;
[Tooltip("The player's stats script for stamina usage")]
[SerializeField] private PlayerStats _playerStats;
[Header("Weapon Management")]
[Tooltip("The active weapon used for damage and stats.")]
[SerializeField] private Weapon _currentWeapon;
@@ -54,10 +57,12 @@ public class MeleeCombatLayerController : MonoBehaviour
private float _equipWeight;
private int _meleeLayerIndex;
private Transform _spineBone;
private void Start()
{
_meleeLayerIndex = _animator.GetLayerIndex(_meleeLayerName);
_spineBone = _animator.GetBoneTransform(HumanBodyBones.Spine);
// If the referenced weapon is a prefab asset (not in the scene), instantiate it
if (_currentWeapon != null && !_currentWeapon.gameObject.scene.IsValid())
@@ -97,10 +102,55 @@ public class MeleeCombatLayerController : MonoBehaviour
_blockAction.Disable();
}
private bool _isAttacking;
private void Update()
{
// Blend the melee layer weight based on equip state
float equipTarget = _isEquipped ? 1f : 0f;
// We only want this layer to be active if we are actively attacking, blocking, or equipping.
bool isLayerActive = false;
_isAttacking = false;
if (_animator != null)
{
// Check all layers for the Attack state
for (int i = 0; i < _animator.layerCount; i++)
{
AnimatorStateInfo stateInfo = _animator.GetCurrentAnimatorStateInfo(i);
if (stateInfo.IsTag("Attack") ||
stateInfo.IsName("Attack") ||
stateInfo.IsName("Attack1") ||
stateInfo.IsName("Attack2") ||
stateInfo.IsName("Attack3") ||
stateInfo.IsName("A_MOD_SWD_Attack_03"))
{
_isAttacking = true;
break;
}
}
if (_meleeLayerIndex != -1)
{
AnimatorStateInfo stateInfo = _animator.GetCurrentAnimatorStateInfo(_meleeLayerIndex);
bool isPlayingCombatAnim = stateInfo.IsName("Equip") ||
stateInfo.IsName("Unequip");
// If attacking on ANY layer, or blocking/equipping, keep the combat state active
if (_isPoweringUp || _isBlocking || isPlayingCombatAnim)
{
isLayerActive = true;
}
// If the attack is actually on the melee layer (old setup), keep weight 1
if (stateInfo.IsName("Attack"))
{
isLayerActive = true;
}
}
}
// Blend the melee layer weight based on if it's currently active
float equipTarget = isLayerActive ? 1f : 0f;
_equipWeight = Mathf.MoveTowards(_equipWeight, equipTarget, _equipBlendSpeed * Time.deltaTime);
if (_meleeLayerIndex != -1)
@@ -121,6 +171,12 @@ public class MeleeCombatLayerController : MonoBehaviour
{
if (_isEquipped) yield break;
if (_currentWeapon == null)
{
Debug.LogWarning("Cannot equip! No melee weapon is assigned.");
yield break;
}
_isEquipped = true;
if (_meleeLayerIndex != -1)
@@ -221,6 +277,11 @@ public class MeleeCombatLayerController : MonoBehaviour
if (!_isEquipped) return;
_isBlocking = true;
if (_playerStats != null)
{
_playerStats.staminaRegenMultiplier = 0.5f; // Half regen while blocking
}
// Slow down to walk speed while blocking
_inputReader.onWalkToggled?.Invoke();
}
@@ -231,6 +292,11 @@ public class MeleeCombatLayerController : MonoBehaviour
{
_isBlocking = false;
if (_playerStats != null)
{
_playerStats.staminaRegenMultiplier = 1f; // Restore regen rate
}
// Return to normal speed
_inputReader.onWalkToggled?.Invoke();
}
@@ -244,6 +310,14 @@ public class MeleeCombatLayerController : MonoBehaviour
{
if (_animator != null)
{
// Optional: You can check if there's enough stamina to attack here
// e.g. if (_playerStats != null && !_playerStats.HasStaminaPercentage(0.1f)) return;
if (_playerStats != null)
{
_playerStats.UseStamina(15f); // Use 15 stamina per swing
}
_animator.SetTrigger(_attackHash);
}
}
@@ -256,11 +330,13 @@ public class MeleeCombatLayerController : MonoBehaviour
/// </summary>
public void Hit()
{
Debug.Log("Animation Event 'Hit' triggered!");
DealDamage();
}
public void DealDamage()
{
Debug.Log("DealDamage called!");
if (_currentWeapon == null)
{
Debug.LogWarning("No weapon assigned to deal damage!");
@@ -271,17 +347,19 @@ public class MeleeCombatLayerController : MonoBehaviour
if (_currentWeapon.hitShape == Weapon.HitShape.Sphere)
{
hitColliders = Physics.OverlapSphere(_currentWeapon.GetHitCenter(), _currentWeapon.attackRadius, _targetLayers);
hitColliders = Physics.OverlapSphere(_currentWeapon.GetHitCenter(), _currentWeapon.attackRadius, _targetLayers, QueryTriggerInteraction.Ignore);
}
else
{
hitColliders = Physics.OverlapBox(_currentWeapon.GetHitCenter(), _currentWeapon.hitBoxHalfExtents, _currentWeapon.GetHitRotation(), _targetLayers);
hitColliders = Physics.OverlapBox(_currentWeapon.GetHitCenter(), _currentWeapon.hitBoxHalfExtents, _currentWeapon.GetHitRotation(), _targetLayers, QueryTriggerInteraction.Ignore);
}
foreach (Collider hit in hitColliders)
{
Debug.Log($"Melee hit {hit.name} for {_currentWeapon.damage} damage!");
hit.GetComponent<WorldObject>()?.TakeDamage(_currentWeapon.damage);
hit.GetComponent<NPCController>()?.TakeDamage(_currentWeapon.damage);
hit.GetComponent<PlayerStats>()?.TakeDamage(_currentWeapon.damage);
}
}
@@ -289,11 +367,40 @@ public class MeleeCombatLayerController : MonoBehaviour
{
if (_isBlocking)
{
Debug.Log($"Blocked {amount} damage!");
return;
if (_playerStats != null)
{
if (_playerStats.currentStamina >= amount)
{
Debug.Log($"Blocked {amount} damage! Cost {amount} stamina.");
_playerStats.UseStamina(amount);
return;
}
else
{
Debug.Log($"Block broken! Took {amount} damage.");
_isBlocking = false;
_playerStats.staminaRegenMultiplier = 1f;
_playerStats.UseStamina(_playerStats.currentStamina); // Drain rest of stamina
_playerStats.PauseStaminaRegen(3f); // Pause regen for 3 seconds
_inputReader.onWalkToggled?.Invoke(); // Stop walking slow
// Optionally, play a guard break animation here if you have one
// _animator.SetTrigger("GuardBreak");
}
}
else
{
Debug.Log($"Blocked {amount} damage!");
return;
}
}
Debug.Log($"Took {amount} damage!");
if (_playerStats != null)
{
_playerStats.TakeDamage(amount);
}
if (_animator != null)
{
_animator.SetTrigger(_takeDamageHash);
@@ -302,5 +409,28 @@ public class MeleeCombatLayerController : MonoBehaviour
#endregion
private void LateUpdate()
{
// If we are not attacking and the equip weight is 0, don't bend the spine
if (_spineBone == null || _cameraTransform == null) return;
if (_equipWeight <= 0f && !_isAttacking) return;
// Calculate the camera's pitch (up/down rotation)
// We use the camera's forward vector compared to the horizontal plane
Vector3 cameraForward = _cameraTransform.forward;
Vector3 horizontalForward = new Vector3(cameraForward.x, 0, cameraForward.z).normalized;
float pitch = Vector3.SignedAngle(horizontalForward, cameraForward, _cameraTransform.right);
// Clamp the pitch so we don't break the spine backward
pitch = Mathf.Clamp(pitch, -45f, 45f);
// Apply pitch to the spine bone.
// If we are doing a full-body attack, force the blend weight to 1.0!
float blendWeight = _isAttacking ? 1.0f : _equipWeight;
// We rotate around the character's right axis to lean forward/backward
_spineBone.rotation = Quaternion.AngleAxis(pitch * blendWeight, transform.right) * _spineBone.rotation;
}
}