first commit
This commit is contained in:
@@ -0,0 +1,307 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class Weapon : MonoBehaviour
|
||||
{
|
||||
public WeaponData data;
|
||||
public Text ammoText;
|
||||
public Text weaponNameText;
|
||||
public Text reloadText;
|
||||
public Transform firePoint;
|
||||
public Camera playerCamera;
|
||||
public WeaponHUD hud;
|
||||
|
||||
private int currentAmmo;
|
||||
private int currentReserve;
|
||||
private float nextFireTime;
|
||||
private bool isReloading;
|
||||
private Vector3 originalLocalPosition;
|
||||
private Quaternion originalCameraRotation;
|
||||
|
||||
private bool initialized;
|
||||
private AudioSource audioSource;
|
||||
private Coroutine reloadRoutine;
|
||||
private Coroutine recoilRoutine;
|
||||
private SimpleFPSController fpsController;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
audioSource = GetComponent<AudioSource>();
|
||||
originalLocalPosition = transform.localPosition;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
OnSelected();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
bool wantsToShoot = data.automatic ? Input.GetButton("Fire1") : Input.GetButtonDown("Fire1");
|
||||
|
||||
if (wantsToShoot)
|
||||
TryShoot();
|
||||
|
||||
if (Input.GetKeyDown(KeyCode.R))
|
||||
Reload();
|
||||
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
if (data == null || initialized)
|
||||
return;
|
||||
|
||||
currentAmmo = Mathf.Max(0, data.magazineSize);
|
||||
currentReserve = Mathf.Max(0, data.totalReserveAmmo);
|
||||
originalLocalPosition = transform.localPosition;
|
||||
|
||||
if (playerCamera != null)
|
||||
{
|
||||
originalCameraRotation = playerCamera.transform.localRotation;
|
||||
fpsController = playerCamera.GetComponentInParent<SimpleFPSController>();
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
public void TryShoot()
|
||||
{
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
// Shooting behavior is data-driven: automatic weapons use held Fire1, semi-auto weapons use one click.
|
||||
if (isReloading)
|
||||
return;
|
||||
|
||||
if (currentAmmo <= 0)
|
||||
{
|
||||
ShowMessage("No ammo. Press R to reload.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Time.time < nextFireTime)
|
||||
return;
|
||||
|
||||
if (!CanShoot())
|
||||
return;
|
||||
|
||||
Shoot();
|
||||
}
|
||||
|
||||
public void Shoot()
|
||||
{
|
||||
if (data == null || playerCamera == null)
|
||||
return;
|
||||
|
||||
currentAmmo--;
|
||||
nextFireTime = Time.time + Mathf.Max(0.01f, data.fireRate);
|
||||
|
||||
PlaySound(data.shootSound);
|
||||
SpawnMuzzleFlash();
|
||||
|
||||
int pelletCount = Mathf.Max(1, data.pelletsPerShot);
|
||||
for (int i = 0; i < pelletCount; i++)
|
||||
FireRay();
|
||||
|
||||
ApplyRecoil();
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
public void Reload()
|
||||
{
|
||||
if (data == null || isReloading)
|
||||
return;
|
||||
|
||||
if (currentAmmo >= data.magazineSize)
|
||||
{
|
||||
ShowMessage("Magazine is full.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentReserve <= 0)
|
||||
{
|
||||
ShowMessage("No reserve ammo.");
|
||||
return;
|
||||
}
|
||||
|
||||
reloadRoutine = StartCoroutine(ReloadRoutine());
|
||||
}
|
||||
|
||||
public void UpdateUI()
|
||||
{
|
||||
if (data != null && weaponNameText != null)
|
||||
weaponNameText.text = "Weapon: " + data.weaponName;
|
||||
|
||||
if (ammoText != null)
|
||||
ammoText.text = "Ammo: " + currentAmmo + " / " + currentReserve;
|
||||
|
||||
if (reloadText != null)
|
||||
reloadText.text = isReloading ? "Reloading..." : "Ready";
|
||||
|
||||
if (hud != null)
|
||||
{
|
||||
hud.SetWeapon(data);
|
||||
hud.SetAmmo(currentAmmo, currentReserve);
|
||||
hud.SetReloading(isReloading);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnSelected()
|
||||
{
|
||||
Initialize();
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
public void OnDeselected()
|
||||
{
|
||||
if (recoilRoutine != null)
|
||||
{
|
||||
StopCoroutine(recoilRoutine);
|
||||
recoilRoutine = null;
|
||||
}
|
||||
|
||||
transform.localPosition = originalLocalPosition;
|
||||
}
|
||||
|
||||
public bool CanShoot()
|
||||
{
|
||||
return data != null && !isReloading && currentAmmo > 0 && Time.time >= nextFireTime;
|
||||
}
|
||||
|
||||
public int GetCurrentAmmo()
|
||||
{
|
||||
return currentAmmo;
|
||||
}
|
||||
|
||||
public int GetCurrentReserve()
|
||||
{
|
||||
return currentReserve;
|
||||
}
|
||||
|
||||
public bool IsReloading()
|
||||
{
|
||||
return isReloading;
|
||||
}
|
||||
|
||||
private IEnumerator ReloadRoutine()
|
||||
{
|
||||
isReloading = true;
|
||||
UpdateUI();
|
||||
PlaySound(data.reloadSound);
|
||||
|
||||
yield return new WaitForSeconds(Mathf.Max(0f, data.reloadTime));
|
||||
|
||||
int neededAmmo = Mathf.Max(0, data.magazineSize - currentAmmo);
|
||||
int ammoToLoad = Mathf.Min(neededAmmo, currentReserve);
|
||||
|
||||
currentAmmo += ammoToLoad;
|
||||
currentReserve -= ammoToLoad;
|
||||
isReloading = false;
|
||||
reloadRoutine = null;
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
private void FireRay()
|
||||
{
|
||||
Vector3 origin = playerCamera.transform.position;
|
||||
|
||||
// Spread is applied around the camera forward vector, so all weapons use the same aiming source.
|
||||
Vector2 randomOffset = Random.insideUnitCircle * Mathf.Max(0f, data.spread);
|
||||
Vector3 direction = playerCamera.transform.forward +
|
||||
playerCamera.transform.right * randomOffset.x +
|
||||
playerCamera.transform.up * randomOffset.y;
|
||||
direction.Normalize();
|
||||
|
||||
Debug.DrawRay(origin, direction * data.range, Color.yellow, 1f);
|
||||
|
||||
if (Physics.Raycast(origin, direction, out RaycastHit hit, Mathf.Max(0.1f, data.range)))
|
||||
{
|
||||
EnemyTarget target = hit.collider.GetComponentInParent<EnemyTarget>();
|
||||
if (target != null)
|
||||
target.TakeDamage(data.damage);
|
||||
|
||||
SpawnHitEffect(hit);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyRecoil()
|
||||
{
|
||||
// The FPS controller owns camera look; the weapon only feeds a temporary visual recoil offset.
|
||||
if (fpsController != null)
|
||||
fpsController.AddRecoil(data.recoilForce);
|
||||
else if (playerCamera != null)
|
||||
playerCamera.transform.localRotation = originalCameraRotation * Quaternion.Euler(-data.recoilForce, 0f, 0f);
|
||||
|
||||
if (recoilRoutine != null)
|
||||
StopCoroutine(recoilRoutine);
|
||||
|
||||
recoilRoutine = StartCoroutine(RecoilRoutine());
|
||||
}
|
||||
|
||||
private IEnumerator RecoilRoutine()
|
||||
{
|
||||
Vector3 recoilPosition = originalLocalPosition - Vector3.forward * Mathf.Clamp(data.recoilForce * 0.035f, 0.02f, 0.18f);
|
||||
float t = 0f;
|
||||
|
||||
while (t < 1f)
|
||||
{
|
||||
t += Time.deltaTime * 20f;
|
||||
transform.localPosition = Vector3.Lerp(transform.localPosition, recoilPosition, t);
|
||||
yield return null;
|
||||
}
|
||||
|
||||
t = 0f;
|
||||
while (t < 1f)
|
||||
{
|
||||
t += Time.deltaTime * 10f;
|
||||
transform.localPosition = Vector3.Lerp(transform.localPosition, originalLocalPosition, t);
|
||||
|
||||
if (fpsController == null && playerCamera != null)
|
||||
playerCamera.transform.localRotation = Quaternion.Slerp(playerCamera.transform.localRotation, originalCameraRotation, t);
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
transform.localPosition = originalLocalPosition;
|
||||
recoilRoutine = null;
|
||||
}
|
||||
|
||||
private void SpawnMuzzleFlash()
|
||||
{
|
||||
if (data.muzzleFlashPrefab == null || firePoint == null)
|
||||
return;
|
||||
|
||||
GameObject flash = Instantiate(data.muzzleFlashPrefab, firePoint.position, firePoint.rotation, firePoint);
|
||||
Destroy(flash, 0.08f);
|
||||
}
|
||||
|
||||
private void SpawnHitEffect(RaycastHit hit)
|
||||
{
|
||||
if (data.hitEffectPrefab == null)
|
||||
return;
|
||||
|
||||
Quaternion rotation = Quaternion.LookRotation(hit.normal);
|
||||
GameObject effect = Instantiate(data.hitEffectPrefab, hit.point + hit.normal * 0.01f, rotation);
|
||||
Destroy(effect, 0.8f);
|
||||
}
|
||||
|
||||
private void PlaySound(AudioClip clip)
|
||||
{
|
||||
if (clip != null && audioSource != null)
|
||||
audioSource.PlayOneShot(clip);
|
||||
}
|
||||
|
||||
private void ShowMessage(string message)
|
||||
{
|
||||
if (hud != null)
|
||||
hud.ShowMessage(message);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user