diff --git a/addons/godot-xr-tools/VERSIONS.md b/addons/godot-xr-tools/VERSIONS.md index 95787c0c..ba1c9049 100644 --- a/addons/godot-xr-tools/VERSIONS.md +++ b/addons/godot-xr-tools/VERSIONS.md @@ -8,6 +8,7 @@ - Moved turning logic from Function_Direct_movement to Function_Turn_movement - Fixed movement provider servicing so disabled/bypassed providers can report their finished events - Added grappling movement provider +- Added snap-zones # 2.3.0 - Added vignette diff --git a/addons/godot-xr-tools/functions/Function_Pickup.gd b/addons/godot-xr-tools/functions/Function_Pickup.gd index 52b88982..ee7e7455 100644 --- a/addons/godot-xr-tools/functions/Function_Pickup.gd +++ b/addons/godot-xr-tools/functions/Function_Pickup.gd @@ -76,7 +76,7 @@ var _grab_area: Area var _grab_collision: CollisionShape var _ranged_area: Area var _ranged_collision: CollisionShape - +var _controller: ARVRController # Called when the node enters the scene tree for the first time. func _ready(): @@ -84,6 +84,8 @@ func _ready(): if Engine.editor_hint: return + _controller = get_parent() + # Create the grab collision shape _grab_collision = CollisionShape.new() _grab_collision.set_name("GrabCollisionShape") @@ -124,12 +126,16 @@ func _ready(): _update_colliders() # Monitor Grab Button - get_parent().connect("button_pressed", self, "_on_button_pressed") - get_parent().connect("button_release", self, "_on_button_release") + _controller.connect("button_pressed", self, "_on_button_pressed") + _controller.connect("button_release", self, "_on_button_release") # Called on each frame to update the pickup func _process(delta): + # Skip if the controller isn't active + if !_controller.get_is_active(): + return + # Calculate average velocity if picked_up_object and picked_up_object.is_picked_up(): # Average velocity of picked up object @@ -258,7 +264,7 @@ func _get_closest_grab() -> Spatial: var new_closest_distance := MAX_GRAB_DISTANCE2 for o in _object_in_grab_area: # skip objects that can not be picked up - if not o.can_pick_up(): + if not o.can_pick_up(self): continue # Save if this object is closer than the current best @@ -278,7 +284,7 @@ func _get_closest_ranged() -> Spatial: var hand_forwards := -global_transform.basis.z for o in _object_in_ranged_area: # skip objects that can not be picked up - if not o.can_pick_up(): + if not o.can_pick_up(self): continue # Save if this object is closer than the current best @@ -319,10 +325,10 @@ func _pick_up_object(target: Spatial) -> void: return # pick up our target - picked_up_object = target picked_up_ranged = not _object_in_grab_area.has(target) - picked_up_object.pick_up(self, get_parent()) - emit_signal("has_picked_up", picked_up_object) + picked_up_object = target.pick_up(self, _controller) + if is_instance_valid(picked_up_object): + emit_signal("has_picked_up", picked_up_object) func _on_button_pressed(p_button) -> void: diff --git a/addons/godot-xr-tools/objects/Object_climbable.gd b/addons/godot-xr-tools/objects/Object_climbable.gd index 9b037834..b0043181 100644 --- a/addons/godot-xr-tools/objects/Object_climbable.gd +++ b/addons/godot-xr-tools/objects/Object_climbable.gd @@ -16,10 +16,10 @@ var press_to_hold := true var grab_locations := {} # Called by Function_pickup -func is_picked_up(): +func is_picked_up() -> bool: return false -func can_pick_up(): +func can_pick_up(_by: Spatial) -> bool: return true # Called by Function_pickup when user presses the action button while holding this object @@ -35,17 +35,18 @@ func decrease_is_closest(): pass # Called by Function_pickup when this is picked up by a controller -func pick_up(by: Function_Pickup, with_controller: ARVRController): +func pick_up(by: Spatial, with_controller: ARVRController) -> Spatial: save_grab_location(by) + return self # Called by Function_pickup when this is let go by a controller -func let_go(p_linear_velocity: Vector3, p_angular_velocity: Vector3): +func let_go(p_linear_velocity: Vector3, p_angular_velocity: Vector3) -> void: pass # Save the grab location -func save_grab_location(p: Function_Pickup): +func save_grab_location(p: Spatial): grab_locations[p.get_instance_id()] = to_local(p.global_transform.origin) # Get the grab location in world-space -func get_grab_location(p: Function_Pickup) -> Vector3: +func get_grab_location(p: Spatial) -> Vector3: return to_global(grab_locations[p.get_instance_id()]) diff --git a/addons/godot-xr-tools/objects/Object_pickable.gd b/addons/godot-xr-tools/objects/Object_pickable.gd index b359cc1c..89b10896 100644 --- a/addons/godot-xr-tools/objects/Object_pickable.gd +++ b/addons/godot-xr-tools/objects/Object_pickable.gd @@ -63,6 +63,12 @@ export (RangedMethod) var ranged_grab_method = RangedMethod.SNAP setget _set_ran ## Speed for ranged grab export var ranged_grab_speed: float = 20.0 +## Refuse pick-by when in the specified group +export var picked_by_exclude: String = "" + +## Require pick-by to be in the specified group +export var picked_by_require: String = "" + # Can object be grabbed at range var can_ranged_grab: bool = true @@ -119,7 +125,7 @@ func _process(delta: float) -> void: # Test if this object can be picked up -func can_pick_up() -> bool: +func can_pick_up(_by: Spatial) -> bool: return _state == PickableState.IDLE @@ -164,13 +170,13 @@ func drop_and_free(): # Called when this object is picked up -func pick_up(by, with_controller): +func pick_up(by: Spatial, with_controller: ARVRController) -> Spatial: # Skip if not idle if _state != PickableState.IDLE: - return + return null if picked_up_by: - let_go() + let_go(Vector3.ZERO, Vector3.ZERO) # remember who picked us up picked_up_by = by @@ -194,9 +200,11 @@ func pick_up(by, with_controller): else: _do_precise_grab() + return self + # Called when this object is dropped -func let_go(p_linear_velocity = Vector3(), p_angular_velocity = Vector3()): +func let_go(p_linear_velocity: Vector3, p_angular_velocity: Vector3) -> void: # Skip if idle if _state == PickableState.IDLE: return @@ -209,7 +217,7 @@ func let_go(p_linear_velocity = Vector3(), p_angular_velocity = Vector3()): picked_up_by.remove_child(self) original_parent.add_child(self) global_transform = original_transform - + HoldMethod.REMOTE_TRANSFORM: _remote_transform.queue_free() _remote_transform = null diff --git a/addons/godot-xr-tools/objects/Snap_Zone.gd b/addons/godot-xr-tools/objects/Snap_Zone.gd new file mode 100644 index 00000000..d3b5e92e --- /dev/null +++ b/addons/godot-xr-tools/objects/Snap_Zone.gd @@ -0,0 +1,205 @@ +class_name XRTSnapZone +extends Area + + +## Signal emitted when the snap-zone picks something up +signal has_picked_up(what) + +## Signal emitted when the snap-zone drops something +signal has_dropped + +# Signal emitted when the highlight state changes +signal highlight_updated(pickable, enable) + +# Signal emitted when the highlight state changes +signal close_highlight_updated(pickable, enable) + + +## Grab distance +export var grab_distance: float = 0.3 setget _set_grab_distance + +## Require snap items to be in specified group +export var snap_require: String = "" + +## Deny snapping items in the specified group +export var snap_exclude: String = "" + +## Require grab-by to be in the specified group +export var grap_require: String = "" + +## Deny grab-by +export var grab_exclude: String= "" + + +# Public fields +var closest_object: Spatial = null +var picked_up_object: Spatial = null +var picked_up_ranged: bool = true + + +# Private fields +var _object_in_grab_area = Array() + + +func _ready(): + # Skip if running in the editor + if Engine.editor_hint: + return + + # Set collision shape radius + $CollisionShape.shape.radius = grab_distance + + # Show highlight when empty + emit_signal("highlight_updated", self, true) + + +# Called on each frame to update the pickup +func _process(delta): + if is_instance_valid(picked_up_object): + return + + for o in _object_in_grab_area: + # skip objects that can not be picked up + if not o.can_pick_up(self): + continue + + # pick up our target + _pick_up_object(o) + return + + +# Pickable Method: snap-zone can be grabbed if holding object +func can_pick_up(by: Spatial) -> bool: + # Refuse if no object is held + if not is_instance_valid(picked_up_object): + return false + + # Refuse if the grab-by is not in the required group + if not grap_require.empty() and not by.is_in_group(grap_require): + return false + + # Refuse if the grab-by is in the excluded group + if not grab_exclude.empty() and by.is_in_group(grab_exclude): + return false + + # Grab is permitted + return true + + +# Pickable Method: Snap points can't be picked up +func is_picked_up() -> bool: + return false + + +# Pickable Method: Gripper-actions can't occur on snap zones +func action(): + pass + + +# Pickable Method: Ignore snap-zone proximity to grippers +func increase_is_closest(): + pass + + +# Pickable Method: Ignore snap-zone proximity to grippers +func decrease_is_closest(): + pass + + +# Pickable Method: Object being grabbed from this snap zone +func pick_up(by: Spatial, with_controller: ARVRController) -> Spatial: + # Ignore if no object in snap-zone + if not is_instance_valid(picked_up_object): + return null + + # Detach the object from snap-zone + var target = picked_up_object + picked_up_object = null + target.let_go(Vector3.ZERO, Vector3.ZERO) + + # Show snap-zone highlight when empty + emit_signal("highlight_updated", self, true) + + # Have the target be picked up by the object + return target.pick_up(by, with_controller) + + +# Pickable Method: Player never graps snap-zone +func let_go(p_linear_velocity: Vector3, p_angular_velocity: Vector3) -> void: + pass + + +# Pickup Method: Drop the currently picked up object +func drop_object() -> void: + if not is_instance_valid(picked_up_object): + return + + # let go of this object + picked_up_object.let_go() + picked_up_object = null + emit_signal("has_dropped") + + +func _on_Snap_Zone_body_entered(target: Spatial) -> void: + # Ignore objects already in area + if _object_in_grab_area.find(target) >= 0: + return + + # Reject objects which don't support picking up + if not target.has_method('pick_up'): + return + + # Reject objects not in the required snap group + if not snap_require.empty() and not target.is_in_group(snap_require): + return + + # Reject objects in the excluded snap group + if not snap_exclude.empty() and target.is_in_group(snap_exclude): + return + + # Reject climbable objects + if target is Object_climbable: + return + + # Add to the list of objects in grab area + _object_in_grab_area.push_back(target) + + # Show highlight when something could be snapped + if not is_instance_valid(picked_up_object): + emit_signal("close_highlight_updated", self, true) + + +func _on_Snap_Zone_body_exited(target: Spatial) -> void: + _object_in_grab_area.erase(target) + + # Hide highlight when nothing could be snapped + if _object_in_grab_area.empty(): + emit_signal("close_highlight_updated", self, false) + + +# Pick up the specified object +func _pick_up_object(target: Spatial) -> void: + # check if already holding an object + if is_instance_valid(picked_up_object): + # skip if holding the target object + if picked_up_object == target: + return + # holding something else? drop it + drop_object() + + # skip if target null or freed + if not is_instance_valid(target): + return + + # pick up our target + picked_up_object = target.pick_up(self, null) + if is_instance_valid(picked_up_object): + emit_signal("has_picked_up", picked_up_object) + emit_signal("highlight_updated", self, false) + + +# Called when the grab distance has been modified +func _set_grab_distance(var new_value: float) -> void: + grab_distance = new_value + if is_inside_tree() and $CollisionShape: + $CollisionShape.shape.radius = grab_distance diff --git a/addons/godot-xr-tools/objects/Snap_Zone.tscn b/addons/godot-xr-tools/objects/Snap_Zone.tscn new file mode 100644 index 00000000..0ed43c5f --- /dev/null +++ b/addons/godot-xr-tools/objects/Snap_Zone.tscn @@ -0,0 +1,14 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://addons/godot-xr-tools/objects/Snap_Zone.gd" type="Script" id=1] + +[sub_resource type="SphereShape" id=1] + +[node name="Snap_Zone" type="Area"] +script = ExtResource( 1 ) + +[node name="CollisionShape" type="CollisionShape" parent="."] +shape = SubResource( 1 ) + +[connection signal="body_entered" from="." to="." method="_on_Snap_Zone_body_entered"] +[connection signal="body_exited" from="." to="." method="_on_Snap_Zone_body_exited"]