chunk 1: core gameplay scripts scenes runtime assets
This commit is contained in:
221
Assets/Scripts/Weapons/WeaponManager.cs
Normal file
221
Assets/Scripts/Weapons/WeaponManager.cs
Normal file
@@ -0,0 +1,221 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Manages equipping/unequipping weapons on the player and overrides attack animations
|
||||
/// so that only the equipped weapon's animations are used.
|
||||
/// Assumes weapon models (bat/hammer) are already attached to the player's hand and hidden.
|
||||
/// </summary>
|
||||
public class WeaponManager : MonoBehaviour
|
||||
{
|
||||
public enum WeaponType { None, Bat, Hammer }
|
||||
|
||||
[Header("In-hand weapon objects (already attached to player hand)")]
|
||||
[SerializeField] private GameObject batInHand;
|
||||
[SerializeField] private GameObject hammerInHand;
|
||||
|
||||
[Header("Optional: disable base attack inputs while a weapon is equipped")]
|
||||
[SerializeField] private bool blockBaseAttacksWhenEquipped = true;
|
||||
|
||||
[Header("Input System")]
|
||||
[SerializeField] private string pickupActionName = "WeaponPickup";
|
||||
|
||||
[Header("Pickup/Drop Settings")]
|
||||
[SerializeField] private float dropReequipBlockSeconds = 0.2f;
|
||||
|
||||
// Tracks the current equipped weapon
|
||||
public WeaponType CurrentWeapon { get; private set; } = WeaponType.None;
|
||||
public bool IsWeaponEquipped => CurrentWeapon != WeaponType.None;
|
||||
|
||||
// Internal state for alternating swings (e.g., BatOne/BatTwo)
|
||||
private int swingIndex = 0;
|
||||
|
||||
// Base attacks to override when a weapon is equipped
|
||||
private static readonly HashSet<string> baseAttackNames = new HashSet<string>
|
||||
{
|
||||
AnimationNames.Jab,
|
||||
AnimationNames.Haymaker,
|
||||
AnimationNames.BasicKick,
|
||||
AnimationNames.SumoSlap,
|
||||
AnimationNames.SpinningBackfist,
|
||||
AnimationNames.ElbowSmash,
|
||||
AnimationNames.AirPunch,
|
||||
AnimationNames.LowKick,
|
||||
// common fallbacks
|
||||
"Punch", "Kick", "Strike"
|
||||
};
|
||||
|
||||
private PlayerInput _playerInput;
|
||||
private InputAction _pickupAction;
|
||||
private float _pickupCooldownUntil = 0f;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_playerInput = GetComponent<PlayerInput>();
|
||||
if (_playerInput != null)
|
||||
{
|
||||
_pickupAction = _playerInput.actions?.FindAction(pickupActionName, false);
|
||||
if (_pickupAction != null && !_pickupAction.enabled)
|
||||
_pickupAction.SafeEnable();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (_pickupAction != null)
|
||||
_pickupAction.performed += OnPickupOrDrop;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (_pickupAction != null)
|
||||
_pickupAction.performed -= OnPickupOrDrop;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// Ensure correct visibility at start
|
||||
ApplyVisibility();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Equip a specific weapon and reveal it on the player's hand.
|
||||
/// </summary>
|
||||
public void EquipWeapon(WeaponType weapon)
|
||||
{
|
||||
if (CurrentWeapon == weapon) return;
|
||||
|
||||
CurrentWeapon = weapon;
|
||||
swingIndex = 0; // reset combo index on equip
|
||||
ApplyVisibility();
|
||||
Debug.Log($"Equipped weapon: {CurrentWeapon}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unequip any weapon and restore default (unarmed) state.
|
||||
/// </summary>
|
||||
public void UnequipWeapon()
|
||||
{
|
||||
if (CurrentWeapon == WeaponType.None) return;
|
||||
CurrentWeapon = WeaponType.None;
|
||||
swingIndex = 0;
|
||||
ApplyVisibility();
|
||||
Debug.Log("Unequipped weapon");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves an incoming requested animation to a weapon-specific animation if a weapon is equipped.
|
||||
/// Non-attack animations are passed through unchanged.
|
||||
/// </summary>
|
||||
public string ResolveAnimation(string requested)
|
||||
{
|
||||
if (!IsWeaponEquipped)
|
||||
return requested;
|
||||
|
||||
// Allow reactions, blocks, locomotion and non-attack anims
|
||||
if (!baseAttackNames.Contains(requested))
|
||||
return requested;
|
||||
|
||||
switch (CurrentWeapon)
|
||||
{
|
||||
case WeaponType.Bat:
|
||||
return NextBatAnimation();
|
||||
case WeaponType.Hammer:
|
||||
return NextHammerAnimation();
|
||||
default:
|
||||
return requested;
|
||||
}
|
||||
}
|
||||
|
||||
private string NextBatAnimation()
|
||||
{
|
||||
// Alternate between two bat swings
|
||||
swingIndex = (swingIndex + 1) % 2;
|
||||
return swingIndex == 0 ? AnimationNames.BatOne : AnimationNames.BatTwo;
|
||||
}
|
||||
|
||||
private string NextHammerAnimation()
|
||||
{
|
||||
swingIndex = (swingIndex + 1) % 2;
|
||||
return swingIndex == 0 ? AnimationNames.HammerOne : AnimationNames.HammerTwo;
|
||||
}
|
||||
|
||||
private void ApplyVisibility()
|
||||
{
|
||||
// Toggle in-hand models based on current weapon
|
||||
if (batInHand != null)
|
||||
{
|
||||
batInHand.SetActive(CurrentWeapon == WeaponType.Bat);
|
||||
Debug.Log($"[WeaponManager] batInHand active={batInHand.activeSelf} (weapon={CurrentWeapon})");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[WeaponManager] 'batInHand' is not assigned in inspector.");
|
||||
}
|
||||
|
||||
if (hammerInHand != null)
|
||||
{
|
||||
hammerInHand.SetActive(CurrentWeapon == WeaponType.Hammer);
|
||||
Debug.Log($"[WeaponManager] hammerInHand active={hammerInHand.activeSelf} (weapon={CurrentWeapon})");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[WeaponManager] 'hammerInHand' is not assigned in inspector.");
|
||||
}
|
||||
}
|
||||
|
||||
// Keep the last pickup object used so we can re-enable it on drop (optional)
|
||||
private WeaponPickup lastPickupSource;
|
||||
|
||||
public void EquipWeaponFromPickup(WeaponType weapon, WeaponPickup pickup)
|
||||
{
|
||||
lastPickupSource = pickup;
|
||||
EquipWeapon(weapon);
|
||||
}
|
||||
|
||||
public void DropWeapon()
|
||||
{
|
||||
if (!IsWeaponEquipped) return;
|
||||
|
||||
// Play the pickup animation even when dropping
|
||||
var anim = GetComponent<AnimationManager>();
|
||||
if (anim != null)
|
||||
{
|
||||
anim.PlayAnimation(AnimationNames.WeaponPickup);
|
||||
}
|
||||
|
||||
if (lastPickupSource != null)
|
||||
{
|
||||
// Let the pickup place itself and avoid instant trigger
|
||||
lastPickupSource.PlaceAsDropped(transform);
|
||||
lastPickupSource = null;
|
||||
}
|
||||
|
||||
// Block immediate re-equip for a short time
|
||||
_pickupCooldownUntil = Time.time + dropReequipBlockSeconds;
|
||||
|
||||
UnequipWeapon();
|
||||
}
|
||||
|
||||
private void OnPickupOrDrop(InputAction.CallbackContext ctx)
|
||||
{
|
||||
// Ignore if within cooldown window
|
||||
if (Time.time < _pickupCooldownUntil)
|
||||
return;
|
||||
|
||||
// Requirement 3: If the character has a weapon in hand, pressing the same button drops it (and plays pickup anim)
|
||||
if (IsWeaponEquipped)
|
||||
{
|
||||
DropWeapon();
|
||||
return;
|
||||
}
|
||||
|
||||
// Requirement 1 & 2: Only when in range and no weapon equipped, allow pickup and play pickup anim
|
||||
if (WeaponPickup.currentPickupInRange != null)
|
||||
{
|
||||
WeaponPickup.currentPickupInRange.TryEquip();
|
||||
}
|
||||
// Otherwise do nothing (no animation out of range)
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Weapons/WeaponManager.cs.meta
Normal file
2
Assets/Scripts/Weapons/WeaponManager.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b100d764858ef1878b2555a4ae5333c0
|
||||
198
Assets/Scripts/Weapons/WeaponPickup.cs
Normal file
198
Assets/Scripts/Weapons/WeaponPickup.cs
Normal file
@@ -0,0 +1,198 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Simple world pickup for bat or hammer. Uses Input System action "WeaponPickup" via WeaponManager to equip when in range.
|
||||
/// Plays the pickup animation on the player, hides the world pickup, and shows the in-hand weapon.
|
||||
/// Assumes the player already has WeaponManager and the weapon models hidden on hand.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Collider))]
|
||||
public class WeaponPickup : MonoBehaviour
|
||||
{
|
||||
public enum PickupType { Bat, Hammer }
|
||||
public PickupType pickupType = PickupType.Bat;
|
||||
|
||||
[Header("Pickup Settings")]
|
||||
public float pickUpRange = 2.5f;
|
||||
public Transform player;
|
||||
|
||||
[Header("Input System")]
|
||||
[SerializeField] private string pickupActionName = "WeaponPickup";
|
||||
|
||||
private bool _playerInTrigger;
|
||||
private PlayerInput _playerInput;
|
||||
private InputAction _pickupAction; // action named "WeaponPickup" in the input asset
|
||||
|
||||
// Tracks the pickup the player is currently inside (single-player assumption)
|
||||
public static WeaponPickup currentPickupInRange;
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
var col = GetComponent<Collider>();
|
||||
col.isTrigger = true;
|
||||
// Ensure trigger events fire by having a kinematic Rigidbody on the pickup
|
||||
var rb = GetComponent<Rigidbody>();
|
||||
if (rb == null)
|
||||
{
|
||||
rb = gameObject.AddComponent<Rigidbody>();
|
||||
}
|
||||
rb.isKinematic = true;
|
||||
rb.useGravity = false;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
// Attempt to bind immediately if player is already assigned
|
||||
TryBindInput();
|
||||
}
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
// Robustly detect the player even if a child collider enters
|
||||
var wm = other.GetComponentInParent<WeaponManager>();
|
||||
if (wm != null)
|
||||
{
|
||||
player = wm.transform;
|
||||
_playerInTrigger = true;
|
||||
currentPickupInRange = this;
|
||||
TryBindInput();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggerExit(Collider other)
|
||||
{
|
||||
var wm = other.GetComponentInParent<WeaponManager>();
|
||||
if (wm != null && player == wm.transform)
|
||||
{
|
||||
_playerInTrigger = false;
|
||||
if (currentPickupInRange == this)
|
||||
currentPickupInRange = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (player == null)
|
||||
{
|
||||
// Lazy find player by component to be safer
|
||||
var wm = FindObjectOfType<WeaponManager>();
|
||||
if (wm != null) player = wm.transform;
|
||||
}
|
||||
|
||||
// Ensure input is bound/enabled (even though we don't read it here)
|
||||
TryBindInput();
|
||||
|
||||
// IMPORTANT: Do not handle input here. WeaponManager exclusively handles the WeaponPickup action.
|
||||
|
||||
// Proximity fallback: if triggers fail, still allow pickup when close
|
||||
if (player != null)
|
||||
{
|
||||
bool near = Vector3.Distance(player.position, transform.position) <= pickUpRange;
|
||||
if (near)
|
||||
{
|
||||
currentPickupInRange = this;
|
||||
}
|
||||
else if (!_playerInTrigger && currentPickupInRange == this)
|
||||
{
|
||||
currentPickupInRange = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TryBindInput()
|
||||
{
|
||||
if (player == null) return;
|
||||
|
||||
if (_playerInput == null)
|
||||
{
|
||||
_playerInput = player.GetComponent<PlayerInput>();
|
||||
}
|
||||
|
||||
if (_playerInput == null) return;
|
||||
|
||||
if (_pickupAction == null)
|
||||
{
|
||||
_pickupAction = _playerInput.actions?.FindAction(pickupActionName, false);
|
||||
}
|
||||
|
||||
if (_pickupAction != null && !_pickupAction.enabled)
|
||||
{
|
||||
_pickupAction.SafeEnable();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
// Keep action enabled via PlayerInput; only clear local refs
|
||||
if (currentPickupInRange == this)
|
||||
currentPickupInRange = null;
|
||||
_pickupAction = null;
|
||||
_playerInput = null;
|
||||
}
|
||||
|
||||
// Expose for global handler (called by WeaponManager when button pressed and in range)
|
||||
public void TryEquip()
|
||||
{
|
||||
if (player == null) return;
|
||||
|
||||
// Safety: ensure still in range and not holding a weapon
|
||||
bool inRange = _playerInTrigger || Vector3.Distance(player.position, transform.position) <= pickUpRange;
|
||||
var wm = player.GetComponent<WeaponManager>();
|
||||
if (!inRange || wm == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (wm.IsWeaponEquipped)
|
||||
{
|
||||
// Let WeaponManager handle dropping with the same button
|
||||
return;
|
||||
}
|
||||
|
||||
// Play pickup animation if available
|
||||
var anim = player.GetComponent<AnimationManager>();
|
||||
if (anim != null)
|
||||
{
|
||||
anim.PlayAnimation(AnimationNames.WeaponPickup);
|
||||
}
|
||||
|
||||
switch (pickupType)
|
||||
{
|
||||
case PickupType.Bat:
|
||||
wm.EquipWeaponFromPickup(WeaponManager.WeaponType.Bat, this);
|
||||
break;
|
||||
case PickupType.Hammer:
|
||||
wm.EquipWeaponFromPickup(WeaponManager.WeaponType.Hammer, this);
|
||||
break;
|
||||
}
|
||||
|
||||
// Hide the world pickup (simulate taking it). You can pool/destroy instead.
|
||||
gameObject.SetActive(false);
|
||||
|
||||
// Clear static reference if this was the current pickup
|
||||
if (currentPickupInRange == this)
|
||||
currentPickupInRange = null;
|
||||
}
|
||||
|
||||
// Called by WeaponManager to place this pickup back into the world when dropping
|
||||
public void PlaceAsDropped(Transform playerTransform)
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
transform.position = playerTransform.position + Vector3.up * 0.25f;
|
||||
transform.rotation = Quaternion.Euler(0f, playerTransform.eulerAngles.y, 0f);
|
||||
|
||||
// Prevent instant re-trigger by disabling collider briefly
|
||||
var col = GetComponent<Collider>();
|
||||
if (col != null)
|
||||
{
|
||||
StartCoroutine(DisableColliderBrief(col));
|
||||
}
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator DisableColliderBrief(Collider col)
|
||||
{
|
||||
bool prev = col.enabled;
|
||||
col.enabled = false;
|
||||
yield return new WaitForSeconds(0.2f);
|
||||
col.enabled = prev;
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Weapons/WeaponPickup.cs.meta
Normal file
2
Assets/Scripts/Weapons/WeaponPickup.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fa190e2a380fa352e84a815c26541105
|
||||
Reference in New Issue
Block a user