Added health bar and snail behavior enhancements

- Introduced a health bar for flowers, which updates based on the current GameState values.
- Enhanced snail behavior with eating and sleeping states. Snails now have a chance to switch to the sleeping state while eating.
- Improved mouse interaction with snails, allowing them to potentially switch to sleep mode when clicked.
- Refactored cursor management into its own class for better code organization and readability.
- Updated drone placement logic to use the new CursorManager class.
- Added functionality for bees to replenish flower nectar levels after a certain number of deposits.
This commit is contained in:
Dan 2024-05-15 13:57:31 +01:00
parent 2a9e78b52e
commit f0a7c5ca05
12 changed files with 167 additions and 33 deletions

View file

@ -126,3 +126,13 @@ libraries = {
autoplay = "Highlight" autoplay = "Highlight"
[node name="Snail" parent="." instance=ExtResource("5_5uu7l")] [node name="Snail" parent="." instance=ExtResource("5_5uu7l")]
[node name="HealthBar" type="ProgressBar" parent="."]
offset_left = -50.0
offset_top = 90.0
offset_right = 50.0
offset_bottom = 110.0
mouse_filter = 2
max_value = 10.0
step = 1.0
show_percentage = false

View file

@ -1,24 +1,33 @@
[gd_scene load_steps=5 format=3 uid="uid://bnwvtlsvxjmel"] [gd_scene load_steps=6 format=3 uid="uid://bnwvtlsvxjmel"]
[ext_resource type="Script" path="res://entities/scripts/snail.gd" id="1_lkvd1"] [ext_resource type="Script" path="res://entities/scripts/snail.gd" id="1_lkvd1"]
[ext_resource type="Script" path="res://entities/scripts/finite_state_machine.gd" id="1_tejvt"] [ext_resource type="Script" path="res://entities/scripts/finite_state_machine.gd" id="1_tejvt"]
[ext_resource type="Script" path="res://entities/snail/states/snail_sleeping.gd" id="3_wnrnl"] [ext_resource type="Script" path="res://entities/snail/states/snail_sleeping.gd" id="3_wnrnl"]
[ext_resource type="Script" path="res://entities/snail/states/snail_eating.gd" id="4_1abwi"] [ext_resource type="Script" path="res://entities/snail/states/snail_eating.gd" id="4_1abwi"]
[node name="Snail" type="Sprite2D"] [sub_resource type="CircleShape2D" id="CircleShape2D_2whjo"]
radius = 42.0476
[node name="Snail" type="CharacterBody2D"]
collision_layer = 8
collision_mask = 8
input_pickable = true
script = ExtResource("1_lkvd1") script = ExtResource("1_lkvd1")
[node name="AnimationPlayer" type="AnimationPlayer" parent="."] [node name="AnimationPlayer" type="AnimationPlayer" parent="."]
[node name="Polygon2D" type="Polygon2D" parent="."] [node name="Polygon2D" type="Polygon2D" parent="."]
polygon = PackedVector2Array(-8, -8, 4, -11, 10, 5, -7, 9, -25, 8, -22, 0, -11, 0) polygon = PackedVector2Array(-8, -8, -5, -1, 10, 5, -7, 9, -25, 8, -28, -5, -20, -11)
[node name="StateMachine" type="Node" parent="." node_paths=PackedStringArray("initial_state")] [node name="StateMachine" type="Node" parent="." node_paths=PackedStringArray("initial_state")]
script = ExtResource("1_tejvt") script = ExtResource("1_tejvt")
initial_state = NodePath("Sleeping") initial_state = NodePath("Eating")
[node name="Sleeping" type="Node" parent="StateMachine"] [node name="Sleeping" type="Node" parent="StateMachine"]
script = ExtResource("3_wnrnl") script = ExtResource("3_wnrnl")
[node name="Eating" type="Node" parent="StateMachine"] [node name="Eating" type="Node" parent="StateMachine"]
script = ExtResource("4_1abwi") script = ExtResource("4_1abwi")
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource("CircleShape2D_2whjo")

View file

@ -1,6 +1,6 @@
class_name DirectorDrone extends Drone class_name DirectorDrone extends Drone
@onready var edit_cursor : Resource = preload("res://resources/cursors/message_dots_round.png")
@onready var label : Label = get_node("Label") @onready var label : Label = get_node("Label")
@export var visit_order : int = 0 : @export var visit_order : int = 0 :
@ -17,21 +17,19 @@ func _on_click_detection_mouse_entered() -> void:
if GameState.placing_drone == false: if GameState.placing_drone == false:
Log.pr("Mouse entered the director drone!") Log.pr("Mouse entered the director drone!")
label.visible = true label.visible = true
Input.set_custom_mouse_cursor(edit_cursor, Input.CURSOR_ARROW, Vector2(32, 32)) CursorMgr.edit()
func _on_click_detection_mouse_exited() -> void: func _on_click_detection_mouse_exited() -> void:
if GameState.placing_drone == false: if GameState.placing_drone == false:
Log.pr("Mouse exited the director drone!") Log.pr("Mouse exited the director drone!")
label.visible = false label.visible = false
#Input.set_custom_mouse_cursor(null) CursorMgr.reset_cursor()
GameState.reset_cursor()
func _on_click_detection_input_event(_viewport:Node, event:InputEvent, _shape_idx:int) -> void: func _on_click_detection_input_event(_viewport:Node, event:InputEvent, _shape_idx:int) -> void:
if GameState.placing_drone == false: if GameState.placing_drone == false:
if (event is InputEventMouseButton && event.button_index == MOUSE_BUTTON_RIGHT && event.pressed): if (event is InputEventMouseButton && event.button_index == MOUSE_BUTTON_RIGHT && event.pressed):
#Input.set_custom_mouse_cursor(null) CursorMgr.reset_cursor()
GameState.reset_cursor()
queue_free() queue_free()
get_parent().get_parent().update_director_drone_list() get_parent().get_parent().update_director_drone_list()

View file

@ -28,6 +28,12 @@ func _physics_process(delta : float) -> void:
if current_state: if current_state:
current_state.physics_update(delta) current_state.physics_update(delta)
func get_current_state_name() -> String:
if current_state:
return current_state.name.to_lower()
else:
return ""
# Use force_change_state cautiously, it immediately switches to a state regardless of any transitions. # Use force_change_state cautiously, it immediately switches to a state regardless of any transitions.
# This is used to force us into a 'death state' when killed # This is used to force us into a 'death state' when killed
func force_change_state(new_state : String) -> void: func force_change_state(new_state : String) -> void:

View file

@ -10,17 +10,25 @@ var spawn_snails : bool = false
@onready var spawn_area : CollisionShape2D = $FlowerCollectionArea/CollisionShape2D @onready var spawn_area : CollisionShape2D = $FlowerCollectionArea/CollisionShape2D
@onready var snail : Snail = $Snail @onready var snail : Snail = $Snail
var last_health_check : float = 0
var health_check_timer : float = 0.5
@onready var health_bar : ProgressBar = $HealthBar
func _ready() -> void: func _ready() -> void:
hide_outline() hide_outline()
setup_healthbar()
## Check if this level is spawning snails or not ## Check if this level is spawning snails or not
if GameState.spawn_snails: if GameState.spawn_snails:
Log.pr("Going to be spawning snails!") Log.pr("Going to be spawning snails!")
spawn_snails = true spawn_snails = true
Log.pr(get_random_snail_spawn()) Log.pr(get_random_circumference_points())
snail.global_position = get_random_snail_spawn() snail.global_position = get_random_circumference_points()
func _process(delta: float) -> void:
update_healthbar(delta)
func show_outline() -> void: func show_outline() -> void:
outline.visible = true outline.visible = true
@ -29,7 +37,7 @@ func hide_outline() -> void:
outline.visible = false outline.visible = false
func get_random_snail_spawn() -> Vector2: func get_random_circumference_points() -> Vector2:
var circle_radius : float = spawn_area.shape.radius var circle_radius : float = spawn_area.shape.radius
var random_angle : float = randf_range(0, TAU) var random_angle : float = randf_range(0, TAU)
@ -40,3 +48,23 @@ func get_random_snail_spawn() -> Vector2:
var random_point : Vector2 = circle_center + Vector2(x, y) var random_point : Vector2 = circle_center + Vector2(x, y)
return random_point return random_point
## Initial setup of the health bar - updating the values
# and hiding it from view.
func setup_healthbar() -> void:
health_bar.max_value = GameState.max_flower_nectar_level
health_bar.value = GameState.flower_nectar_level
health_bar.visible = false
## Update the health bar based on the current GameState values
# and display it if required. Conversely, hide it if the flower is at max powah.
func update_healthbar(delta : float) -> void:
last_health_check += delta
if last_health_check >= health_check_timer:
last_health_check = 0
if GameState.flower_nectar_level < GameState.max_flower_nectar_level:
health_bar.value = GameState.flower_nectar_level
health_bar.visible = true
else:
health_bar.value = GameState.max_flower_nectar_level
health_bar.visible = false

View file

@ -1,6 +1,46 @@
extends Sprite2D extends CharacterBody2D
class_name Snail class_name Snail
@onready var fsm : FiniteStateMachine = $StateMachine as FiniteStateMachine
@onready var flowers : Flowers = get_parent()
var enabled : bool = false var enabled : bool = false
var eating : bool = false var eating : bool = false
var speed : float = 20.0
var mouse_over : bool = false
func _ready() -> void:
connect("mouse_entered", Callable(self, "on_mouse_entered"))
connect("mouse_exited", Callable(self, "on_mouse_exited"))
# Detect mouse left click and trigger function
func _input(event : InputEvent) -> void:
if mouse_over and eating:
if event is InputEventMouseButton:
if (event is InputEventMouseButton && event.button_index == MOUSE_BUTTON_LEFT && event.pressed):
maybe_sleep()
func eat() -> void:
# Play the munch animation and noise
# Reduce the GameState flower_nectar_level
GameState.flower_nectar_level -= 1
func maybe_sleep() -> void:
# If the snail is still eating, then we want a 30% chance of switching it it to the sleeping state
if eating:
if randf() < 0.3:
if fsm.get_current_state_name() == "eating":
fsm.change_state(fsm.current_state, "sleeping")
func get_random_target() -> Vector2:
return flowers.get_random_circumference_points()
func on_mouse_entered() -> void:
mouse_over = true
Log.pr("Mouse entered the snail!")
func on_mouse_exited() -> void:
# Reset the cursor to the default
mouse_over = false
CursorMgr.reset_cursor()

View file

@ -3,16 +3,36 @@ class_name SnailEating
@onready var snail : Snail = get_parent().get_parent() as Snail # I think this is bad but I dont care it works @onready var snail : Snail = get_parent().get_parent() as Snail # I think this is bad but I dont care it works
var eat_interval : float = 3.0
var eat_timer : float = 0.0
var move_to : Vector2 = Vector2.ZERO
func enter(_msg : Dictionary = {}) -> void: func enter(_msg : Dictionary = {}) -> void:
Log.pr("I am a snail...") Log.pr("I am a snail and I will eat!")
snail.eating = true snail.eating = true
func exit() -> void: func exit() -> void:
snail.eating = false snail.eating = false
func update(_delta : float) -> void: func update(delta : float) -> void:
pass eat_timer += delta
if eat_timer >= eat_interval:
snail.eat()
eat_timer = 0.0
func physics_update(_delta : float) -> void: func physics_update(_delta : float) -> void:
pass
if move_to == Vector2.ZERO:
move_to = snail.get_random_target()
if snail.global_position.distance_to(move_to) > 3:
snail.velocity = snail.global_position.direction_to(move_to) * snail.speed
snail.move_and_slide()
snail.look_at(move_to)
else:
move_to = Vector2.ZERO

View file

@ -144,6 +144,7 @@ unique_name_in_owner = true
z_index = 900 z_index = 900
offset_right = 1280.0 offset_right = 1280.0
offset_bottom = 720.0 offset_bottom = 720.0
mouse_filter = 1
[node name="DroneManager" parent="." instance=ExtResource("13_pibpn")] [node name="DroneManager" parent="." instance=ExtResource("13_pibpn")]
unique_name_in_owner = true unique_name_in_owner = true

View file

@ -19,8 +19,9 @@ config/windows_native_icon="res://resources/icons/icon.ico"
[autoload] [autoload]
GameState="*res://utility/game_state.gd"
Str="*res://utility/utility_strings.gd" Str="*res://utility/utility_strings.gd"
CursorMgr="*res://utility/cursor_manager.gd"
GameState="*res://utility/game_state.gd"
SceneMgr="*res://utility/global_scene_manager.gd" SceneMgr="*res://utility/global_scene_manager.gd"
HighScoreMgr="*res://utility/high_scores.gd" HighScoreMgr="*res://utility/high_scores.gd"

View file

@ -13,7 +13,6 @@ var director_drones : Array = [] # List of all director drones in the world
@onready var drone_controls : HBoxContainer = %DroneControls @onready var drone_controls : HBoxContainer = %DroneControls
@onready var ui_controls : UIComponent = get_parent().get_node("UiComponent") @onready var ui_controls : UIComponent = get_parent().get_node("UiComponent")
@onready var spawned_drones_container : Node = get_node("SpawnedDrones") @onready var spawned_drones_container : Node = get_node("SpawnedDrones")
@onready var place_cursor : Resource = preload("res://resources/cursors/target_round_b.png")
# Drones! # Drones!
@onready var director_drone : Resource = preload("res://entities/DirectorDrone.tscn") @onready var director_drone : Resource = preload("res://entities/DirectorDrone.tscn")
@ -83,7 +82,7 @@ func spawn_drone(drone_type : String) -> void:
func place_drone(drone_type : String) -> void: func place_drone(drone_type : String) -> void:
if !spawning_drone: if !spawning_drone:
Input.set_custom_mouse_cursor(place_cursor, Input.CURSOR_ARROW, Vector2(32, 32)) CursorMgr.place()
drone_controls.disable_buttons() drone_controls.disable_buttons()
Log.pr("Placing " + drone_type + "...") Log.pr("Placing " + drone_type + "...")
spawning_drone = true spawning_drone = true
@ -91,8 +90,7 @@ func place_drone(drone_type : String) -> void:
spawning_type = drone_type spawning_type = drone_type
func cancel_spawning() -> void: func cancel_spawning() -> void:
#Input.set_custom_mouse_cursor(null) CursorMgr.reset_cursor()
GameState.reset_cursor()
drone_controls.reset_button_focus() drone_controls.reset_button_focus()
drone_controls.enable_buttons() drone_controls.enable_buttons()
spawning_drone = false spawning_drone = false

15
utility/cursor_manager.gd Normal file
View file

@ -0,0 +1,15 @@
extends Node
class_name CursorManager
@onready var default_cursor : Resource = preload("res://resources/cursors/pointer_a.png")
@onready var place_cursor : Resource = preload("res://resources/cursors/target_round_b.png")
@onready var edit_cursor : Resource = preload("res://resources/cursors/message_dots_round.png")
func reset_cursor() -> void:
Input.set_custom_mouse_cursor(default_cursor, Input.CURSOR_ARROW, Vector2(8, 8))
func place() -> void:
Input.set_custom_mouse_cursor(place_cursor, Input.CURSOR_ARROW, Vector2(32, 32))
func edit() -> void:
Input.set_custom_mouse_cursor(edit_cursor, Input.CURSOR_ARROW, Vector2(32, 32))

View file

@ -1,8 +1,5 @@
class_name GameStateManager extends Node class_name GameStateManager extends Node
## THIS SHOULD NOT EXIST HERE BUT I AM NOT GOING TO MAKE A NEW CLASS FOR IT RIGHT NOW
@onready var default_cursor : Resource = preload("res://resources/cursors/pointer_a.png")
var placing_drone : bool = false var placing_drone : bool = false
var level_timer : float = 0.0 var level_timer : float = 0.0
@ -15,15 +12,20 @@ var game_over : bool = false
## Game Rules ############################################################## ## Game Rules ##############################################################
# Nectar levels - Depleted by snails, increased by bee visits?? Or time, TBD # Nectar levels - Depleted by snails, increased by bee nectar deposits
# TODO: Decide how to renew the nectar levels
# This is capped out at 10... Seems like a good amount # This is capped out at 10... Seems like a good amount
# This has to be at least 1 at all times or the bees dont know wtf is going on
var flower_nectar_level_charge : int = 0
var flower_nectar_level_charge_required : int = 10
var max_flower_nectar_level : int = 10
var flower_nectar_level : int = 10 : var flower_nectar_level : int = 10 :
get: get:
return flower_nectar_level return flower_nectar_level
set(value): set(value):
if value > 10: if value > max_flower_nectar_level:
flower_nectar_level = 10 flower_nectar_level = 10
elif value < 1:
flower_nectar_level = 1
else: else:
flower_nectar_level = value flower_nectar_level = value
@ -69,11 +71,20 @@ func _process(delta : float) -> void:
if level_started and !level_complete and !game_over: if level_started and !level_complete and !game_over:
level_timer += delta level_timer += delta
## For every 10 times the bees deposit nectar it will charge
# the nectar level of the flowers by 1
func pollenate() -> void:
flower_nectar_level_charge += 1
if flower_nectar_level_charge >= flower_nectar_level_charge_required:
flower_nectar_level += 1
flower_nectar_level_charge = 0
func bee_died() -> void: func bee_died() -> void:
dead_bees += 1 dead_bees += 1
# Add the nectar to the total gathered nectar # Add the nectar to the total gathered nectar
func add_nectar(nectar : int) -> void: func add_nectar(nectar : int) -> void:
pollenate()
gathered_nectar += nectar gathered_nectar += nectar
func add_drone() -> void: func add_drone() -> void:
@ -107,6 +118,3 @@ func reset() -> void:
drones_used = 0 drones_used = 0
dead_bees = 0 dead_bees = 0
spawn_snails = false spawn_snails = false
func reset_cursor() -> void:
Input.set_custom_mouse_cursor(default_cursor, Input.CURSOR_ARROW, Vector2(8, 8))