ft: nucleotide prey FSM

This commit is contained in:
Djairo Hougee 2026-01-26 23:54:15 +01:00
parent c11afd9ddd
commit adf1438bc8
18 changed files with 121 additions and 45 deletions

View File

@ -63,7 +63,7 @@ func _on_attack_hit(body: Node2D) -> void:
var hit_hittable = false var hit_hittable = false
if body.is_in_group("prey") or body.is_in_group("predators"): if body.is_in_group("prey") or body.is_in_group("predators"):
if body.has_method("handle_damage"): if body.has_method("handle_damage"):
body.handle_damage(damage) body.handle_damage(damage, self)
hit_hittable = true hit_hittable = true
elif body.is_in_group("resources"): elif body.is_in_group("resources"):
pass pass

View File

@ -5,7 +5,7 @@
[ext_resource type="Texture2D" uid="uid://c3cuhrmulyy1s" path="res://molecular/assets/background/bg-near.png" id="4_b1jr0"] [ext_resource type="Texture2D" uid="uid://c3cuhrmulyy1s" path="res://molecular/assets/background/bg-near.png" id="4_b1jr0"]
[ext_resource type="Script" uid="uid://ceut2lrvkns75" path="res://debug_label.gd" id="4_mys4o"] [ext_resource type="Script" uid="uid://ceut2lrvkns75" path="res://debug_label.gd" id="4_mys4o"]
[ext_resource type="Script" uid="uid://umx4w11edif" path="res://molecular/prey_manager.gd" id="5_cthuy"] [ext_resource type="Script" uid="uid://umx4w11edif" path="res://molecular/prey_manager.gd" id="5_cthuy"]
[ext_resource type="PackedScene" uid="uid://c3iw2v3x6ngrb" path="res://molecular/nucleotide_prey.tscn" id="6_a5cls"] [ext_resource type="PackedScene" uid="uid://c3iw2v3x6ngrb" path="res://molecular/prey/nucleotide_prey.tscn" id="6_a5cls"]
[sub_resource type="TileMapPattern" id="TileMapPattern_a5cls"] [sub_resource type="TileMapPattern" id="TileMapPattern_a5cls"]
tile_data = PackedInt32Array(0, 65536, 2, 65536, 65536, 3, 1, 131072, 2, 65537, 131072, 3) tile_data = PackedInt32Array(0, 65536, 2, 65536, 65536, 3, 1, 131072, 2, 65537, 131072, 3)
@ -108,9 +108,8 @@ text = "Debug: You made it into the game!
This is running from C++: " This is running from C++: "
script = ExtResource("4_mys4o") script = ExtResource("4_mys4o")
[node name="PreyManager" type="Node" parent="." node_paths=PackedStringArray("cam")] [node name="PreyManager" type="Node" parent="."]
script = ExtResource("5_cthuy") script = ExtResource("5_cthuy")
cam = NodePath("../player/Camera2D")
scene = ExtResource("6_a5cls") scene = ExtResource("6_a5cls")
minCount = 100 minCount = 100
maxCount = 300 maxCount = 300

View File

@ -1,17 +0,0 @@
extends NucleotidePreyState
@onready var timer = $Timer
var dir: float = 0.0
func enter(previous_state_path: String, data := {}) -> void:
timer.start(randi() % 5)
dir = randi() % 360
func physics_update(_delta: float) -> void:
# TODO: move in direction of dir
pass
func _on_timer_timeout() -> void:
dir = randi() % 360
timer.start(randi() % 5)

View File

@ -1,6 +0,0 @@
class_name NucleotidePreyState extends State
enum State {IDLE, FORAGING, FEEDING, FLEEING}
func _ready() -> void:
await owner.ready

View File

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

View File

@ -1,6 +1,7 @@
extends AbstractPrey2D extends AbstractPrey2D
@onready var sprite = get_node("AnimatedSprite2D") @onready var sprite = get_node("AnimatedSprite2D")
@onready var fsm = $StateMachine
# Mirroed sprites for periodic boundary # Mirroed sprites for periodic boundary
var mirrorSprite1: Node2D var mirrorSprite1: Node2D
@ -29,6 +30,7 @@ func _process(delta: float) -> void:
func _physics_process(delta: float) -> void: func _physics_process(delta: float) -> void:
#self.move(Vector3(randfn(0, 1), randfn(0, 1), 0)) #self.move(Vector3(randfn(0, 1), randfn(0, 1), 0))
# TODO: state transition logic (bot controller code)
pass pass
func move(motion: Vector3) -> void: func move(motion: Vector3) -> void:
@ -37,12 +39,13 @@ func move(motion: Vector3) -> void:
# Apply boundary to new position # Apply boundary to new position
position = GameManager.get_boundaried_position(position) position = GameManager.get_boundaried_position(position)
func handle_damage(dmg: int) -> void: func handle_damage(dmg: int, src: Node) -> void:
health = max(0, health-dmg) health = max(0, health-dmg)
if health == 0: if health == 0:
die() die()
if health < maxHealth: if health < maxHealth:
become_injured() become_injured()
fsm.transition_to_next_state(fsm.States.FLEEING, {"threat": src})
func die() -> void: func die() -> void:
sprite.play("Dying") sprite.play("Dying")
@ -143,3 +146,7 @@ func _handle_wrapping():
mirrorSprite3.position = Vector2(0, - GameManager.screen_size.y) mirrorSprite3.position = Vector2(0, - GameManager.screen_size.y)
func _on_timer_timeout() -> void:
pass # Replace with function body.

View File

@ -1,15 +1,17 @@
[gd_scene load_steps=12 format=3 uid="uid://c3iw2v3x6ngrb"] [gd_scene load_steps=14 format=3 uid="uid://c3iw2v3x6ngrb"]
[ext_resource type="PackedScene" uid="uid://bvsdg1v3ksixy" path="res://shared/npc/prey2D.tscn" id="1_qvulj"] [ext_resource type="PackedScene" uid="uid://bvsdg1v3ksixy" path="res://shared/npc/prey2D.tscn" id="1_qvulj"]
[ext_resource type="Script" uid="uid://bgossk6xo31gi" path="res://molecular/nucleotide_prey.gd" id="2_0227s"] [ext_resource type="Script" uid="uid://bgossk6xo31gi" path="res://molecular/prey/nucleotide_prey.gd" id="2_0227s"]
[ext_resource type="Texture2D" uid="uid://bhcb5g7g7um8" path="res://molecular/assets/prey/prey-dying-frame0.png" id="2_lkj7f"] [ext_resource type="Texture2D" uid="uid://bhcb5g7g7um8" path="res://molecular/assets/prey/prey-dying-frame0.png" id="2_lkj7f"]
[ext_resource type="Texture2D" uid="uid://bxn11avw7dykl" path="res://molecular/assets/prey/prey-dying-frame1.png" id="3_svqyr"] [ext_resource type="Texture2D" uid="uid://bxn11avw7dykl" path="res://molecular/assets/prey/prey-dying-frame1.png" id="3_svqyr"]
[ext_resource type="Texture2D" uid="uid://ctkehsavw6ghx" path="res://molecular/assets/prey/prey-healthy-frame0.png" id="4_ee1gb"] [ext_resource type="Texture2D" uid="uid://ctkehsavw6ghx" path="res://molecular/assets/prey/prey-healthy-frame0.png" id="4_ee1gb"]
[ext_resource type="Texture2D" uid="uid://uy28y3mkk6nt" path="res://molecular/assets/prey/prey-healthy-frame1.png" id="5_ae5nf"] [ext_resource type="Texture2D" uid="uid://uy28y3mkk6nt" path="res://molecular/assets/prey/prey-healthy-frame1.png" id="5_ae5nf"]
[ext_resource type="Texture2D" uid="uid://btnyajci8ptb2" path="res://molecular/assets/prey/prey-injured-frame0.png" id="6_0f87h"] [ext_resource type="Texture2D" uid="uid://btnyajci8ptb2" path="res://molecular/assets/prey/prey-injured-frame0.png" id="6_0f87h"]
[ext_resource type="Texture2D" uid="uid://bqll8ge4cr2uf" path="res://molecular/assets/prey/prey-injured-frame1.png" id="7_w7inl"] [ext_resource type="Texture2D" uid="uid://bqll8ge4cr2uf" path="res://molecular/assets/prey/prey-injured-frame1.png" id="7_w7inl"]
[ext_resource type="Script" uid="uid://c7o7sp02u0wkv" path="res://molecular/nucleotide_prey_state.gd" id="9_guu3v"] [ext_resource type="Script" uid="uid://0vwv2nt16gpv" path="res://molecular/prey/nucleotide_prey_state_machine.gd" id="9_xxtgy"]
[ext_resource type="Script" uid="uid://ubcu8fdfxxj1" path="res://molecular/nucleotide_prey_foraging.gd" id="10_rgguv"] [ext_resource type="Script" uid="uid://ubcu8fdfxxj1" path="res://molecular/prey/nucleotide_prey_random_movement.gd" id="10_rgguv"]
[ext_resource type="Script" uid="uid://xbiqj7ubmj7d" path="res://molecular/prey/nucleotide_prey_idle.gd" id="12_ubfhk"]
[ext_resource type="Script" uid="uid://dlw7inlh6asvu" path="res://molecular/prey/nucleotide_prey_fleeing.gd" id="12_xxtgy"]
[sub_resource type="SpriteFrames" id="SpriteFrames_66x8p"] [sub_resource type="SpriteFrames" id="SpriteFrames_66x8p"]
animations = [{ animations = [{
@ -58,15 +60,26 @@ scale = Vector2(0.1, 0.1)
sprite_frames = SubResource("SpriteFrames_66x8p") sprite_frames = SubResource("SpriteFrames_66x8p")
animation = &"Injured" animation = &"Injured"
[node name="State" type="Node" parent="." index="2"] [node name="StateMachine" type="Node" parent="." index="2" node_paths=PackedStringArray("initial_state")]
script = ExtResource("9_guu3v") script = ExtResource("9_xxtgy")
metadata/_custom_type_script = "uid://co2xp7gauamql" initial_state = NodePath("Idle")
metadata/_custom_type_script = "uid://ck7k8ht54snsy"
[node name="Foraging" type="Node" parent="State" index="0"] [node name="RandomMovement" type="Node" parent="StateMachine" index="0"]
script = ExtResource("10_rgguv") script = ExtResource("10_rgguv")
metadata/_custom_type_script = "uid://c7o7sp02u0wkv"
[node name="Timer" type="Timer" parent="State/Foraging" index="0"] [node name="Timer" type="Timer" parent="StateMachine/RandomMovement" index="0"]
one_shot = true one_shot = true
[connection signal="timeout" from="State/Foraging/Timer" to="State/Foraging" method="_on_timer_timeout"] [node name="Fleeing" type="Node" parent="StateMachine" index="1"]
script = ExtResource("12_xxtgy")
[node name="Idle" type="Node" parent="StateMachine" index="2"]
script = ExtResource("12_ubfhk")
metadata/_custom_type_script = "uid://co2xp7gauamql"
[node name="Timer" type="Timer" parent="StateMachine/Idle" index="0"]
one_shot = true
[connection signal="timeout" from="StateMachine/RandomMovement/Timer" to="StateMachine/RandomMovement" method="_on_timer_timeout"]
[connection signal="timeout" from="StateMachine/Idle/Timer" to="StateMachine/Idle" method="_on_timer_timeout"]

View File

@ -0,0 +1,22 @@
extends State
var threat: Node2D
var threshold: float = 100
func enter(previous_state_path: String, data := {}) -> void:
if data.has("threat"):
threat = data["threat"]
else:
# default behaviour; do nothing
threat = owner
func physics_update(_delta: float) -> void:
if owner.position.distance_to(threat.position) > threshold:
finished.emit(owner.fsm.States.IDLE, {})
return
owner.move(flee_from(threat.position))
func flee_from(pos: Vector2) -> Vector3:
var diff = threat.position - owner.position
diff = diff.normalized() * -1
return Vector3(diff.x, diff.y ,0)

View File

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

View File

@ -0,0 +1,19 @@
extends State
@onready var timer = $Timer
func enter(previous_state_path: String, data := {}) -> void:
timer.start((float)(randi() % 5)/5)
func physics_update(_delta: float) -> void:
owner.move(Vector3(randfn(0, 1), randfn(0, 1), 0))
func _on_timer_timeout() -> void:
if (randi() % 4 != 0):
finished.emit(owner.fsm.States.RANDOMMOVEMENT, {})
else:
finished.emit(owner.fsm.States.IDLE, {})
func exit() -> void:
timer.stop()

View File

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

View File

@ -0,0 +1,20 @@
extends State
@onready var timer = $Timer
var dir: Vector3 = Vector3(0,0,0);
func enter(previous_state_path: String, data := {}) -> void:
timer.start((float)(randi() % 10)/20)
dir = calc_dir(randi() % 360)
func physics_update(_delta: float) -> void:
owner.move(dir)
func calc_dir(angle: float) -> Vector3:
return Vector3(cos(angle), sin(angle), 0)
func _on_timer_timeout() -> void:
finished.emit(owner.fsm.States.IDLE, {})
func exit() -> void:
timer.stop()

View File

@ -0,0 +1,17 @@
extends StateMachine
enum States {IDLE, RANDOMMOVEMENT, FEEDING, FLEEING}
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
super()
await owner.ready
func transition_to_next_state(target: int, data: Dictionary = {}) -> void:
match target:
States.IDLE: _transition_to_next_state("Idle", data)
States.RANDOMMOVEMENT: _transition_to_next_state("RandomMovement", data)
States.FEEDING: _transition_to_next_state("Feeding", data)
States.FLEEING: _transition_to_next_state("Fleeing", data)
_: push_error("Trying to transition to unknown state {target}")

View File

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

View File

@ -1,9 +1,6 @@
extends NPC2D extends NPC2D
class_name AbstractPrey2D class_name AbstractPrey2D
enum States {IDLE, FORAGING, FLEEING}
var state = States.IDLE
# Called when the node enters the scene tree for the first time. # Called when the node enters the scene tree for the first time.
func _ready() -> void: func _ready() -> void:

View File

@ -10,7 +10,7 @@ class_name StateMachine extends Node
# Connect states on ready, then wait. # Connect states on ready, then wait.
func _ready() -> void: func _ready() -> void:
for node: State in find_children("*", "State"): for node: State in find_children("*", "State"):
node.finished.connect(_transition_to_next_state) node.finished.connect(transition_to_next_state)
await owner.ready await owner.ready
state.enter("") state.enter("")
@ -37,3 +37,6 @@ func _transition_to_next_state(target_path: String, data: Dictionary = {}) -> vo
state.exit() state.exit()
state = get_node(target_path) state = get_node(target_path)
state.enter(previous_state_path, data) state.enter(previous_state_path, data)
func transition_to_next_state(target: int, data: Dictionary = {}) -> void:
push_error("Child FSM failed to implement transition function.")