2023-10-02 13:12:08 +00:00
|
|
|
extends Area2D
|
|
|
|
class_name Ghost
|
|
|
|
|
2023-10-02 15:53:13 +00:00
|
|
|
## Ghost previewing the instantiation of a scene.
|
2023-10-02 13:12:08 +00:00
|
|
|
|
2023-10-02 15:53:13 +00:00
|
|
|
## The [CollisionShape2D] to use to check for placement checks.
|
|
|
|
##
|
|
|
|
## MUST consist of a [RectangleShape2D].
|
|
|
|
@onready var placement_shape: CollisionShape2D = $PlacementShape
|
2023-10-02 14:22:03 +00:00
|
|
|
|
2023-10-02 15:53:13 +00:00
|
|
|
## The [Sprite2D] node previewing the scene.
|
|
|
|
@onready var preview_sprite: Sprite2D = $PlacementShape/PreviewSprite
|
2023-10-02 14:22:03 +00:00
|
|
|
|
2023-10-02 16:29:05 +00:00
|
|
|
## The collision mask of objects that should prevent this object's placement.
|
|
|
|
@export_flags_2d_physics var collision_mask_prevent_placement: int
|
|
|
|
|
|
|
|
## The collision mask of objects that should be deleted on this object's placement.
|
|
|
|
@export_flags_2d_physics var collision_mask_delete_placement: int
|
|
|
|
|
2023-10-02 15:53:13 +00:00
|
|
|
## The texture that the preview sprite should display.
|
|
|
|
@export var preview_texture: Texture2D:
|
|
|
|
get:
|
|
|
|
return preview_texture
|
|
|
|
set(value):
|
|
|
|
preview_texture = value
|
2023-10-02 16:29:05 +00:00
|
|
|
# Quick priority fix
|
|
|
|
if preview_sprite:
|
|
|
|
preview_sprite.texture = value
|
2023-10-02 14:22:03 +00:00
|
|
|
|
2023-10-02 15:53:13 +00:00
|
|
|
## Whether the ghost can be placed at the current location of the ghost.
|
|
|
|
##
|
|
|
|
## Computed by checking if [placement_shape] overlaps any entity and is inside the [PlacementArea] of the [Bottle].
|
|
|
|
var can_place: bool:
|
2023-10-02 14:22:03 +00:00
|
|
|
get:
|
|
|
|
return can_place
|
|
|
|
set(value):
|
2023-10-02 18:28:08 +00:00
|
|
|
can_place = value
|
2023-10-02 14:22:03 +00:00
|
|
|
if value:
|
2023-10-02 15:53:13 +00:00
|
|
|
preview_sprite.modulate = Color(1.0, 1.0, 1.0, 0.5)
|
2023-10-02 14:22:03 +00:00
|
|
|
else:
|
2023-10-02 15:53:13 +00:00
|
|
|
preview_sprite.modulate = Color(1.0, 0.0, 0.0, 0.5)
|
|
|
|
|
2023-10-05 22:09:24 +00:00
|
|
|
|
|
|
|
## Whether the ghost is currently being dragged or not.
|
|
|
|
var is_being_dragged: bool
|
2023-10-02 15:53:13 +00:00
|
|
|
|
|
|
|
|
2023-10-02 16:29:05 +00:00
|
|
|
func _ready():
|
|
|
|
collision_mask = collision_mask_prevent_placement | collision_mask_delete_placement
|
|
|
|
preview_sprite.texture = preview_texture
|
|
|
|
|
|
|
|
|
2023-10-05 22:09:24 +00:00
|
|
|
## The degrees to rotate by when rotation is quantized.
|
|
|
|
@export_range(1, 180, 1) var rotation_quantized_degrees: float = 45
|
|
|
|
|
|
|
|
## The degrees to rotate by when rotation is quantized and precise rotation is being requested.
|
|
|
|
@export_range(1, 180, 1) var rotation_quantized_precise_degrees: float = 5
|
|
|
|
|
|
|
|
|
|
|
|
## Get the degrees a piece should be rotated by when rotation is quantized.
|
|
|
|
func get_rotation_quantized_degrees() -> float:
|
|
|
|
var degrees = rotation_quantized_precise_degrees if Input.is_action_pressed("ghost_precise") else rotation_quantized_degrees
|
|
|
|
var intensity = 1.0 if Input.is_action_just_pressed("ghost_clockwise") else 0.0 - 1.0 if Input.is_action_just_pressed("ghost_counterclockwise") else 0.0
|
|
|
|
return degrees * intensity
|
|
|
|
|
|
|
|
## The last [InputEventMouse] received.
|
|
|
|
var last_mouse_event: InputEventMouse = null
|
|
|
|
|
|
|
|
## The last [InputEventScreenTouch] or [InputEventScreenDrag] received for each possible touch index.
|
|
|
|
var last_touch_events: Array[InputEvent] = [
|
|
|
|
null, null, null, null, null,
|
|
|
|
null, null, null, null, null,
|
|
|
|
]
|
|
|
|
|
|
|
|
func count_active_touch_events() -> int:
|
|
|
|
var total = 0
|
|
|
|
for event in last_touch_events:
|
|
|
|
if event != null:
|
|
|
|
total += 1
|
|
|
|
return total
|
|
|
|
|
2023-10-02 15:53:13 +00:00
|
|
|
func _input(event: InputEvent):
|
2023-10-05 22:09:24 +00:00
|
|
|
# Count active events
|
|
|
|
var count = count_active_touch_events()
|
|
|
|
#print("[Ghost] Counting ", count, " active events!")
|
|
|
|
|
|
|
|
# Mouse drag
|
|
|
|
if count == 0:
|
|
|
|
# Mouse drag
|
|
|
|
if event is InputEventMouseMotion and last_mouse_event:
|
|
|
|
position += event.relative
|
|
|
|
# Touch drag
|
|
|
|
elif count == 1:
|
|
|
|
# Touch drag
|
|
|
|
if event is InputEventScreenDrag:
|
|
|
|
position += event.relative
|
|
|
|
# Handle rotation
|
|
|
|
elif count == 2:
|
|
|
|
# Find the previous event
|
|
|
|
var previous
|
|
|
|
if event is InputEventScreenTouch or event is InputEventScreenDrag:
|
|
|
|
previous = last_touch_events[event.index]
|
|
|
|
# At this point previous shouldn't be null
|
|
|
|
# If it is, just try again at the next frame
|
|
|
|
if previous == null:
|
2023-10-05 22:14:54 +00:00
|
|
|
#print("[Ghost] Rotation occurred, but previous was null, so it was cancelled.")
|
2023-10-05 22:09:24 +00:00
|
|
|
return
|
|
|
|
# Find the other event
|
|
|
|
var other
|
|
|
|
for last_touch_index in len(last_touch_events):
|
|
|
|
if event is InputEventScreenTouch or event is InputEventScreenDrag:
|
|
|
|
if event.index == last_touch_index:
|
|
|
|
continue
|
|
|
|
if last_touch_events[last_touch_index] != null:
|
|
|
|
other = last_touch_events[last_touch_index]
|
|
|
|
# At this point other shouldn't be null
|
|
|
|
# If it is, just try again at the next frame
|
|
|
|
if other == null:
|
2023-10-05 22:14:54 +00:00
|
|
|
#print("[Ghost] Rotation occurred, but other was null, so it was cancelled.")
|
2023-10-05 22:09:24 +00:00
|
|
|
return
|
|
|
|
# Find the two vectors between the touches, one using the previous position, and one using the current one
|
|
|
|
var previous_vec: Vector2 = previous.position - other.position
|
|
|
|
var current_vec: Vector2 = event.position - other.position
|
2023-10-05 22:14:54 +00:00
|
|
|
#print("[Ghost] previous_vec: ", previous_vec, " | current_vec: ", current_vec)
|
2023-10-05 22:09:24 +00:00
|
|
|
# Find the angle between the two vectors
|
|
|
|
var rotation_radians = previous_vec.angle_to(current_vec)
|
2023-10-05 22:14:54 +00:00
|
|
|
#print("[Ghost] Rotation was successful, rotating by: ", rotation_radians)
|
2023-10-05 22:09:24 +00:00
|
|
|
# Apply the rotation
|
|
|
|
rotation += rotation_radians
|
|
|
|
|
|
|
|
# Store last events
|
2023-10-02 15:53:13 +00:00
|
|
|
if event is InputEventMouseButton:
|
2023-10-05 22:09:24 +00:00
|
|
|
last_mouse_event = event if event.pressed else null
|
|
|
|
#print("[Ghost] last_mouse_event updated in response to a InputEventMouseButton: ", last_mouse_event)
|
|
|
|
elif event is InputEventMouseMotion and last_mouse_event != null:
|
|
|
|
last_mouse_event = event
|
|
|
|
#print("[Ghost] last_mouse_event updated in response to a InputEventMouseMotion: ", last_mouse_event)
|
2023-10-02 15:53:13 +00:00
|
|
|
elif event is InputEventScreenTouch:
|
2023-10-05 22:09:24 +00:00
|
|
|
last_touch_events[event.index] = event if event.pressed else null
|
|
|
|
#print("[Ghost] last_touch_events[", event.index , "] updated in response to a InputEventScreenTouch: ", last_mouse_event)
|
|
|
|
elif event is InputEventScreenDrag:
|
|
|
|
last_touch_events[event.index] = event
|
|
|
|
#print("[Ghost] last_touch_events[", event.index , "] updated in response to a InputEventScreenDrag: ", last_mouse_event)
|
|
|
|
|
|
|
|
|
|
|
|
func _physics_process(_delta: float):
|
|
|
|
# Handle quantized rotation
|
|
|
|
rotation_degrees += get_rotation_quantized_degrees()
|
2023-10-05 22:11:08 +00:00
|
|
|
# Update collision
|
|
|
|
update_can_place()
|
2023-10-05 22:09:24 +00:00
|
|
|
|
2023-10-02 15:53:13 +00:00
|
|
|
|
2023-10-02 18:28:08 +00:00
|
|
|
## Update the value of [can_place].
|
2023-10-02 16:29:05 +00:00
|
|
|
# DIRTY HACK: Relies on the placeable area being perfectly surrounded by solid bodies.
|
2023-10-02 18:28:08 +00:00
|
|
|
func update_can_place():
|
2023-10-02 16:29:05 +00:00
|
|
|
var no_overlapping_bodies: bool = true
|
2023-10-02 15:53:13 +00:00
|
|
|
var overlapping_bodies = get_overlapping_bodies()
|
2023-10-02 16:29:05 +00:00
|
|
|
for body in overlapping_bodies:
|
|
|
|
if body is TileMap:
|
|
|
|
no_overlapping_bodies = false
|
|
|
|
elif body is PhysicsBody2D:
|
|
|
|
var body_prevents_placement = bool(body.collision_layer & collision_mask_prevent_placement)
|
|
|
|
no_overlapping_bodies = no_overlapping_bodies and not body_prevents_placement
|
2023-10-02 14:22:03 +00:00
|
|
|
|
2023-10-02 16:29:05 +00:00
|
|
|
var is_in_placeable_area: bool = false
|
2023-10-02 15:53:13 +00:00
|
|
|
var overlapping_areas = get_overlapping_areas()
|
|
|
|
for area in overlapping_areas:
|
2023-10-02 16:29:05 +00:00
|
|
|
if area is PlaceableArea:
|
|
|
|
is_in_placeable_area = true
|
|
|
|
|
|
|
|
can_place = no_overlapping_bodies and is_in_placeable_area
|
|
|
|
|
|
|
|
|
|
|
|
## The [PackedScene] that this node should instantiate.
|
|
|
|
@export var scene_to_instantiate: PackedScene
|
|
|
|
|
|
|
|
## The [Node] instatiated scenes should be added as children to.
|
|
|
|
@export var target: Node
|
|
|
|
|
|
|
|
## Emitted when the [materialize] function has finished executing.
|
|
|
|
signal materialized(node: Node)
|
|
|
|
|
|
|
|
func materialize():
|
2023-10-02 18:28:08 +00:00
|
|
|
if not can_place:
|
|
|
|
return null
|
|
|
|
var overlapping_bodies = get_overlapping_bodies()
|
|
|
|
for body in overlapping_bodies:
|
|
|
|
if body is PhysicsBody2D:
|
|
|
|
if body.collision_layer & collision_mask_delete_placement:
|
|
|
|
body.queue_free()
|
2023-10-02 16:29:05 +00:00
|
|
|
var instantiated = scene_to_instantiate.instantiate()
|
|
|
|
instantiated.global_position = global_position
|
|
|
|
instantiated.rotation = rotation
|
|
|
|
target.add_child(instantiated)
|
|
|
|
materialized.emit(instantiated)
|
2023-10-02 17:49:55 +00:00
|
|
|
return instantiated
|