Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interaction Ray + Shapecasting for easier object interaction #281

Merged
merged 4 commits into from
Sep 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 6 additions & 19 deletions COGITO/Components/PlayerInteractionComponent.gd
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,9 @@ signal started_carrying(interaction_node: Node)
var look_vector: Vector3
var device_id: int = -1 # Used for displaying correct input prompts depending on input device.

@export var use_shapecast : bool = false
## Raycast3D for interaction check.
@export var interaction_raycast: InteractionRayCast
@export var interaction_shapecast: InteractionShapeCast


var interactable: # Updated via signals from InteractionRayCast
set = _set_interactable
Expand Down Expand Up @@ -50,8 +49,6 @@ func _ready():
func exclude_player(rid: RID):
player_rid = rid
interaction_raycast.add_exception_rid(rid)
if interaction_shapecast:
interaction_shapecast.add_exception_rid(rid)


func _process(_delta):
Expand Down Expand Up @@ -111,24 +108,14 @@ func _handle_interaction(action: String) -> void:

## Helper function to always get raycast destination point
func get_interaction_raycast_tip(distance_offset: float) -> Vector3:
if !use_shapecast:
var destination_point = interaction_raycast.global_position + (interaction_raycast.target_position.z - distance_offset) * get_viewport().get_camera_3d().get_global_transform().basis.z
if interaction_raycast.is_colliding():
if destination_point == interaction_raycast.get_collision_point():
return interaction_raycast.get_collision_point()
else:
return destination_point
var destination_point = interaction_raycast.global_position + (interaction_raycast.target_position.z - distance_offset) * get_viewport().get_camera_3d().get_global_transform().basis.z
if interaction_raycast.is_colliding():
if destination_point == interaction_raycast.get_collision_point():
return interaction_raycast.get_collision_point()
else:
return destination_point
else:
var destination_point = interaction_shapecast.global_position + (interaction_shapecast.target_position.z - distance_offset) * get_viewport().get_camera_3d().get_global_transform().basis.z
if interaction_shapecast.is_colliding():
if destination_point == interaction_shapecast.get_collision_point(0):
return interaction_shapecast.get_collision_point(0)
else:
return destination_point
else:
return destination_point
return destination_point


### Carryable Management
Expand Down
52 changes: 52 additions & 0 deletions COGITO/Components/interaction_raycast.tscn
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
[gd_scene load_steps=7 format=3 uid="uid://cbd2ojah4nun6"]

[ext_resource type="Script" path="res://COGITO/Scripts/interaction_raycast.gd" id="1_yf5t7"]

[sub_resource type="SphereShape3D" id="SphereShape3D_e2hxm"]
margin = 0.075
radius = 0.075

[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_iac3b"]
transparency = 1
shading_mode = 0
albedo_color = Color(0.333333, 1, 0, 0.501961)

[sub_resource type="SphereMesh" id="SphereMesh_1juxf"]
material = SubResource("StandardMaterial3D_iac3b")
radius = 0.05
height = 0.1
radial_segments = 8
rings = 4

[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_icisa"]
transparency = 1
shading_mode = 0
albedo_color = Color(1, 0, 0, 0.501961)

[sub_resource type="SphereMesh" id="SphereMesh_4320q"]
material = SubResource("StandardMaterial3D_icisa")
radius = 0.025
height = 0.05
radial_segments = 8
rings = 4

[node name="InteractionRaycast" type="RayCast3D"]
target_position = Vector3(0, 0, -2.5)
collision_mask = 3
script = ExtResource("1_yf5t7")

[node name="InteractionShapecast" type="ShapeCast3D" parent="."]
shape = SubResource("SphereShape3D_e2hxm")
target_position = Vector3(0, 0, -2.5)
margin = 0.08
max_results = 16
collision_mask = 3

[node name="ShapecastHotspot" type="Node3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -1.5)

[node name="RaycastHighlighter" type="MeshInstance3D" parent="."]
mesh = SubResource("SphereMesh_1juxf")

[node name="TargetHighlighter" type="MeshInstance3D" parent="."]
mesh = SubResource("SphereMesh_4320q")
7 changes: 2 additions & 5 deletions COGITO/Enemies/cogito_basic_enemy.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
[ext_resource type="Script" path="res://COGITO/DynamicFootstepSystem/Scripts/footstep_surface_detector.gd" id="13_mhpd1"]
[ext_resource type="AudioStream" uid="uid://dc03jiw2a6y3j" path="res://COGITO/DynamicFootstepSystem/FootstepProfiles/generic_footstep_profile.tres" id="14_kpkpk"]
[ext_resource type="Resource" uid="uid://ca0q2t6w08ubh" path="res://COGITO/DynamicFootstepSystem/FootstepMaterialLibrary/sample_footstep_material_library.tres" id="15_vx81w"]
[ext_resource type="Script" path="res://COGITO/Scripts/interaction_raycast.gd" id="17_x20mi"]
[ext_resource type="PackedScene" uid="uid://cbd2ojah4nun6" path="res://COGITO/Components/interaction_raycast.tscn" id="17_0qkwj"]

[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_p3jhp"]
height = 1.8
Expand Down Expand Up @@ -136,11 +136,8 @@ footstep_material_library = ExtResource("15_vx81w")
generic_fallback_landing_profile = ExtResource("14_kpkpk")
landing_material_library = ExtResource("15_vx81w")

[node name="InteractionRaycast" type="RayCast3D" parent="."]
[node name="InteractionRaycast" parent="." instance=ExtResource("17_0qkwj")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
target_position = Vector3(0, 0, -2.5)
collision_mask = 3
script = ExtResource("17_x20mi")

[connection signal="object_detected" from="SecurityCamera" to="." method="switch_to_chasing"]
[connection signal="object_no_longer_detected" from="SecurityCamera" to="." method="switch_to_patrolling"]
Expand Down
2 changes: 2 additions & 0 deletions COGITO/InventoryPD/Items/Cogito_Flashlight.tres
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ name = "Flashlight"
description = "Eats batteries like it's its job."
icon = ExtResource("1_f43pq")
is_stackable = false
is_droppable = true
stack_size = 0
drop_scene = "res://COGITO/PackedScenes/Pickups/pickup_flashlight.tscn"
hint_text_on_use = ""
item_size = Vector2(1, 1)
sound_pickup = ExtResource("3_f88a5")
3 changes: 3 additions & 0 deletions COGITO/InventoryPD/Items/Cogito_StaminaExtension.tres
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@ script = ExtResource("3_vpu4k")
attribute_name = "stamina"
attribute_change_amount = 25.0
value_to_change = 1
consumable_effects = Array[Resource("res://COGITO/InventoryPD/CustomResources/ConsumableEffect.gd")]([])
name = "Stamina Extension"
description = "Drinking this permanently increases your maximum stamina."
icon = ExtResource("2_ylptx")
is_stackable = true
is_droppable = true
stack_size = 5
drop_scene = "res://COGITO/PackedScenes/Pickups/pickup_stamina_extension.tscn"
hint_icon_on_use = ExtResource("1_1g5jd")
hint_text_on_use = "Max stamina increased by 25."
item_size = Vector2(1, 1)
sound_use = ExtResource("4_obcds")
sound_pickup = ExtResource("4_3code")
4 changes: 2 additions & 2 deletions COGITO/PackedScenes/Pickups/pickup_dart.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ metallic = 1.0
height = 0.28
radius = 0.05

[sub_resource type="Resource" id="Resource_2sk3k"]
[sub_resource type="Resource" id="Resource_yhike"]
resource_local_to_scene = true
script = ExtResource("4_qk8k6")
inventory_item = ExtResource("3_ohc35")
Expand Down Expand Up @@ -116,7 +116,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.14, 0)
shape = SubResource("CylinderShape3D_xbicr")

[node name="PickupComponent" parent="." instance=ExtResource("2_u01ym")]
slot_data = SubResource("Resource_2sk3k")
slot_data = SubResource("Resource_yhike")

[node name="Lifespan" type="Timer" parent="."]

Expand Down
29 changes: 6 additions & 23 deletions COGITO/PackedScenes/cogito_player.tscn
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[gd_scene load_steps=27 format=3 uid="uid://kicjwmh02uwf"]
[gd_scene load_steps=25 format=3 uid="uid://kicjwmh02uwf"]

[ext_resource type="Script" path="res://COGITO/CogitoObjects/cogito_player.gd" id="1_87we8"]
[ext_resource type="Resource" uid="uid://ev2xuamhfojm" path="res://COGITO/InventoryPD/Inventories/PlayerInventory.tres" id="2_iyhv4"]
Expand All @@ -8,9 +8,8 @@
[ext_resource type="PackedScene" uid="uid://cetc123v5gnff" path="res://COGITO/Components/Attributes/SanityAttribute.tscn" id="7_27cpi"]
[ext_resource type="PackedScene" uid="uid://ce7bjv28uakxl" path="res://COGITO/Components/Attributes/StaminaAttribute.tscn" id="8_kodbk"]
[ext_resource type="PackedScene" uid="uid://cqgg1nng0vvbh" path="res://COGITO/Components/Attributes/HealthAttribute.tscn" id="9_ky5mf"]
[ext_resource type="PackedScene" uid="uid://cbd2ojah4nun6" path="res://COGITO/Components/interaction_raycast.tscn" id="10_2dndr"]
[ext_resource type="AnimationLibrary" uid="uid://cdchpsv104er2" path="res://COGITO/Assets/Animations/player_eyes.tres" id="10_tp6cj"]
[ext_resource type="Script" path="res://COGITO/Scripts/interaction_shapecast.gd" id="11_fryyl"]
[ext_resource type="Script" path="res://COGITO/Scripts/interaction_raycast.gd" id="11_miluh"]
[ext_resource type="AudioStream" uid="uid://up2hfhgq1qx6" path="res://COGITO/Assets/Audio/Kenney/Footsteps/footstep00.ogg" id="12_ug3wt"]
[ext_resource type="AudioStream" uid="uid://crj07wq4oocwi" path="res://COGITO/Assets/Audio/Kenney/Footsteps/footstep01.ogg" id="13_fyfhw"]
[ext_resource type="AudioStream" uid="uid://dewyukd562k37" path="res://COGITO/Assets/Audio/Kenney/Footsteps/footstep02.ogg" id="14_opnk5"]
Expand All @@ -29,9 +28,6 @@ size = Vector3(0.6, 1.7, 0.6)
[sub_resource type="BoxShape3D" id="BoxShape3D_ut3wm"]
size = Vector3(0.6, 0.7, 0.6)

[sub_resource type="BoxShape3D" id="BoxShape3D_e32pq"]
size = Vector3(0.5, 0.5, 0.5)

[sub_resource type="AudioStreamRandomizer" id="AudioStreamRandomizer_af0lu"]
streams_count = 5
stream_0/stream = ExtResource("12_ug3wt")
Expand Down Expand Up @@ -103,18 +99,7 @@ libraries = {
[node name="Camera" type="Camera3D" parent="Body/Neck/Head/Eyes"]
transform = Transform3D(1, 0, 0, 0, 1, 4.46638e-06, 0, -4.46638e-06, 1, 0, 0, 0)

[node name="InteractionRaycast" type="RayCast3D" parent="Body/Neck/Head/Eyes/Camera"]
target_position = Vector3(0, 0, -2.5)
collision_mask = 3
script = ExtResource("11_miluh")

[node name="InteractionShapecast" type="ShapeCast3D" parent="Body/Neck/Head/Eyes/Camera"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -0.7)
shape = SubResource("BoxShape3D_e32pq")
target_position = Vector3(0, 0, -1.55)
max_results = 10
collision_mask = 3
script = ExtResource("11_fryyl")
[node name="InteractionRaycast" parent="Body/Neck/Head/Eyes/Camera" instance=ExtResource("10_2dndr")]

[node name="Wieldables" type="Node3D" parent="Body/Neck/Head"]
unique_name_in_owner = true
Expand All @@ -139,9 +124,7 @@ landing_material_library = ExtResource("19_pc36t")
wait_time = 0.5
one_shot = true

[node name="PlayerInteractionComponent" parent="." node_paths=PackedStringArray("interaction_shapecast", "wieldable_container") instance=ExtResource("20_4f25o")]
use_shapecast = true
interaction_shapecast = NodePath("../Body/Neck/Head/Eyes/Camera/InteractionShapecast")
[node name="PlayerInteractionComponent" parent="." node_paths=PackedStringArray("wieldable_container") instance=ExtResource("20_4f25o")]
wieldable_container = NodePath("../Body/Neck/Head/Wieldables")

[node name="Player_HUD" parent="." node_paths=PackedStringArray("player") instance=ExtResource("21_j3p88")]
Expand All @@ -150,6 +133,6 @@ player = NodePath("..")
[node name="PauseMenu" parent="." instance=ExtResource("22_haksr")]

[connection signal="animation_finished" from="Body/Neck/Head/Eyes/AnimationPlayer" to="." method="_on_animation_player_animation_finished"]
[connection signal="interactable_seen" from="Body/Neck/Head/Eyes/Camera/InteractionShapecast" to="PlayerInteractionComponent" method="_on_interaction_raycast_interactable_seen"]
[connection signal="interactable_unseen" from="Body/Neck/Head/Eyes/Camera/InteractionShapecast" to="PlayerInteractionComponent" method="_on_interaction_raycast_interactable_unseen"]
[connection signal="interactable_seen" from="Body/Neck/Head/Eyes/Camera/InteractionRaycast" to="PlayerInteractionComponent" method="_on_interaction_raycast_interactable_seen"]
[connection signal="interactable_unseen" from="Body/Neck/Head/Eyes/Camera/InteractionRaycast" to="PlayerInteractionComponent" method="_on_interaction_raycast_interactable_unseen"]
[connection signal="timeout" from="SlidingTimer" to="." method="_on_sliding_timer_timeout"]
71 changes: 69 additions & 2 deletions COGITO/Scripts/interaction_raycast.gd
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,30 @@ extends RayCast3D
signal interactable_seen(interactable)
signal interactable_unseen()

@onready var shapecast : ShapeCast3D = $InteractionShapecast
# currently a node, but could just be an offset position
# if shapecasting, the collider closest to the hotspot's position is used
@onready var hotspot : Node3D = $ShapecastHotspot
# the starting position of the hotspot, which is closer to the raycast target position
var hotspot_base_pos_z : float = -1.5

# DEBUGGING
@onready var raycast_highlighter = $RaycastHighlighter
@onready var target_highlighter = $TargetHighlighter


@export var show_debug_shapes : bool = false

var _interactable = null:
set = _set_interactable


func _ready() -> void:
pass
if hotspot != null:
hotspot_base_pos_z = hotspot.transform.origin.z
if !show_debug_shapes:
raycast_highlighter.visible = false
target_highlighter.visible = false


func _process(_delta: float) -> void:
Expand All @@ -26,7 +44,14 @@ func _set_interactable(value) -> void:


func _update_interactable() -> void:
var collider = get_collider()
var raycasted_collider = get_collider()
# populate with any raycasted collider, else with any shapecasted collider
var collider = raycasted_collider
# used for positioning the hotspot to refine shapecasted target selection
var hotspot_global_position: Vector3 = get_collision_point() if collider else global_position

if show_debug_shapes: # DEBUGGING
target_highlighter.visible = false

# Handle freed objects.
# is_instance_valid() will be false for null and for freed objects, but only
Expand All @@ -39,6 +64,48 @@ func _update_interactable() -> void:
# Handle all colliders that aren't in the interactable group as null.
if collider != null and not collider.is_in_group("interactable"):
collider = null

# conver the global position to local position and move
# the hotspot out to the distance of the hit point to better
# predict what the player seems to intend to interact with
# **this could be a problem when attempting to interact around corners**
hotspot.transform.origin = to_local(hotspot_global_position)

if show_debug_shapes: # DEBUGGING
raycast_highlighter.global_position = hotspot_global_position
raycast_highlighter.visible = true
else:
# set the hotspot to the base position if not raycasting anything
hotspot.transform.origin = Vector3(0.0, 0.0, hotspot_base_pos_z)

if show_debug_shapes: # DEBUGGING
raycast_highlighter.transform.origin = Vector3(0.0, 0.0, hotspot_base_pos_z)
raycast_highlighter.visible = false


# if not raycasting a collider, then attempt a shapecast
if collider == null:
if shapecast.is_colliding():
# select the closest of all colliders detected by the shapecast
var closest_distance = INF
for i in range(shapecast.get_collision_count()):
var shape_collider = shapecast.get_collider(i)
if shape_collider != null and shape_collider.is_in_group("interactable"):
var collision_point = shapecast.get_collision_point(i)
#print("shapecast colliding with %s" % shape_collider)
var distance = collision_point.distance_squared_to(hotspot_global_position)
# always return the closest interactable to the hotspot's global position
if distance < closest_distance:
collider = shape_collider
closest_distance = distance

if show_debug_shapes: # DEBUGGING
target_highlighter.global_position = collision_point
target_highlighter.visible = true
else:
if show_debug_shapes: # DEBUGGING
target_highlighter.global_position = hotspot_global_position
target_highlighter.visible = true

if collider == _interactable:
return
Expand Down
Loading