State machine
parent
63aa1c0cb5
commit
0930edc477
|
@ -0,0 +1,134 @@
|
||||||
|
{
|
||||||
|
"start": {
|
||||||
|
"state": "Idle"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"Idle": {
|
||||||
|
"transitions": [
|
||||||
|
{
|
||||||
|
"target": "PickupTrash",
|
||||||
|
"signal": "waste_detected",
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"type": ">=",
|
||||||
|
"left": {
|
||||||
|
"value": 400
|
||||||
|
},
|
||||||
|
"right": {
|
||||||
|
"function": "distance",
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"accessor": [
|
||||||
|
"character",
|
||||||
|
"position"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"accessor": [
|
||||||
|
"signals",
|
||||||
|
"waste_detected",
|
||||||
|
"args",
|
||||||
|
"waste",
|
||||||
|
"position"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"transfer": {
|
||||||
|
"waste": {
|
||||||
|
"accessor": [
|
||||||
|
"signals",
|
||||||
|
"waste_detected",
|
||||||
|
"args",
|
||||||
|
"waste"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"PickupTrash": {
|
||||||
|
"transitions": [
|
||||||
|
{
|
||||||
|
"target": "ThrowTrashAway",
|
||||||
|
"signal": "waste_detected",
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"type": ">=",
|
||||||
|
"left": {
|
||||||
|
"value": 75
|
||||||
|
},
|
||||||
|
"right": {
|
||||||
|
"function": "distance",
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"accessor": [
|
||||||
|
"character",
|
||||||
|
"position"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"accessor": [
|
||||||
|
"signals",
|
||||||
|
"waste_detected",
|
||||||
|
"args",
|
||||||
|
"waste",
|
||||||
|
"position"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"transfer": {
|
||||||
|
"waste": {
|
||||||
|
"accessor": [
|
||||||
|
"signals",
|
||||||
|
"waste_detected",
|
||||||
|
"args",
|
||||||
|
"waste"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ThrowTrashAway": {
|
||||||
|
"transitions": [
|
||||||
|
{
|
||||||
|
"target": "Idle",
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"type": ">=",
|
||||||
|
"left": {
|
||||||
|
"value": 90
|
||||||
|
},
|
||||||
|
"right": {
|
||||||
|
"function": "distance",
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"accessor": [
|
||||||
|
"character",
|
||||||
|
"position"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"accessor": [
|
||||||
|
"root_nodes",
|
||||||
|
"StateMachineWorld",
|
||||||
|
"child_nodes",
|
||||||
|
"TrashBin",
|
||||||
|
"position"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 82 KiB |
|
@ -0,0 +1,34 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://cx04xknqfdscp"
|
||||||
|
path="res://.godot/imported/cleaning_robot.png-014de1c8447fd18dff622d82a883fa1e.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/cleaning_robot.png"
|
||||||
|
dest_files=["res://.godot/imported/cleaning_robot.png-014de1c8447fd18dff622d82a883fa1e.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
Binary file not shown.
After Width: | Height: | Size: 935 KiB |
|
@ -0,0 +1,34 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://dkqw1wsjbvl0i"
|
||||||
|
path="res://.godot/imported/floor.png-38406586736af2fe805d7b886727ee47.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/floor.png"
|
||||||
|
dest_files=["res://.godot/imported/floor.png-38406586736af2fe805d7b886727ee47.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
|
@ -0,0 +1,34 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://s0vco3jt5y8m"
|
||||||
|
path="res://.godot/imported/trash_bag.png-26088d6e8428343ce319693f7898cb67.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/trash_bag.png"
|
||||||
|
dest_files=["res://.godot/imported/trash_bag.png-26088d6e8428343ce319693f7898cb67.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
|
@ -0,0 +1,34 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://di8eotyycurps"
|
||||||
|
path="res://.godot/imported/trash_bin.png-1284d871f56467e75f465b364d170d20.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/trash_bin.png"
|
||||||
|
dest_files=["res://.godot/imported/trash_bin.png-1284d871f56467e75f465b364d170d20.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
|
@ -11,5 +11,18 @@ config_version=5
|
||||||
[application]
|
[application]
|
||||||
|
|
||||||
config/name="state"
|
config/name="state"
|
||||||
|
run/main_scene="res://scenes/state/StateMachineWorld.tscn"
|
||||||
config/features=PackedStringArray("4.3", "Forward Plus")
|
config/features=PackedStringArray("4.3", "Forward Plus")
|
||||||
config/icon="res://icon.svg"
|
config/icon="res://icon.svg"
|
||||||
|
|
||||||
|
[display]
|
||||||
|
|
||||||
|
window/stretch/mode="viewport"
|
||||||
|
|
||||||
|
[input]
|
||||||
|
|
||||||
|
spawn_trash={
|
||||||
|
"deadzone": 0.5,
|
||||||
|
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":false,"double_click":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
extends CharacterBody2D
|
||||||
|
|
||||||
|
@onready var state_machine: StateMachine = $StateMachine
|
||||||
|
#
|
||||||
|
@onready var sprite: Node = $Sprite2D
|
||||||
|
#
|
||||||
|
const MAX_SPEED: float = 150.0
|
||||||
|
const MAX_ACCELERATION: float = 300.0
|
||||||
|
const SLOWING_RADIUS: float = 80.0
|
||||||
|
const SHAKE_INTENSITY: float = 8.0
|
||||||
|
const ROTATION_SPEED: float = 5.0 # adians per second
|
||||||
|
#
|
||||||
|
@onready var detection_area: Area2D = $VisionCone
|
||||||
|
signal waste_detected(waste)
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# detection_area.body_entered.connect(Callable(self, "_on_body_entered"))
|
||||||
|
# detection_area.area_entered.connect(Callable(self, "_on_area_entered"))
|
||||||
|
|
||||||
|
|
||||||
|
# func _on_body_entered(body):
|
||||||
|
# if body.is_in_group("waste"):
|
||||||
|
# emit_signal("waste_detected", body)
|
||||||
|
|
||||||
|
|
||||||
|
# func _on_area_entered(area):
|
||||||
|
# if area.is_in_group("waste"):
|
||||||
|
# emit_signal("waste_detected", area)
|
||||||
|
|
||||||
|
|
||||||
|
func _draw() -> void:
|
||||||
|
if velocity.length() > 0.1:
|
||||||
|
var local_velocity: Vector2 = global_transform.basis_xform_inv(velocity.normalized() * 400)
|
||||||
|
draw_line(Vector2.ZERO, local_velocity, Color(1, 0, 0), 2)
|
||||||
|
|
||||||
|
|
||||||
|
func _physics_process(delta: float) -> void:
|
||||||
|
rotate_towards_velocity(delta)
|
||||||
|
detect_waste()
|
||||||
|
queue_redraw()
|
||||||
|
|
||||||
|
|
||||||
|
func detect_waste() -> void:
|
||||||
|
var overlapping_bodies: Array[Node2D] = detection_area.get_overlapping_bodies()
|
||||||
|
for body in overlapping_bodies:
|
||||||
|
if body.is_in_group("waste"):
|
||||||
|
emit_signal("waste_detected", body)
|
||||||
|
|
||||||
|
|
||||||
|
func move_towards(target: Vector2, delta: float) -> void:
|
||||||
|
var to_target: Vector2 = target - position
|
||||||
|
var distance: float = to_target.length()
|
||||||
|
|
||||||
|
if distance < 5.0:
|
||||||
|
velocity = Vector2.ZERO
|
||||||
|
return
|
||||||
|
|
||||||
|
var desired_speed: float = MAX_SPEED
|
||||||
|
if distance < SLOWING_RADIUS:
|
||||||
|
desired_speed = lerp(0, int(MAX_SPEED), distance / SLOWING_RADIUS)
|
||||||
|
|
||||||
|
var desired_velocity: Vector2 = to_target.normalized() * desired_speed
|
||||||
|
|
||||||
|
var steering: Vector2 = desired_velocity - velocity
|
||||||
|
|
||||||
|
var steering_length: float = steering.length()
|
||||||
|
if steering_length > MAX_ACCELERATION * delta:
|
||||||
|
steering = steering.normalized() * MAX_ACCELERATION * delta
|
||||||
|
|
||||||
|
velocity += steering
|
||||||
|
|
||||||
|
if velocity.length() > 0.1:
|
||||||
|
var shake: Vector2 = Vector2(
|
||||||
|
rand_range(-SHAKE_INTENSITY, SHAKE_INTENSITY),
|
||||||
|
rand_range(-SHAKE_INTENSITY, SHAKE_INTENSITY)
|
||||||
|
)
|
||||||
|
velocity += shake
|
||||||
|
|
||||||
|
if velocity.length() > MAX_SPEED:
|
||||||
|
velocity = velocity.normalized() * MAX_SPEED
|
||||||
|
|
||||||
|
|
||||||
|
func rotate_towards_velocity(delta: float) -> void:
|
||||||
|
if velocity.length() > 0.1:
|
||||||
|
var target_angle: float = velocity.angle()
|
||||||
|
rotation = lerp_angle(rotation, target_angle, ROTATION_SPEED * delta)
|
||||||
|
sprite.rotation = -rotation
|
||||||
|
|
||||||
|
|
||||||
|
func rand_range(min: float, max: float) -> float:
|
||||||
|
return randf() * (max - min) + min
|
|
@ -0,0 +1,16 @@
|
||||||
|
extends Node2D
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
func _process(delta: float) -> void:
|
||||||
|
if Input.is_action_just_pressed("spawn_trash"):
|
||||||
|
var trash_scene: PackedScene = preload("res://scenes/state/Waste.tscn")
|
||||||
|
var trash_instance: Node2D = trash_scene.instantiate() as Node2D
|
||||||
|
|
||||||
|
var spawn_location: Vector2 = get_global_mouse_position()
|
||||||
|
trash_instance.position = Vector2(spawn_location)
|
||||||
|
|
||||||
|
get_tree().get_root().add_child(trash_instance)
|
||||||
|
print("Spawned trash at: ", spawn_location, " ", trash_instance.position)
|
|
@ -6,13 +6,28 @@ var character: CharacterBody2D
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
func state_enter() -> void:
|
func state_enter() -> void:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
func state_process(delta: float) -> void:
|
func state_process(delta: float) -> void:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
func state_exit() -> void:
|
func state_exit() -> void:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
func contribute_transfer_variables(transfer_variables: Dictionary) -> void:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
func change_state(new_state: State) -> void:
|
||||||
|
state_machine.current_state = new_state
|
||||||
|
|
||||||
|
|
||||||
|
func rand_range(min: float, max: float) -> float:
|
||||||
|
return randf() * (max - min) + min
|
||||||
|
|
|
@ -1,19 +1,243 @@
|
||||||
class_name StateMachine
|
class_name StateMachine
|
||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
|
var character: CharacterBody2D
|
||||||
|
# json read from file, contains all states and transitions
|
||||||
|
var state_machine_data: Dictionary = {}
|
||||||
|
# records all signals that were fired during the last frame, dictionary contains signal name and arguments
|
||||||
|
var received_signals: Array[Dictionary] = []
|
||||||
|
# state change parameters ("transfer")
|
||||||
|
var state_transfer_variables: Dictionary = {}
|
||||||
|
|
||||||
var current_state: State:
|
var current_state: State:
|
||||||
set(value):
|
set(value):
|
||||||
if current_state:
|
if current_state:
|
||||||
current_state.state_exit()
|
current_state.state_exit()
|
||||||
current_state = value
|
current_state = value
|
||||||
current_state.state_enter()
|
current_state.state_enter()
|
||||||
|
|
||||||
|
|
||||||
|
# INITIALIZATION
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
for child in self.get_children():
|
if get_parent() is CharacterBody2D:
|
||||||
if true:
|
character = get_parent()
|
||||||
print(child)
|
else:
|
||||||
|
push_error("No character as parent node.")
|
||||||
|
|
||||||
|
for child in self.get_children():
|
||||||
|
if child is State:
|
||||||
|
print('Detected state ', child)
|
||||||
|
child.state_machine = self
|
||||||
|
child.character = character
|
||||||
|
|
||||||
|
var loaded_state_machine_data: JSON = load_json("res://assets/character_state_machine.json")
|
||||||
|
if loaded_state_machine_data:
|
||||||
|
state_machine_data = loaded_state_machine_data.get_data() as Dictionary
|
||||||
|
print("Loaded JSON character_state_machine.json ", state_machine_data)
|
||||||
|
else:
|
||||||
|
push_error("Failed to load JSON character_state_machine.json")
|
||||||
|
|
||||||
|
current_state = find_state_by_name(state_machine_data["start"]["state"])
|
||||||
|
|
||||||
|
call_deferred("defer_ready")
|
||||||
|
|
||||||
|
|
||||||
|
func defer_ready():
|
||||||
|
character.waste_detected.connect(Callable(self, "_on_waste_detected"))
|
||||||
|
|
||||||
|
|
||||||
|
# STATE MACHINE
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
pass
|
if current_state:
|
||||||
|
current_state.state_process(delta)
|
||||||
|
check_transitions()
|
||||||
|
received_signals = []
|
||||||
|
|
||||||
|
|
||||||
|
func check_transitions() -> void:
|
||||||
|
if current_state:
|
||||||
|
for transition in state_machine_data["states"][current_state.get_name()]["transitions"]:
|
||||||
|
|
||||||
|
if "signal" in transition and transition["signal"] and not was_signal_received(transition["signal"]):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if "conditions" in transition and transition["conditions"]:
|
||||||
|
var all_conditions_met: bool = true
|
||||||
|
for condition in transition["conditions"]:
|
||||||
|
if not transition_check_condition(condition):
|
||||||
|
all_conditions_met = false
|
||||||
|
break
|
||||||
|
if not all_conditions_met:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# evaluate transfer parameters if any
|
||||||
|
state_transfer_variables = {}
|
||||||
|
if "transfer" in transition and transition["transfer"]:
|
||||||
|
for key in transition["transfer"]:
|
||||||
|
state_transfer_variables[key] = transition_resolve_parameter(transition["transfer"][key])
|
||||||
|
|
||||||
|
# allow current state to contribute to transfer variables
|
||||||
|
current_state.contribute_transfer_variables(state_transfer_variables)
|
||||||
|
|
||||||
|
print("All criteria were met for switching to state ", transition["target"], " with transfer variables ", state_transfer_variables)
|
||||||
|
current_state = find_state_by_name(transition["target"])
|
||||||
|
|
||||||
|
|
||||||
|
func transition_check_condition(condition: Dictionary) -> bool:
|
||||||
|
var type: String = condition["type"]
|
||||||
|
var left: Variant = transition_resolve_parameter(condition["left"])
|
||||||
|
var right: Variant = transition_resolve_parameter(condition["right"])
|
||||||
|
|
||||||
|
var result: bool = false
|
||||||
|
if type == "<":
|
||||||
|
result = left < right
|
||||||
|
elif type == ">":
|
||||||
|
result = left > right
|
||||||
|
elif type == "<=":
|
||||||
|
result = left <= right
|
||||||
|
elif type == ">=":
|
||||||
|
result = left >= right
|
||||||
|
elif type == "==":
|
||||||
|
result = left == right
|
||||||
|
elif type == "!=":
|
||||||
|
result = left != right
|
||||||
|
else:
|
||||||
|
print("Unknown condition type [", type, "]")
|
||||||
|
|
||||||
|
print("[check_condition] ", left, " ", type, " ", right, " = ", result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
func transition_resolve_parameter(parameter: Dictionary) -> Variant:
|
||||||
|
if "value" in parameter:
|
||||||
|
return parameter["value"]
|
||||||
|
elif "function" in parameter:
|
||||||
|
return transition_resolve_function(parameter)
|
||||||
|
elif "accessor" in parameter:
|
||||||
|
return transition_resolve_accessor(parameter["accessor"])
|
||||||
|
else:
|
||||||
|
print("Unknown parameter type")
|
||||||
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
func transition_resolve_function(function: Dictionary) -> Variant:
|
||||||
|
var args: Array = []
|
||||||
|
if "args" in function:
|
||||||
|
for arg in function["args"]:
|
||||||
|
if arg is Dictionary:
|
||||||
|
args.append(transition_resolve_parameter(arg))
|
||||||
|
else:
|
||||||
|
print("Unknown argument type [", arg, "]")
|
||||||
|
|
||||||
|
if function["function"] == "distance":
|
||||||
|
var result = args[0].distance_to(args[1])
|
||||||
|
print("[resolve_function] distance(", args[0], ", ", args[1], ") = ", result)
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
print("Unknown function [", function, "]")
|
||||||
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
func transition_resolve_accessor(accessor: Array) -> Variant:
|
||||||
|
var current_item: Variant = self
|
||||||
|
for next_item in accessor:
|
||||||
|
|
||||||
|
print("[resolve_accessor] current_item: ", current_item, ' next_item: ', next_item)
|
||||||
|
|
||||||
|
if current_item == null:
|
||||||
|
print("[resolve_accessor] Null value in ", accessor)
|
||||||
|
break
|
||||||
|
|
||||||
|
if objects_equal(current_item, self):
|
||||||
|
if next_item == "signals":
|
||||||
|
current_item = received_signals
|
||||||
|
continue
|
||||||
|
|
||||||
|
if objects_equal(current_item, received_signals):
|
||||||
|
var detected_signal: Dictionary = find_detected_signal(next_item)
|
||||||
|
if detected_signal.size() > 0:
|
||||||
|
current_item = detected_signal
|
||||||
|
continue
|
||||||
|
|
||||||
|
if objects_equal(next_item, "child_nodes"):
|
||||||
|
# build a dictionary of names:child nodes
|
||||||
|
var child_nodes: Dictionary = {}
|
||||||
|
for child in current_item.get_children():
|
||||||
|
child_nodes[child.get_name()] = child
|
||||||
|
current_item = child_nodes
|
||||||
|
continue
|
||||||
|
|
||||||
|
if objects_equal(next_item, "root_nodes"):
|
||||||
|
var root_nodes: Dictionary = {}
|
||||||
|
for root in current_item.get_tree().get_root().get_children():
|
||||||
|
root_nodes[root.get_name()] = root
|
||||||
|
current_item = root_nodes
|
||||||
|
continue
|
||||||
|
|
||||||
|
if current_item is Dictionary:
|
||||||
|
print("[resolve_accessor] Dictionary accessor, keys: ", current_item.keys())
|
||||||
|
if next_item in current_item:
|
||||||
|
current_item = current_item[next_item]
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
current_item = current_item.get(next_item)
|
||||||
|
|
||||||
|
if current_item == null:
|
||||||
|
print("[resolve_accessor] Failed to resolve accessor ", accessor)
|
||||||
|
|
||||||
|
print("[resolve_accessor] resolved to ", current_item)
|
||||||
|
return current_item
|
||||||
|
|
||||||
|
|
||||||
|
# SIGNALS
|
||||||
|
func _on_waste_detected(waste):
|
||||||
|
received_signals.append({"signal": "waste_detected", "args": {"waste": waste}})
|
||||||
|
|
||||||
|
|
||||||
|
func find_detected_signal(signal_name: String) -> Dictionary:
|
||||||
|
for received_signal in received_signals:
|
||||||
|
if received_signal["signal"] == signal_name:
|
||||||
|
return received_signal
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
# UTILITY FUNCTIONS
|
||||||
|
func find_state_by_name(name: String) -> State:
|
||||||
|
for child in self.get_children():
|
||||||
|
if child is State:
|
||||||
|
if child.get_name() == name:
|
||||||
|
return child
|
||||||
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
func was_signal_received(signal_name: String) -> bool:
|
||||||
|
for received_signal in received_signals:
|
||||||
|
if received_signal["signal"] == signal_name:
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
func load_json(path: String) -> JSON:
|
||||||
|
var file: FileAccess = FileAccess.open(path, FileAccess.READ)
|
||||||
|
if not file:
|
||||||
|
print("Cannot open file: ", path)
|
||||||
|
return JSON.new()
|
||||||
|
|
||||||
|
var json_string: String = file.get_as_text()
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
var json = JSON.new()
|
||||||
|
var error = json.parse(json_string)
|
||||||
|
if error != OK:
|
||||||
|
print("JSON parse error: ", error)
|
||||||
|
return JSON.new()
|
||||||
|
|
||||||
|
return json
|
||||||
|
|
||||||
|
|
||||||
|
func objects_equal(a: Variant, b: Variant) -> bool:
|
||||||
|
if typeof(a) != typeof(b):
|
||||||
|
return false
|
||||||
|
return a == b
|
||||||
|
|
|
@ -1,25 +1,63 @@
|
||||||
[gd_scene load_steps=5 format=3 uid="uid://cgdvptekjbpgq"]
|
[gd_scene load_steps=12 format=3 uid="uid://cgdvptekjbpgq"]
|
||||||
|
|
||||||
[ext_resource type="Texture2D" uid="uid://beggt5ndi6j4p" path="res://icon.svg" id="1_jgx5y"]
|
[ext_resource type="Script" path="res://scenes/state/SMCharacter.gd" id="1_84313"]
|
||||||
|
[ext_resource type="Script" path="res://scenes/state/SceneManagement.gd" id="1_s1suw"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://cx04xknqfdscp" path="res://assets/cleaning_robot.png" id="2_cwwab"]
|
||||||
[ext_resource type="Script" path="res://scenes/state/StateMachine.gd" id="2_d1xqo"]
|
[ext_resource type="Script" path="res://scenes/state/StateMachine.gd" id="2_d1xqo"]
|
||||||
[ext_resource type="Script" path="res://scenes/state/state_idle.gd" id="3_r1btx"]
|
[ext_resource type="Script" path="res://scenes/state/state_idle.gd" id="3_r1btx"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://dkqw1wsjbvl0i" path="res://assets/floor.png" id="6_15pjb"]
|
||||||
|
[ext_resource type="Script" path="res://scenes/state/state_PickupTrash.gd" id="7_1rfah"]
|
||||||
|
[ext_resource type="Script" path="res://scenes/state/state_ThrowTrashAway.gd" id="8_677fi"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://dng6a8i8kp4kw" path="res://scenes/state/TrashBin.tscn" id="9_sd6r3"]
|
||||||
|
|
||||||
[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_tr1gq"]
|
[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_tr1gq"]
|
||||||
radius = 60.0
|
radius = 60.0
|
||||||
height = 122.0
|
height = 122.0
|
||||||
|
|
||||||
|
[sub_resource type="RectangleShape2D" id="RectangleShape2D_ji64e"]
|
||||||
|
size = Vector2(601.536, 254.293)
|
||||||
|
|
||||||
[node name="StateMachineWorld" type="Node2D"]
|
[node name="StateMachineWorld" type="Node2D"]
|
||||||
|
script = ExtResource("1_s1suw")
|
||||||
|
|
||||||
[node name="CharacterBody2D" type="CharacterBody2D" parent="."]
|
[node name="Floor" type="Sprite2D" parent="."]
|
||||||
|
position = Vector2(573, 329)
|
||||||
|
scale = Vector2(0.635347, 0.635347)
|
||||||
|
texture = ExtResource("6_15pjb")
|
||||||
|
|
||||||
[node name="Sprite2D" type="Sprite2D" parent="CharacterBody2D"]
|
[node name="TrashBin" parent="." instance=ExtResource("9_sd6r3")]
|
||||||
texture = ExtResource("1_jgx5y")
|
position = Vector2(67, 78)
|
||||||
|
scale = Vector2(4.42446, 4.42446)
|
||||||
|
|
||||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="CharacterBody2D"]
|
[node name="CleaningRobotCB2D" type="CharacterBody2D" parent="."]
|
||||||
|
position = Vector2(546, 342)
|
||||||
|
script = ExtResource("1_84313")
|
||||||
|
|
||||||
|
[node name="Sprite2D" type="Sprite2D" parent="CleaningRobotCB2D"]
|
||||||
|
scale = Vector2(0.345498, 0.345498)
|
||||||
|
texture = ExtResource("2_cwwab")
|
||||||
|
|
||||||
|
[node name="CollisionShape2D" type="CollisionShape2D" parent="CleaningRobotCB2D"]
|
||||||
|
scale = Vector2(0.702629, 0.702629)
|
||||||
shape = SubResource("CapsuleShape2D_tr1gq")
|
shape = SubResource("CapsuleShape2D_tr1gq")
|
||||||
|
|
||||||
[node name="StateMachine" type="Node" parent="CharacterBody2D"]
|
[node name="StateMachine" type="Node" parent="CleaningRobotCB2D"]
|
||||||
script = ExtResource("2_d1xqo")
|
script = ExtResource("2_d1xqo")
|
||||||
|
|
||||||
[node name="idle" type="Node" parent="CharacterBody2D/StateMachine"]
|
[node name="Idle" type="Node" parent="CleaningRobotCB2D/StateMachine"]
|
||||||
script = ExtResource("3_r1btx")
|
script = ExtResource("3_r1btx")
|
||||||
|
|
||||||
|
[node name="PickupTrash" type="Node" parent="CleaningRobotCB2D/StateMachine"]
|
||||||
|
script = ExtResource("7_1rfah")
|
||||||
|
|
||||||
|
[node name="ThrowTrashAway" type="Node" parent="CleaningRobotCB2D/StateMachine"]
|
||||||
|
script = ExtResource("8_677fi")
|
||||||
|
|
||||||
|
[node name="VisionCone" type="Area2D" parent="CleaningRobotCB2D"]
|
||||||
|
|
||||||
|
[node name="CollisionShape2D" type="CollisionShape2D" parent="CleaningRobotCB2D/VisionCone"]
|
||||||
|
position = Vector2(295.232, -1)
|
||||||
|
scale = Vector2(1, 0.48)
|
||||||
|
shape = SubResource("RectangleShape2D_ji64e")
|
||||||
|
|
||||||
|
[node name="Node2D" type="Node2D" parent="CleaningRobotCB2D"]
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
[gd_scene load_steps=3 format=3 uid="uid://dng6a8i8kp4kw"]
|
||||||
|
|
||||||
|
[ext_resource type="Texture2D" uid="uid://di8eotyycurps" path="res://assets/trash_bin.png" id="1_eaj15"]
|
||||||
|
|
||||||
|
[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_bhmyx"]
|
||||||
|
radius = 0.0
|
||||||
|
height = 0.0
|
||||||
|
|
||||||
|
[node name="TrashBin" type="StaticBody2D"]
|
||||||
|
|
||||||
|
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||||
|
scale = Vector2(0.0900715, 0.0900715)
|
||||||
|
texture = ExtResource("1_eaj15")
|
||||||
|
|
||||||
|
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||||
|
shape = SubResource("CapsuleShape2D_bhmyx")
|
|
@ -0,0 +1,17 @@
|
||||||
|
[gd_scene load_steps=3 format=3 uid="uid://cqew2pkk5en21"]
|
||||||
|
|
||||||
|
[ext_resource type="Texture2D" uid="uid://s0vco3jt5y8m" path="res://assets/trash_bag.png" id="1_mr4i5"]
|
||||||
|
|
||||||
|
[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_4fcy4"]
|
||||||
|
radius = 42.0
|
||||||
|
height = 128.0
|
||||||
|
|
||||||
|
[node name="Waste" type="StaticBody2D" groups=["waste"]]
|
||||||
|
|
||||||
|
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||||
|
scale = Vector2(0.5, 0.5)
|
||||||
|
texture = ExtResource("1_mr4i5")
|
||||||
|
|
||||||
|
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||||
|
scale = Vector2(0.5, 0.5)
|
||||||
|
shape = SubResource("CapsuleShape2D_4fcy4")
|
|
@ -0,0 +1,15 @@
|
||||||
|
extends State
|
||||||
|
|
||||||
|
var pickup_trash_target: Vector2 = Vector2.ZERO
|
||||||
|
|
||||||
|
|
||||||
|
func state_enter():
|
||||||
|
var waste = state_machine.state_transfer_variables["waste"]
|
||||||
|
pickup_trash_target = waste.position
|
||||||
|
print(waste, " ", pickup_trash_target)
|
||||||
|
|
||||||
|
|
||||||
|
func state_process(delta: float) -> void:
|
||||||
|
character.move_towards(pickup_trash_target, delta)
|
||||||
|
character.move_and_slide()
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
extends State
|
||||||
|
|
||||||
|
@onready var trash_bin: Node2D = $"../../../TrashBin"
|
||||||
|
|
||||||
|
var carry_trash: Node2D = null
|
||||||
|
|
||||||
|
|
||||||
|
func state_enter():
|
||||||
|
carry_trash = state_machine.state_transfer_variables["waste"]
|
||||||
|
# disable it's collision to avoid it from colliding with the character
|
||||||
|
carry_trash.get_node("CollisionShape2D").disabled = true
|
||||||
|
print("Carry ", carry_trash, " to ", trash_bin.position)
|
||||||
|
|
||||||
|
|
||||||
|
func state_process(delta: float) -> void:
|
||||||
|
# put the trash on the character
|
||||||
|
carry_trash.position = character.position
|
||||||
|
# carry the trash to the trash bin
|
||||||
|
var target_position: Vector2 = trash_bin.position
|
||||||
|
character.move_towards(target_position, delta)
|
||||||
|
character.move_and_slide()
|
||||||
|
|
||||||
|
|
||||||
|
func state_exit() -> void:
|
||||||
|
carry_trash.queue_free()
|
|
@ -1,10 +1,16 @@
|
||||||
extends State
|
extends State
|
||||||
|
|
||||||
|
var idle_target: Vector2 = Vector2.ZERO
|
||||||
|
|
||||||
|
|
||||||
func state_enter():
|
func state_enter():
|
||||||
print("idle enter")
|
idle_target = Vector2(rand_range(0, get_viewport().size.x), rand_range(0, get_viewport().size.y))
|
||||||
|
|
||||||
|
|
||||||
func state_process(delta: float) -> void:
|
func state_process(delta: float) -> void:
|
||||||
print("idle process")
|
# move towards the idle_target until reached, then set a new target randomly on the screen
|
||||||
|
if character.position.distance_to(idle_target) < 10:
|
||||||
func state_exit() -> void:
|
idle_target = Vector2(rand_range(0, get_viewport().size.x), rand_range(0, get_viewport().size.y))
|
||||||
print("idle exit")
|
else:
|
||||||
|
character.move_towards(idle_target, delta)
|
||||||
|
character.move_and_slide()
|
||||||
|
|
Loading…
Reference in New Issue