diff --git a/Assets/Scenes/Testing Scenes/Movement.unity b/Assets/Scenes/Testing Scenes/Movement.unity index db3c93a..58f93d0 100644 --- a/Assets/Scenes/Testing Scenes/Movement.unity +++ b/Assets/Scenes/Testing Scenes/Movement.unity @@ -357,6 +357,50 @@ PrefabInstance: insertIndex: -1 addedObject: {fileID: 2039163536} m_SourcePrefab: {fileID: 100100000, guid: 33de4b621d70a49aab5df775f2b826ef, type: 3} +--- !u!1 &193247563 stripped +GameObject: + m_CorrespondingSourceObject: {fileID: -8098169881513260187, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} + m_PrefabInstance: {fileID: 279169685} + m_PrefabAsset: {fileID: 0} +--- !u!114 &193247564 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 193247563} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4b2ef1ad29b012344bd26f70130d3c2e, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::WeaponBob + bobFrequency: 10 + bobHorizontalAmplitude: 0.05 + bobVerticalAmplitude: 0.03 + sprintBobMultiplier: 1.5 + returnSpeed: 6 +--- !u!114 &193247565 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 193247563} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 95709fa8fd6c75045b1ef8e4f505c152, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::PickupItem + definition: {fileID: 11400000, guid: e09f7b05a155ba8449a0f0d8412ebe9d, type: 2} + itemName: Item + itemIcon: {fileID: 0} + spinSpeed: 120 + bobHeight: 0.18 + bobSpeed: 2.2 + pickupRadius: 2 + pickupKey: 101 + pickupParticlesPrefab: {fileID: 0} + pickupSound: {fileID: 0} --- !u!1001 &197010634 PrefabInstance: m_ObjectHideFlags: 0 @@ -693,6 +737,127 @@ MeshCollider: m_Convex: 0 m_CookingOptions: 30 m_Mesh: {fileID: 1050908021570271116, guid: 4e219defa86804242a4efe4306a4b26b, type: 3} +--- !u!1001 &279169685 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: -8098169881513260187, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} + propertyPath: m_Name + value: digitakt_18_47_20 (1) + objectReference: {fileID: 0} + - target: {fileID: 3447742079981357586, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} + propertyPath: m_LocalScale.x + value: 2.2728 + objectReference: {fileID: 0} + - target: {fileID: 3447742079981357586, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} + propertyPath: m_LocalScale.y + value: 2.2728 + objectReference: {fileID: 0} + - target: {fileID: 3447742079981357586, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} + propertyPath: m_LocalScale.z + value: 2.2728 + objectReference: {fileID: 0} + - target: {fileID: 3447742079981357586, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} + propertyPath: m_LocalPosition.x + value: 363.437 + objectReference: {fileID: 0} + - target: {fileID: 3447742079981357586, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} + propertyPath: m_LocalPosition.y + value: -20.075 + objectReference: {fileID: 0} + - target: {fileID: 3447742079981357586, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} + propertyPath: m_LocalPosition.z + value: 135.534 + objectReference: {fileID: 0} + - target: {fileID: 3447742079981357586, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} + propertyPath: m_LocalRotation.w + value: 0.75653136 + objectReference: {fileID: 0} + - target: {fileID: 3447742079981357586, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} + propertyPath: m_LocalRotation.x + value: -0 + objectReference: {fileID: 0} + - target: {fileID: 3447742079981357586, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} + propertyPath: m_LocalRotation.y + value: -0.6539574 + objectReference: {fileID: 0} + - target: {fileID: 3447742079981357586, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} + propertyPath: m_LocalRotation.z + value: -0 + objectReference: {fileID: 0} + - target: {fileID: 3447742079981357586, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3447742079981357586, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: -81.681 + objectReference: {fileID: 0} + - target: {fileID: 3447742079981357586, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: + - targetCorrespondingSourceObject: {fileID: -8098169881513260187, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} + insertIndex: -1 + addedObject: {fileID: 193247565} + - targetCorrespondingSourceObject: {fileID: -8098169881513260187, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} + insertIndex: -1 + addedObject: {fileID: 193247564} + m_SourcePrefab: {fileID: 3150474306388093854, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} +--- !u!1 &306565998 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 306566000} + - component: {fileID: 306565999} + m_Layer: 0 + m_Name: GameObject (1) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &306565999 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 306565998} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 180c6606991a4b64f812f9713907fd0c, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::EnemySpawner + enemyType: 0 + healthOverride: 0 +--- !u!4 &306566000 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 306565998} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 350.33, y: -17.28, z: 166.448} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1001 &343869061 PrefabInstance: m_ObjectHideFlags: 0 @@ -6350,6 +6515,52 @@ Transform: m_Children: [] m_Father: {fileID: 1716472518} m_LocalEulerAnglesHint: {x: 0, y: -176, z: 0} +--- !u!1 &478620897 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 478620899} + - component: {fileID: 478620898} + m_Layer: 0 + m_Name: GameObject (2) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &478620898 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 478620897} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 180c6606991a4b64f812f9713907fd0c, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::EnemySpawner + enemyType: 0 + healthOverride: 0 +--- !u!4 &478620899 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 478620897} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 350.33, y: -17.28, z: 166.448} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1001 &483729265 PrefabInstance: m_ObjectHideFlags: 0 @@ -6472,6 +6683,52 @@ MeshCollider: m_Convex: 0 m_CookingOptions: 30 m_Mesh: {fileID: 0} +--- !u!1 &541212320 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 541212322} + - component: {fileID: 541212321} + m_Layer: 0 + m_Name: GameObject + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &541212321 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 541212320} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 180c6606991a4b64f812f9713907fd0c, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::EnemySpawner + enemyType: 0 + healthOverride: 0 +--- !u!4 &541212322 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 541212320} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 341.37, y: -19.34, z: 161.81} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &571790233 stripped GameObject: m_CorrespondingSourceObject: {fileID: -8098169881513260187, guid: 3df8bb31de452e64fa85a1265898d698, type: 3} @@ -6680,6 +6937,23 @@ MonoBehaviour: pickupKey: 101 pickupParticlesPrefab: {fileID: 0} pickupSound: {fileID: 0} +--- !u!114 &715417268 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 715417260} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4b2ef1ad29b012344bd26f70130d3c2e, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::WeaponBob + bobFrequency: 10 + bobHorizontalAmplitude: 0.05 + bobVerticalAmplitude: 0.03 + sprintBobMultiplier: 1.5 + returnSpeed: 6 --- !u!1001 &791320992 PrefabInstance: m_ObjectHideFlags: 0 @@ -6706,15 +6980,15 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 3447742079981357586, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} propertyPath: m_LocalPosition.x - value: 363.888 + value: 364.046 objectReference: {fileID: 0} - target: {fileID: 3447742079981357586, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} propertyPath: m_LocalPosition.y - value: -20.149 + value: -19.847 objectReference: {fileID: 0} - target: {fileID: 3447742079981357586, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} propertyPath: m_LocalPosition.z - value: 157.267 + value: 158.352 objectReference: {fileID: 0} - target: {fileID: 3447742079981357586, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} propertyPath: m_LocalRotation.w @@ -6751,6 +7025,9 @@ PrefabInstance: - targetCorrespondingSourceObject: {fileID: -8098169881513260187, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} insertIndex: -1 addedObject: {fileID: 715417264} + - targetCorrespondingSourceObject: {fileID: -8098169881513260187, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} + insertIndex: -1 + addedObject: {fileID: 715417268} m_SourcePrefab: {fileID: 3150474306388093854, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} --- !u!1001 &806276174 PrefabInstance: @@ -6888,7 +7165,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 6346070617587720957, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3} propertyPath: m_LocalPosition.z - value: 167.46 + value: 168.503 objectReference: {fileID: 0} - target: {fileID: 6346070617587720957, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3} propertyPath: m_LocalRotation.w @@ -7356,6 +7633,52 @@ MeshCollider: m_Convex: 0 m_CookingOptions: 30 m_Mesh: {fileID: 1050908021570271116, guid: fa9ce4c3e422d4dfb8c7546b16bc9f33, type: 3} +--- !u!1 &1258283936 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1258283938} + - component: {fileID: 1258283937} + m_Layer: 0 + m_Name: GameObject (3) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1258283937 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1258283936} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 180c6606991a4b64f812f9713907fd0c, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::EnemySpawner + enemyType: 2 + healthOverride: 0 +--- !u!4 &1258283938 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1258283936} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 348.81857, y: -17.28, z: 165.226} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1360218907 GameObject: m_ObjectHideFlags: 0 @@ -8575,6 +8898,7 @@ SceneRoots: - {fileID: 483729265} - {fileID: 197010634} - {fileID: 1039074614} + - {fileID: 279169685} - {fileID: 207773673} - {fileID: 1366152832} - {fileID: 1235679} @@ -8585,3 +8909,7 @@ SceneRoots: - {fileID: 65142585} - {fileID: 1114521749} - {fileID: 1007367441} + - {fileID: 541212322} + - {fileID: 306566000} + - {fileID: 478620899} + - {fileID: 1258283938} diff --git a/Assets/Scripts/EnemyHeadHitbox.cs b/Assets/Scripts/EnemyHeadHitbox.cs new file mode 100644 index 0000000..2524f5d --- /dev/null +++ b/Assets/Scripts/EnemyHeadHitbox.cs @@ -0,0 +1,10 @@ +using UnityEngine; + +/// +/// Attach this to an enemy's head GameObject. +/// When the player shoots it, SimpleGun checks for this component and instakills. +/// +public class EnemyHeadHitbox : MonoBehaviour +{ + [HideInInspector] public EnemyHealth enemyHealth; +} diff --git a/Assets/Scripts/EnemyHeadHitbox.cs.meta b/Assets/Scripts/EnemyHeadHitbox.cs.meta new file mode 100644 index 0000000..ca1bdb7 --- /dev/null +++ b/Assets/Scripts/EnemyHeadHitbox.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 23988a1164d49b7498b3b4dad22c92f4 \ No newline at end of file diff --git a/Assets/Scripts/HumanoidEnemy.cs b/Assets/Scripts/HumanoidEnemy.cs index b3db006..b10ea11 100644 --- a/Assets/Scripts/HumanoidEnemy.cs +++ b/Assets/Scripts/HumanoidEnemy.cs @@ -15,10 +15,18 @@ public class HumanoidEnemy : MonoBehaviour public float attackRange = 2f; public float gravity = -20f; - [Header("Combat")] + [Header("Melee Combat")] public float attackDamage = 10f; public float attackCooldown = 1f; + [Header("Ranged Combat")] + public bool canShoot = true; + public float shootRange = 18f; // within this range (but outside attackRange) the enemy shoots + public float shootDamage = 8f; + public float shootCooldown = 1.5f; + public float bulletSpeed = 40f; // visual tracer speed + public float shootAccuracyAngle = 5f; // random spread in degrees + [Header("Patrol (optional)")] public float patrolRadius = 8f; public float patrolWaitTime = 2f; @@ -30,6 +38,7 @@ public class HumanoidEnemy : MonoBehaviour private Vector3 spawnPoint; private Vector3 patrolTarget; private float nextAttackTime; + private float nextShootTime; private float verticalVelocity; private float patrolWaitTimer; private bool isAggro = false; @@ -49,8 +58,8 @@ public class HumanoidEnemy : MonoBehaviour controller = GetComponent(); health = GetComponent(); - // Find player - GameObject playerObj = GameObject.Find("Player"); + GameObject playerObj = GameObject.FindGameObjectWithTag("Player"); + if (playerObj == null) playerObj = GameObject.Find("Player"); if (playerObj != null) player = playerObj.transform; @@ -70,6 +79,11 @@ public class HumanoidEnemy : MonoBehaviour attackDamage = 10f; attackCooldown = 1.2f; chaseRange = 20f; + canShoot = true; + shootRange = 16f; + shootDamage = 8f; + shootCooldown = 1.4f; + shootAccuracyAngle = 6f; if (health != null) health.maxHealth = 80f; break; @@ -78,6 +92,7 @@ public class HumanoidEnemy : MonoBehaviour attackDamage = 25f; attackCooldown = 2f; chaseRange = 18f; + canShoot = false; // Brute is pure melee if (health != null) health.maxHealth = 200f; break; @@ -86,6 +101,11 @@ public class HumanoidEnemy : MonoBehaviour attackDamage = 8f; attackCooldown = 0.6f; chaseRange = 30f; + canShoot = true; + shootRange = 22f; + shootDamage = 6f; + shootCooldown = 0.8f; + shootAccuracyAngle = 10f; // less accurate but fast if (health != null) health.maxHealth = 50f; break; } @@ -101,16 +121,26 @@ public class HumanoidEnemy : MonoBehaviour float distToPlayer = Vector3.Distance(transform.position, player.position); - // Aggro check if (distToPlayer <= chaseRange) isAggro = true; if (isAggro) { if (distToPlayer <= attackRange) - Attack(); + { + // Close enough to punch + MeleeAttack(); + } + else if (canShoot && distToPlayer <= shootRange) + { + // In shoot range — strafe / stand and fire + FacePlayer(); + ShootAtPlayer(); + } else + { ChasePlayer(); + } } else { @@ -123,8 +153,16 @@ public class HumanoidEnemy : MonoBehaviour verticalVelocity += gravity * Time.deltaTime; controller.Move(Vector3.up * verticalVelocity * Time.deltaTime); - // Animate - AnimateModel(isAggro && distToPlayer > attackRange); + bool isWalking = isAggro && distToPlayer > attackRange && !(canShoot && distToPlayer <= shootRange); + AnimateModel(isWalking); + } + + void FacePlayer() + { + Vector3 direction = (player.position - transform.position); + direction.y = 0f; + if (direction != Vector3.zero) + transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(direction), Time.deltaTime * 8f); } void ChasePlayer() @@ -135,7 +173,6 @@ public class HumanoidEnemy : MonoBehaviour controller.Move(direction * moveSpeed * Time.deltaTime); - // Face the player if (direction != Vector3.zero) transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(direction), Time.deltaTime * 8f); } @@ -171,37 +208,106 @@ public class HumanoidEnemy : MonoBehaviour return spawnPoint + new Vector3(randomCircle.x, 0f, randomCircle.y); } - void Attack() + // ─── Melee ─── + + void MeleeAttack() { - // Face the player - Vector3 direction = (player.position - transform.position); - direction.y = 0f; - if (direction != Vector3.zero) - transform.rotation = Quaternion.LookRotation(direction); + FacePlayer(); if (Time.time >= nextAttackTime) { nextAttackTime = Time.time + attackCooldown; - Debug.Log($"{gameObject.name} attacks for {attackDamage} damage!"); - - // Punch animation StartCoroutine(PunchAnimation()); - // Damage the player via Player.health Player playerHealth = player.GetComponent(); if (playerHealth != null) { playerHealth.health -= attackDamage; - Debug.Log($"[Enemy] Player health: {playerHealth.health}"); + Debug.Log($"[Enemy] Melee hit! Player health: {playerHealth.health}"); } } } + // ─── Shooting ─── + + void ShootAtPlayer() + { + if (Time.time < nextShootTime) return; + nextShootTime = Time.time + shootCooldown; + + StartCoroutine(ShootAnimation()); + + // Aim at player's centre-ish (chest height) with some spread + Vector3 aimOrigin = headTransform != null + ? headTransform.position + : transform.position + Vector3.up * 1.4f; + + Vector3 aimTarget = player.position + Vector3.up * 0.9f; // aim for chest + Vector3 aimDir = (aimTarget - aimOrigin).normalized; + + // Apply accuracy spread + aimDir = Quaternion.Euler( + Random.Range(-shootAccuracyAngle, shootAccuracyAngle), + Random.Range(-shootAccuracyAngle, shootAccuracyAngle), + 0f) * aimDir; + + RaycastHit hit; + if (Physics.Raycast(aimOrigin, aimDir, out hit, shootRange * 1.5f)) + { + // Spawn a visual tracer + StartCoroutine(SpawnBulletTracer(aimOrigin, hit.point)); + + Player playerHealth = hit.transform.GetComponentInParent(); + if (playerHealth != null) + { + playerHealth.health -= shootDamage; + Debug.Log($"[Enemy] Shot hit player! Player health: {playerHealth.health}"); + } + } + else + { + // Missed — still show tracer going into the distance + StartCoroutine(SpawnBulletTracer(aimOrigin, aimOrigin + aimDir * shootRange)); + } + } + + IEnumerator SpawnBulletTracer(Vector3 from, Vector3 to) + { + GameObject tracer = GameObject.CreatePrimitive(PrimitiveType.Cube); + tracer.name = "EnemyBulletTracer"; + Destroy(tracer.GetComponent()); + + float length = Vector3.Distance(from, to); + tracer.transform.localScale = new Vector3(0.02f, 0.02f, Mathf.Min(length, 0.3f)); + + Renderer r = tracer.GetComponent(); + Material mat = new Material(Shader.Find("Standard")); + mat.color = new Color(1f, 0.9f, 0.3f); + // Make it emissive so it's visible + mat.EnableKeyword("_EMISSION"); + mat.SetColor("_EmissionColor", new Color(1f, 0.8f, 0.1f) * 2f); + r.material = mat; + + float travelTime = length / bulletSpeed; + float elapsed = 0f; + + while (elapsed < travelTime) + { + elapsed += Time.deltaTime; + float t = Mathf.Clamp01(elapsed / travelTime); + Vector3 pos = Vector3.Lerp(from, to, t); + tracer.transform.position = pos; + tracer.transform.rotation = Quaternion.LookRotation((to - from).normalized); + yield return null; + } + + Destroy(tracer); + } + // ─── Procedural Humanoid Model ─── void BuildHumanoidModel() { - // Sizing based on type float scale = 1f; Color skinColor = new Color(0.6f, 0.45f, 0.35f); Color shirtColor; @@ -211,16 +317,16 @@ public class HumanoidEnemy : MonoBehaviour { case EnemyType.Grunt: scale = 1f; - shirtColor = new Color(0.4f, 0.25f, 0.2f); // brown shirt + shirtColor = new Color(0.4f, 0.25f, 0.2f); break; case EnemyType.Brute: scale = 1.3f; - shirtColor = new Color(0.5f, 0.1f, 0.1f); // red shirt + shirtColor = new Color(0.5f, 0.1f, 0.1f); skinColor = new Color(0.5f, 0.35f, 0.3f); break; case EnemyType.Runner: scale = 0.9f; - shirtColor = new Color(0.2f, 0.35f, 0.2f); // green shirt + shirtColor = new Color(0.2f, 0.35f, 0.2f); break; default: shirtColor = Color.gray; @@ -232,13 +338,17 @@ public class HumanoidEnemy : MonoBehaviour bodyRoot.localPosition = Vector3.zero; bodyRoot.localRotation = Quaternion.identity; - // Head + // Head — kept with a collider and EnemyHeadHitbox for headshots headTransform = CreatePart("Head", bodyRoot, new Vector3(0f, 1.65f, 0f) * scale, new Vector3(0.3f, 0.3f, 0.3f) * scale, - skinColor, PrimitiveType.Sphere); + skinColor, PrimitiveType.Sphere, keepCollider: true); - // Eyes (two small dark spheres) + // Wire up head hitbox + EnemyHeadHitbox headHitbox = headTransform.gameObject.AddComponent(); + headHitbox.enemyHealth = health; + + // Eyes CreatePart("LeftEye", headTransform, new Vector3(-0.08f, 0.03f, 0.12f), new Vector3(0.08f, 0.08f, 0.05f), @@ -253,39 +363,38 @@ public class HumanoidEnemy : MonoBehaviour torsoTransform = CreatePart("Torso", bodyRoot, new Vector3(0f, 1.15f, 0f) * scale, new Vector3(0.45f, 0.6f, 0.25f) * scale, - shirtColor, PrimitiveType.Cube); + shirtColor, PrimitiveType.Cube, keepCollider: true); - // Left Arm + // Arms leftArm = CreatePart("LeftArm", bodyRoot, new Vector3(-0.35f, 1.15f, 0f) * scale, new Vector3(0.15f, 0.55f, 0.15f) * scale, - skinColor, PrimitiveType.Cube); + skinColor, PrimitiveType.Cube, keepCollider: true); - // Right Arm rightArm = CreatePart("RightArm", bodyRoot, new Vector3(0.35f, 1.15f, 0f) * scale, new Vector3(0.15f, 0.55f, 0.15f) * scale, - skinColor, PrimitiveType.Cube); + skinColor, PrimitiveType.Cube, keepCollider: true); - // Left Leg + // Legs leftLeg = CreatePart("LeftLeg", bodyRoot, new Vector3(-0.12f, 0.4f, 0f) * scale, new Vector3(0.18f, 0.6f, 0.18f) * scale, - pantsColor, PrimitiveType.Cube); + pantsColor, PrimitiveType.Cube, keepCollider: true); - // Right Leg rightLeg = CreatePart("RightLeg", bodyRoot, new Vector3(0.12f, 0.4f, 0f) * scale, new Vector3(0.18f, 0.6f, 0.18f) * scale, - pantsColor, PrimitiveType.Cube); + pantsColor, PrimitiveType.Cube, keepCollider: true); - // Adjust CharacterController to fit controller.height = 1.9f * scale; controller.radius = 0.3f * scale; controller.center = new Vector3(0f, 0.95f * scale, 0f); } - Transform CreatePart(string name, Transform parent, Vector3 localPos, Vector3 localScale, Color color, PrimitiveType shape = PrimitiveType.Cube) + /// True to leave the collider on so raycasts can hit this part. + Transform CreatePart(string name, Transform parent, Vector3 localPos, Vector3 localScale, Color color, + PrimitiveType shape = PrimitiveType.Cube, bool keepCollider = false) { GameObject part = GameObject.CreatePrimitive(shape); part.name = name; @@ -294,9 +403,11 @@ public class HumanoidEnemy : MonoBehaviour part.transform.localScale = localScale; part.transform.localRotation = Quaternion.identity; - // Remove collider from body parts (CharacterController handles collision) - Collider col = part.GetComponent(); - if (col != null) Destroy(col); + if (!keepCollider) + { + Collider col = part.GetComponent(); + if (col != null) Destroy(col); + } Renderer rend = part.GetComponent(); rend.material = new Material(Shader.Find("Standard")); @@ -317,7 +428,6 @@ public class HumanoidEnemy : MonoBehaviour float limbSwing = Mathf.Sin(animTimer) * 30f; - // Arms swing opposite to legs if (leftArm != null) leftArm.localRotation = Quaternion.Euler(limbSwing, 0f, 0f); if (rightArm != null) @@ -327,7 +437,6 @@ public class HumanoidEnemy : MonoBehaviour if (rightLeg != null) rightLeg.localRotation = Quaternion.Euler(limbSwing, 0f, 0f); - // Slight torso bob if (torsoTransform != null) { Vector3 torsoPos = torsoTransform.localPosition; @@ -338,7 +447,6 @@ public class HumanoidEnemy : MonoBehaviour } else { - // Return to idle pose animTimer = 0f; if (leftArm != null) leftArm.localRotation = Quaternion.Slerp(leftArm.localRotation, Quaternion.identity, Time.deltaTime * 5f); @@ -355,7 +463,6 @@ public class HumanoidEnemy : MonoBehaviour { if (rightArm == null) yield break; - // Wind up float timer = 0f; float punchDuration = 0.15f; @@ -367,7 +474,6 @@ public class HumanoidEnemy : MonoBehaviour yield return null; } - // Snap back timer = 0f; while (timer < punchDuration) { @@ -380,19 +486,57 @@ public class HumanoidEnemy : MonoBehaviour rightArm.localRotation = Quaternion.identity; } + IEnumerator ShootAnimation() + { + if (rightArm == null) yield break; + + float timer = 0f; + float duration = 0.1f; + + // Raise arm to aim + while (timer < duration) + { + timer += Time.deltaTime; + float t = timer / duration; + rightArm.localRotation = Quaternion.Euler(Mathf.Lerp(0f, -70f, t), 0f, 0f); + yield return null; + } + + yield return new WaitForSeconds(0.05f); + + // Recoil kick back + rightArm.localRotation = Quaternion.Euler(-60f, 0f, 0f); + yield return new WaitForSeconds(0.08f); + + // Return + timer = 0f; + while (timer < duration * 2f) + { + timer += Time.deltaTime; + float t = timer / (duration * 2f); + rightArm.localRotation = Quaternion.Euler(Mathf.Lerp(-70f, 0f, t), 0f, 0f); + yield return null; + } + + rightArm.localRotation = Quaternion.identity; + } + // ─── Gizmos ─── void OnDrawGizmosSelected() { - // Chase range Gizmos.color = new Color(1f, 0f, 0f, 0.15f); Gizmos.DrawWireSphere(transform.position, chaseRange); - // Attack range Gizmos.color = new Color(1f, 0.5f, 0f, 0.3f); Gizmos.DrawWireSphere(transform.position, attackRange); - // Patrol radius + if (canShoot) + { + Gizmos.color = new Color(1f, 1f, 0f, 0.2f); + Gizmos.DrawWireSphere(Application.isPlaying ? transform.position : transform.position, shootRange); + } + Gizmos.color = new Color(0f, 0.5f, 1f, 0.15f); Gizmos.DrawWireSphere(Application.isPlaying ? spawnPoint : transform.position, patrolRadius); } diff --git a/Assets/Scripts/SimpleGun.cs b/Assets/Scripts/SimpleGun.cs index ad100e9..ed2c2ab 100644 --- a/Assets/Scripts/SimpleGun.cs +++ b/Assets/Scripts/SimpleGun.cs @@ -209,9 +209,20 @@ public class SimpleGun : MonoBehaviour Debug.Log($"Hit: {hit.transform.name}"); SpawnImpactEffect(hit.point, hit.normal); - EnemyHealth enemy = hit.transform.GetComponent(); - if (enemy != null) - enemy.TakeDamage(damage); + // Head hitbox = one shot kill + EnemyHeadHitbox head = hit.transform.GetComponent(); + if (head != null && head.enemyHealth != null) + { + Debug.Log("HEADSHOT! Instakill."); + head.enemyHealth.TakeDamage(head.enemyHealth.maxHealth * 10f); + } + else + { + // Body shot — check the hit object AND walk up to parent for EnemyHealth + EnemyHealth enemy = hit.transform.GetComponentInParent(); + if (enemy != null) + enemy.TakeDamage(damage); + } } } diff --git a/Assets/Scripts/WeaponBob.cs b/Assets/Scripts/WeaponBob.cs index e6e2b92..cbcfe7f 100644 --- a/Assets/Scripts/WeaponBob.cs +++ b/Assets/Scripts/WeaponBob.cs @@ -37,13 +37,15 @@ public class WeaponBob : MonoBehaviour bobTimer += Time.deltaTime * bobFrequency * multiplier; float bobX = Mathf.Sin(bobTimer) * bobHorizontalAmplitude * multiplier; - float bobY = Mathf.Sin(bobTimer * 2f) * bobVerticalAmplitude * multiplier; + // Abs(Sin) means the weapon bounces UP on every step (left and right) + // instead of looping in a circle/figure-8 + float bobY = Mathf.Abs(Mathf.Sin(bobTimer)) * bobVerticalAmplitude * multiplier; transform.localPosition = originalLocalPos + new Vector3(bobX, bobY, 0f); } else { - bobTimer = 0f; + // Don't reset bobTimer so the phase doesn't snap when you start moving again transform.localPosition = Vector3.Lerp( transform.localPosition, originalLocalPos,