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,