diff --git a/interface/ghost/ghost.gd b/interface/ghost/ghost.gd index 7f9ab7e..edaa3fe 100644 --- a/interface/ghost/ghost.gd +++ b/interface/ghost/ghost.gd @@ -1,8 +1,27 @@ extends Area2D class_name Ghost - ## Ghost previewing the instantiation of a scene. + +## Color to modulate this with if the body currently isn't overlapping anything. +@export var valid_color: Color = Color.WHITE + +## Color to modulate this with if the body currently is overlapping something. +@export var invalid_color: Color = Color.RED + + +## The [Instantiator] to use to spawn the ghosted item. +@onready var instantiator: Instantiator = $Instantiator + +## The [OverlapChecker] to use to see if a solid block is overlapping the ghost. +@onready var overlap_checker: OverlapChecker = $OverlapChecker + +## The [PlaceableAreaChecker] to use to see if the ghost is currently inside the placeable area. +@onready var placeable_area_checker: PlaceableAreaChecker = $PlaceableAreaChecker + +## The [OverlapFreer] to use to delete the [PhysicsBody2D] behind the ghost before instantiation. +@onready var overlap_freer: OverlapFreer = $OverlapFreer + ## The [CollisionShape2D] to use to check for placement checks. ## ## MUST consist of a [RectangleShape2D]. @@ -27,19 +46,6 @@ class_name Ghost if preview_sprite: preview_sprite.texture = value -## 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: - get: - return can_place - set(value): - can_place = value - if value: - preview_sprite.modulate = Color(1.0, 1.0, 1.0, 0.5) - else: - preview_sprite.modulate = Color(1.0, 0.0, 0.0, 0.5) - func _ready(): collision_mask = collision_mask_prevent_placement | collision_mask_delete_placement @@ -51,37 +57,22 @@ func _physics_process(_delta: float): update_can_place() +var can_place: bool: + get: + return can_place + set(value): + can_place = value + modulate = valid_color if value else invalid_color + + ## Update the value of [can_place]. # DIRTY HACK: Relies on the placeable area being perfectly surrounded by solid bodies. -func update_can_place(): - var no_overlapping_bodies: bool = true - var overlapping_bodies = get_overlapping_bodies() - 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 +func update_can_place() -> void: + can_place = overlap_checker.is_overlapping_with == null and placeable_area_checker.is_overlapping_with != null - var is_in_placeable_area: bool = false - var overlapping_areas = get_overlapping_areas() - for area in overlapping_areas: - 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(): + # Compatibility stub for Instantiator if not can_place: return null var overlapping_bodies = get_overlapping_bodies() @@ -89,9 +80,6 @@ func materialize(): if body is PhysicsBody2D: if body.collision_layer & collision_mask_delete_placement: body.queue_free() - var instantiated = scene_to_instantiate.instantiate() - instantiated.global_position = global_position - instantiated.rotation = rotation - target.add_child(instantiated) - materialized.emit(instantiated) - return instantiated + var inst = instantiator.instantiate() + # TODO: Remove this + return inst diff --git a/interface/ghost/ghost.tscn b/interface/ghost/ghost.tscn index 3d96549..f472711 100644 --- a/interface/ghost/ghost.tscn +++ b/interface/ghost/ghost.tscn @@ -1,8 +1,12 @@ -[gd_scene load_steps=4 format=3 uid="uid://qtk4tm6l367w"] +[gd_scene load_steps=8 format=3 uid="uid://qtk4tm6l367w"] [ext_resource type="Script" path="res://interface/ghost/ghost.gd" id="1_1bq64"] [ext_resource type="PackedScene" uid="uid://c3p0jdf7416ac" path="res://converters/full_converter_shape.tscn" id="2_bo8dp"] -[ext_resource type="PackedScene" uid="uid://cgpjm06hleokk" path="res://interface/ghost/precise_placement.tscn" id="3_70ahv"] +[ext_resource type="PackedScene" uid="uid://cgpjm06hleokk" path="res://interface/ghost/precise_touch_placer.tscn" id="3_70ahv"] +[ext_resource type="PackedScene" uid="uid://dhrtfpyfsdf3f" path="res://interface/ghost/instantiator.tscn" id="4_f7fmh"] +[ext_resource type="PackedScene" uid="uid://cm3gvvcsh8i7a" path="res://interface/overlap_checker.tscn" id="5_twds7"] +[ext_resource type="PackedScene" uid="uid://bu3alsb2ufaxu" path="res://interface/ghost/overlap_freer.tscn" id="6_y1rxa"] +[ext_resource type="PackedScene" uid="uid://blk5uc5ta7nwq" path="res://interface/placeable_area_checker.tscn" id="7_wemqm"] [node name="Ghost" type="Area2D"] collision_layer = 0 @@ -19,4 +23,12 @@ scale = Vector2(2.5, 2.5) modulate = Color(1, 1, 1, 0.5) z_index = 10 -[node name="PrecisePlacement" parent="." instance=ExtResource("3_70ahv")] +[node name="PreciseTouchPlacer" parent="." instance=ExtResource("3_70ahv")] + +[node name="Instantiator" parent="." instance=ExtResource("4_f7fmh")] + +[node name="OverlapChecker" parent="." instance=ExtResource("5_twds7")] + +[node name="OverlapFreer" parent="." instance=ExtResource("6_y1rxa")] + +[node name="PlaceableAreaChecker" parent="." instance=ExtResource("7_wemqm")] diff --git a/interface/ghost/instantiator.gd b/interface/ghost/instantiator.gd new file mode 100644 index 0000000..3d6424c --- /dev/null +++ b/interface/ghost/instantiator.gd @@ -0,0 +1,27 @@ +extends Node +class_name Instantiator + + +## The [PackedScene] that this node should instantiate. +@export var scene_to_instantiate: PackedScene + +## The [Node] instantiated scenes should be attached to. +@export var container: Node + + +## The [Node2D] instantiated scenes should get properties from. +@onready var target: Node2D = get_parent() + + +func instantiate(): + var inst = scene_to_instantiate.instantiate() + inst.global_position = target.global_position + inst.rotation = target.rotation + container.add_child(inst) + instantiated.emit(inst) + # TODO: Remove this + return inst + + +## Emitted when the [instantiate] function has finished executing. +signal instantiated(new_scene: Node) diff --git a/interface/ghost/instantiator.tscn b/interface/ghost/instantiator.tscn new file mode 100644 index 0000000..858829a --- /dev/null +++ b/interface/ghost/instantiator.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://dhrtfpyfsdf3f"] + +[ext_resource type="Script" path="res://interface/ghost/instantiator.gd" id="1_waf2e"] + +[node name="Instantiator" type="Node"] +script = ExtResource("1_waf2e") diff --git a/interface/ghost/overlap_freer.gd b/interface/ghost/overlap_freer.gd new file mode 100644 index 0000000..a237054 --- /dev/null +++ b/interface/ghost/overlap_freer.gd @@ -0,0 +1,37 @@ +extends Node +class_name OverlapFreer + + +## Layers to consider when checking overlap with [PhysicBody2D]. +@export_flags_2d_physics var overlap_mask: int + +## Whether [TileMap]s should be considered in collisions or not. +## +## Their [collision_layer]s are always ignored. +@export var overlap_with_tilemap: bool = false + + +## The [Area2D] this script should act on. +@onready var target: Area2D = get_parent() + + +## Get all bodies overlapping the [target]. +func get_all_overlapping_bodies() -> Array[Node2D]: + var bodies: Array[Node2D] = [] + for body in target.get_overlapping_bodies(): + if body is TileMap: + if overlap_with_tilemap: + bodies.append(body) + elif body is PhysicsBody2D: + if body.collision_layer & overlap_mask: + bodies.append(body) + return bodies + +## Emitted when a body is about to be [queue_free]d. +signal body_queueing_free(body: Node2D) + +## Queue free all overlapping bodies. +func area_queue_free() -> void: + for body in get_all_overlapping_bodies(): + body_queueing_free.emit(body) + body.queue_free() diff --git a/interface/ghost/overlap_freer.tscn b/interface/ghost/overlap_freer.tscn new file mode 100644 index 0000000..cfa2c65 --- /dev/null +++ b/interface/ghost/overlap_freer.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://bu3alsb2ufaxu"] + +[ext_resource type="Script" path="res://interface/ghost/overlap_freer.gd" id="1_vr3yj"] + +[node name="OverlapFreer" type="Node"] +script = ExtResource("1_vr3yj") diff --git a/interface/ghost/precise_placement.gd b/interface/ghost/precise_touch_placer.gd similarity index 99% rename from interface/ghost/precise_placement.gd rename to interface/ghost/precise_touch_placer.gd index b12fa63..de0e8e3 100644 --- a/interface/ghost/precise_placement.gd +++ b/interface/ghost/precise_touch_placer.gd @@ -1,5 +1,5 @@ extends Node -class_name PrecisePlacement +class_name PreciseTouchPlacer ## The degrees to rotate by when rotation is quantized. diff --git a/interface/ghost/precise_placement.tscn b/interface/ghost/precise_touch_placer.tscn similarity index 67% rename from interface/ghost/precise_placement.tscn rename to interface/ghost/precise_touch_placer.tscn index 81189b8..0a74166 100644 --- a/interface/ghost/precise_placement.tscn +++ b/interface/ghost/precise_touch_placer.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=2 format=3 uid="uid://cgpjm06hleokk"] -[ext_resource type="Script" path="res://interface/ghost/precise_placement.gd" id="1_e7b5h"] +[ext_resource type="Script" path="res://interface/ghost/precise_touch_placer.gd" id="1_e7b5h"] -[node name="PrecisePlacement" type="Node"] +[node name="PreciseTouchPlacer" type="Node"] script = ExtResource("1_e7b5h") diff --git a/interface/overlap_checker.gd b/interface/overlap_checker.gd new file mode 100644 index 0000000..91fddf5 --- /dev/null +++ b/interface/overlap_checker.gd @@ -0,0 +1,45 @@ +extends Node +class_name OverlapChecker + + +## Layers to consider when checking overlap with [PhysicBody2D]. +@export_flags_2d_physics var overlap_mask: int + +## Whether [TileMap]s should be considered in collisions or not. +## +## Their [collision_layer]s are always ignored. +@export var overlap_with_tilemap: bool = true + + +## The [Area2D] this script should act on. +@onready var target: Area2D = get_parent() + + +## What the [target] object is currently overlapping with. +var is_overlapping_with: Node2D = null + + +## Get the first body overlapping the [target]. +func get_first_overlapping_body() -> Node2D: + for body in target.get_overlapping_bodies(): + if body is TileMap: + if overlap_with_tilemap: + return body + elif body is PhysicsBody2D: + if body.collision_layer & overlap_mask: + return body + return null + +## Update the [is_overlapping_with] variable. +func _update_is_overlapping_with() -> void: + var current_overlap = get_first_overlapping_body() + if current_overlap != is_overlapping_with: + overlap_changing.emit(current_overlap) + is_overlapping_with = current_overlap + +## Emitted when the value of [is_overlapping_with] changes because of [_update_is_overlapping_with]. +signal overlap_changing(to: Node2D) + +## Calculate overlap on every physics frame. +func _physics_process(_delta): + _update_is_overlapping_with() diff --git a/interface/overlap_checker.tscn b/interface/overlap_checker.tscn new file mode 100644 index 0000000..6e1b3db --- /dev/null +++ b/interface/overlap_checker.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://cm3gvvcsh8i7a"] + +[ext_resource type="Script" path="res://interface/overlap_checker.gd" id="1_s3ksv"] + +[node name="OverlapChecker" type="Node"] +script = ExtResource("1_s3ksv") diff --git a/interface/placeable_area_checker.gd b/interface/placeable_area_checker.gd new file mode 100644 index 0000000..e15e00f --- /dev/null +++ b/interface/placeable_area_checker.gd @@ -0,0 +1,31 @@ +extends Node +class_name PlaceableAreaChecker + + +## The [Area2D] this script should act on. +@onready var target: Area2D = get_parent() + + +## What the [target] object is currently overlapping with. +var is_overlapping_with: PlaceableArea = null + + +## Get the first [PlaceableArea] overlapping the [target]. +func get_first_overlapping_placeable_area() -> PlaceableArea: + for area in target.get_overlapping_areas(): + if area is PlaceableArea: + return area + return null + +## Emitted when the value of [is_overlapping_with] changes because of [_update_is_overlapping_with]. +signal overlap_changing(to: PlaceableArea) + +func _update_is_overlapping_with() -> void: + var current_overlap = get_first_overlapping_placeable_area() + if current_overlap != is_overlapping_with: + overlap_changing.emit(current_overlap) + is_overlapping_with = current_overlap + +## Calculate overlap on every physics frame. +func _physics_process(_delta): + _update_is_overlapping_with() diff --git a/interface/placeable_area_checker.tscn b/interface/placeable_area_checker.tscn new file mode 100644 index 0000000..a6a6c60 --- /dev/null +++ b/interface/placeable_area_checker.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://blk5uc5ta7nwq"] + +[ext_resource type="Script" path="res://interface/placeable_area_checker.gd" id="1_yyb05"] + +[node name="PlaceableAreaChecker" type="Node"] +script = ExtResource("1_yyb05")