158 lines
4.5 KiB
GDScript
158 lines
4.5 KiB
GDScript
extends Area2D
|
|
class_name SnakePart
|
|
|
|
const TILE_SIZE = 20
|
|
enum PartTypes {HEAD, BODY, TAIL, DEAD}
|
|
|
|
var part_type : PartTypes = PartTypes.HEAD
|
|
var current_direction : Vector2 = Vector2.RIGHT
|
|
|
|
var inputs : Dictionary[String, Vector2] = {"right": Vector2.RIGHT,
|
|
"left": Vector2.LEFT,
|
|
"up": Vector2.UP,
|
|
"down": Vector2.DOWN}
|
|
|
|
|
|
@onready
|
|
var raycast_right : RayCast2D = $RightRayCast2D
|
|
@onready
|
|
var raycast_left : RayCast2D = $LeftRayCast2D
|
|
@onready
|
|
var raycast_up : RayCast2D = $UpRayCast2D
|
|
@onready
|
|
var raycast_down : RayCast2D = $DownRayCast2D
|
|
|
|
|
|
var snake_part_obj : PackedScene = preload("res://Scenes/snake_part.tscn")
|
|
|
|
@onready
|
|
var timer_ref : Timer = $"../Timer"
|
|
|
|
var next_part : SnakePart = null
|
|
var skip_next_move_propagation : bool = false
|
|
|
|
signal do_movement(new_dir)
|
|
|
|
func _ready() -> void:
|
|
if part_type == PartTypes.HEAD:
|
|
|
|
# Add to group for easy access from other nodes
|
|
add_to_group("Head")
|
|
|
|
# Set up a body part
|
|
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)
|
|
|
|
# Set up a tail part
|
|
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()
|
|
|
|
|
|
|
|
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)
|
|
|
|
# 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
|
|
|
|
func check_movement() -> void:
|
|
# Get the correct raycast for the direction we are moving
|
|
var raycast_to_use : RayCast2D
|
|
if current_direction == Vector2.RIGHT:
|
|
raycast_to_use = raycast_right
|
|
elif current_direction == Vector2.LEFT:
|
|
raycast_to_use = raycast_left
|
|
elif current_direction == Vector2.UP:
|
|
raycast_to_use = raycast_up
|
|
elif current_direction == Vector2.DOWN:
|
|
raycast_to_use = raycast_down
|
|
|
|
# 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 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!
|
|
print("Game lost")
|
|
timer_ref.stop()
|
|
|
|
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]
|
|
|
|
|
|
# If we leave the arena
|
|
func _on_level_area_exited(area: Area2D) -> void:
|
|
lose_game()
|