gd,refacto: added state chart addon and namespace cleanup
This commit is contained in:
276
godot_state_charts_examples/ant_hill/ant/ant.gd
Normal file
276
godot_state_charts_examples/ant_hill/ant/ant.gd
Normal file
@ -0,0 +1,276 @@
|
||||
extends CharacterBody2D
|
||||
|
||||
signal clicked(node:Node2D)
|
||||
|
||||
@export var marker_scene:PackedScene
|
||||
|
||||
## The navigation agent we use.
|
||||
@onready var navigation_agent:NavigationAgent2D = $NavigationAgent2D
|
||||
|
||||
## The state chart
|
||||
@onready var state_chart:StateChart = $StateChart
|
||||
|
||||
## Set of close food markers
|
||||
var food_markers:Dictionary = {}
|
||||
|
||||
## Set of close nest markers
|
||||
var nest_markers:Dictionary = {}
|
||||
|
||||
## Set of food nearby
|
||||
var food:Dictionary = {}
|
||||
|
||||
## The nest, if nearby
|
||||
var nest:Node2D = null
|
||||
|
||||
## The currently carried food
|
||||
var carried_food:Node = null
|
||||
|
||||
const SEGMENT_LENGTH = 150
|
||||
|
||||
func _ready():
|
||||
# start the state chart
|
||||
state_chart.send_event.call_deferred("initialized")
|
||||
|
||||
|
||||
## Called when we are seeking for food and need a new target.
|
||||
func _on_idle_seeking_food():
|
||||
|
||||
var current_position := get_global_position()
|
||||
|
||||
# if we have food nearby grab it
|
||||
if food.size() > 0:
|
||||
state_chart.send_event("food_detected")
|
||||
return
|
||||
|
||||
var target_position := Vector2()
|
||||
# if we have food markers nearby travel into the general direction of the closest one points
|
||||
if food_markers.size() > 0:
|
||||
var closest_food_marker := _find_closest(food_markers.keys(), current_position)
|
||||
var direction = Vector2.RIGHT.rotated(closest_food_marker.get_rotation())
|
||||
target_position = current_position + (direction * SEGMENT_LENGTH)
|
||||
|
||||
# otherwise or if we couldn't reach the last target position, pick a random
|
||||
# direction
|
||||
if food_markers.size() == 0 or not navigation_agent.is_target_reachable():
|
||||
# otherwise pick a random position in a radius of SEGMENT_LENGTH pixels
|
||||
# first calculate a random angle in radians
|
||||
var random_angle := randf() * 2 * PI
|
||||
# then calculate the x and y components of the vector
|
||||
var random_x := cos(random_angle) * SEGMENT_LENGTH
|
||||
var random_y := sin(random_angle) * SEGMENT_LENGTH
|
||||
|
||||
# add the random vector to the current position
|
||||
target_position = current_position + Vector2(random_x, random_y)
|
||||
|
||||
|
||||
navigation_agent.set_target_position(target_position)
|
||||
state_chart.set_expression_property("target_position", target_position)
|
||||
state_chart.send_event("destination_set")
|
||||
|
||||
|
||||
## Called when we have found food nearby and want to go to it
|
||||
func _on_food_detected():
|
||||
# set the target position to the closest food
|
||||
var closest_food_position = _find_closest(food.keys(), get_global_position()).global_position
|
||||
navigation_agent.set_target_position(closest_food_position)
|
||||
|
||||
|
||||
## Called when we arrived at the food and want to pick it up
|
||||
func _on_food_reached():
|
||||
var closest_food = _find_closest(food.keys(), get_global_position())
|
||||
if not is_instance_valid(closest_food):
|
||||
# some other ant must have picked it up
|
||||
state_chart.send_event("food_vanished")
|
||||
return
|
||||
|
||||
closest_food.get_parent().remove_child(closest_food)
|
||||
carried_food = closest_food
|
||||
# remove it from the food set
|
||||
food.erase(closest_food)
|
||||
# it's collected, so remove it from the food group
|
||||
closest_food.remove_from_group("food")
|
||||
# add it to our ant so it moves with us
|
||||
add_child(closest_food)
|
||||
closest_food.position = Vector2.ZERO
|
||||
closest_food.scale = Vector2(0.5, 0.5)
|
||||
|
||||
# place a marker pointing to the food (0 means point into the current direction)
|
||||
var marker = _place_marker(Marker.MarkerType.FOOD, global_position, 0)
|
||||
food_markers[marker] = true
|
||||
|
||||
# notify the state chart that we picked up food
|
||||
state_chart.send_event("food_picked_up")
|
||||
|
||||
|
||||
|
||||
## Called when we are returning home and need a new target.
|
||||
func _on_idle_returning_home():
|
||||
var current_position := get_global_position()
|
||||
|
||||
# if the nest is nearby, drop off the food
|
||||
if nest != null:
|
||||
state_chart.send_event("nest_detected")
|
||||
return
|
||||
|
||||
var target_position := Vector2()
|
||||
# if we have nest markers nearby travel into the general direction of the closest one points
|
||||
if nest_markers.size() > 0:
|
||||
# refresh them
|
||||
for marker in nest_markers.keys():
|
||||
marker.refresh()
|
||||
|
||||
var closest_nest_marker := _find_closest(nest_markers.keys(), current_position)
|
||||
var direction = Vector2.RIGHT.rotated(closest_nest_marker.get_rotation())
|
||||
target_position = current_position + (direction * SEGMENT_LENGTH)
|
||||
|
||||
# if we have no nest markers or the navigation agent couldn't reach
|
||||
# the position of the last target pick a random direction
|
||||
if nest_markers.size() == 0 or not navigation_agent.is_target_reachable():
|
||||
var random_angle := randf() * 2 * PI
|
||||
# then calculate the x and y components of the vector
|
||||
var random_x := cos(random_angle) * SEGMENT_LENGTH
|
||||
var random_y := sin(random_angle) * SEGMENT_LENGTH
|
||||
|
||||
# add the random vector to the current position
|
||||
target_position = current_position + Vector2(random_x, random_y)
|
||||
|
||||
navigation_agent.set_target_position(target_position)
|
||||
state_chart.set_expression_property("target_position", target_position)
|
||||
state_chart.send_event("destination_set")
|
||||
return
|
||||
|
||||
## Called when we are returning home and detected the nest
|
||||
func _on_nest_detected():
|
||||
# travel to the nest
|
||||
navigation_agent.set_target_position(nest.global_position)
|
||||
state_chart.set_expression_property("target_position", nest.global_position)
|
||||
|
||||
|
||||
## Called when we have arrived at the nest and want to drop off the food
|
||||
func _on_nest_reached():
|
||||
# drop off the food
|
||||
carried_food.get_parent().remove_child(carried_food)
|
||||
carried_food.queue_free()
|
||||
carried_food = null
|
||||
# notify the state chart that we dropped off the food
|
||||
state_chart.send_event("food_dropped")
|
||||
|
||||
|
||||
## Called while travelling to a destination
|
||||
func _on_travelling_state_physics_processing(_delta):
|
||||
# get the next position on the path
|
||||
var path_position = navigation_agent.get_next_path_position()
|
||||
# and move towards it
|
||||
velocity = (path_position - get_global_position()).normalized() * navigation_agent.max_speed
|
||||
look_at(path_position)
|
||||
move_and_slide()
|
||||
|
||||
|
||||
func _on_input_event(_viewport, event, _shape_idx):
|
||||
# if the left mouse button is up emit the clicked signal
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed() == false:
|
||||
# print("clicked")
|
||||
clicked.emit(self)
|
||||
|
||||
|
||||
## Called when the ant is sensing something nearby.
|
||||
func _on_sensor_area_area_entered(area:Area2D):
|
||||
var node = area
|
||||
if area.has_meta("owner"):
|
||||
node = area.get_node(area.get_meta("owner"))
|
||||
|
||||
|
||||
if node.is_in_group("marker"):
|
||||
# it's a marker
|
||||
if node.is_in_group("food"):
|
||||
food_markers[node] = true
|
||||
elif node.is_in_group("nest"):
|
||||
nest_markers[node] = true
|
||||
elif node.is_in_group("food"):
|
||||
# it's food
|
||||
food[node] = true
|
||||
elif node.is_in_group("nest"):
|
||||
# it's the nest
|
||||
nest = node
|
||||
|
||||
state_chart.set_expression_property("nest_markers", nest_markers.size())
|
||||
state_chart.set_expression_property("food_markers", food_markers.size())
|
||||
|
||||
|
||||
|
||||
func _on_sensor_area_area_exited(area:Area2D):
|
||||
var node = area
|
||||
if area.has_meta("owner"):
|
||||
node = area.get_node(area.get_meta("owner"))
|
||||
|
||||
if node.is_in_group("marker"):
|
||||
# it's a marker
|
||||
if node.is_in_group("food"):
|
||||
food_markers.erase(node)
|
||||
elif node.is_in_group("nest"):
|
||||
nest_markers.erase(node)
|
||||
elif node.is_in_group("food"):
|
||||
# it's food
|
||||
food.erase(node)
|
||||
elif node.is_in_group("nest"):
|
||||
# it's the nest
|
||||
nest = null
|
||||
|
||||
state_chart.set_expression_property("nest_markers", nest_markers.size())
|
||||
state_chart.set_expression_property("food_markers", nest_markers.size())
|
||||
|
||||
|
||||
|
||||
|
||||
## Finds the closest position to the given position from the given list of nodes.
|
||||
func _find_closest(targets:Array, from:Vector2) -> Node2D:
|
||||
var shortest_distance := 99999999.00
|
||||
var result = null
|
||||
|
||||
for target in targets:
|
||||
var distance := from.distance_squared_to(target.get_global_position())
|
||||
if distance < shortest_distance:
|
||||
shortest_distance = distance
|
||||
result = target
|
||||
|
||||
return result
|
||||
|
||||
|
||||
## Places a marker of the given type at the given position
|
||||
func _place_marker(type:Marker.MarkerType, target_position:Vector2, offset:float = PI) -> Marker:
|
||||
var marker = marker_scene.instantiate()
|
||||
marker.initialize(type)
|
||||
# add to the tree on our parent
|
||||
get_parent().add_child.call_deferred(marker)
|
||||
# set the position to our current position
|
||||
marker.set_global_position(target_position)
|
||||
# set the marker rotation to look opposite to the direction we are facing
|
||||
marker.set_rotation(get_rotation() + offset)
|
||||
return marker
|
||||
|
||||
|
||||
func _place_nest_marker():
|
||||
# if there are already nest markers around, just refresh them
|
||||
if nest_markers.size() > 0:
|
||||
for marker in nest_markers:
|
||||
marker.refresh()
|
||||
else:
|
||||
# otherwise place a new one
|
||||
_place_marker(Marker.MarkerType.NEST, global_position)
|
||||
|
||||
|
||||
func _place_food_marker():
|
||||
_place_marker(Marker.MarkerType.FOOD, global_position)
|
||||
|
||||
|
||||
func _maintenance(_delta):
|
||||
# remove all markers which are no longer valid
|
||||
for marker in food_markers.keys():
|
||||
if not is_instance_valid(marker):
|
||||
food_markers.erase(marker)
|
||||
|
||||
for marker in nest_markers.keys():
|
||||
if not is_instance_valid(marker):
|
||||
nest_markers.erase(marker)
|
||||
|
||||
|
Reference in New Issue
Block a user