added enemy state machine with idle, stunned, and dead states

This commit is contained in:
derek
2025-04-21 16:41:16 -05:00
parent 423feb8fc3
commit ca81947325
20 changed files with 1289 additions and 539 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -12,7 +12,7 @@ config_version=5
config/name="First Person Test" config/name="First Person Test"
config/tags=PackedStringArray("fps") config/tags=PackedStringArray("fps")
run/main_scene="uid://f7e0v1r6ra6c" run/main_scene="uid://bk4pn4k7n51ux"
config/features=PackedStringArray("4.4", "Forward Plus") config/features=PackedStringArray("4.4", "Forward Plus")
config/icon="uid://6svuq1l83al5" config/icon="uid://6svuq1l83al5"

View File

@@ -28,7 +28,7 @@ script = ExtResource("1_8cuhv")
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -31.749, 0, 44.3496) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -31.749, 0, 44.3496)
script = ExtResource("5_ngmqi") script = ExtResource("5_ngmqi")
room_lockdown = true room_lockdown = true
number_of_enemies = 25 number_of_enemies = 100
number_of_drops = 30 number_of_drops = 30
[node name="EnemySpawner" parent="Level" instance=ExtResource("3_q77vb")] [node name="EnemySpawner" parent="Level" instance=ExtResource("3_q77vb")]

50
scripts/EnemyIdle.gd Normal file
View File

@@ -0,0 +1,50 @@
extends EnemyState
class_name EnemyIdle
var move_direction : Vector3
var scan_direction : float
var wander_time : float
var scan_time : float
const WANDER_AMT = 50
const TURRET_TURN_AMT : float = 180.0
func randomize_wander():
var x = randf_range(-WANDER_AMT,WANDER_AMT)
var z = randf_range(-WANDER_AMT,WANDER_AMT)
move_direction = enemy.global_position + Vector3(x,0,z)
enemy.nav_agent.set_target_position(move_direction)
wander_time = randf_range(1,3)
func randomize_turret_scan():
scan_direction = randf_range(-TURRET_TURN_AMT,TURRET_TURN_AMT)
scan_time = randf_range(1,3)
func _Enter():
randomize_wander()
func Update(delta: float):
if wander_time > 0:
wander_time -= delta
else:
randomize_wander()
if scan_time > 0:
scan_time -= delta
else:
randomize_turret_scan()
func Physics_Update(delta : float):
if enemy:
var destination = enemy.nav_agent.get_next_path_position()
var local_destination = destination - enemy.global_position
var direction = local_destination.normalized()
if enemy.global_position.distance_to(local_destination) > 1:
enemy.velocity = direction * move_speed
enemy.spider_look_next.look_at(destination)
var look_target = enemy.spider_look_next.global_rotation.y
enemy.global_rotation.y = lerp(enemy.global_rotation.y,look_target,delta * 3)
enemy.turret.rotation.y = lerp(enemy.turret.rotation.y,deg_to_rad(scan_direction),delta)

1
scripts/EnemyIdle.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://eiw16yqbsj2s

24
scripts/EnemyStates.gd Normal file
View File

@@ -0,0 +1,24 @@
extends State
class_name EnemyState
@export var enemy : CharacterBody3D
@export var enemy_targets : Array[Area3D]
@export var move_speed : float = 3
func Enter():
if enemy_targets != null:
for target in enemy_targets:
target.body_part_hit.connect(take_damage)
func take_damage(dam,bullet_damage):
SignalBus.emit_signal("enemy_hit")
enemy.health -= dam * bullet_damage
enemy.health_bar_sprite.visible = true
enemy.health_bar_sprite.health_update()
var number_spawn = enemy.damage_number.instantiate()
number_spawn.damage_amt = bullet_damage * dam
number_spawn.position = enemy.global_position + Vector3(0,2,0)
get_tree().get_root().add_child(number_spawn)
if enemy.health <= 0:
Transitioned.emit(self,"die")

View File

@@ -0,0 +1 @@
uid://dhxolagi0b5s1

View File

@@ -2,28 +2,10 @@ extends Area3D
@export var damage : float = 1 @export var damage : float = 1
var damage_number = preload("res://assets/damage_number.tscn")
signal body_part_hit(dam,bullet_damage) signal body_part_hit(dam,bullet_damage)
# Called when the node enters the scene tree for the first time.
func _ready():
pass # Replace with function body.
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
pass
func hit(bullet_damage): func hit(bullet_damage):
emit_signal("body_part_hit", damage, bullet_damage) emit_signal("body_part_hit", damage, bullet_damage)
SignalBus.emit_signal("enemy_hit")
var number_spawn = damage_number.instantiate()
number_spawn.damage_amt = bullet_damage * damage
number_spawn.position = global_position + Vector3(0,2,0)
get_tree().get_root().add_child(number_spawn)
func _on_body_entered(body: Node3D) -> void: func _on_body_entered(body: Node3D) -> void:
hit(body.bullet_damage) hit(body.bullet_damage)
SignalBus.emit_signal("enemy_hit")

16
scripts/State.gd Normal file
View File

@@ -0,0 +1,16 @@
extends Node
class_name State
signal Transitioned
func Enter():
pass
func Exit():
pass
func Update(_delta : float):
pass
func Physics_Update(_delta : float):
pass

1
scripts/State.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://bftaqmh54r3ib

39
scripts/StateMachine.gd Normal file
View File

@@ -0,0 +1,39 @@
extends Node
@export var initial_state : State
var current_state : State
var states : Dictionary = {}
func _ready() -> void:
for child in get_children():
if child is State:
states[child.name.to_lower()] = child
child.Transitioned.connect(on_child_transition)
if initial_state:
initial_state.Enter()
current_state = initial_state
func _process(delta: float) -> void:
if current_state:
current_state.Update(delta)
func _physics_process(delta: float) -> void:
if current_state:
current_state.Physics_Update(delta)
func on_child_transition(state,new_state_name):
if state != current_state:
return
var new_state = states.get(new_state_name.to_lower())
if !new_state:
return
if current_state:
current_state.Exit()
new_state.Enter()
current_state = new_state

View File

@@ -0,0 +1 @@
uid://csju024nerln6

60
scripts/enemy_die.gd Normal file
View File

@@ -0,0 +1,60 @@
extends EnemyState
const MAX_LV = 10
const MAX_AV = 10
func Enter():
drop_loot()
func die():
#remove from parent array
var particlespawn = enemy.die_particles.instantiate()
particlespawn.position = enemy.global_position
particlespawn.transform.basis = enemy.global_transform.basis
for particle in particlespawn.get_children():
if particle is RigidBody3D:
particle.linear_velocity += random_av_lv()["linear_velocity"]
particle.angular_velocity += random_av_lv()["angular_velocity"]
get_tree().get_root().add_child(particlespawn)
if GameGlobals.last_hit_path == str(get_path()):
GameGlobals.last_hit_path = null
SignalBus.emit_signal("enemy_killed")
enemy.queue_free()
func drop_loot():
var number_of_drops = enemy.loot_amount
#pickup drop
while number_of_drops > 0:
var pickup_spawn = enemy.level_control.ITEM_PICKUP.instantiate()
var item_stats = enemy.level_control.pickup_spawn(false)
##SET VARIABLES
pickup_spawn.pickup_type = item_stats["pickup_type"]
pickup_spawn.ammo_type = item_stats["ammo_type"]
pickup_spawn.value = item_stats["value"]
# Random Item Drop
pickup_spawn.position = enemy.global_position + Vector3(0,2,0) #added height to spawn location since origin is on the ground
pickup_spawn.transform.basis = enemy.global_transform.basis
pickup_spawn.linear_velocity += enemy.global_transform.basis * random_av_lv()["linear_velocity"]
pickup_spawn.angular_velocity += enemy.global_transform.basis * random_av_lv()["angular_velocity"]
await get_tree().create_timer(.05).timeout
get_tree().get_root().add_child(pickup_spawn)
number_of_drops -= 1
await number_of_drops <= 0
die()
func random_av_lv():
var lv_x = randf_range(-MAX_LV,MAX_LV)
var lv_y = randf_range(-MAX_LV,MAX_LV)
var lv_z = randf_range(-MAX_LV,MAX_LV)
var av_x = randf_range(-MAX_AV,MAX_AV)
var av_y = randf_range(-MAX_AV,MAX_AV)
var av_z = randf_range(-MAX_AV,MAX_AV)
return {"linear_velocity":Vector3(lv_x,lv_y,lv_z),"angular_velocity":Vector3(av_x,av_y,av_z)}

1
scripts/enemy_die.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://bgxdpmc0ra6ic

12
scripts/enemy_stunned.gd Normal file
View File

@@ -0,0 +1,12 @@
extends EnemyState
class_name EnemyStunned
@export var stunned_stars : Node
func _Enter():
if stunned_stars:
stunned_stars.visible = true
func _Exit():
if stunned_stars:
stunned_stars.visible = false

View File

@@ -0,0 +1 @@
uid://cldu3lpegcuie

View File

@@ -270,7 +270,7 @@ func _physics_process(delta):
crouch_audio.play() crouch_audio.play()
velocity += direction * 20 velocity += direction * 20
else: else:
velocity.y -= 15 velocity.y -= 25
#walking #walking
if is_on_floor() and !is_climbing: if is_on_floor() and !is_climbing:

View File

@@ -26,39 +26,7 @@ func _ready():
# Called every frame. 'delta' is the elapsed time since the previous frame. # Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta): func _process(delta):
if GameGlobals.game_loaded: pass
if enemies.size() > 0:
#calculate move position for each child
for i in enemies:
if i.player_in_view == true:
#by number of minions determine the amount they should rotate around the player to be evenly distributed
number_enemies = enemies.size()
#return current array index
var enemy_rot_amount = enemies.find(i) * deg_to_rad(360 / number_enemies)
var player_pos = i.player.global_transform.origin
var enemy_pos = player_pos + Vector3(4,0,0)
var nav_pos : Vector3
nav_pos.x = player_pos.x + (enemy_pos.x-player_pos.x)*cos(enemy_rot_amount) - (enemy_pos.z-player_pos.z)*sin(enemy_rot_amount)
nav_pos.z = player_pos.z + (enemy_pos.x-player_pos.x)*sin(enemy_rot_amount) - (enemy_pos.z-player_pos.z)*cos(enemy_rot_amount)
i.nav_agent.set_target_position(nav_pos)
var next_nav_point = i.nav_agent.get_next_path_position()
i.hive_velocity = (next_nav_point - i.global_transform.origin).normalized() * i.SPEED
elif i.player_in_view != true and i.player_last_seen != null:
i.nav_agent.set_target_position(i.player_last_seen)
var next_nav_point = i.nav_agent.get_next_path_position()
i.hive_velocity = (next_nav_point - i.global_transform.origin).normalized() * i.SPEED
# loot on last enemy
if enemies.size() == 1:
for i in enemies:
i.loot_amount = number_of_drops #assign loot to the last enemy drop from this section
i.last_enemy = true
else:
enemy_in_room_killed()
func assign_elements(): func assign_elements():
for i in self.get_children(): for i in self.get_children():

View File

@@ -5,12 +5,10 @@ signal last_enemy_dead()
var player var player
var last_enemy : bool = false var last_enemy : bool = false
@export var start_health = 3 @export var start_health = 3
@export var SPEED = 3.0 @export var SPEED = 3.0
@export var loot_amount = 2 @export var loot_amount = 2
const MAX_LV = 10 @export var nav_agent : NavigationAgent3D
const MAX_AV = 10
@export_enum("Enemy", "Trap") var enemy_type: int @export_enum("Enemy", "Trap") var enemy_type: int
@export var bullet : Resource @export var bullet : Resource
@@ -20,17 +18,13 @@ const MAX_AV = 10
@export var random_spread_amt = .01 @export var random_spread_amt = .01
@export var bullet_damage = 1 @export var bullet_damage = 1
@export var turret_look_speed = 6 @export var turret_look_speed = 6
@export var stamina : Resource
@export var ammo : Resource
@export var money : Resource
@export var health_pickup : Resource
@export var die_particles : Resource @export var die_particles : Resource
@export var damage_number : Resource @export var damage_number : Resource
@export_group("Taunts") @export_group("Taunts")
@export var taunts : Array[String] = [] @export var taunts : Array[String] = []
@onready var level_control = get_tree().current_scene @onready var level_control = get_tree().current_scene
@onready var nav_agent = $NavigationAgent3D
@onready var movement_shape = $MovementShape @onready var movement_shape = $MovementShape
@onready var barrel_1 = $TurretLook/Turret/Barrel1 @onready var barrel_1 = $TurretLook/Turret/Barrel1
@onready var barrel_2 = $TurretLook/Turret/Barrel2 @onready var barrel_2 = $TurretLook/Turret/Barrel2
@@ -90,52 +84,7 @@ func _ready():
func _process(delta): func _process(delta):
# Navigation
if !knocked:
if hive_velocity:
velocity = hive_velocity
if !stunned:
stunned_stars.visible = false
#Sightline
if turret_look_next.get_collider() != null:
if turret_look_next.get_collider().is_in_group("player"):
player_in_view = true
player_last_seen = turret_look_next.get_collider().global_position
if player != null:
spider_look_next.look_at(Vector3(player.global_position.x, 0, player.global_position.z), Vector3.UP)
body.rotation.y = lerp(body.rotation.y, spider_look_next.rotation.y, delta * 1)
turret_look_next.look_at(player.global_position,Vector3.UP)
turret_look.rotation = lerp(turret_look.rotation,turret_look_next.rotation,delta * turret_look_speed)
else:
body.rotation.y = lerp(body.rotation.y, body.rotation.y + 10, delta * .1)
turret_look.rotation.y = lerp(turret_look.rotation.y,turret_look.rotation.y - 10,delta * .5)
stunned_stars.visible = true
#apply gravity
if !is_on_floor():
velocity.y -= gravity * delta
move_and_slide() move_and_slide()
if dying:
turret.global_rotation.y += 200 * delta
#fire(barrel_1)
#fire(barrel_2)
if can_die:
die()
func _on_area_3d_body_part_hit(dam,bullet_damage):
health_bar_sprite.visible = true
health_bar_sprite.health_update()
if !dying:
health -= dam * bullet_damage
if health <= 0:
dying_throws()
func _on_prefire_timer_timeout(): func _on_prefire_timer_timeout():
fire(barrel_1) fire(barrel_1)
@@ -187,58 +136,6 @@ func spawn_casing():
instance_casing.player_velocity = velocity * transform.basis instance_casing.player_velocity = velocity * transform.basis
get_tree().get_root().add_child(instance_casing) get_tree().get_root().add_child(instance_casing)
func dying_throws():
dying = true
drop_loot(loot_amount)
func die():
#remove from parent array
get_parent().enemies.erase(self)
level_control.enemy_count()
particlespawn = die_particles.instantiate()
particlespawn.position = self.global_position
particlespawn.transform.basis = self.global_transform.basis
get_tree().get_root().add_child(particlespawn)
drop_loot(loot_amount)
if GameGlobals.last_hit_path == str(get_path()):
GameGlobals.last_hit_path = null
SignalBus.emit_signal("enemy_killed")
queue_free()
func drop_loot(number_of_drops):
#pickup drop
while number_of_drops > 0:
var pickup_spawn = level_control.ITEM_PICKUP.instantiate()
var item_stats = level_control.pickup_spawn(false)
##SET VARIABLES
pickup_spawn.pickup_type = item_stats["pickup_type"]
pickup_spawn.ammo_type = item_stats["ammo_type"]
pickup_spawn.value = item_stats["value"]
var lv_x = randf_range(-MAX_LV,MAX_LV)
var lv_y = randf_range(0,MAX_LV)
var lv_z = randf_range(-MAX_LV,MAX_LV)
var av_x = randf_range(-MAX_AV,MAX_AV)
var av_y = randf_range(-MAX_AV,MAX_AV)
var av_z = randf_range(-MAX_AV,MAX_AV)
# Random Item Drop
pickup_spawn.position = self.global_position + Vector3(0,2,0) #added height to spawn location since origin is on the ground
pickup_spawn.transform.basis = self.global_transform.basis
pickup_spawn.linear_velocity += self.global_transform.basis * Vector3(lv_x,lv_y,lv_z)
pickup_spawn.angular_velocity += self.global_transform.basis * Vector3(av_x,av_y,av_z)
await get_tree().create_timer(.05).timeout
get_tree().get_root().add_child(pickup_spawn)
number_of_drops -= 1
if number_of_drops <= 0:
can_die = true
func save(): func save():
var save_dict = { var save_dict = {