initial weapons
This commit is contained in:
1 parent
b87010d2e5
commit
afcd8761e6
66 files changed
+6388
-49
No files matched your search
@@ -1,6 +1,8 @@
|
||||
using Code.DI;
|
||||
using Code.Gameplay.Camera;
|
||||
using Code.Gameplay.Character.Controller;
|
||||
using Code.Gameplay.Character.Inventory;
|
||||
using Code.Gameplay.Character.Stats;
|
||||
using Code.Input;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -9,25 +11,64 @@ namespace Code.Gameplay.Character.Core
|
||||
public class CharacterProvider : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private CustomCharacterController customCharacterController;
|
||||
public CustomCharacterController CustomCharacterController => customCharacterController;
|
||||
|
||||
[SerializeField] private Transform cameraFollowPoint;
|
||||
public Transform GetFollowTransform() => cameraFollowPoint;
|
||||
[SerializeField] private HealthSystem healthSystem;
|
||||
[SerializeField] private WeaponInventory weaponInventory;
|
||||
|
||||
private IInputService _inputService;
|
||||
private CameraController _cameraController;
|
||||
|
||||
public CustomCharacterController CustomCharacterController => customCharacterController;
|
||||
public HealthSystem HealthSystem => healthSystem;
|
||||
public WeaponInventory WeaponInventory => weaponInventory;
|
||||
public Transform GetFollowTransform() => cameraFollowPoint;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_inputService = FindFirstObjectByType<DIContainer>().Resolve<IInputService>();
|
||||
_inputService = FindFirstObjectByType<DIContainer>()?.Resolve<IInputService>();
|
||||
_cameraController = FindFirstObjectByType<CameraController>();
|
||||
|
||||
if (_inputService == null)
|
||||
{
|
||||
Debug.LogError("IInputService not found!");
|
||||
}
|
||||
|
||||
if (_cameraController == null)
|
||||
{
|
||||
Debug.LogError("CameraController not found!");
|
||||
}
|
||||
|
||||
if (healthSystem != null)
|
||||
{
|
||||
healthSystem.OnDied += HandleDeath;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleDeath()
|
||||
{
|
||||
enabled = false;
|
||||
if (customCharacterController != null)
|
||||
{
|
||||
customCharacterController.enabled = false;
|
||||
}
|
||||
|
||||
Debug.Log("Character died!");
|
||||
}
|
||||
|
||||
public void Teleport(Vector3 position, Quaternion rotation)
|
||||
{
|
||||
customCharacterController.SetPosition(position);
|
||||
customCharacterController.SetRotation(rotation);
|
||||
if (customCharacterController != null)
|
||||
{
|
||||
customCharacterController.SetPosition(position);
|
||||
customCharacterController.SetRotation(rotation);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleCharacterInput()
|
||||
{
|
||||
if (_inputService == null || _cameraController == null || customCharacterController == null)
|
||||
return;
|
||||
|
||||
var inputs = new CharacterInputs();
|
||||
var moveInput = _inputService.MoveInput;
|
||||
inputs.MoveAxisForward = moveInput.y;
|
||||
@@ -35,15 +76,63 @@ namespace Code.Gameplay.Character.Core
|
||||
inputs.SprintPressed = _inputService.SprintHeld;
|
||||
inputs.JumpPressed = _inputService.JumpPressed;
|
||||
|
||||
inputs.CameraRotation = FindFirstObjectByType<CameraController>().GetCameraRotation();
|
||||
inputs.CameraPlanarDirection = FindFirstObjectByType<CameraController>().GetPlanarDirection();
|
||||
inputs.CameraRotation = _cameraController.GetCameraRotation();
|
||||
inputs.CameraPlanarDirection = _cameraController.GetPlanarDirection();
|
||||
|
||||
customCharacterController.SetInputs(ref inputs);
|
||||
|
||||
HandleWeaponInput();
|
||||
}
|
||||
|
||||
private void HandleWeaponInput()
|
||||
{
|
||||
if (weaponInventory == null) return;
|
||||
|
||||
if (_inputService.AttackPressed)
|
||||
{
|
||||
weaponInventory.ShootCurrentWeapon();
|
||||
}
|
||||
else
|
||||
{
|
||||
weaponInventory.StopShootingCurrentWeapon();
|
||||
}
|
||||
|
||||
var scroll = UnityEngine.Input.GetAxis("Mouse ScrollWheel");
|
||||
switch (scroll)
|
||||
{
|
||||
case > 0f:
|
||||
weaponInventory.NextWeapon();
|
||||
break;
|
||||
case < 0f:
|
||||
weaponInventory.PreviousWeapon();
|
||||
break;
|
||||
}
|
||||
|
||||
if (UnityEngine.Input.GetKeyDown(KeyCode.R))
|
||||
{
|
||||
weaponInventory.ReloadCurrentWeapon();
|
||||
}
|
||||
|
||||
for (var i = 0; i < 9; i++)
|
||||
{
|
||||
if (UnityEngine.Input.GetKeyDown(KeyCode.Alpha1 + i))
|
||||
{
|
||||
weaponInventory.SwitchWeapon(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
HandleCharacterInput();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (healthSystem != null)
|
||||
{
|
||||
healthSystem.OnDied -= HandleDeath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 21d89144fc42461e9132c47c9abc8144
|
||||
timeCreated: 1771856381
|
||||
@@ -0,0 +1,103 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Code.Gameplay.Weapons;
|
||||
using System;
|
||||
|
||||
namespace Code.Gameplay.Character.Inventory
|
||||
{
|
||||
public class WeaponInventory : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private List<WeaponBase> weapons = new List<WeaponBase>();
|
||||
[SerializeField] private Transform weaponHolder;
|
||||
|
||||
[SerializeField] private int currentWeaponIndex = 0;
|
||||
|
||||
public event Action<WeaponBase> OnWeaponSwitched;
|
||||
public event Action OnWeaponsInitialized;
|
||||
|
||||
public WeaponBase CurrentWeapon => weapons.Count > 0 ? weapons[currentWeaponIndex] : null;
|
||||
public int WeaponCount => weapons.Count;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
InitializeWeapons();
|
||||
}
|
||||
|
||||
private void InitializeWeapons()
|
||||
{
|
||||
for (var i = 0; i < weapons.Count; i++)
|
||||
{
|
||||
weapons[i] = Instantiate(weapons[i].gameObject, weaponHolder).GetComponent<WeaponBase>();
|
||||
weapons[i].gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
if (weapons.Count > 0 && currentWeaponIndex < weapons.Count)
|
||||
{
|
||||
weapons[currentWeaponIndex].gameObject.SetActive(true);
|
||||
OnWeaponSwitched?.Invoke(weapons[currentWeaponIndex]);
|
||||
}
|
||||
|
||||
OnWeaponsInitialized?.Invoke();
|
||||
}
|
||||
|
||||
public void SwitchWeapon(int index)
|
||||
{
|
||||
if (index < 0 || index >= weapons.Count) return;
|
||||
if (index == currentWeaponIndex) return;
|
||||
|
||||
weapons[currentWeaponIndex].gameObject.SetActive(false);
|
||||
currentWeaponIndex = index;
|
||||
weapons[currentWeaponIndex].gameObject.SetActive(true);
|
||||
|
||||
OnWeaponSwitched?.Invoke(weapons[currentWeaponIndex]);
|
||||
}
|
||||
|
||||
public void NextWeapon()
|
||||
{
|
||||
int nextIndex = (currentWeaponIndex + 1) % weapons.Count;
|
||||
SwitchWeapon(nextIndex);
|
||||
}
|
||||
|
||||
public void PreviousWeapon()
|
||||
{
|
||||
int prevIndex = (currentWeaponIndex - 1 + weapons.Count) % weapons.Count;
|
||||
SwitchWeapon(prevIndex);
|
||||
}
|
||||
|
||||
public void RemoveWeapon(WeaponBase weapon)
|
||||
{
|
||||
if (weapons.Contains(weapon))
|
||||
{
|
||||
int index = weapons.IndexOf(weapon);
|
||||
weapons.Remove(weapon);
|
||||
Destroy(weapon.gameObject);
|
||||
|
||||
if (index <= currentWeaponIndex)
|
||||
{
|
||||
currentWeaponIndex = Mathf.Max(0, currentWeaponIndex - 1);
|
||||
}
|
||||
|
||||
if (weapons.Count > 0)
|
||||
{
|
||||
weapons[currentWeaponIndex].gameObject.SetActive(true);
|
||||
OnWeaponSwitched?.Invoke(weapons[currentWeaponIndex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ReloadCurrentWeapon()
|
||||
{
|
||||
CurrentWeapon?.Reload();
|
||||
}
|
||||
|
||||
public void ShootCurrentWeapon()
|
||||
{
|
||||
CurrentWeapon?.StartShooting();
|
||||
}
|
||||
|
||||
public void StopShootingCurrentWeapon()
|
||||
{
|
||||
CurrentWeapon?.StopShooting();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c24ec1ac73384bd6a84bdff6028a6479
|
||||
timeCreated: 1771856390
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 916b1a7d923b4f95b9215a2c89deca6c
|
||||
timeCreated: 1771856166
|
||||
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using Code.Gameplay.Core;
|
||||
|
||||
namespace Code.Gameplay.Character.Stats
|
||||
{
|
||||
public class HealthSystem : MonoBehaviour, IDamageable
|
||||
{
|
||||
[SerializeField] private float maxHealth = 100f;
|
||||
[SerializeField] private float currentHealth;
|
||||
|
||||
public event Action<float, float> OnHealthChanged;
|
||||
public event Action OnDied;
|
||||
|
||||
public float CurrentHealth => currentHealth;
|
||||
public float MaxHealth => maxHealth;
|
||||
public float HealthPercentage => currentHealth / maxHealth;
|
||||
public bool IsAlive => currentHealth > 0;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
currentHealth = maxHealth;
|
||||
}
|
||||
|
||||
public void TakeDamage(float damage)
|
||||
{
|
||||
if (!IsAlive) return;
|
||||
|
||||
currentHealth = Mathf.Max(0, currentHealth - damage);
|
||||
OnHealthChanged?.Invoke(currentHealth, maxHealth);
|
||||
|
||||
if (currentHealth <= 0)
|
||||
{
|
||||
Die();
|
||||
}
|
||||
}
|
||||
|
||||
public void Heal(float amount)
|
||||
{
|
||||
if (!IsAlive) return;
|
||||
|
||||
currentHealth = Mathf.Min(maxHealth, currentHealth + amount);
|
||||
OnHealthChanged?.Invoke(currentHealth, maxHealth);
|
||||
}
|
||||
|
||||
private void Die()
|
||||
{
|
||||
OnDied?.Invoke();
|
||||
}
|
||||
|
||||
public Transform GetTransform() => transform;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d0a69fde097419bb4933773d22d739b
|
||||
timeCreated: 1771856171
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cb192cceba6542cfb1421a4fc918f98e
|
||||
timeCreated: 1771856099
|
||||
@@ -0,0 +1,11 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Code.Gameplay.Core
|
||||
{
|
||||
public interface IDamageable
|
||||
{
|
||||
void TakeDamage(float damage);
|
||||
Transform GetTransform();
|
||||
bool IsAlive { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 220922d554ae49819144924d31f9ab51
|
||||
timeCreated: 1771856130
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2842ad672e65490483b34c3eb1c40ba7
|
||||
timeCreated: 1771856456
|
||||
@@ -0,0 +1,145 @@
|
||||
using Code.Gameplay.Character.Core;
|
||||
using UnityEngine;
|
||||
using Code.Gameplay.Core;
|
||||
using Code.Gameplay.Character.Stats;
|
||||
using Code.Gameplay.Weapons;
|
||||
|
||||
namespace Code.Gameplay.Enemy
|
||||
{
|
||||
public class SimpleEnemy : MonoBehaviour, IDamageable
|
||||
{
|
||||
[SerializeField] private HealthSystem healthSystem;
|
||||
[SerializeField] private WeaponBase weapon;
|
||||
[SerializeField] private Transform firePoint;
|
||||
[SerializeField] private Transform target;
|
||||
|
||||
[SerializeField] private float moveSpeed = 3f;
|
||||
[SerializeField] private float rotationSpeed = 5f;
|
||||
[SerializeField] private float detectionRange = 20f;
|
||||
[SerializeField] private float attackRange = 15f;
|
||||
[SerializeField] private float stopDistance = 5f;
|
||||
|
||||
[SerializeField] private float attackCooldown = 1f;
|
||||
[SerializeField] private LayerMask obstacleMask;
|
||||
|
||||
private float lastAttackTime;
|
||||
private bool isAttacking;
|
||||
private CharacterController characterController;
|
||||
|
||||
public bool IsAlive => healthSystem != null && healthSystem.IsAlive;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
characterController = GetComponent<CharacterController>();
|
||||
if (characterController == null)
|
||||
characterController = gameObject.AddComponent<CharacterController>();
|
||||
|
||||
if (healthSystem == null)
|
||||
healthSystem = GetComponent<HealthSystem>();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (healthSystem != null)
|
||||
{
|
||||
healthSystem.OnDied += HandleDeath;
|
||||
}
|
||||
|
||||
if (target != null) return;
|
||||
|
||||
var player = FindFirstObjectByType<CharacterProvider>();
|
||||
if (player != null)
|
||||
target = player.transform;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!IsAlive || target == null) return;
|
||||
|
||||
var distanceToTarget = Vector3.Distance(transform.position, target.position);
|
||||
|
||||
if (!(distanceToTarget <= detectionRange)) return;
|
||||
|
||||
RotateTowardsTarget();
|
||||
|
||||
if (distanceToTarget <= attackRange)
|
||||
{
|
||||
Attack();
|
||||
|
||||
if (distanceToTarget < stopDistance)
|
||||
{
|
||||
MoveAway();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MoveTowardsTarget();
|
||||
}
|
||||
}
|
||||
|
||||
private void RotateTowardsTarget()
|
||||
{
|
||||
var direction = (target.position - transform.position).normalized;
|
||||
direction.y = 0;
|
||||
|
||||
var lookRotation = Quaternion.LookRotation(direction);
|
||||
transform.rotation = Quaternion.Slerp(transform.rotation, lookRotation, Time.deltaTime * rotationSpeed);
|
||||
}
|
||||
|
||||
private void MoveTowardsTarget()
|
||||
{
|
||||
var direction = (target.position - transform.position).normalized;
|
||||
direction.y = 0;
|
||||
|
||||
characterController.Move(direction * (moveSpeed * Time.deltaTime));
|
||||
}
|
||||
|
||||
private void MoveAway()
|
||||
{
|
||||
var direction = (transform.position - target.position).normalized;
|
||||
direction.y = 0;
|
||||
|
||||
characterController.Move(direction * (moveSpeed * Time.deltaTime));
|
||||
}
|
||||
|
||||
private void Attack()
|
||||
{
|
||||
if (Time.time - lastAttackTime < attackCooldown) return;
|
||||
if (weapon == null) return;
|
||||
|
||||
if (!HasLineOfSight()) return;
|
||||
|
||||
lastAttackTime = Time.time;
|
||||
weapon.Shoot();
|
||||
}
|
||||
|
||||
private bool HasLineOfSight()
|
||||
{
|
||||
if (target == null) return false;
|
||||
|
||||
var direction = (target.position - firePoint.position).normalized;
|
||||
var distance = Vector3.Distance(firePoint.position, target.position);
|
||||
|
||||
if (Physics.Raycast(firePoint.position, direction, out var hit, distance, obstacleMask))
|
||||
{
|
||||
return hit.collider.transform == target;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void HandleDeath()
|
||||
{
|
||||
enabled = false;
|
||||
|
||||
Destroy(gameObject, 2f);
|
||||
}
|
||||
|
||||
public void TakeDamage(float damage)
|
||||
{
|
||||
healthSystem?.TakeDamage(damage);
|
||||
}
|
||||
|
||||
public Transform GetTransform() => transform;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dad72915bd7146e198bcb246c6c11b55
|
||||
timeCreated: 1771856464
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 57cd3189c7a94a09a1625e1b79f40d65
|
||||
timeCreated: 1771856189
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Code.Gameplay.Weapons
|
||||
{
|
||||
public class AssaultRifle : WeaponBase
|
||||
{
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
weaponType = WeaponType.AssaultRifle;
|
||||
fireMode = FireMode.Automatic;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f211f8dc45884d2a9b2ff62f3e89da6f
|
||||
timeCreated: 1771856305
|
||||
@@ -0,0 +1,76 @@
|
||||
using UnityEngine;
|
||||
using Code.Gameplay.Core;
|
||||
|
||||
namespace Code.Gameplay.Weapons
|
||||
{
|
||||
public class Bullet : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private float lifeTime = 3f;
|
||||
[SerializeField] private LayerMask hitLayers = -1;
|
||||
[SerializeField] private GameObject impactEffect;
|
||||
[SerializeField] private AudioClip impactSound;
|
||||
|
||||
private float _speed;
|
||||
private float _damage;
|
||||
private Vector3 _direction;
|
||||
private bool _isInitialized;
|
||||
private AudioSource _audioSource;
|
||||
|
||||
public void Initialize(float speed, float damage, Vector3 direction)
|
||||
{
|
||||
_speed = speed;
|
||||
_damage = damage;
|
||||
_direction = direction.normalized;
|
||||
_isInitialized = true;
|
||||
|
||||
_audioSource = GetComponent<AudioSource>();
|
||||
|
||||
Destroy(gameObject, lifeTime);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!_isInitialized) return;
|
||||
|
||||
transform.position += _direction * (_speed * Time.deltaTime);
|
||||
|
||||
transform.rotation = Quaternion.LookRotation(_direction);
|
||||
|
||||
if (Physics.Raycast(transform.position, _direction, out var hit, _speed * Time.deltaTime, hitLayers))
|
||||
{
|
||||
HitTarget(hit);
|
||||
}
|
||||
}
|
||||
|
||||
private void HitTarget(RaycastHit hit)
|
||||
{
|
||||
var damageable = hit.collider.GetComponentInParent<IDamageable>();
|
||||
damageable?.TakeDamage(_damage);
|
||||
|
||||
if (impactEffect != null)
|
||||
{
|
||||
Instantiate(impactEffect, hit.point, Quaternion.LookRotation(hit.normal));
|
||||
}
|
||||
|
||||
if (impactSound != null && _audioSource != null)
|
||||
{
|
||||
_audioSource.PlayOneShot(impactSound);
|
||||
}
|
||||
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
var damageable = other.GetComponentInParent<IDamageable>();
|
||||
damageable?.TakeDamage(_damage);
|
||||
|
||||
if (impactEffect != null)
|
||||
{
|
||||
Instantiate(impactEffect, transform.position, Quaternion.identity);
|
||||
}
|
||||
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1cacd4ec13d3416db0148f5758966f0a
|
||||
timeCreated: 1771856241
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Code.Gameplay.Weapons
|
||||
{
|
||||
public enum FireMode
|
||||
{
|
||||
SemiAutomatic,
|
||||
Automatic,
|
||||
BoltAction
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d68015d7f9d94843a7dcfb0d1ddc4d4f
|
||||
timeCreated: 1771856216
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Code.Gameplay.Weapons
|
||||
{
|
||||
public class Pistol : WeaponBase
|
||||
{
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
weaponType = WeaponType.Pistol;
|
||||
fireMode = FireMode.SemiAutomatic;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a7a144ebf90447faadef43bde7844c84
|
||||
timeCreated: 1771856293
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Code.Gameplay.Weapons
|
||||
{
|
||||
public class Rifle : WeaponBase
|
||||
{
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
weaponType = WeaponType.Rifle;
|
||||
fireMode = FireMode.BoltAction;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c181bf00d2e4431c8fd029e8a6bffd83
|
||||
timeCreated: 1771856324
|
||||
@@ -0,0 +1,177 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using Code.Gameplay.Camera;
|
||||
using UnityEngine;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
namespace Code.Gameplay.Weapons
|
||||
{
|
||||
public abstract class WeaponBase : MonoBehaviour
|
||||
{
|
||||
[SerializeField] protected WeaponType weaponType;
|
||||
[SerializeField] protected string weaponName;
|
||||
[SerializeField] protected FireMode fireMode;
|
||||
|
||||
[SerializeField] protected float damage = 20f;
|
||||
[SerializeField] protected float fireRate = 0.5f;
|
||||
[SerializeField] protected float bulletSpeed = 50f;
|
||||
[SerializeField] protected int magazineSize = 10;
|
||||
[SerializeField] protected int currentAmmo;
|
||||
[SerializeField] protected int reserveAmmo = 30;
|
||||
[SerializeField] protected float reloadTime = 1.5f;
|
||||
[SerializeField] protected float spreadAngle = 1f;
|
||||
|
||||
private Transform _firePoint;
|
||||
[SerializeField] protected GameObject bulletPrefab;
|
||||
[SerializeField] protected ParticleSystem muzzleFlash;
|
||||
[SerializeField] protected AudioClip shootSound;
|
||||
[SerializeField] protected AudioClip reloadSound;
|
||||
[SerializeField] protected AudioClip emptySound;
|
||||
|
||||
protected AudioSource audioSource;
|
||||
protected bool isShooting;
|
||||
protected bool isReloading;
|
||||
protected float lastShotTime;
|
||||
|
||||
public WeaponType Type => weaponType;
|
||||
public int CurrentAmmo => currentAmmo;
|
||||
public int ReserveAmmo => reserveAmmo;
|
||||
public int MagazineSize => magazineSize;
|
||||
public bool IsReloading => isReloading;
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
audioSource = GetComponent<AudioSource>();
|
||||
if (audioSource == null)
|
||||
audioSource = gameObject.AddComponent<AudioSource>();
|
||||
|
||||
currentAmmo = magazineSize;
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
_firePoint = FindFirstObjectByType<CameraController>().transform;
|
||||
}
|
||||
|
||||
public virtual void Shoot()
|
||||
{
|
||||
if (isReloading) return;
|
||||
if (currentAmmo <= 0)
|
||||
{
|
||||
PlayEmptySound();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Time.time - lastShotTime < fireRate)
|
||||
return;
|
||||
|
||||
switch (fireMode)
|
||||
{
|
||||
case FireMode.SemiAutomatic:
|
||||
if (!isShooting) return;
|
||||
break;
|
||||
case FireMode.Automatic:
|
||||
break;
|
||||
case FireMode.BoltAction:
|
||||
if (!isShooting) return;
|
||||
StartCoroutine(BoltActionDelay());
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
PerformShot();
|
||||
}
|
||||
|
||||
protected virtual void PerformShot()
|
||||
{
|
||||
currentAmmo--;
|
||||
lastShotTime = Time.time;
|
||||
|
||||
SpawnBullet();
|
||||
|
||||
if (muzzleFlash != null)
|
||||
muzzleFlash.Play();
|
||||
|
||||
if (shootSound != null)
|
||||
audioSource.PlayOneShot(shootSound);
|
||||
|
||||
OnShoot();
|
||||
}
|
||||
|
||||
protected virtual void SpawnBullet()
|
||||
{
|
||||
if (bulletPrefab == null) return;
|
||||
|
||||
var spread = Random.insideUnitSphere * (spreadAngle / 100f);
|
||||
var direction = (_firePoint.forward + spread).normalized;
|
||||
|
||||
var bullet = Instantiate(bulletPrefab, _firePoint.position, Quaternion.LookRotation(direction));
|
||||
var bulletComponent = bullet.GetComponent<Bullet>();
|
||||
|
||||
if (bulletComponent != null)
|
||||
{
|
||||
bulletComponent.Initialize(bulletSpeed, damage, direction);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnShoot()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void StartShooting()
|
||||
{
|
||||
isShooting = true;
|
||||
|
||||
Shoot();
|
||||
}
|
||||
|
||||
public virtual void StopShooting()
|
||||
{
|
||||
isShooting = false;
|
||||
}
|
||||
|
||||
public virtual void Reload()
|
||||
{
|
||||
if (isReloading) return;
|
||||
if (reserveAmmo <= 0) return;
|
||||
if (currentAmmo == magazineSize) return;
|
||||
|
||||
StartCoroutine(ReloadCoroutine());
|
||||
}
|
||||
|
||||
protected virtual IEnumerator ReloadCoroutine()
|
||||
{
|
||||
isReloading = true;
|
||||
|
||||
if (reloadSound != null)
|
||||
audioSource.PlayOneShot(reloadSound);
|
||||
|
||||
yield return new WaitForSeconds(reloadTime);
|
||||
|
||||
var ammoNeeded = magazineSize - currentAmmo;
|
||||
var ammoToTake = Mathf.Min(ammoNeeded, reserveAmmo);
|
||||
|
||||
currentAmmo += ammoToTake;
|
||||
reserveAmmo -= ammoToTake;
|
||||
|
||||
isReloading = false;
|
||||
}
|
||||
|
||||
protected virtual void PlayEmptySound()
|
||||
{
|
||||
if (emptySound != null)
|
||||
audioSource.PlayOneShot(emptySound);
|
||||
}
|
||||
|
||||
public virtual void AddAmmo(int amount)
|
||||
{
|
||||
reserveAmmo += amount;
|
||||
}
|
||||
|
||||
protected virtual IEnumerator BoltActionDelay()
|
||||
{
|
||||
yield return new WaitForSeconds(reloadTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ea8f25ad07a74b75b439e057339b1d7e
|
||||
timeCreated: 1771856259
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Code.Gameplay.Weapons
|
||||
{
|
||||
public enum WeaponType
|
||||
{
|
||||
Pistol,
|
||||
AssaultRifle,
|
||||
Rifle
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 582d1a42f0d543ec92a00b8e20c19520
|
||||
timeCreated: 1771856203
|
||||
Reference in new issue
Block a user