diff --git a/Assets/Scripts/AIBehaviour.cs b/Assets/Scripts/AIBehaviour.cs
new file mode 100644
index 0000000..b182b89
--- /dev/null
+++ b/Assets/Scripts/AIBehaviour.cs
@@ -0,0 +1,298 @@
+using UnityEngine;
+using UnityEngine.AI;
+
+///
+/// Call of Duty Zombies-style enemy AI.
+/// States: Seeking (chase player) → BreakingBarrier (hammer a blocked door/window) → Attacking (melee the player)
+///
+/// Setup requirements:
+/// - NavMeshAgent on this GameObject
+/// - Player GameObject tagged "Player" with a PlayerHealth component
+/// - Barrier objects on a layer assigned to barrierLayer, with a Barrier component + NavMeshObstacle (carving on)
+///
+[RequireComponent(typeof(NavMeshAgent))]
+public class AIBehaviour : MonoBehaviour
+{
+ public enum ZombieState { Seeking, BreakingBarrier, Attacking, Dead }
+
+ // -------------------------------------------------------------------------
+ // Inspector
+ // -------------------------------------------------------------------------
+ [Header("Movement")]
+ public float moveSpeed = 3.5f;
+ public float acceleration = 12f;
+
+ [Header("Attack — Player")]
+ public float attackRange = 1.8f;
+ public float attackDamage = 15f;
+ [Tooltip("Attacks per second")] public float attackRate = 1f;
+
+ [Header("Attack — Barrier")]
+ public float barrierDetectRange = 1.6f;
+ public float barrierDamage = 25f;
+ [Tooltip("Barrier hits per second")] public float barrierAttackRate = 1.5f;
+ public LayerMask barrierLayer;
+
+ [Header("Health")]
+ public float maxHealth = 100f;
+
+ [Header("Animation (optional)")]
+ public Animator animator;
+
+ // -------------------------------------------------------------------------
+ // Private state
+ // -------------------------------------------------------------------------
+ private NavMeshAgent _agent;
+ private Transform _player;
+ private PlayerHealth _playerHealth;
+ private ZombieState _state = ZombieState.Seeking;
+ private float _health;
+ private float _attackTimer;
+ private float _barrierTimer;
+ private Barrier _targetBarrier;
+
+ // -------------------------------------------------------------------------
+ // Lifecycle
+ // -------------------------------------------------------------------------
+ void Awake()
+ {
+ _agent = GetComponent();
+ _agent.speed = moveSpeed;
+ _agent.acceleration = acceleration;
+ _agent.stoppingDistance = attackRange * 0.85f;
+ _agent.autoBraking = true;
+ _health = maxHealth;
+ }
+
+ void Start()
+ {
+ GameObject playerObj = GameObject.FindGameObjectWithTag("Player");
+ if (playerObj != null)
+ {
+ _player = playerObj.transform;
+ _playerHealth = playerObj.GetComponent();
+ }
+ else
+ {
+ Debug.LogWarning("[ZombieAI] No GameObject tagged 'Player' found.");
+ }
+ }
+
+ void Update()
+ {
+ if (_state == ZombieState.Dead || _player == null) return;
+
+ _attackTimer -= Time.deltaTime;
+ _barrierTimer -= Time.deltaTime;
+
+ switch (_state)
+ {
+ case ZombieState.Seeking: UpdateSeeking(); break;
+ case ZombieState.BreakingBarrier: UpdateBreakingBarrier(); break;
+ case ZombieState.Attacking: UpdateAttacking(); break;
+ }
+
+ UpdateAnimator();
+ }
+
+ // -------------------------------------------------------------------------
+ // Seeking — navigate toward the player, detect barriers blocking the path
+ // -------------------------------------------------------------------------
+ void UpdateSeeking()
+ {
+ float dist = Vector3.Distance(transform.position, _player.position);
+
+ // Close enough to swing
+ if (dist <= attackRange)
+ {
+ _agent.ResetPath();
+ TransitionTo(ZombieState.Attacking);
+ return;
+ }
+
+ // Keep chasing
+ _agent.SetDestination(_player.position);
+
+ // If the NavMesh path can't reach the player, a barrier may be blocking
+ if (_agent.pathStatus == NavMeshPathStatus.PathPartial ||
+ _agent.pathStatus == NavMeshPathStatus.PathInvalid)
+ {
+ Barrier b = ScanForBarrierAhead();
+ if (b != null)
+ {
+ _targetBarrier = b;
+ _agent.ResetPath();
+ TransitionTo(ZombieState.BreakingBarrier);
+ return;
+ }
+ }
+
+ // Also do a short spherecast while moving in case the path is briefly valid
+ // but a barrier is right in front of us (tight corridor)
+ if (_agent.velocity.magnitude > 0.2f)
+ {
+ Barrier bAhead = ScanForBarrierAhead();
+ if (bAhead != null)
+ {
+ _targetBarrier = bAhead;
+ _agent.ResetPath();
+ TransitionTo(ZombieState.BreakingBarrier);
+ }
+ }
+ }
+
+ // -------------------------------------------------------------------------
+ // Breaking barrier — face it and hammer until it falls
+ // -------------------------------------------------------------------------
+ void UpdateBreakingBarrier()
+ {
+ // Barrier was destroyed (by us or another zombie)
+ if (_targetBarrier == null || _targetBarrier.IsDestroyed)
+ {
+ _targetBarrier = null;
+ TransitionTo(ZombieState.Seeking);
+ return;
+ }
+
+ // Face the barrier
+ FaceTarget(_targetBarrier.transform.position);
+
+ // Hit on cooldown
+ if (_barrierTimer <= 0f)
+ {
+ _targetBarrier.TakeDamage(barrierDamage);
+ _barrierTimer = 1f / barrierAttackRate;
+ TriggerAnimatorTrigger("BarrierHit");
+ }
+ }
+
+ // -------------------------------------------------------------------------
+ // Attacking — melee the player
+ // -------------------------------------------------------------------------
+ void UpdateAttacking()
+ {
+ float dist = Vector3.Distance(transform.position, _player.position);
+
+ // Player escaped melee range — resume chase
+ if (dist > attackRange + 0.6f)
+ {
+ TransitionTo(ZombieState.Seeking);
+ return;
+ }
+
+ FaceTarget(_player.position);
+
+ if (_attackTimer <= 0f)
+ {
+ PlayerHealth ph = _player.GetComponent();
+ if (ph != null) ph.TakeDamage(attackDamage);
+ _attackTimer = 1f / attackRate;
+ TriggerAnimatorTrigger("Attack");
+ }
+ }
+
+ // -------------------------------------------------------------------------
+ // Public API — take damage / die
+ // -------------------------------------------------------------------------
+ public void TakeDamage(float damage)
+ {
+ if (_state == ZombieState.Dead) return;
+ _health -= damage;
+ if (_health <= 0f) Die();
+ }
+
+ void Die()
+ {
+ TransitionTo(ZombieState.Dead);
+ _agent.ResetPath();
+ _agent.enabled = false;
+ TriggerAnimatorTrigger("Die");
+ Destroy(gameObject, 3f);
+ }
+
+ // -------------------------------------------------------------------------
+ // Helpers
+ // -------------------------------------------------------------------------
+
+ /// Spherecast in the direction of movement to find a Barrier collider.
+ Barrier ScanForBarrierAhead()
+ {
+ Vector3 origin = transform.position + Vector3.up * 0.8f;
+ Vector3 dir = _agent.desiredVelocity.magnitude > 0.1f
+ ? _agent.desiredVelocity.normalized
+ : transform.forward;
+
+ if (Physics.SphereCast(origin, 0.45f, dir, out RaycastHit hit,
+ barrierDetectRange, barrierLayer,
+ QueryTriggerInteraction.Ignore))
+ {
+ Barrier b = hit.collider.GetComponentInParent();
+ if (b != null && !b.IsDestroyed)
+ return b;
+ }
+ return null;
+ }
+
+ void FaceTarget(Vector3 targetPos)
+ {
+ Vector3 dir = (targetPos - transform.position);
+ dir.y = 0f;
+ if (dir.sqrMagnitude < 0.001f) return;
+ Quaternion target = Quaternion.LookRotation(dir);
+ transform.rotation = Quaternion.Slerp(transform.rotation, target, 10f * Time.deltaTime);
+ }
+
+ void TransitionTo(ZombieState next)
+ {
+ _state = next;
+ }
+
+ // -------------------------------------------------------------------------
+ // Animator bridge — all parameters are optional; missing params are ignored
+ // -------------------------------------------------------------------------
+ void UpdateAnimator()
+ {
+ if (animator == null) return;
+ SetAnimFloat("Speed", _agent.velocity.magnitude);
+ SetAnimBool("IsAttacking", _state == ZombieState.Attacking);
+ SetAnimBool("IsBreakingBarrier",_state == ZombieState.BreakingBarrier);
+ SetAnimBool("IsDead", _state == ZombieState.Dead);
+ }
+
+ void SetAnimFloat(string name, float value)
+ {
+ if (animator.HasParameterByName(name)) animator.SetFloat(name, value);
+ }
+
+ void SetAnimBool(string name, bool value)
+ {
+ if (animator.HasParameterByName(name)) animator.SetBool(name, value);
+ }
+
+ void TriggerAnimatorTrigger(string name)
+ {
+ if (animator != null && animator.HasParameterByName(name))
+ animator.SetTrigger(name);
+ }
+
+#if UNITY_EDITOR
+ void OnDrawGizmosSelected()
+ {
+ Gizmos.color = Color.red;
+ Gizmos.DrawWireSphere(transform.position, attackRange);
+ Gizmos.color = Color.yellow;
+ Gizmos.DrawWireSphere(transform.position, barrierDetectRange);
+ }
+#endif
+}
+
+/// Extension to safely check Animator parameter existence
+public static class AnimatorExtensions
+{
+ public static bool HasParameterByName(this Animator animator, string name)
+ {
+ foreach (AnimatorControllerParameter p in animator.parameters)
+ if (p.name == name) return true;
+ return false;
+ }
+}
diff --git a/Assets/Scripts/AIBehaviour.cs.meta b/Assets/Scripts/AIBehaviour.cs.meta
new file mode 100644
index 0000000..6523321
--- /dev/null
+++ b/Assets/Scripts/AIBehaviour.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 1dace61880c5f3c4ebca0fd5711cc442
\ No newline at end of file
diff --git a/Assets/Scripts/Barrier.cs b/Assets/Scripts/Barrier.cs
new file mode 100644
index 0000000..c18c65a
--- /dev/null
+++ b/Assets/Scripts/Barrier.cs
@@ -0,0 +1,82 @@
+using UnityEngine;
+using UnityEngine.AI;
+
+///
+/// Attach to any door/window barrier the zombie should break through.
+///
+/// Setup:
+/// - Add a NavMeshObstacle component (Carve = true) so the NavMesh treats it as blocked.
+/// - Assign the barrier's layer to match AIBehaviour.barrierLayer.
+/// - Optionally populate the 'boards' array with child visuals that get hidden as health drops.
+///
+public class Barrier : MonoBehaviour
+{
+ [Header("Health")]
+ public float maxHealth = 100f;
+
+ [Header("Board Visuals (optional)")]
+ [Tooltip("Child objects representing planks/boards. They are hidden progressively as health drops.")]
+ public GameObject[] boards;
+
+ public bool IsDestroyed { get; private set; }
+
+ private float _health;
+ private NavMeshObstacle _obstacle;
+
+ void Awake()
+ {
+ _health = maxHealth;
+ _obstacle = GetComponent();
+ }
+
+ public void TakeDamage(float damage)
+ {
+ if (IsDestroyed) return;
+
+ _health -= damage;
+ UpdateBoards();
+
+ if (_health <= 0f)
+ Break();
+ }
+
+ void UpdateBoards()
+ {
+ if (boards == null || boards.Length == 0) return;
+
+ // Show boards proportional to remaining health
+ int boardsToShow = Mathf.CeilToInt((_health / maxHealth) * boards.Length);
+ for (int i = 0; i < boards.Length; i++)
+ {
+ if (boards[i] != null)
+ boards[i].SetActive(i < boardsToShow);
+ }
+ }
+
+ void Break()
+ {
+ IsDestroyed = true;
+
+ // Disable the NavMesh obstacle so pathfinding opens back up
+ if (_obstacle != null)
+ _obstacle.enabled = false;
+
+ // Disable colliders so zombies walk through
+ foreach (Collider col in GetComponentsInChildren())
+ col.enabled = false;
+
+ // Hide visuals
+ foreach (GameObject board in boards)
+ if (board != null) board.SetActive(false);
+
+ Destroy(gameObject, 0.1f);
+ }
+
+#if UNITY_EDITOR
+ void OnDrawGizmosSelected()
+ {
+ Gizmos.color = new Color(1f, 0.5f, 0f, 0.5f);
+ Gizmos.DrawWireCube(transform.position, transform.localScale);
+ }
+#endif
+}
diff --git a/Assets/Scripts/Barrier.cs.meta b/Assets/Scripts/Barrier.cs.meta
new file mode 100644
index 0000000..c11db32
--- /dev/null
+++ b/Assets/Scripts/Barrier.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: db8fa53b3e3928e42878084d32a9d6b6
\ No newline at end of file
diff --git a/Assets/Scripts/PlayerHealth.cs b/Assets/Scripts/PlayerHealth.cs
new file mode 100644
index 0000000..284ad71
--- /dev/null
+++ b/Assets/Scripts/PlayerHealth.cs
@@ -0,0 +1,49 @@
+using UnityEngine;
+using UnityEngine.Events;
+
+///
+/// Simple player health component. Attach to the Player GameObject.
+///
+public class PlayerHealth : MonoBehaviour
+{
+ [Header("Health")]
+ public float maxHealth = 100f;
+
+ [Header("Events")]
+ public UnityEvent onDeath;
+ public UnityEvent onHealthChanged; // passes current health
+
+ public float CurrentHealth { get; private set; }
+ public bool IsDead { get; private set; }
+
+ void Awake()
+ {
+ CurrentHealth = maxHealth;
+ }
+
+ public void TakeDamage(float damage)
+ {
+ if (IsDead) return;
+
+ CurrentHealth = Mathf.Max(0f, CurrentHealth - damage);
+ onHealthChanged?.Invoke(CurrentHealth);
+
+ if (CurrentHealth <= 0f)
+ Die();
+ }
+
+ public void Heal(float amount)
+ {
+ if (IsDead) return;
+
+ CurrentHealth = Mathf.Min(maxHealth, CurrentHealth + amount);
+ onHealthChanged?.Invoke(CurrentHealth);
+ }
+
+ void Die()
+ {
+ IsDead = true;
+ onDeath?.Invoke();
+ Debug.Log("[PlayerHealth] Player died.");
+ }
+}
diff --git a/Assets/Scripts/PlayerHealth.cs.meta b/Assets/Scripts/PlayerHealth.cs.meta
new file mode 100644
index 0000000..89c621d
--- /dev/null
+++ b/Assets/Scripts/PlayerHealth.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 4004eaf09fe63cb48bd59da9575615ff
\ No newline at end of file
diff --git a/Assets/Settings/Mobile_RPAsset.asset b/Assets/Settings/Mobile_RPAsset.asset
index 7ceffe7..fedee07 100644
--- a/Assets/Settings/Mobile_RPAsset.asset
+++ b/Assets/Settings/Mobile_RPAsset.asset
@@ -12,8 +12,8 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: bf2edee5c58d82540a51f03df9d42094, type: 3}
m_Name: Mobile_RPAsset
m_EditorClassIdentifier:
- k_AssetVersion: 12
- k_AssetPreviousVersion: 12
+ k_AssetVersion: 13
+ k_AssetPreviousVersion: 13
m_RendererType: 1
m_RendererData: {fileID: 0}
m_RendererDataList:
@@ -53,6 +53,7 @@ MonoBehaviour:
m_AdditionalLightsShadowResolutionTierHigh: 1024
m_ReflectionProbeBlending: 1
m_ReflectionProbeBoxProjection: 1
+ m_ReflectionProbeAtlas: 1
m_ShadowDistance: 50
m_ShadowCascadeCount: 1
m_Cascade2Split: 0.25
@@ -78,11 +79,11 @@ MonoBehaviour:
m_UseAdaptivePerformance: 1
m_ColorGradingMode: 0
m_ColorGradingLutSize: 32
+ m_AllowPostProcessAlphaOutput: 0
m_UseFastSRGBLinearConversion: 1
m_SupportDataDrivenLensFlare: 1
m_SupportScreenSpaceLensFlare: 1
m_GPUResidentDrawerMode: 0
- m_UseLegacyLightmaps: 0
m_SmallMeshScreenPercentage: 0
m_GPUResidentDrawerEnableOcclusionCullingInCameras: 0
m_ShadowType: 1
@@ -109,6 +110,7 @@ MonoBehaviour:
m_PrefilterDebugKeywords: 1
m_PrefilterWriteRenderingLayers: 1
m_PrefilterHDROutput: 1
+ m_PrefilterAlphaOutput: 0
m_PrefilterSSAODepthNormals: 1
m_PrefilterSSAOSourceDepthLow: 1
m_PrefilterSSAOSourceDepthMedium: 0
@@ -126,8 +128,14 @@ MonoBehaviour:
m_PrefilterSoftShadowsQualityHigh: 1
m_PrefilterSoftShadows: 0
m_PrefilterScreenCoord: 1
+ m_PrefilterScreenSpaceIrradiance: 0
m_PrefilterNativeRenderPass: 1
m_PrefilterUseLegacyLightmaps: 0
+ m_PrefilterBicubicLightmapSampling: 0
+ m_PrefilterReflectionProbeRotation: 0
+ m_PrefilterReflectionProbeBlending: 0
+ m_PrefilterReflectionProbeBoxProjection: 0
+ m_PrefilterReflectionProbeAtlas: 0
m_ShaderVariantLogLevel: 0
m_ShadowCascades: 0
m_Textures: