All right, so I’m done eating. Here is what I said I would show. Note that this is very specific to our game, so a lot of the particulars pertain directly to that.
So in our system we can have multiple weapons. Melee, Ranged, and some others (like a mine that can be placed on the ground). As a result the way we ‘swap’ weapons is abstracted into its own class.
You can see this here:
using UnityEngine;
using System.Collections.Generic;
using com.spacepuppy;
using com.spacepuppy.Anim;
using com.spacepuppy.SPInput;
using com.spacepuppy.Utils;
using com.mansion.Entities.Inventory;
using com.mansion.Entities.Weapons;
using com.mansion.UserInput;
namespace com.mansion.Entities.Actors.Player
{
public class PlayerSwapWeaponActionStyle : SPComponent, IPlayerActionStyle, PlayerWeaponPouch.ICurrentWeaponChangedHandler
{
#region Fields
[Header("References")]
[SerializeField]
[DefaultFromSelf(Relativity = EntityRelativity.Entity)]
private PlayerWeaponPouch _weaponPouch;
[SerializeField]
[DefaultFromSelf(Relativity = EntityRelativity.Entity)]
private AmmoPouch _ammoPouch;
[SerializeField]
[DefaultFromSelf]
private PlayerMeleeWeaponStyle _melee;
[SerializeField]
[DefaultFromSelf]
private PlayerFreeFocusMeleeWeaponStyle _freeFocusMelee;
[SerializeField]
[DefaultFromSelf]
private PlayerRangedWeaponStyle _ranged;
[SerializeField]
[DefaultFromSelf]
private PlayerLockOn _lockon;
[System.NonSerialized()]
private PlayerEntity _entity;
[System.NonSerialized]
private IPlayerWeaponStyle _currentWeaponStyle;
[System.NonSerialized]
private RadicalCoroutine _swapRoutine;
#endregion
#region CONSTRUCTOR
protected override void Awake()
{
base.Awake();
_entity = SPEntity.Pool.GetFromSource<PlayerEntity>(this);
if (_melee != null) _melee.IsActiveWeapon = false;
if (_freeFocusMelee != null) _freeFocusMelee.IsActiveWeapon = false;
if (_ranged != null) _ranged.IsActiveWeapon = false;
_currentWeaponStyle = null;
}
protected override void OnEnable()
{
base.OnEnable();
if (_currentWeaponStyle == null && !this.IsSwapping && _weaponPouch.CurrentWeapon != null)
{
_swapRoutine = this.StartRadicalCoroutine(this.SetWeapon(_weaponPouch.CurrentWeapon));
if (_swapRoutine != null && _swapRoutine.Finished) _swapRoutine = null;
}
}
protected override void Start()
{
base.Start();
}
#endregion
#region Properties
public IEntity Entity
{
get { return _entity; }
}
public AmmoPouch AmmoPouch
{
get { return _ammoPouch; }
}
public PlayerWeaponPouch WeaponPouch
{
get { return _weaponPouch; }
}
public bool IsSwapping
{
get { return _swapRoutine != null && !_swapRoutine.Finished; }
}
#endregion
#region Methods
private System.Collections.IEnumerator SetWeapon(IWeapon weapon)
{
_entity.InSwappingWeapon = true;
/*
* make sure we drop weapon if it's up...
* this technically shouldn't happen since we stopped the ability to swap weapon while aiming
* but we're keeping it here just in case it happens
*/
IRadicalWaitHandle waitHandle = null;
if (_currentWeaponStyle != null)
{
waitHandle = _currentWeaponStyle.DropWeapon();
_currentWeaponStyle = null;
}
if (_lockon != null) _lockon.DropTarget();
if (_melee != null) _melee.IsActiveWeapon = false;
if (_freeFocusMelee != null) _freeFocusMelee.IsActiveWeapon = false;
if (_ranged != null) _ranged.IsActiveWeapon = false;
_entity.InStrafe = false;
if (waitHandle != null && !waitHandle.IsComplete)
{
yield return waitHandle;
}
//disable weapons
for (int i = 0; i < _weaponPouch.Weapons.Count; i++)
{
_weaponPouch.Weapons[i].gameObject.SetActive(false);
}
//set the new weapon
_currentWeaponStyle = this.GetWeaponStyle(weapon);
if (_currentWeaponStyle != null && _currentWeaponStyle.SetWeapon(weapon))
{
_currentWeaponStyle.IsActiveWeapon = true;
//play swap in animation
ISPAnim a;
if(_currentWeaponStyle.WeaponAnimator != null &&
_currentWeaponStyle.WeaponAnimator.PlaySwapIn(out a))
{
yield return a;
}
}
_swapRoutine = null;
_entity.InSwappingWeapon = false;
}
private IPlayerWeaponStyle GetWeaponStyle(IWeapon weapon)
{
if (weapon is IMeleeWeapon)
{
//TODO - distinguish between focused/free-focus melee weapons
switch((weapon as IMeleeWeapon).Style)
{
case MeleeWeaponStyle.Undefined:
case MeleeWeaponStyle.Focused:
return _melee;
case MeleeWeaponStyle.FreeFocused:
return _freeFocusMelee;
}
}
else if (weapon is GunWeapon)
{
return _ranged;
}
return null;
}
#endregion
#region IPlayerActionStyle Interface
bool IPlayerActionStyle.OverridingAction
{
get { return false; }
}
void IPlayerActionStyle.OnStateEntered(PlayerActionMotor motor, IPlayerActionStyle lastState)
{
}
void IPlayerActionStyle.OnStateExited(PlayerActionMotor motor, IPlayerActionStyle nextState)
{
}
void IPlayerActionStyle.DoUpdate(MansionInputDevice input, bool isActiveAction)
{
if (!this.enabled || _entity.Stalled) return;
if (_weaponPouch == null) return;
//if (_ranged.WeaponIsDrawn || _melee.WeaponIsDrawn) return;
if(!this.IsSwapping)
{
_entity.InSwappingWeapon = false;
if (input.GetButtonState(MansionInputs.BumpRight) == ButtonState.Down
|| input.GetButtonState(MansionInputs.BumpLeft) == ButtonState.Down)
{
var inventory = Services.Get<IInventoryDisplay>();
if(inventory != null)
{
inventory.Show(InventoryDisplayMode.WeaponSwap);
}
}
}
}
#endregion
#region ICurrentWeaponChangedHandler Interface
void PlayerWeaponPouch.ICurrentWeaponChangedHandler.OnCurrentWeaponChanged(PlayerWeaponPouch pouch, IWeapon weapon)
{
if (_currentWeaponStyle != null && _currentWeaponStyle.Weapon == weapon) return;
if(_swapRoutine != null)
{
_swapRoutine.Cancel();
_swapRoutine = null;
}
_swapRoutine = this.StartRadicalCoroutine(this.SetWeapon(weapon));
if (_swapRoutine != null && _swapRoutine.Finished) _swapRoutine = null;
}
#endregion
}
}
Basically during update we check for tapping of the L/R bumpers which pull up the quick inventory display. And when a weapon is selected that ‘SetWeapon’ is called which swaps out the weapon. In doing so we clean up what needs to be, determine the appropriate WeaponStyle controller (this would be the ‘PlayerShoot’ in your example’) and then signal to that to do the appropriate and play animations. Note that animations are all played by an abstracted ‘IWeaponAnimator’ interface so that it can integrate with either Legacy or Mecanim.
When swapping to a gun that means we used the ‘PlayerRangedWeaponStyle’. Please note that technically this uses the ‘IPlayerWeaponStyle’ interface, so really if we wanted we could have an abstraction of this, thus allowing us to composite any weapon style we wanted. But for now we really only have the PlayerRangedWeaponStyle in use for ranged weapons.
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using com.spacepuppy;
using com.spacepuppy.Anim;
using com.spacepuppy.Movement;
using com.spacepuppy.SPInput;
using com.spacepuppy.Utils;
using com.mansion.Entities.Weapons;
using com.mansion.Messages;
using com.mansion.Entities.Proxy;
using com.mansion.UserInput;
using com.mansion.Statistics;
namespace com.mansion.Entities.Actors.Player
{
[RequireComponentInEntity(typeof(PlayerEntity))]
public class PlayerRangedWeaponStyle : SPComponent, IPlayerWeaponStyle
{
private enum InputCacheState
{
None,
Fire,
Reload
}
private enum ActionState
{
None,
Firing,
Reloading
}
#region Fields
[Header("Weapon")]
[SerializeField]
private GunWeapon _weapon;
[SerializeField]
private Transform _gunAimOrigin;
[SerializeField]
[DefaultFromSelf(Relativity = EntityRelativity.Entity)]
private AmmoPouch _ammo;
[Header("References")]
[SerializeField]
[DefaultFromSelf]
private PlayerActionMotor _actionMotor;
[SerializeField]
[DefaultFromSelf]
private MovementMotor _movementMotor;
[SerializeField]
[DefaultFromSelf(Relativity = EntityRelativity.Entity)]
private MovementAnimatorProxy _movementAnimatorProxy;
[SerializeField]
[DefaultFromSelf]
private PlayerLockOn _lockon;
[System.NonSerialized()]
private PlayerEntity _entity;
[System.NonSerialized]
private IReadiedWeaponAnimator _weaponAnimator;
[System.NonSerialized]
private PlayerWeaponAux _weaponAux;
[System.NonSerialized()]
private InputCacheState _inputCache;
[System.NonSerialized]
private ActionState _state;
#endregion
#region CONSTRUCTOR
protected override void Awake()
{
base.Awake();
_entity = SPEntity.Pool.GetFromSource<PlayerEntity>(this);
if (_movementMotor == null) _movementMotor = this.GetComponent<MovementMotor>();
if (_actionMotor == null) _actionMotor = this.GetComponent<PlayerActionMotor>();
if (_lockon == null) _lockon = this.GetComponent<PlayerLockOn>();
}
protected override void Start()
{
base.Start();
this.SetWeapon(_weapon);
}
#endregion
#region Properties
public Transform GunAimOrigin
{
get { return _gunAimOrigin; }
set { _gunAimOrigin = value; }
}
public AmmoPouch Ammo
{
get { return _ammo; }
}
public bool WeaponIsDrawn
{
get { return this.enabled && _weaponAnimator != null && _weaponAnimator.State != ReadiedWeaponAnimatorState.None; }
}
#endregion
#region Methods
private void UpdateAsActive(MansionInputDevice input)
{
if (_lockon != null && _lockon.HasTarget())
{
var dir = (_lockon.LockedOnAspect.transform.position - _gunAimOrigin.position).normalized;
var a = Mathf.Asin(dir.y) * Mathf.Rad2Deg;
_weaponAnimator.AimWeight = a / 45f;
}
else
{
_weaponAnimator.AimWeight = 0f;
}
switch (_weaponAnimator.State)
{
case ReadiedWeaponAnimatorState.Holstering:
case ReadiedWeaponAnimatorState.None:
this.ForceHolsterWeapon();
return;
case ReadiedWeaponAnimatorState.Ready:
{
if (input.GetButtonState(MansionInputs.Aim) <= ButtonState.None)
{
this.ForceHolsterWeapon();
return;
}
else if(_state > ActionState.None || _entity.AttackNotifier.InAttack)
{
if (input.GetButtonState(MansionInputs.Action) == ButtonState.Down)
{
_inputCache = InputCacheState.Fire;
}
else if (input.GetButtonState(MansionInputs.Reload) == ButtonState.Down)
{
_inputCache = InputCacheState.Reload;
}
}
else if (_inputCache == InputCacheState.Fire || input.GetButtonState(MansionInputs.Action) == ButtonState.Down)
{
this.StartRadicalCoroutine(this.DoFire(_weapon));
}
else if (_inputCache == InputCacheState.Reload || input.GetButtonState(MansionInputs.Reload) == ButtonState.Down)
{
this.StartRadicalCoroutine(this.DoReload(_weapon));
}
}
break;
case ReadiedWeaponAnimatorState.Drawing:
case ReadiedWeaponAnimatorState.Firing:
case ReadiedWeaponAnimatorState.Reload:
{
if (input.GetButtonState(MansionInputs.Aim) <= ButtonState.None)
{
this.ForceHolsterWeapon();
return;
}
else if(input.GetButtonState(MansionInputs.Action) == ButtonState.Down)
{
_inputCache = InputCacheState.Fire;
}
else if(input.GetButtonState(MansionInputs.Reload) == ButtonState.Down)
{
_inputCache = InputCacheState.Reload;
}
}
break;
}
if (_weaponAux != null && _weaponAux.ProneStyle > WeaponProneStyle.ProneWhileAiming) _entity.Stun.Begin(this);
_entity.InStrafe = true;
if (_weaponAux != null) _actionMotor.MovementSettings = _weaponAux.OverrideAimMovementSettings;
var h = input.GetDualAxleState(MansionInputs.RStick).x;
if (_lockon != null)
{
if (!_lockon.HasTarget() || h != 0f)
{
_lockon.AttemptFindTarget();
}
else
{
_lockon.FaceTarget();
if (_lockon.LockedOnEntity.Type == IEntity.EntityType.NPC)
_lockon.AttemptFindTarget();
}
}
_actionMotor.ResetIdleActionTicker(false);
}
private void UpdateAsInactive(MansionInputDevice input)
{
if (_actionMotor.IsInactiveUpdateOverriden(this) || _weaponAnimator == null) return;
if (_weaponAnimator.State <= ReadiedWeaponAnimatorState.None)
{
if (_weapon == null) return;
if (_weaponAnimator.State <= ReadiedWeaponAnimatorState.None)
{
ISPAnim anim;
if (input.GetButtonState(MansionInputs.Aim) > ButtonState.None)
{
//draw weapon
_weaponAnimator.Draw(out anim);
_actionMotor.ResetIdleActionTicker(true);
_actionMotor.States.StackState(this);
if (_weaponAux != null) _actionMotor.MovementSettings = _weaponAux.OverrideAimMovementSettings;
}
else if (input.GetButtonState(MansionInputs.Reload) == ButtonState.Down)
{
//reload weapon
float ammoCnt = _ammo.GetAmmoCount(_weapon.AmmoType) - _weapon.AmmoInMagazine;
if (ammoCnt > 0)
{
if (_weapon.AmmoInMagazine < _weapon.MagazineSize && _weaponAnimator.Reload(out anim))
{
_weapon.Reload(_ammo.GetAmmoCount(_weapon.AmmoType));
}
}
else
{
if (_weaponAnimator.ReloadNoAmmo(out anim))
{
_weapon.ReloadNoAmmo();
}
}
_actionMotor.ResetIdleActionTicker(false);
}
_inputCache = InputCacheState.None;
}
}
else
{
this.ForceHolsterWeapon();
}
}
private System.Collections.IEnumerator DoFire(GunWeapon weapon)
{
_state = ActionState.Firing;
_entity.AttackNotifier.BeginAttack(weapon);
if (_weaponAux != null && _weaponAux.ProneStyle == WeaponProneStyle.ProneWhileAttacking) _entity.Stun.Begin(this);
_inputCache = InputCacheState.None;
float ammoCnt = _ammo.GetAmmoCount(_weapon.AmmoType);
ISPAnim anim = null;
if (ammoCnt <= 0f)
{
_weapon.AmmoInMagazine = 0f;
if (_weaponAnimator.FireEmptyShot(out anim))
{
_inputCache = InputCacheState.None;
_weapon.OnFireEmptyShot.ActivateTrigger(_weapon, null);
Messaging.Broadcast<IPlayerFiredGunGlobalHandler>((o) => { o.OnWeaponFiredEmptyShot(_entity, _weapon); });
}
else
{
_inputCache = InputCacheState.Fire;
}
}
else if (_weapon.AmmoInMagazine <= 0f)
{
if (_weapon.QuickReload)
{
_weapon.Reload(ammoCnt);
}
else
{
if (_weaponAnimator.Reload(out anim))
{
_weapon.Reload(ammoCnt);
}
else
{
_inputCache = InputCacheState.Fire;
}
}
}
else if (_weaponAnimator.Fire(out anim))
{
_ammo.RemoveAmmo(_weapon.AmmoType, 1f);
_entity.AttackNotifier.BeginAttack(_weapon);
bool hit = false;
if (_lockon != null && _lockon.HasTarget())
hit = _weapon.Fire(_entity, _gunAimOrigin.position, (_lockon.LockedOnAspect.transform.position - _gunAimOrigin.position).normalized);
else
hit = _weapon.Fire(_entity, _gunAimOrigin);
if (hit && _lockon != null)
_lockon.DropTarget();
_entity.AttackNotifier.EndAttack(_weapon);
var stats = Services.Get<GameStatistics>();
if (stats != null)
stats.AdjustStat(GameStatistics.STAT_ROUNDSFIRED, 1);
Messaging.Broadcast<IPlayerFiredGunGlobalHandler>((o) => { o.OnWeaponFired(_entity, _weapon); });
}
else
{
_inputCache = InputCacheState.Fire;
}
yield return anim;
if (_weaponAux == null || _weaponAux.ProneStyle < WeaponProneStyle.ProneWhileAiming) _entity.Stun.End(this);
_entity.AttackNotifier.EndAttack(weapon);
_state = ActionState.None;
}
private System.Collections.IEnumerator DoReload(GunWeapon weapon)
{
_state = ActionState.Reloading;
_entity.AttackNotifier.BeginAttack(weapon);
ISPAnim anim = null;
float ammoCnt = _ammo.GetAmmoCount(weapon.AmmoType) - weapon.AmmoInMagazine;
if (ammoCnt > 0)
{
if (weapon.AmmoInMagazine < weapon.MagazineSize && _weaponAnimator.Reload(out anim))
{
weapon.Reload(_ammo.GetAmmoCount(weapon.AmmoType));
}
}
else
{
if (_weaponAnimator.ReloadNoAmmo(out anim))
{
weapon.ReloadNoAmmo();
}
}
yield return anim;
_inputCache = InputCacheState.None;
_entity.AttackNotifier.EndAttack(weapon);
_state = ActionState.None;
}
#endregion
#region IPlayerActionStyle Interface
bool IPlayerActionStyle.OverridingAction
{
get
{
return _movementMotor.States.Current is IPlayerMovementStyle && _weaponAnimator != null && _weaponAnimator.State > ReadiedWeaponAnimatorState.None;
}
}
void IPlayerActionStyle.OnStateEntered(PlayerActionMotor motor, IPlayerActionStyle lastState)
{
}
void IPlayerActionStyle.OnStateExited(PlayerActionMotor motor, IPlayerActionStyle nextState)
{
if (_weaponAnimator != null && _weaponAnimator.State > ReadiedWeaponAnimatorState.None)
{
ISPAnim anim;
_weaponAnimator.Holster(out anim);
}
if (_lockon != null) _lockon.DropTarget();
_inputCache = InputCacheState.None;
_entity.InStrafe = false;
}
void IPlayerActionStyle.DoUpdate(MansionInputDevice input, bool isActiveAction)
{
if (!this.enabled || _entity.Stalled) return;
if (_weapon == null) return;
if (isActiveAction)
this.UpdateAsActive(input);
else
this.UpdateAsInactive(input);
}
#endregion
#region IPlayerWeaponStyle Interface
public bool IsActiveWeapon
{
get { return this.enabled; }
set { this.enabled = value; }
}
public IWeapon Weapon
{
get { return _weapon; }
}
public IReadiedWeaponAnimator WeaponAnimator
{
get { return _weaponAnimator; }
}
public bool SetWeapon(IWeapon weapon)
{
this.ForceHolsterWeapon();
_weapon = weapon as GunWeapon;
if (_weapon != null) _weapon.gameObject.SetActive(true);
_weaponAux = null;
if (_weapon.GetComponentInChildren<PlayerWeaponAux>(out _weaponAux))
{
_weaponAnimator = _weaponAux.WeaponAnimator;
if (_movementAnimatorProxy != null) _movementAnimatorProxy.SetCurrent(_weaponAux.MovementAnimator);
_actionMotor.MovementSettings = _weaponAux.OverrideMovementSettings;
}
else
{
_weaponAnimator = null;
if (_movementAnimatorProxy != null) _movementAnimatorProxy.SetCurrent(null);
_actionMotor.MovementSettings = null;
}
return _weapon != null;
}
public IRadicalWaitHandle DropWeapon()
{
var result = this.ForceHolsterWeapon();
_weapon = null;
_weaponAnimator = null;
return result;
}
public IRadicalWaitHandle ForceHolsterWeapon()
{
if (_lockon != null) _lockon.DropTarget();
if (_weaponAux != null) _actionMotor.MovementSettings = _weaponAux.OverrideMovementSettings;
_inputCache = InputCacheState.None;
_state = ActionState.None;
_entity.AttackNotifier.EndAttack(_weapon);
_entity.Stun.End(this);
ISPAnim anim = null;
if (_weaponAnimator != null && _weaponAnimator.State > ReadiedWeaponAnimatorState.None)
{
_weaponAnimator.Holster(out anim);
}
if (object.ReferenceEquals(_actionMotor.States.Current, this))
{
_actionMotor.States.PopAllStates();
return null;
}
return anim;
}
#endregion
}
}
This handles all the logic about how the player acts while holding a gun. It plays the appropriate animations when necessary, it handles the inputs for when to draw the weapon and the holster it. It tells the weapon when fire was pressed. That sort.
Note, it does not handle the actual firing logic. That’s the guns job. It just handles signaling to the weapon to fire.
…
And finally we have a ‘GunWeapon’. This represents nearly all guns. We can inherit from it if we wanted to do otherwise. Or really if I could refactor this into a ‘IGunWeapon’ interface if need be in the future to allow for even more custom guns.
using UnityEngine;
using System.Collections.Generic;
using com.spacepuppy;
using com.spacepuppy.Scenario;
using com.spacepuppy.Utils;
namespace com.mansion.Entities.Weapons
{
public class GunWeapon : SPComponent, IAmmoLoadedWeapon
{
#region Fields
[SerializeField]
private WeaponTag _tag;
[SerializeField]
private AmmoType _ammoType;
[SerializeField()]
[UnityEngine.Serialization.FormerlySerializedAs("_clipSize")]
private DiscreteFloat _magazineSize = 6;
[SerializeField()]
[UnityEngine.Serialization.FormerlySerializedAs("_ammoInClip")]
private DiscreteFloat _ammoInMagazine = 6;
[SerializeField()]
private float _damage = 10f;
[SerializeField]
[EnumFlags]
private CritType _crit;
[SerializeField]
[Range(0f, 1f)]
private float _critPercent;
[Header("Event Triggers")]
[SerializeField()]
private Trigger _onFireWeapon;
[SerializeField()]
private Trigger _onReloadWeapon;
[SerializeField()]
private Trigger _onFireEmptyShot;
[SerializeField()]
private Trigger _onReloadNoAmmo;
[SerializeField]
private Trigger _onStrike;
[SerializeField]
private Trigger _onKill;
#endregion
#region Properties
public AmmoType AmmoType
{
get { return _ammoType; }
set { _ammoType = value; }
}
public float MagazineSize
{
get { return _magazineSize; }
set { _magazineSize = value; }
}
public float AmmoInMagazine
{
get { return _ammoInMagazine; }
set { _ammoInMagazine = Mathf.Clamp(value, 0f, _magazineSize); }
}
public bool QuickReload
{
get;
set;
}
public Trigger OnFireWeapon { get { return _onFireWeapon; } }
public Trigger OnReloadWeapon { get { return _onReloadWeapon; } }
public Trigger OnFireEmptyShot { get { return _onFireEmptyShot; } }
public Trigger OnReloadNoAmmo { get { return _onReloadNoAmmo; } }
#endregion
#region Methods
public float Reload(float max)
{
float amt = Mathf.Min((float)max, (float)_magazineSize);
if(amt > 0f)
{
_onReloadWeapon.ActivateTrigger(this, null);
float result = amt - _ammoInMagazine;
_ammoInMagazine = amt;
return result;
}
else
{
_onReloadNoAmmo.ActivateTrigger(this, null);
return 0f;
}
}
public void ReloadNoAmmo()
{
_onReloadNoAmmo.ActivateTrigger(this, null);
}
/// <summary>
/// Fires gun causing damage, Returns true if enemy died from strike
/// </summary>
/// <param name="dir">direction of gun fire</param>
/// <param name="ignoreFilter">A delegate which returns true if the entity in question should be ignored</param>
/// <returns></returns>
public bool Fire(IEntity entity, Transform shotFireTransform, WeaponStrikeFilter ignoreFilter = null)
{
return this.Fire(entity, shotFireTransform.position, shotFireTransform.forward, ignoreFilter);
}
/// <summary>
/// Fires gun causing damage, Returns true if enemy died from strike
/// </summary>
/// <param name="dir">direction of gun fire</param>
/// <param name="positionOffset">Offset from gun position</param>
/// <param name="ignoreFilter">A delegate which returns true if the entity in question should be ignored</param>
/// <returns></returns>
public bool Fire(IEntity entity, Vector3 shotFirePos, Vector3 dir, WeaponStrikeFilter ignoreFilter = null)
{
if (_ammoInMagazine <= 0f)
{
_onFireEmptyShot.ActivateTrigger(this, null);
return false;
}
_ammoInMagazine--;
_onFireWeapon.ActivateTrigger(this, null);
RaycastHit hit;
if (Physics.Raycast(shotFirePos, dir, out hit, float.PositiveInfinity, Constants.MASK_HITBOX | Constants.MASK_OBSTRUCTION, QueryTriggerInteraction.Collide))
{
if (hit.collider.gameObject.layer == Constants.LAYER_HITBOX)
{
var e = SPEntity.Pool.GetFromSource<IEntity>(hit.collider);
if (e != null && e.HealthMeter != null)
{
if (e == entity) return false;
if (ignoreFilter != null && ignoreFilter(e, hit.collider)) return false;
if (e.HealthMeter.Strike(this))
{
_onKill.ActivateTrigger(this, e);
return true;
}
else
{
_onStrike.ActivateTrigger(this, e);
return false;
}
}
}
}
return false;
}
#endregion
#region IWeapon Interface
public WeaponTag Tag
{
get { return _tag; }
set { _tag = value; }
}
public float Damage
{
get { return _damage; }
set { _damage = value; }
}
public CritType Crit
{
get { return _crit; }
set { _crit = value; }
}
public float CritPercent
{
get { return _critPercent; }
set { _critPercent = Mathf.Clamp01(value); }
}
#endregion
}
}
In it we have things like the magazine size, how much ammo is in the magazine, damage, crit type, crit percent, some triggers (UnityEvents) for the various events so that we can wire up sound FX and particle FX.
And yes we even have the ‘WeaponTag’ which is similar to your ‘WeaponList’. Which has these values:
public enum WeaponTag
{
/*
* Only ever append new entries. Inserting them in the middle will mess up any already configured weapons.
*/
Undefined = 0,
Knife,
NineMM,
Shotgun,
Revolver,
BBGun,
ElephantGun,
BombFire,
BombCryo,
BombElectric,
Crowbar,
Flashlight
}
Speaking of that.
Ummm…
Why in god’s name did you name this similar enum of yours ‘WeaponList’? That name is very confusing. It implies that the object is a list. But actually it’s not even an object, it’s a value, but it’s called a ‘List’. Like I get that the enum holds the various types of weapons and it’s sort of in list form.
But that’s not a traditional List. It’s not an actual collection. You can say:
WeaponList lst;
lst.Add(...);
Consider the MSDN example enum:
enum Day {Sat, Sun, Mon, Tue, Wed, Thu, Fri};
It’s called ‘Day’. Not ‘DayList’. Sure we listed off the days in the enum. But when you have a value of Day… it’s only a SINGLE Day.
This same thing goes with your abstracted class ‘WeaponCollection’. Which again implies that it is a collection. But it’s not. It’s a Weapon.
Sure, you’ll inherit from WeaponCollection multiple times possibly. And you now have a ‘set of weapons’. But ‘WeaponCollection’ does not represent that ‘set of weapons’. It’s the abstracted form of that weapon.
Think of it like animal hierarchies.
It’s not called a ‘MammalCollection’, it’s called a ‘Mammal’. My pet dog is not a Mammals, or MammalCollection, it’s a Mammal. All of the people in my house as a colleciton are Mammals, but each individual is a Mammal.
So like… if you had a collection of Mammals, you might say:
List<Mammal> mammalCollection = new List<Mammal>();
Or in the case of weapons, lets call it ‘Weapon’, or ‘AbstractWeapon’. And you’d say:
List<Weapon> weaponCollection = new List<Weapon>();
I mean… I’m not saying you’re wrong necessarily. You can name things however you’d like.
But for most people reading your code, they’re going to be confused. When you wrote this:
void SwapWeapon(WeaponList changeTo) {
// Disable previous weapon
weaponDict[currentWeapon].prefab.SetActive(false);
// Enable next weapon
weaponDict[changeTo].prefab.SetActive(true);
// Store script
currentWeaponScript = weaponDict[changeTo].script;
// Store animator
currentWeaponAnimator = weaponDict[changeTo].animator;
// Fetch shooting cooldown
shootWaitTime = weaponDict[changeTo].script.GetShootWaitTime();
nextShootTime = Time.time + shootWaitTime;
// Current weapon
currentWeapon = changeTo;
}
I was very confused at first. I was like “Wait, how do you ‘changeTo’ a collection? Am I passing in the collection of weapons the Swap method picks from? But then how is the weapon selected? Ohhhhh, that’s an enum. Weird.”