Skip to content

Commit

Permalink
Merge pull request #130 from Malcolmnixon/snap-zone
Browse files Browse the repository at this point in the history
Added support for snap-zones
  • Loading branch information
BastiaanOlij authored May 23, 2022
2 parents 2d450a7 + 6ebc699 commit 0d5ebbf
Show file tree
Hide file tree
Showing 6 changed files with 255 additions and 20 deletions.
1 change: 1 addition & 0 deletions addons/godot-xr-tools/VERSIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 14 additions & 8 deletions addons/godot-xr-tools/functions/Function_Pickup.gd
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,16 @@ 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():
# Skip if running in the editor
if Engine.editor_hint:
return

_controller = get_parent()

# Create the grab collision shape
_grab_collision = CollisionShape.new()
_grab_collision.set_name("GrabCollisionShape")
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down
13 changes: 7 additions & 6 deletions addons/godot-xr-tools/objects/Object_climbable.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()])
20 changes: 14 additions & 6 deletions addons/godot-xr-tools/objects/Object_pickable.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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


Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
205 changes: 205 additions & 0 deletions addons/godot-xr-tools/objects/Snap_Zone.gd
Original file line number Diff line number Diff line change
@@ -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
14 changes: 14 additions & 0 deletions addons/godot-xr-tools/objects/Snap_Zone.tscn
Original file line number Diff line number Diff line change
@@ -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"]

0 comments on commit 0d5ebbf

Please sign in to comment.