From 6853154a546a732ab9d20c85430fc21159a82040 Mon Sep 17 00:00:00 2001 From: Lorenzo Rossi Date: Tue, 16 Apr 2024 01:00:49 +0200 Subject: [PATCH] Add state_holder --- behaviours/draggable.gd | 29 +++++++++----- behaviours/eat_target.gd | 9 ++++- behaviours/move_towards_mouse.gd | 35 ++++++++++------ behaviours/random_walk.gd | 56 ++++++++++++++++++++++++++ behaviours/random_walk.tscn | 11 ++++++ behaviours/skitter_from_mouse.gd | 28 +++++++------ behaviours/state_holder.gd | 68 ++++++++++++++++++++++++++++++++ behaviours/state_holder.tscn | 6 +++ entities/imp.gd | 11 +----- entities/imp.tscn | 15 ++++--- entities/sheep.gd | 10 +---- entities/sheep.tscn | 23 +++++++++-- 12 files changed, 237 insertions(+), 64 deletions(-) create mode 100644 behaviours/random_walk.gd create mode 100644 behaviours/random_walk.tscn create mode 100644 behaviours/state_holder.gd create mode 100644 behaviours/state_holder.tscn diff --git a/behaviours/draggable.gd b/behaviours/draggable.gd index c02fd6b..b9a9ab4 100644 --- a/behaviours/draggable.gd +++ b/behaviours/draggable.gd @@ -6,29 +6,38 @@ signal move(movement: Vector2) signal dragged signal dropped +@export var state_holder: StateHolder @onready var mover: Node2D = $"MatchMousePosition" -var being_dragged: bool: +var is_being_dragged: bool: get: - return being_dragged - set(value): - being_dragged = value - mover.set_process(being_dragged) - mover.set_physics_process(being_dragged) + return state_holder.state == 'dragged' func drag(): - being_dragged = true + if not state_holder.propose_state('dragged', 100, _on_dropped): + return false + + _update_mover_process() dragged.emit() func drop(): - being_dragged = false + state_holder.remove_state('dragged') + +func _on_dropped(): dropped.emit() +func _update_mover_process(): + var value = is_being_dragged + mover.set_process(value) + mover.set_physics_process(value) func _ready(): - being_dragged = false + if state_holder == null: + state_holder = StateHolder.get_default(self) + _update_mover_process() func _on_move(movement: Vector2): - move.emit(movement) + if is_being_dragged: + move.emit(movement) diff --git a/behaviours/eat_target.gd b/behaviours/eat_target.gd index 3ffd6d2..5a0995b 100644 --- a/behaviours/eat_target.gd +++ b/behaviours/eat_target.gd @@ -16,19 +16,24 @@ signal move(movement: Vector2) eater.tag = value +@export var state_holder: StateHolder + @onready var hunt_target: HuntTarget = $"HuntTarget" @onready var eater: Eater = $"Eater" @onready var move_towards: MoveTowardsTarget = $"MoveTowardsTarget" func _ready(): + if state_holder == null: + state_holder = StateHolder.get_default(self) hunt_target.tag = tag eater.tag = tag func _on_target_selected(body: Node2D) -> void: - move_towards.target = body + if state_holder.propose_state('hunt', 60, func(): move_towards.target = null): + move_towards.target = body func _on_target_abandoned(_body: Node2D) -> void: - move_towards.target = null + state_holder.remove_state('hunt') func _on_eater_eaten(edible): eaten.emit(edible) diff --git a/behaviours/move_towards_mouse.gd b/behaviours/move_towards_mouse.gd index 4bbcee7..f858a55 100644 --- a/behaviours/move_towards_mouse.gd +++ b/behaviours/move_towards_mouse.gd @@ -11,10 +11,13 @@ signal captured @export var speed: float = 100.0 @export var can_detach: bool = false +@export var state_holder: StateHolder +@export var state_name: StringName = '' -enum State { DETACHED, CAPTURED } -var state: State = State.DETACHED +var is_captured: bool : + get: + return state_holder.state == state_name func get_followed_global_position(): return game.camera.get_global_mouse_position() @@ -26,21 +29,27 @@ func get_followed_mouse_position(): return relative_followed_position +func _ready(): + if state_holder == null: + if state_name == '': + state_holder = StateHolder.new() + else: + state_holder = StateHolder.get_default(self) + func _physics_process(delta: float) -> void: - match state: - State.CAPTURED: - var relative_followed_position: Vector2 = get_followed_mouse_position() - var direction: Vector2 = position.direction_to(relative_followed_position) - var actual_speed: float = min(delta * speed, relative_followed_position.length()) # Don't overshoot. - var movement: Vector2 = direction * actual_speed - move.emit(movement) + if is_captured: + var relative_followed_position: Vector2 = get_followed_mouse_position() + var direction: Vector2 = position.direction_to(relative_followed_position) + var actual_speed: float = min(delta * speed, relative_followed_position.length()) # Don't overshoot. + var movement: Vector2 = direction * actual_speed + move.emit(movement) func _on_capture_area_mouse_entered() -> void: - state = State.CAPTURED - captured.emit() + if state_holder.propose_state(state_name, 70, func (): detached.emit()): + captured.emit() func _on_capture_area_mouse_exited() -> void: if can_detach: - state = State.DETACHED - detached.emit() + state_holder.remove_state(state_name) + diff --git a/behaviours/random_walk.gd b/behaviours/random_walk.gd new file mode 100644 index 0000000..4d7eec9 --- /dev/null +++ b/behaviours/random_walk.gd @@ -0,0 +1,56 @@ +extends Node2D +class_name RandomWalk + + +signal move(movement: Vector2) + + +@export var speed: float = 100.0 + +@export var wait_time_mean: float = 4 +@export var wait_time_dev: float = 1.5 +@export var wander_time_mean: float = 5.0 +@export var wander_time_dev: float = 1.0 + +@export var state_holder: StateHolder + +@onready var rand_walk_timer: Timer = Timer.new() + +var walk_dir: Vector2 = Vector2.ZERO + + +func _ready(): + if state_holder == null: + state_holder = StateHolder.get_default(self) + + add_child(rand_walk_timer) + rand_walk_timer.one_shot = true; + rand_walk_timer.connect("timeout", _on_timer_timeout) + + state_holder.on_changed.connect(init_timer) + init_timer() + +func _physics_process(delta: float) -> void: + if state_holder.state != 'wander': + return + var movement: Vector2 = speed * walk_dir * delta + move.emit(movement) + +func init_timer(): + match state_holder.state: + 'idle': + var rand_time = max(0, Random.rng.randfn(wait_time_mean, wait_time_dev)) + rand_walk_timer.start(rand_time) + 'wander': + var rand_time = max(0, Random.rng.randfn(wander_time_mean, wander_time_dev)) + rand_walk_timer.start(rand_time) + _: + rand_walk_timer.stop() + +func _on_timer_timeout(): + match state_holder.state: + 'idle': + state_holder.set_state('wander', 10) + walk_dir = Vector2.from_angle(Random.rng.randf_range(0, 2*PI)) + 'wander': + state_holder.reset_state() \ No newline at end of file diff --git a/behaviours/random_walk.tscn b/behaviours/random_walk.tscn new file mode 100644 index 0000000..2d3c946 --- /dev/null +++ b/behaviours/random_walk.tscn @@ -0,0 +1,11 @@ +[gd_scene load_steps=2 format=3 uid="uid://dqlhv4j3b62hd"] + +[ext_resource type="Script" path="res://behaviours/random_walk.gd" id="1_hs213"] + +[node name="RandomWalk" type="Node2D"] +script = ExtResource("1_hs213") +speed = null +wait_time_mean = null +wait_time_dev = null +wander_time_mean = null +wander_time_dev = null diff --git a/behaviours/skitter_from_mouse.gd b/behaviours/skitter_from_mouse.gd index b7ee4b8..40de8c8 100644 --- a/behaviours/skitter_from_mouse.gd +++ b/behaviours/skitter_from_mouse.gd @@ -6,7 +6,7 @@ signal move(movement: Vector2) signal scared signal calmed - +@export var state_holder: StateHolder; @export_range(1, 500, 1) var speed: float = 300.0 @export_range(0.1, 5, 0.1) var scare_secs: float = 0.2 @export var directions: Array[Vector2] = [] @@ -14,25 +14,26 @@ signal calmed @onready var calm_timer: Timer = $"CalmTimer" @onready var scare_area: HoverDetector = $"ScareArea" - -enum State { CALM, SCARED } - -var state := State.CALM var direction := Vector2.ZERO +const SCARED_PRIORITY = 50 + func recheck(): + calm_timer.stop() if scare_area.mouse_inside: scare() else: calm() func calm(): - state = State.CALM - calmed.emit() + if state_holder.state == 'scared': + state_holder.reset_state() + calmed.emit() func scare(): - state = State.SCARED + if not state_holder.propose_state('scared', SCARED_PRIORITY, recheck): + return # Pick a direction var idx := Random.rng.randi_range(0, len(directions) - 1) direction = directions[idx] @@ -42,15 +43,16 @@ func scare(): func _ready(): + if state_holder == null: + state_holder = StateHolder.get_default(self) if not directions: Log.w(self, "No directions defined, object won't skitter.") func _physics_process(delta: float) -> void: - match state: - State.SCARED: - if len(directions) > 0: - var movement: Vector2 = direction * delta * speed - move.emit(movement) + if state_holder.state == 'scared': + if len(directions) > 0: + var movement: Vector2 = direction * delta * speed + move.emit(movement) func _on_calm_timer_timeout() -> void: recheck() diff --git a/behaviours/state_holder.gd b/behaviours/state_holder.gd new file mode 100644 index 0000000..93345ab --- /dev/null +++ b/behaviours/state_holder.gd @@ -0,0 +1,68 @@ +extends Node2D +class_name StateHolder + +signal on_changed(state: StringName, priority: int) + +const DEFAULT_STATE: StringName = "idle" +const DEFAULT_PRIORITY: int = 0 + +var _state: StringName = DEFAULT_STATE +var _priority: int +var _exit_callback: Callable = no_callback + +var state: StringName : + get: + return _state + +var priority: int : + get: + return _priority + +func set_state(new_state: StringName, new_priority: int, exit_callback: Callable = no_callback) -> void: + # Priority should be checked outside otherwise we never decrease it + + # Prevent recursion problems + var prev_callback = _exit_callback + _state = new_state + _priority = new_priority + _exit_callback = exit_callback + prev_callback.call() + on_changed.emit(new_state, new_priority) + +func propose_state(new_state: StringName, new_priority: int, exit_callback: Callable = no_callback) -> bool: + if new_priority > _priority: + set_state(new_state, new_priority, exit_callback) + return true + return false + +func reset_state() -> void: + set_state(DEFAULT_STATE, DEFAULT_PRIORITY) + +func remove_state(previous_state: StringName) -> void: + if _state == previous_state: + reset_state() + + +func no_callback(): + pass + + +static func get_default(current: Node2D) -> StateHolder: + # Try some default paths + for path in [^"../StateHolder", ^"../State"]: + var node = current.get_node_or_null(path) + if node != null: + return node + # Try to get a StateHolder from the parent's children + for child in current.get_parent().get_children(): + if child is StateHolder: + return child + + # Create one (hard way) + Log.w(current, "No StateHolder found, please create one or connect one") + var holder = StateHolder.new() + holder.set_name("StateHolder") + current.get_parent().add_child(holder) + return holder + + diff --git a/behaviours/state_holder.tscn b/behaviours/state_holder.tscn new file mode 100644 index 0000000..73b4b0c --- /dev/null +++ b/behaviours/state_holder.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://d31h5lcitnlsd"] + +[ext_resource type="Script" path="res://behaviours/state_holder.gd" id="1_md0rn"] + +[node name="StateHolder" type="Node2D"] +script = ExtResource("1_md0rn") diff --git a/entities/imp.gd b/entities/imp.gd index bbbd025..5221a01 100644 --- a/entities/imp.gd +++ b/entities/imp.gd @@ -13,13 +13,6 @@ func _on_eat_target_eaten(target: Edible): if Random.rng.randf() < skull_chance: skull_spawner.spawn() - -func _on_draggable_move(movement:Vector2): - if draggable.being_dragged: - move_and_collide(movement) - sprite.handle_move(movement) - func _on_move(movement:Vector2): - if not draggable.being_dragged: - move_and_collide(movement) - sprite.handle_move(movement) + move_and_collide(movement) + sprite.handle_move(movement) diff --git a/entities/imp.tscn b/entities/imp.tscn index 6ec0350..8ddb185 100644 --- a/entities/imp.tscn +++ b/entities/imp.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=10 format=3 uid="uid://4d3ksr3171x4"] +[gd_scene load_steps=11 format=3 uid="uid://4d3ksr3171x4"] [ext_resource type="Script" path="res://entities/imp.gd" id="1_dixpc"] [ext_resource type="PackedScene" uid="uid://bxbjfev0lhwws" path="res://behaviours/sprite_left_right.tscn" id="2_eqcdi"] @@ -6,8 +6,9 @@ [ext_resource type="PackedScene" uid="uid://b7bdlh5akhi8s" path="res://behaviours/eat_target.tscn" id="3_iybf3"] [ext_resource type="Texture2D" uid="uid://crhwsob76ieya" path="res://entities/imp_left.png" id="3_qda0k"] [ext_resource type="Texture2D" uid="uid://bubehid53q8h1" path="res://entities/imp_right.png" id="4_0sckn"] -[ext_resource type="PackedScene" path="res://behaviours/spawner.tscn" id="4_d8lgm"] +[ext_resource type="PackedScene" uid="uid://cyrg770fsetyu" path="res://behaviours/spawner.tscn" id="4_d8lgm"] [ext_resource type="PackedScene" uid="uid://uoxwjpmgg27a" path="res://entities/gold.tscn" id="5_yrfoq"] +[ext_resource type="PackedScene" uid="uid://d31h5lcitnlsd" path="res://behaviours/state_holder.tscn" id="9_myo4m"] [sub_resource type="CircleShape2D" id="CircleShape2D_ide4n"] radius = 8.0 @@ -27,8 +28,9 @@ right_texture = ExtResource("4_0sckn") scale = Vector2(4, 4) shape = SubResource("CircleShape2D_ide4n") -[node name="Draggable" parent="." instance=ExtResource("3_4528r")] +[node name="Draggable" parent="." node_paths=PackedStringArray("state_holder") instance=ExtResource("3_4528r")] scale = Vector2(4, 4) +state_holder = NodePath("../StateHolder") [node name="DragSound" type="AudioStreamPlayer2D" parent="Draggable"] scale = Vector2(0.5, 0.5) @@ -36,13 +38,16 @@ scale = Vector2(0.5, 0.5) [node name="DropSound" type="AudioStreamPlayer2D" parent="Draggable"] scale = Vector2(0.5, 0.5) -[node name="EatTarget" parent="." instance=ExtResource("3_iybf3")] +[node name="EatTarget" parent="." node_paths=PackedStringArray("state_holder") instance=ExtResource("3_iybf3")] scale = Vector2(2, 2) tag = &"Sheep" +state_holder = NodePath("../StateHolder") [node name="SkullSpawner" parent="." instance=ExtResource("4_d8lgm")] scene = ExtResource("5_yrfoq") -[connection signal="move" from="Draggable" to="." method="_on_draggable_move"] +[node name="StateHolder" parent="." instance=ExtResource("9_myo4m")] + +[connection signal="move" from="Draggable" to="." method="_on_move"] [connection signal="eaten" from="EatTarget" to="." method="_on_eat_target_eaten"] [connection signal="move" from="EatTarget" to="." method="_on_move"] diff --git a/entities/sheep.gd b/entities/sheep.gd index eab2986..a07d88e 100644 --- a/entities/sheep.gd +++ b/entities/sheep.gd @@ -9,14 +9,8 @@ class_name Sheep func _on_move(movement: Vector2) -> void: - if not draggable.being_dragged: - move_and_collide(movement) - sprite.handle_move(movement) - -func _on_drag_move(movement: Vector2) -> void: - if draggable.being_dragged: - move_and_collide(movement) - sprite.handle_move(movement) + move_and_collide(movement) + sprite.handle_move(movement) func _on_draggable_dragged() -> void: drag_sound.play() diff --git a/entities/sheep.tscn b/entities/sheep.tscn index 8750781..2ac5075 100644 --- a/entities/sheep.tscn +++ b/entities/sheep.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=11 format=3 uid="uid://bc2bm8lbol18w"] +[gd_scene load_steps=13 format=3 uid="uid://bc2bm8lbol18w"] [ext_resource type="Script" path="res://entities/sheep.gd" id="1_4dmll"] [ext_resource type="Texture2D" uid="uid://iljp5yn3ehfk" path="res://entities/sheep_left.png" id="2_t13f5"] @@ -9,6 +9,8 @@ [ext_resource type="AudioStream" uid="uid://buxgivpkh8dyl" path="res://entities/drop.wav" id="4_nxjnl"] [ext_resource type="AudioStream" uid="uid://bmfscpnugaejk" path="res://entities/sheep_drag.wav" id="4_oalqu"] [ext_resource type="PackedScene" uid="uid://dfdr3e32lohq" path="res://behaviours/edible.tscn" id="6_3odsh"] +[ext_resource type="PackedScene" uid="uid://dqlhv4j3b62hd" path="res://behaviours/random_walk.tscn" id="6_ekxsq"] +[ext_resource type="PackedScene" uid="uid://d31h5lcitnlsd" path="res://behaviours/state_holder.tscn" id="10_mm8v8"] [sub_resource type="CircleShape2D" id="CircleShape2D_c5tcn"] radius = 8.0 @@ -28,13 +30,23 @@ right_texture = ExtResource("4_5qoof") scale = Vector2(3, 3) shape = SubResource("CircleShape2D_c5tcn") -[node name="MoveTowardsMouse" parent="." instance=ExtResource("2_tfd2i")] +[node name="MoveTowardsMouse" parent="." node_paths=PackedStringArray("state_holder") instance=ExtResource("2_tfd2i")] scale = Vector2(30, 30) speed = -30.0 can_detach = true +state_holder = NodePath("../StateHolder") +state_name = &"scared" -[node name="Draggable" parent="." instance=ExtResource("3_8ku7r")] +[node name="RandomWalk" parent="." instance=ExtResource("6_ekxsq")] +speed = 10.0 +wait_time_mean = 4.0 +wait_time_dev = 1.5 +wander_time_mean = 5.0 +wander_time_dev = 1.0 + +[node name="Draggable" parent="." node_paths=PackedStringArray("state_holder") instance=ExtResource("3_8ku7r")] scale = Vector2(3, 3) +state_holder = NodePath("../StateHolder") [node name="DragSound" type="AudioStreamPlayer2D" parent="Draggable"] scale = Vector2(0.5, 0.5) @@ -47,7 +59,10 @@ stream = ExtResource("4_nxjnl") [node name="Edible" parent="." instance=ExtResource("6_3odsh")] tag = &"Sheep" +[node name="StateHolder" parent="." instance=ExtResource("10_mm8v8")] + [connection signal="move" from="MoveTowardsMouse" to="." method="_on_move"] +[connection signal="move" from="RandomWalk" to="." method="_on_move"] [connection signal="dragged" from="Draggable" to="." method="_on_draggable_dragged"] [connection signal="dropped" from="Draggable" to="." method="_on_draggable_dropped"] -[connection signal="move" from="Draggable" to="." method="_on_drag_move"] +[connection signal="move" from="Draggable" to="." method="_on_move"]