From 7de5b5088492fcd6cb93dfb5e4ad7e0952be3d82 Mon Sep 17 00:00:00 2001 From: TechieDamien Date: Wed, 30 Jul 2025 21:35:10 +0100 Subject: [PATCH] Adds fruit to the arena that grow the snake --- Scenes/fruit.tscn | 18 +++++++++ Scenes/level.tscn | 2 +- Scenes/main.tscn | 6 ++- Scenes/snake_part.tscn | 2 +- Scripts/fruit.gd | 33 +++++++++++++++++ Scripts/fruit.gd.uid | 1 + Scripts/snake_part.gd | 84 ++++++++++++++++++++++++++++-------------- project.godot | 5 +++ 8 files changed, 120 insertions(+), 31 deletions(-) create mode 100644 Scenes/fruit.tscn create mode 100644 Scripts/fruit.gd create mode 100644 Scripts/fruit.gd.uid diff --git a/Scenes/fruit.tscn b/Scenes/fruit.tscn new file mode 100644 index 0000000..2b392b2 --- /dev/null +++ b/Scenes/fruit.tscn @@ -0,0 +1,18 @@ +[gd_scene load_steps=4 format=3 uid="uid://bldekmt1rakjl"] + +[ext_resource type="Script" uid="uid://dcdogfvb8xjod" path="res://Scripts/fruit.gd" id="1_o20pv"] + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_hf2pu"] +size = Vector2(18, 18) + +[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_kum7e"] +size = Vector2(20, 20) + +[node name="Fruit" type="Area2D" groups=["BlocksSpawn"]] +script = ExtResource("1_o20pv") + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +shape = SubResource("RectangleShape2D_hf2pu") + +[node name="Sprite2D" type="Sprite2D" parent="."] +texture = SubResource("PlaceholderTexture2D_kum7e") diff --git a/Scenes/level.tscn b/Scenes/level.tscn index d11fc95..3f64cab 100644 --- a/Scenes/level.tscn +++ b/Scenes/level.tscn @@ -16,7 +16,7 @@ fill = 2 fill_from = Vector2(0.5, 0.495413) fill_to = Vector2(0, 0) -[node name="Level" type="Area2D"] +[node name="Level" type="Area2D" groups=["Level"]] monitorable = false [node name="CollisionShape2D" type="CollisionShape2D" parent="."] diff --git a/Scenes/main.tscn b/Scenes/main.tscn index 1c09971..2d4c5e2 100644 --- a/Scenes/main.tscn +++ b/Scenes/main.tscn @@ -1,7 +1,8 @@ -[gd_scene load_steps=3 format=3 uid="uid://bs6an72avch86"] +[gd_scene load_steps=4 format=3 uid="uid://bs6an72avch86"] [ext_resource type="PackedScene" uid="uid://gkqku38yb2ng" path="res://Scenes/level.tscn" id="1_bo1nx"] [ext_resource type="PackedScene" uid="uid://d0okbjqyaoe0w" path="res://Scenes/snake_part.tscn" id="2_8gbba"] +[ext_resource type="PackedScene" uid="uid://bldekmt1rakjl" path="res://Scenes/fruit.tscn" id="3_8gbba"] [node name="Main" type="Node2D"] @@ -16,5 +17,8 @@ autostart = true [node name="SnakePart" parent="." instance=ExtResource("2_8gbba")] position = Vector2(420, 240) +[node name="Fruit" parent="." instance=ExtResource("3_8gbba")] +position = Vector2(707, 325) + [connection signal="area_exited" from="Level" to="SnakePart" method="_on_level_area_exited"] [connection signal="timeout" from="Timer" to="SnakePart" method="process_movement" binds= [Vector2(0, 0)]] diff --git a/Scenes/snake_part.tscn b/Scenes/snake_part.tscn index aab471a..3f123d7 100644 --- a/Scenes/snake_part.tscn +++ b/Scenes/snake_part.tscn @@ -8,7 +8,7 @@ size = Vector2(18, 18) [sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_kum7e"] size = Vector2(20, 20) -[node name="SnakePart" type="Area2D"] +[node name="SnakePart" type="Area2D" groups=["BlocksSpawn"]] script = ExtResource("1_iuiyg") [node name="CollisionShape2D" type="CollisionShape2D" parent="."] diff --git a/Scripts/fruit.gd b/Scripts/fruit.gd new file mode 100644 index 0000000..545a567 --- /dev/null +++ b/Scripts/fruit.gd @@ -0,0 +1,33 @@ +extends Area2D +class_name Fruit + +const TILE_SIZE = 20 + +var all_positions : Array[Vector2] = [] + +func _ready() -> void: + var level : Area2D = get_tree().get_first_node_in_group("Level") + var x_bounds : Vector2 = level.position.x * Vector2.ONE + Vector2(-TILE_SIZE, TILE_SIZE) * level.scale.x / 2 + var y_bounds : Vector2 = level.position.y * Vector2.ONE + Vector2(-TILE_SIZE, TILE_SIZE) * level.scale.y / 2 + + var possible_x : Array = range(x_bounds.x, x_bounds.y, TILE_SIZE) + var possible_y : Array = range(y_bounds.x, y_bounds.y, TILE_SIZE) + + for x in possible_x: + for y in possible_y: + all_positions.append(Vector2(x, y)) + + respawn() + +func respawn() -> void: + var possible_positions = all_positions.duplicate() + + var obstacles : Array[Node] = get_tree().get_nodes_in_group("BlocksSpawn") + + for obstacle in obstacles: + if obstacle is Area2D: + possible_positions.erase(obstacle.position) + + var chosen_position = possible_positions.pick_random() + + position = chosen_position diff --git a/Scripts/fruit.gd.uid b/Scripts/fruit.gd.uid new file mode 100644 index 0000000..df368d4 --- /dev/null +++ b/Scripts/fruit.gd.uid @@ -0,0 +1 @@ +uid://dcdogfvb8xjod diff --git a/Scripts/snake_part.gd b/Scripts/snake_part.gd index 6d91bb5..0611979 100644 --- a/Scripts/snake_part.gd +++ b/Scripts/snake_part.gd @@ -14,19 +14,22 @@ var inputs : Dictionary[String, Vector2] = {"right": Vector2.RIGHT, @onready -var raycast_right = $RightRayCast2D +var raycast_right : RayCast2D = $RightRayCast2D @onready -var raycast_left = $LeftRayCast2D +var raycast_left : RayCast2D = $LeftRayCast2D @onready -var raycast_up = $UpRayCast2D +var raycast_up : RayCast2D = $UpRayCast2D @onready -var raycast_down = $DownRayCast2D +var raycast_down : RayCast2D = $DownRayCast2D -var snake_part_obj = preload("res://Scenes/snake_part.tscn") +var snake_part_obj : PackedScene = preload("res://Scenes/snake_part.tscn") @onready -var timer_ref = $"../Timer" +var timer_ref : Timer = $"../Timer" + +var next_part : SnakePart = null +var skip_next_move_propagation : bool = false signal do_movement(new_dir) @@ -40,6 +43,7 @@ func _ready() -> void: var body = snake_part_obj.instantiate() body.part_type = PartTypes.BODY body.position = (position - current_direction * TILE_SIZE).snapped(Vector2.ONE * TILE_SIZE) + next_part = body get_parent().add_child.call_deferred(body) do_movement.connect(body.process_movement) @@ -47,26 +51,34 @@ func _ready() -> void: var tail = snake_part_obj.instantiate() tail.part_type = PartTypes.TAIL tail.position = (position - current_direction * TILE_SIZE * 2).snapped(Vector2.ONE * TILE_SIZE) + body.next_part = tail get_parent().add_child.call_deferred(tail) body.do_movement.connect(tail.process_movement) else: + + # Free raycasts if we don't need them (consider only loading them in for the head for performance if needed) raycast_down.queue_free() raycast_up.queue_free() raycast_left.queue_free() raycast_right.queue_free() - #elif part_type == PartTypes.BODY: - #area_entered.connect(_on_part_area_entered) - #elif part_type == PartTypes.TAIL: - #area_entered.connect(_on_part_area_entered) func process_movement(new_direction : Vector2) -> void: + + # The head needs to check if we are about to collide with something if part_type == PartTypes.HEAD: check_movement() + + # Update the position of this part position = (position + current_direction * TILE_SIZE).snapped(Vector2.ONE * TILE_SIZE) - #process_collisions(); - do_movement.emit(current_direction) + + # Tell the next part to move and give it the direction we are moving + if !skip_next_move_propagation: + do_movement.emit(current_direction) + skip_next_move_propagation = false + + # Unless we are the head, change our direction to the direction given if new_direction != Vector2.ZERO: current_direction = new_direction @@ -84,24 +96,44 @@ func check_movement() -> void: # Look using the raycast for anything to collide with var object_in_path : Object = raycast_to_use.get_collider() + + # If we found nothing, stop processing collision if object_in_path == null: return + + # If we found a snake part, then either end the game or perform an ouroboros maneouvre if object_in_path is SnakePart: match object_in_path.part_type: PartTypes.BODY: lose_game() PartTypes.TAIL: ouroboros() + if object_in_path is Fruit: + object_in_path.respawn() + extend() -#func process_collisions() -> void: - #var colliding_areas = get_overlapping_areas() - #for area in colliding_areas: - #if area is SnakePart: - #match area.part_type: - #PartTypes.BODY: - #lose_game() - #PartTypes.TAIL: - #ouroboros() +func extend() -> void: + # Remove old connection to previous next part + do_movement.disconnect(next_part.process_movement) + + # Set up a body part to be in the chain + var new_body = snake_part_obj.instantiate() + new_body.part_type = PartTypes.BODY + new_body.position = (position).snapped(Vector2.ONE * TILE_SIZE) + get_parent().add_child(new_body) + do_movement.connect(new_body.process_movement) + new_body.do_movement.connect(next_part.process_movement) + new_body.next_part = next_part + + # Set direction of new part + new_body.current_direction = current_direction + + # Update reference to next part + next_part = new_body + + # Mark the next movement to not propagate the movement down the chain + skip_next_move_propagation = true + func lose_game() -> void: # You lose! @@ -112,18 +144,14 @@ func ouroboros() -> void: print("You have achieved the ouroboros!") func _unhandled_input(event: InputEvent) -> void: + + # Change direction if we are the head if event is InputEventKey and part_type == PartTypes.HEAD: for dir in inputs.keys(): if event.is_action_pressed(dir): current_direction = inputs[dir] -#func _on_part_area_entered(area: Area2D) -> void: - #if area.get("part_type") != PartTypes.HEAD: - #return - #if part_type == PartTypes.BODY or part_type == PartTypes.DEAD: - #lose_game() - #elif part_type == PartTypes.TAIL: - #ouroboros() +# If we leave the arena func _on_level_area_exited(area: Area2D) -> void: lose_game() diff --git a/project.godot b/project.godot index 37c8b28..00507ff 100644 --- a/project.godot +++ b/project.godot @@ -14,6 +14,11 @@ config/name="Ouroboros" config/features=PackedStringArray("4.4", "GL Compatibility") config/icon="res://icon.svg" +[global_group] + +Level="" +BlocksSpawn="" + [input] right={