first commit

This commit is contained in:
2026-05-25 23:10:02 +03:00
commit c9b9947a9c
127 changed files with 31887 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
using UnityEngine;
public class AmmoPack : MonoBehaviour
{
public int ammoAmount = 18;
public float rotateSpeed = 80f;
public float bobHeight = 0.12f;
public float bobSpeed = 2.4f;
private Vector3 _basePosition;
private void Start()
{
_basePosition = transform.position;
}
private void Update()
{
transform.Rotate(Vector3.up, rotateSpeed * Time.deltaTime, Space.World);
transform.position = _basePosition + Vector3.up * (Mathf.Sin(Time.time * bobSpeed) * bobHeight);
}
private void OnTriggerEnter(Collider other)
{
WeaponSwitcher weaponSwitcher = other.GetComponent<WeaponSwitcher>() ?? other.GetComponentInParent<WeaponSwitcher>();
if (weaponSwitcher != null && weaponSwitcher.AddAmmoToCurrentWeapon(ammoAmount))
{
Destroy(gameObject);
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 20e1c15a37d94f6558f3278d472c1e89
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+270
View File
@@ -0,0 +1,270 @@
using System.Collections;
using UnityEngine;
public class EnemyTarget : MonoBehaviour, IDamageable
{
public static int RemainingObjectives { get; private set; }
public static event System.Action<int> ObjectiveCountChanged;
[Header("Health")]
public float maxHealth = 60f;
public bool countsAsObjective = true;
[Header("Combat")]
public bool canAttack = true;
public float attackRange = 28f;
public float attackDamage = 12f;
public float attackCooldown = 1.3f;
public Color tracerColor = new Color(1f, 0.35f, 0.25f);
[Header("Movement")]
public Vector3 patrolAxis = Vector3.zero;
public float patrolAmplitude = 0f;
public float patrolSpeed = 1f;
[Header("References")]
public Transform muzzlePoint;
private static Material _sharedTracerMaterial;
private float _currentHealth;
private float _nextAttackTime;
private Vector3 _startPosition;
private PlayerHealth _playerHealth;
private Transform _playerTarget;
private Renderer[] _renderers;
private Color[] _baseColors;
private bool _destroyed;
public static void ResetObjectives()
{
RemainingObjectives = 0;
ObjectiveCountChanged?.Invoke(RemainingObjectives);
}
public static void RecalculateObjectivesInScene()
{
EnemyTarget[] targets = FindObjectsOfType<EnemyTarget>(true);
int total = 0;
for (int i = 0; i < targets.Length; i++)
{
if (targets[i] != null && targets[i].countsAsObjective)
{
total++;
}
}
RemainingObjectives = total;
ObjectiveCountChanged?.Invoke(RemainingObjectives);
}
public void Initialize(PlayerHealth playerHealth, Transform playerTarget)
{
_playerHealth = playerHealth;
_playerTarget = playerTarget;
}
private void Awake()
{
_currentHealth = maxHealth;
_startPosition = transform.position;
_renderers = GetComponentsInChildren<Renderer>();
_baseColors = new Color[_renderers.Length];
for (int i = 0; i < _renderers.Length; i++)
{
_baseColors[i] = _renderers[i].material.color;
}
if (countsAsObjective)
{
RemainingObjectives++;
ObjectiveCountChanged?.Invoke(RemainingObjectives);
}
}
private void Start()
{
SnapToSurface();
_startPosition = transform.position;
if (_playerHealth == null)
{
_playerHealth = FindObjectOfType<PlayerHealth>();
}
if (_playerTarget == null && _playerHealth != null)
{
Camera playerCamera = _playerHealth.GetComponentInChildren<Camera>(true);
_playerTarget = playerCamera != null ? playerCamera.transform : _playerHealth.transform;
}
}
private void SnapToSurface()
{
if (_renderers == null || _renderers.Length == 0)
{
return;
}
Bounds bounds = _renderers[0].bounds;
for (int i = 1; i < _renderers.Length; i++)
{
bounds.Encapsulate(_renderers[i].bounds);
}
float currentBottomY = bounds.min.y;
Vector3 rayOrigin = bounds.center + Vector3.up * 20f;
RaycastHit[] hits = Physics.RaycastAll(rayOrigin, Vector3.down, 60f, ~0, QueryTriggerInteraction.Ignore);
if (hits.Length == 0)
{
return;
}
System.Array.Sort(hits, (a, b) => a.distance.CompareTo(b.distance));
for (int i = 0; i < hits.Length; i++)
{
if (hits[i].collider == null || hits[i].collider.transform.IsChildOf(transform))
{
continue;
}
float offsetY = hits[i].point.y - currentBottomY;
if (Mathf.Abs(offsetY) > 0.01f)
{
transform.position += Vector3.up * offsetY;
}
break;
}
}
private void Update()
{
if (_destroyed)
{
return;
}
if (patrolAmplitude > 0.01f && patrolAxis.sqrMagnitude > 0.001f)
{
Vector3 offset = patrolAxis.normalized * Mathf.Sin(Time.time * patrolSpeed) * patrolAmplitude;
transform.position = _startPosition + offset;
}
if (!canAttack || _playerHealth == null || _playerHealth.IsDead || _playerTarget == null)
{
return;
}
Vector3 aimPoint = _playerTarget.position;
Vector3 flatDirection = aimPoint - transform.position;
flatDirection.y = 0f;
if (flatDirection.sqrMagnitude > 0.001f)
{
Quaternion targetRotation = Quaternion.LookRotation(flatDirection.normalized, Vector3.up);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * 5f);
}
Vector3 fireOrigin = muzzlePoint != null ? muzzlePoint.position : transform.position + transform.forward * 0.8f + Vector3.up * 1.2f;
Vector3 direction = (aimPoint - fireOrigin).normalized;
if (Vector3.Distance(fireOrigin, aimPoint) > attackRange || Time.time < _nextAttackTime)
{
return;
}
if (Physics.Raycast(fireOrigin, direction, out RaycastHit hit, attackRange, ~0, QueryTriggerInteraction.Ignore))
{
PlayerHealth hitPlayer = hit.collider.GetComponentInParent<PlayerHealth>();
if (hitPlayer != null)
{
_nextAttackTime = Time.time + attackCooldown;
hitPlayer.TakeDamage(attackDamage);
ShowTracer(fireOrigin, hit.point, tracerColor);
}
}
}
public void TakeDamage(float amount, Vector3 hitPoint, Vector3 hitNormal)
{
if (_destroyed)
{
return;
}
_currentHealth -= amount;
StartCoroutine(FlashHit());
if (_currentHealth <= 0f)
{
DestroyTarget();
}
}
private IEnumerator FlashHit()
{
for (int i = 0; i < _renderers.Length; i++)
{
_renderers[i].material.color = Color.white;
}
yield return new WaitForSeconds(0.08f);
for (int i = 0; i < _renderers.Length; i++)
{
if (_renderers[i] != null)
{
_renderers[i].material.color = _baseColors[i];
}
}
}
private void DestroyTarget()
{
_destroyed = true;
if (countsAsObjective)
{
RemainingObjectives = Mathf.Max(0, RemainingObjectives - 1);
ObjectiveCountChanged?.Invoke(RemainingObjectives);
}
GameObject burst = GameObject.CreatePrimitive(PrimitiveType.Sphere);
Destroy(burst.GetComponent<Collider>());
burst.transform.position = transform.position + Vector3.up;
burst.transform.localScale = Vector3.one * 0.75f;
Renderer burstRenderer = burst.GetComponent<Renderer>();
burstRenderer.material = new Material(Shader.Find("Standard"));
burstRenderer.material.color = new Color(1f, 0.45f, 0.15f);
Destroy(burst, 0.18f);
Destroy(gameObject);
}
private void ShowTracer(Vector3 start, Vector3 end, Color color)
{
GameObject tracer = new GameObject("EnemyTracer");
LineRenderer renderer = tracer.AddComponent<LineRenderer>();
renderer.material = GetTracerMaterial();
renderer.positionCount = 2;
renderer.SetPosition(0, start);
renderer.SetPosition(1, end);
renderer.startWidth = 0.045f;
renderer.endWidth = 0.015f;
renderer.startColor = color;
renderer.endColor = new Color(color.r, color.g, color.b, 0.05f);
renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
renderer.receiveShadows = false;
Destroy(tracer, 0.07f);
}
private static Material GetTracerMaterial()
{
if (_sharedTracerMaterial == null)
{
_sharedTracerMaterial = new Material(Shader.Find("Sprites/Default"));
}
return _sharedTracerMaterial;
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 463384ddbb235af2d98a46667c43000f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+187
View File
@@ -0,0 +1,187 @@
using UnityEngine;
[RequireComponent(typeof(CharacterController))]
public class FPSController : MonoBehaviour
{
[Header("References")]
public Camera playerCamera;
public Transform weaponHolder;
[Header("Movement")]
public float walkSpeed = 6f;
public float sprintSpeed = 8.5f;
public float jumpHeight = 1.2f;
public float gravity = -22f;
[Header("Look")]
public float mouseSensitivity = 260f;
public float maxLookAngle = 85f;
[Header("Recoil")]
public float recoilReturnSpeed = 12f;
public float recoilSnappiness = 18f;
[Header("Weapon Bob")]
public float bobFrequency = 9f;
public float bobHorizontalAmount = 0.018f;
public float bobVerticalAmount = 0.014f;
public float bobSmoothing = 10f;
private CharacterController _controller;
private Vector2 _lookAngles;
private Vector2 _recoilTarget;
private Vector2 _recoilCurrent;
private float _verticalVelocity;
private Vector3 _weaponHolderBaseLocalPosition;
private float _bobTime;
private bool _lookEnabled = true;
public void Initialize(Camera controlledCamera, Transform controlledWeaponHolder)
{
playerCamera = controlledCamera;
weaponHolder = controlledWeaponHolder;
}
private void Awake()
{
_controller = GetComponent<CharacterController>();
if (playerCamera == null)
{
playerCamera = GetComponentInChildren<Camera>(true);
}
if (weaponHolder == null && playerCamera != null)
{
Transform foundHolder = playerCamera.transform.Find("WeaponHolder");
if (foundHolder != null)
{
weaponHolder = foundHolder;
}
}
}
private void Start()
{
if (playerCamera != null)
{
_lookAngles.x = transform.eulerAngles.y;
_lookAngles.y = playerCamera.transform.localEulerAngles.x;
if (_lookAngles.y > 180f)
{
_lookAngles.y -= 360f;
}
}
if (weaponHolder != null)
{
_weaponHolderBaseLocalPosition = weaponHolder.localPosition;
}
SetCursorLocked(true);
}
private void Update()
{
if (!_lookEnabled || playerCamera == null)
{
return;
}
UpdateLook();
UpdateMovement();
UpdateWeaponBob();
}
public void SetLookEnabled(bool enabled)
{
_lookEnabled = enabled;
if (!enabled)
{
_recoilCurrent = Vector2.zero;
_recoilTarget = Vector2.zero;
}
}
public void SetCursorLocked(bool locked)
{
Cursor.lockState = locked ? CursorLockMode.Locked : CursorLockMode.None;
Cursor.visible = !locked;
}
public void ApplyRecoil(float verticalKick, float horizontalKick)
{
_recoilTarget += new Vector2(verticalKick, Random.Range(-horizontalKick, horizontalKick));
}
private void UpdateLook()
{
float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity * Time.deltaTime;
float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity * Time.deltaTime;
_lookAngles.x += mouseX;
_lookAngles.y = Mathf.Clamp(_lookAngles.y - mouseY, -maxLookAngle, maxLookAngle);
_recoilTarget = Vector2.Lerp(_recoilTarget, Vector2.zero, recoilReturnSpeed * Time.deltaTime);
_recoilCurrent = Vector2.Lerp(_recoilCurrent, _recoilTarget, recoilSnappiness * Time.deltaTime);
transform.rotation = Quaternion.Euler(0f, _lookAngles.x + _recoilCurrent.y, 0f);
playerCamera.transform.localRotation = Quaternion.Euler(_lookAngles.y - _recoilCurrent.x, 0f, 0f);
}
private void UpdateMovement()
{
Vector3 input = new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical"));
input = Vector3.ClampMagnitude(input, 1f);
float moveSpeed = Input.GetKey(KeyCode.LeftShift) ? sprintSpeed : walkSpeed;
Vector3 move = transform.TransformDirection(input) * moveSpeed;
if (_controller.isGrounded && _verticalVelocity < 0f)
{
_verticalVelocity = -2f;
}
if (_controller.isGrounded && Input.GetButtonDown("Jump"))
{
_verticalVelocity = Mathf.Sqrt(jumpHeight * -2f * gravity);
}
_verticalVelocity += gravity * Time.deltaTime;
move.y = _verticalVelocity;
_controller.Move(move * Time.deltaTime);
}
private void UpdateWeaponBob()
{
if (weaponHolder == null)
{
return;
}
Vector3 horizontalVelocity = _controller.velocity;
horizontalVelocity.y = 0f;
float speed = horizontalVelocity.magnitude;
if (_controller.isGrounded && speed > 0.15f)
{
_bobTime += Time.deltaTime * bobFrequency * (speed / walkSpeed);
Vector3 bobOffset = new Vector3(
Mathf.Cos(_bobTime * 0.5f) * bobHorizontalAmount,
Mathf.Sin(_bobTime) * bobVerticalAmount,
0f);
weaponHolder.localPosition = Vector3.Lerp(
weaponHolder.localPosition,
_weaponHolderBaseLocalPosition + bobOffset,
bobSmoothing * Time.deltaTime);
}
else
{
weaponHolder.localPosition = Vector3.Lerp(
weaponHolder.localPosition,
_weaponHolderBaseLocalPosition,
bobSmoothing * Time.deltaTime);
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0baef4c686d397a6ab45e19229bf948d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+85
View File
@@ -0,0 +1,85 @@
using System.Collections;
using UnityEngine;
public class GameSession : MonoBehaviour
{
[TextArea]
public string hintText = "WASD move SHIFT sprint SPACE jump LMB fire R reload 1/2/3 or wheel switch";
[TextArea]
public string objectiveIntroText = "Clear the turrets, use cover, then push the boss platform.";
public float introMessageDuration = 4.5f;
private HUDController _hud;
private PlayerHealth _playerHealth;
private Coroutine _centerMessageRoutine;
private void Awake()
{
_hud = FindObjectOfType<HUDController>(true);
_playerHealth = FindObjectOfType<PlayerHealth>(true);
}
private void OnEnable()
{
EnemyTarget.ObjectiveCountChanged += HandleObjectiveCountChanged;
}
private void OnDisable()
{
EnemyTarget.ObjectiveCountChanged -= HandleObjectiveCountChanged;
}
private void Start()
{
EnemyTarget.RecalculateObjectivesInScene();
if (_hud == null)
{
return;
}
_hud.HideOverlay();
_hud.SetHint(hintText);
_hud.SetCenterMessage(objectiveIntroText);
_hud.SetObjectiveCount(EnemyTarget.RemainingObjectives);
if (!string.IsNullOrWhiteSpace(objectiveIntroText) && introMessageDuration > 0f)
{
_centerMessageRoutine = StartCoroutine(ClearCenterMessageAfterDelay());
}
if (_playerHealth != null)
{
_hud.SetHealth(_playerHealth.CurrentHealth, _playerHealth.maxHealth);
}
}
private void HandleObjectiveCountChanged(int remaining)
{
if (_hud == null)
{
return;
}
_hud.SetObjectiveCount(remaining);
if (remaining <= 0)
{
_hud.ShowVictoryScreen();
_hud.SetCenterMessage("Mission Complete");
}
}
private IEnumerator ClearCenterMessageAfterDelay()
{
yield return new WaitForSeconds(introMessageDuration);
if (_hud != null && EnemyTarget.RemainingObjectives > 0)
{
_hud.SetCenterMessage(string.Empty);
}
_centerMessageRoutine = null;
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: de0f6982846cdbded89ed2b6806049da
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+373
View File
@@ -0,0 +1,373 @@
using System.Collections;
using UnityEngine;
public class Gun : MonoBehaviour
{
[Header("Stats")]
public string weaponName = "Pistol";
public bool automatic;
public float damage = 20f;
public float range = 90f;
public float fireRate = 3f;
public float spread = 0.008f;
public int projectilesPerShot = 1;
public int magazineSize = 12;
public int reserveAmmo = 48;
public float reloadTime = 1.1f;
public float recoilPitch = 1.6f;
public float recoilYaw = 0.45f;
public Color tracerColor = new Color(1f, 0.85f, 0.3f);
[Header("References")]
public Transform muzzlePoint;
public ParticleSystem muzzleFlash;
public AudioSource shootAudioSource;
public AudioSource reloadAudioSource;
public AudioClip shootClip;
public AudioClip reloadClip;
[Header("Audio")]
public Vector2 shootPitchRange = new Vector2(0.98f, 1.02f);
public Vector2 reloadPitchRange = new Vector2(0.98f, 1.02f);
public float shootVolume = 1f;
public float reloadVolume = 1f;
public int AmmoInMagazine { get; private set; }
public int ReserveAmmo => reserveAmmo;
public bool IsReloading { get; private set; }
private static Material _sharedTracerMaterial;
private Camera _firingCamera;
private FPSController _controller;
private HUDController _hud;
private WeaponSwitcher _weaponSwitcher;
private float _nextFireTime;
private Coroutine _reloadRoutine;
private bool _initialized;
public void Initialize(Camera firingCamera, FPSController controller, HUDController hud, WeaponSwitcher weaponSwitcher)
{
_firingCamera = firingCamera;
_controller = controller;
_hud = hud;
_weaponSwitcher = weaponSwitcher;
AmmoInMagazine = magazineSize;
_initialized = true;
}
public void BindOwnership(HUDController hud, WeaponSwitcher weaponSwitcher)
{
_hud = hud;
_weaponSwitcher = weaponSwitcher;
UpdateHud();
}
private void OnEnable()
{
EnsureInitialized();
UpdateHud();
}
private void OnDisable()
{
CancelReload();
muzzleFlash?.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
}
private void Update()
{
EnsureInitialized();
if (_firingCamera == null)
{
return;
}
if (Input.GetKeyDown(KeyCode.R))
{
TryReload();
}
bool firePressed = automatic ? Input.GetButton("Fire1") : Input.GetButtonDown("Fire1");
if (firePressed)
{
TryFire();
}
}
public void SetEquipCooldown(float cooldown)
{
_nextFireTime = Mathf.Max(_nextFireTime, Time.time + cooldown);
}
public bool AddReserveAmmo(int amount)
{
if (amount <= 0)
{
return false;
}
reserveAmmo += amount;
UpdateHud();
return true;
}
public void CancelReload()
{
if (_reloadRoutine != null)
{
StopCoroutine(_reloadRoutine);
_reloadRoutine = null;
}
IsReloading = false;
if (reloadAudioSource != null)
{
reloadAudioSource.Stop();
}
UpdateHud();
}
private void TryFire()
{
if (IsReloading || Time.time < _nextFireTime)
{
return;
}
if (AmmoInMagazine <= 0)
{
TryReload();
return;
}
AmmoInMagazine--;
_nextFireTime = Time.time + (1f / fireRate);
_controller?.ApplyRecoil(recoilPitch, recoilYaw);
PlayShootEffects();
Vector3 tracerStart = muzzlePoint != null ? muzzlePoint.position : _firingCamera.transform.position;
Vector3 lastEndPoint = tracerStart + _firingCamera.transform.forward * range;
for (int i = 0; i < Mathf.Max(1, projectilesPerShot); i++)
{
Vector3 viewportPoint = new Vector3(
0.5f + Random.Range(-spread, spread),
0.5f + Random.Range(-spread, spread),
0f);
Ray ray = _firingCamera.ViewportPointToRay(viewportPoint);
Vector3 endPoint = ray.origin + ray.direction * range;
if (Physics.Raycast(ray, out RaycastHit hit, range, ~0, QueryTriggerInteraction.Ignore))
{
endPoint = hit.point;
IDamageable damageable = hit.collider.GetComponentInParent<IDamageable>();
damageable?.TakeDamage(damage, hit.point, hit.normal);
}
lastEndPoint = endPoint;
if (projectilesPerShot == 1 || i < 2 || i == projectilesPerShot - 1)
{
ShowTracer(tracerStart, endPoint, tracerColor);
}
}
if (projectilesPerShot > 1)
{
ShowTracer(tracerStart, lastEndPoint, tracerColor);
}
UpdateHud();
}
private void TryReload()
{
if (IsReloading || AmmoInMagazine >= magazineSize || reserveAmmo <= 0)
{
return;
}
_reloadRoutine = StartCoroutine(ReloadRoutine());
}
private IEnumerator ReloadRoutine()
{
IsReloading = true;
UpdateHud();
PlayReloadSound();
yield return new WaitForSeconds(reloadTime);
int ammoNeeded = magazineSize - AmmoInMagazine;
int ammoToLoad = Mathf.Min(ammoNeeded, reserveAmmo);
AmmoInMagazine += ammoToLoad;
reserveAmmo -= ammoToLoad;
IsReloading = false;
_reloadRoutine = null;
UpdateHud();
}
private void UpdateHud()
{
if (_hud == null || _weaponSwitcher == null || _weaponSwitcher.CurrentGun != this)
{
return;
}
_hud.SetWeapon(weaponName);
_hud.SetAmmo(AmmoInMagazine, reserveAmmo);
_hud.SetCenterMessage(IsReloading ? "Reloading..." : string.Empty);
}
private void EnsureInitialized()
{
if (_firingCamera == null)
{
_firingCamera = GetComponentInParent<Camera>();
if (_firingCamera == null)
{
_firingCamera = Camera.main;
}
}
if (_controller == null)
{
_controller = GetComponentInParent<FPSController>();
}
if (_weaponSwitcher == null)
{
_weaponSwitcher = GetComponentInParent<WeaponSwitcher>();
}
if (_hud == null)
{
_hud = FindObjectOfType<HUDController>();
}
if (muzzlePoint == null)
{
Transform foundMuzzle = transform.Find("Muzzle");
if (foundMuzzle != null)
{
muzzlePoint = foundMuzzle;
}
}
if (muzzleFlash == null)
{
Transform flashTransform = muzzlePoint != null ? muzzlePoint.Find("MuzzleFlash") : transform.Find("Muzzle/MuzzleFlash");
if (flashTransform != null)
{
muzzleFlash = flashTransform.GetComponent<ParticleSystem>();
}
else
{
muzzleFlash = GetComponentInChildren<ParticleSystem>(true);
}
}
if (shootAudioSource == null)
{
Transform shootTransform = transform.Find("ShootAudio");
if (shootTransform != null)
{
shootAudioSource = shootTransform.GetComponent<AudioSource>();
}
}
if (reloadAudioSource == null)
{
Transform reloadTransform = transform.Find("ReloadAudio");
if (reloadTransform != null)
{
reloadAudioSource = reloadTransform.GetComponent<AudioSource>();
}
}
if (shootClip == null)
{
shootClip = Resources.Load<AudioClip>($"Audio/{weaponName.ToLowerInvariant()}_fire");
}
if (reloadClip == null)
{
reloadClip = Resources.Load<AudioClip>($"Audio/{weaponName.ToLowerInvariant()}_reload");
}
if (AmmoInMagazine <= 0)
{
AmmoInMagazine = magazineSize;
}
_initialized = _firingCamera != null;
}
private void PlayShootEffects()
{
if (muzzleFlash != null)
{
muzzleFlash.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
muzzleFlash.Play();
}
PlayClip(shootAudioSource, shootClip, shootPitchRange, shootVolume, false);
}
private void PlayReloadSound()
{
PlayClip(reloadAudioSource, reloadClip, reloadPitchRange, reloadVolume, true);
}
private static void PlayClip(AudioSource source, AudioClip clip, Vector2 pitchRange, float volume, bool replaceClip)
{
if (source == null || clip == null)
{
return;
}
source.pitch = Random.Range(pitchRange.x, pitchRange.y);
source.volume = volume;
if (replaceClip)
{
source.Stop();
source.clip = clip;
source.Play();
return;
}
source.PlayOneShot(clip, volume);
}
private void ShowTracer(Vector3 start, Vector3 end, Color color)
{
GameObject tracer = new GameObject($"{weaponName}_Tracer");
LineRenderer renderer = tracer.AddComponent<LineRenderer>();
renderer.material = GetTracerMaterial();
renderer.positionCount = 2;
renderer.SetPosition(0, start);
renderer.SetPosition(1, end);
renderer.startWidth = 0.032f;
renderer.endWidth = 0.012f;
renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
renderer.receiveShadows = false;
renderer.startColor = color;
renderer.endColor = new Color(color.r, color.g, color.b, 0.1f);
Destroy(tracer, 0.06f);
}
private static Material GetTracerMaterial()
{
if (_sharedTracerMaterial == null)
{
_sharedTracerMaterial = new Material(Shader.Find("Sprites/Default"));
}
return _sharedTracerMaterial;
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 140904bcc3661786c825713b75ab5142
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+239
View File
@@ -0,0 +1,239 @@
using UnityEngine;
using UnityEngine.UI;
public class HUDController : MonoBehaviour
{
public Text healthText;
public Text ammoText;
public Text weaponText;
public Text objectiveText;
public Text hintText;
public Text centerMessageText;
public Text crosshairText;
public Image damageFlash;
public GameObject overlayPanel;
public Text overlayTitleText;
public Text overlayBodyText;
private float _damageFlashAlpha;
private void Awake()
{
if (healthText == null)
{
healthText = FindText("HealthText");
}
if (ammoText == null)
{
ammoText = FindText("AmmoText");
}
if (weaponText == null)
{
weaponText = FindText("WeaponText");
}
if (objectiveText == null)
{
objectiveText = FindText("ObjectiveText");
}
if (hintText == null)
{
hintText = FindText("HintText");
}
if (centerMessageText == null)
{
centerMessageText = FindText("CenterMessage");
}
if (crosshairText == null)
{
crosshairText = FindText("Crosshair");
}
if (overlayTitleText == null)
{
overlayTitleText = FindText("OverlayTitle");
}
if (overlayBodyText == null)
{
overlayBodyText = FindText("OverlayBody");
}
if (damageFlash == null)
{
Transform flashTransform = transform.Find("DamageFlash");
if (flashTransform != null)
{
damageFlash = flashTransform.GetComponent<Image>();
}
}
if (overlayPanel == null)
{
Transform overlayTransform = transform.Find("Overlay");
if (overlayTransform != null)
{
overlayPanel = overlayTransform.gameObject;
}
}
EnsureCrosshair();
}
private void Update()
{
if (damageFlash == null)
{
return;
}
_damageFlashAlpha = Mathf.MoveTowards(_damageFlashAlpha, 0f, Time.unscaledDeltaTime * 1.8f);
Color color = damageFlash.color;
color.a = _damageFlashAlpha;
damageFlash.color = color;
}
public void SetHealth(float current, float max)
{
if (healthText != null)
{
healthText.text = $"HP {Mathf.CeilToInt(current)} / {Mathf.CeilToInt(max)}";
}
}
public void SetAmmo(int currentMagazine, int reserveAmmo)
{
if (ammoText != null)
{
ammoText.text = $"MAG {currentMagazine}\nRES {reserveAmmo}";
}
}
public void SetWeapon(string weaponName)
{
if (weaponText != null)
{
weaponText.text = weaponName.ToUpperInvariant();
}
}
public void SetObjectiveCount(int remainingTargets)
{
if (objectiveText != null)
{
objectiveText.text = remainingTargets > 0
? $"TARGETS LEFT {remainingTargets}"
: "SECTOR CLEAR";
}
}
public void SetHint(string hint)
{
if (hintText != null)
{
hintText.text = hint;
}
}
public void SetCenterMessage(string message)
{
if (centerMessageText != null)
{
centerMessageText.text = message;
centerMessageText.enabled = !string.IsNullOrWhiteSpace(message);
}
}
public void ShowDamageFlash()
{
_damageFlashAlpha = 0.35f;
}
public void ShowDeathScreen()
{
SetOverlay(true, "You Are Down", "Press R to restart the arena.");
SetCenterMessage(string.Empty);
}
public void ShowVictoryScreen()
{
SetOverlay(true, "Arena Cleared", "All hostile targets destroyed.");
SetCenterMessage("Mission Complete");
}
public void HideOverlay()
{
SetOverlay(false, string.Empty, string.Empty);
}
private void SetOverlay(bool visible, string title, string body)
{
if (overlayPanel != null)
{
overlayPanel.SetActive(visible);
}
if (overlayTitleText != null)
{
overlayTitleText.text = title;
}
if (overlayBodyText != null)
{
overlayBodyText.text = body;
}
}
private Text FindText(string objectName)
{
Transform child = transform.Find(objectName);
if (child != null)
{
return child.GetComponent<Text>();
}
Text[] texts = GetComponentsInChildren<Text>(true);
for (int i = 0; i < texts.Length; i++)
{
if (texts[i].name == objectName)
{
return texts[i];
}
}
return null;
}
private void EnsureCrosshair()
{
if (crosshairText == null)
{
GameObject crosshairObject = new GameObject("Crosshair");
crosshairObject.transform.SetParent(transform, false);
crosshairText = crosshairObject.AddComponent<Text>();
crosshairText.font = healthText != null ? healthText.font : Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
crosshairText.fontSize = 30;
crosshairText.alignment = TextAnchor.MiddleCenter;
crosshairText.color = new Color(1f, 1f, 1f, 0.9f);
RectTransform rectTransform = crosshairText.rectTransform;
rectTransform.anchorMin = new Vector2(0.5f, 0.5f);
rectTransform.anchorMax = new Vector2(0.5f, 0.5f);
rectTransform.sizeDelta = new Vector2(32f, 32f);
rectTransform.anchoredPosition = new Vector2(0f, -6f);
}
crosshairText.text = "+";
crosshairText.alignment = TextAnchor.MiddleCenter;
crosshairText.fontStyle = FontStyle.Bold;
Color color = crosshairText.color;
color.a = 0.9f;
crosshairText.color = color;
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8c5e964a7032df579aa1a38e198076a8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+31
View File
@@ -0,0 +1,31 @@
using UnityEngine;
public class HealthPack : MonoBehaviour
{
public int healAmount = 35;
public float rotateSpeed = 70f;
public float bobHeight = 0.15f;
public float bobSpeed = 2f;
private Vector3 _basePosition;
private void Start()
{
_basePosition = transform.position;
}
private void Update()
{
transform.Rotate(Vector3.up, rotateSpeed * Time.deltaTime, Space.World);
transform.position = _basePosition + Vector3.up * (Mathf.Sin(Time.time * bobSpeed) * bobHeight);
}
private void OnTriggerEnter(Collider other)
{
PlayerHealth playerHealth = other.GetComponent<PlayerHealth>() ?? other.GetComponentInParent<PlayerHealth>();
if (playerHealth != null && playerHealth.Heal(healAmount))
{
Destroy(gameObject);
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c04e81be5df3bb80cbd83310f484d788
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+6
View File
@@ -0,0 +1,6 @@
using UnityEngine;
public interface IDamageable
{
void TakeDamage(float amount, Vector3 hitPoint, Vector3 hitNormal);
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0bbfbc1659902bdf899a765c52216f77
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+116
View File
@@ -0,0 +1,116 @@
using UnityEngine;
using UnityEngine.SceneManagement;
public class PlayerHealth : MonoBehaviour
{
public float maxHealth = 200f;
public float CurrentHealth { get; private set; }
public bool IsDead { get; private set; }
private HUDController _hud;
private FPSController _controller;
private WeaponSwitcher _weaponSwitcher;
public void Initialize(HUDController hud, FPSController controller, WeaponSwitcher weaponSwitcher)
{
_hud = hud;
_controller = controller;
_weaponSwitcher = weaponSwitcher;
CurrentHealth = maxHealth;
_hud?.SetHealth(CurrentHealth, maxHealth);
}
private void Awake()
{
CurrentHealth = maxHealth;
}
private void Start()
{
if (_hud == null)
{
_hud = FindObjectOfType<HUDController>();
}
if (_controller == null)
{
_controller = GetComponent<FPSController>();
}
if (_weaponSwitcher == null)
{
_weaponSwitcher = GetComponent<WeaponSwitcher>();
}
if (CurrentHealth <= 0f)
{
CurrentHealth = maxHealth;
}
_hud?.SetHealth(CurrentHealth, maxHealth);
}
private void Update()
{
if (!IsDead)
{
return;
}
if (Input.GetKeyDown(KeyCode.R))
{
Time.timeScale = 1f;
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
}
public void TakeDamage(float amount)
{
if (IsDead)
{
return;
}
CurrentHealth = Mathf.Clamp(CurrentHealth - amount, 0f, maxHealth);
_hud?.SetHealth(CurrentHealth, maxHealth);
_hud?.ShowDamageFlash();
if (CurrentHealth <= 0f)
{
Die();
}
}
public bool Heal(int amount)
{
if (IsDead || CurrentHealth >= maxHealth)
{
return false;
}
CurrentHealth = Mathf.Clamp(CurrentHealth + amount, 0f, maxHealth);
_hud?.SetHealth(CurrentHealth, maxHealth);
return true;
}
private void Die()
{
IsDead = true;
Time.timeScale = 0f;
if (_controller != null)
{
_controller.SetLookEnabled(false);
_controller.enabled = false;
_controller.SetCursorLocked(false);
}
if (_weaponSwitcher != null)
{
_weaponSwitcher.enabled = false;
}
_hud?.ShowDeathScreen();
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d6382d1a79d3aece4bce053caca8bb92
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+153
View File
@@ -0,0 +1,153 @@
using System.Collections.Generic;
using UnityEngine;
public class WeaponSwitcher : MonoBehaviour
{
public float switchCooldown = 0.2f;
public Gun CurrentGun { get; private set; }
private readonly List<Gun> _weapons = new List<Gun>();
private HUDController _hud;
private int _currentIndex = -1;
private void Start()
{
if (_hud == null)
{
_hud = FindObjectOfType<HUDController>();
}
if (_weapons.Count == 0)
{
DiscoverWeapons();
}
}
public void Initialize(IEnumerable<Gun> weapons, HUDController hud)
{
_weapons.Clear();
_weapons.AddRange(weapons);
_hud = hud;
for (int i = 0; i < _weapons.Count; i++)
{
if (_weapons[i] != null)
{
_weapons[i].BindOwnership(_hud, this);
_weapons[i].gameObject.SetActive(false);
}
}
SelectWeapon(0, true);
}
private void Update()
{
if (_weapons.Count == 0)
{
DiscoverWeapons();
}
if (_weapons.Count == 0)
{
return;
}
if (Input.GetKeyDown(KeyCode.Alpha1))
{
SelectWeapon(0);
}
if (Input.GetKeyDown(KeyCode.Alpha2))
{
SelectWeapon(1);
}
if (Input.GetKeyDown(KeyCode.Alpha3))
{
SelectWeapon(2);
}
float mouseWheel = Input.GetAxis("Mouse ScrollWheel");
if (mouseWheel > 0.05f)
{
SelectWeapon((_currentIndex + 1) % _weapons.Count);
}
else if (mouseWheel < -0.05f)
{
SelectWeapon((_currentIndex - 1 + _weapons.Count) % _weapons.Count);
}
}
public bool AddAmmoToCurrentWeapon(int amount)
{
return CurrentGun != null && CurrentGun.AddReserveAmmo(amount);
}
public void RefreshHud()
{
if (CurrentGun == null || _hud == null)
{
return;
}
_hud.SetWeapon(CurrentGun.weaponName);
_hud.SetAmmo(CurrentGun.AmmoInMagazine, CurrentGun.ReserveAmmo);
}
private void SelectWeapon(int index, bool force = false)
{
if (_weapons.Count == 0 || index < 0 || index >= _weapons.Count)
{
return;
}
if (!force && index == _currentIndex)
{
return;
}
for (int i = 0; i < _weapons.Count; i++)
{
if (_weapons[i] == null)
{
continue;
}
if (i != index)
{
_weapons[i].CancelReload();
_weapons[i].gameObject.SetActive(false);
}
}
_currentIndex = index;
CurrentGun = _weapons[_currentIndex];
if (CurrentGun != null)
{
CurrentGun.gameObject.SetActive(true);
CurrentGun.SetEquipCooldown(force ? 0f : switchCooldown);
}
RefreshHud();
}
private void DiscoverWeapons()
{
_weapons.Clear();
Gun[] foundWeapons = GetComponentsInChildren<Gun>(true);
for (int i = 0; i < foundWeapons.Length; i++)
{
_weapons.Add(foundWeapons[i]);
foundWeapons[i].BindOwnership(_hud, this);
foundWeapons[i].gameObject.SetActive(false);
}
if (_weapons.Count > 0)
{
SelectWeapon(0, true);
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b7da74c11ed2cf1a6a44985e70f61901
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: