Basic platformer with rope
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
extends "res://addons/pinjoint-ropephysics/path_3d_rope.gd"
|
||||
|
||||
## Tether variant of the pinjoint rope.
|
||||
##
|
||||
## Adds two things the base addon does not do:
|
||||
## - endpoints can be any PhysicsBody3D (the addon only exports RigidBody3D,
|
||||
## and its `rigidbody_attached_to_start` path overwrites node_b instead of
|
||||
## setting node_a, which pins the body to world space rather than to the rope)
|
||||
## - every segment is locked to the X-Y plane so the rope behaves as a 2D rope
|
||||
##
|
||||
## Expects to be a direct child of an untransformed parent: the base addon bakes
|
||||
## its own local `position` into the segment/joint positions and zeroes the node.
|
||||
|
||||
## Bodies the two rope ends pin to. NodePath rather than a typed node export so
|
||||
## the value resolves reliably when set from a .tscn instance override.
|
||||
@export var attach_start_path : NodePath
|
||||
@export var attach_end_path : NodePath
|
||||
|
||||
var attach_start : PhysicsBody3D
|
||||
var attach_end : PhysicsBody3D
|
||||
|
||||
## Extra rope length as a fraction of the gap between the two endpoints.
|
||||
## 0.0 spawns the rope taut (pin joints will fight); ~0.15 gives a natural sag.
|
||||
@export_range(0.0, 1.0, 0.01) var slack := 0.15
|
||||
|
||||
## Constrain segments to the X-Y plane (linear Z, angular X and Y).
|
||||
@export var plane_lock_z := true
|
||||
|
||||
@export_flags_3d_physics var segment_collision_layer := 4
|
||||
@export_flags_3d_physics var segment_collision_mask := 1
|
||||
|
||||
# The base script zeroes `position` and bakes it into child positions, so keep
|
||||
# our own copy to place the end joint we add.
|
||||
var _origin_offset := Vector3.ZERO
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
attach_start = get_node_or_null(attach_start_path) as PhysicsBody3D
|
||||
attach_end = get_node_or_null(attach_end_path) as PhysicsBody3D
|
||||
_origin_offset = position
|
||||
_fit_curve_to_endpoints()
|
||||
super()
|
||||
_apply_plane_lock()
|
||||
_wire_endpoints()
|
||||
|
||||
|
||||
# Rebuild the curve as a sagging arc between the two endpoints so the rope
|
||||
# always spans wherever the players actually spawn.
|
||||
func _fit_curve_to_endpoints() -> void:
|
||||
if attach_start == null or attach_end == null:
|
||||
return
|
||||
var a := to_local(attach_start.global_position)
|
||||
var b := to_local(attach_end.global_position)
|
||||
var span := a.distance_to(b)
|
||||
if span < 0.001:
|
||||
return
|
||||
# Bow the spawn arc *upward*. A downward sag can start inside level geometry
|
||||
# (a rope between two grounded players dips below the floor), and segments
|
||||
# that spawn embedded tunnel straight through it. Starting high is always
|
||||
# clear, and gravity drapes the rope into place within a few frames.
|
||||
var mid := (a + b) * 0.5 + Vector3(0, span * slack, 0)
|
||||
|
||||
var fitted := Curve3D.new()
|
||||
# The scene's curve ships with a huge bake_interval, which would make
|
||||
# sample_baked() miss the midpoint entirely.
|
||||
fitted.bake_interval = maxf(span / float(number_of_segments) * 0.25, 0.05)
|
||||
fitted.add_point(a)
|
||||
fitted.add_point(mid)
|
||||
fitted.add_point(b)
|
||||
curve = fitted
|
||||
# `distance` is an @onready in the base script; recompute it explicitly so
|
||||
# the value is correct regardless of when that initializer runs.
|
||||
distance = curve.get_baked_length()
|
||||
|
||||
|
||||
func _apply_plane_lock() -> void:
|
||||
for segment in segments:
|
||||
segment.collision_layer = segment_collision_layer
|
||||
segment.collision_mask = segment_collision_mask
|
||||
segment.continuous_cd = true
|
||||
if plane_lock_z:
|
||||
segment.axis_lock_linear_z = true
|
||||
segment.axis_lock_angular_x = true
|
||||
segment.axis_lock_angular_y = true
|
||||
# The base script offsets look_at by (0.001, 0, -0.001) to dodge a
|
||||
# degenerate up vector, which nudges segments off the plane.
|
||||
segment.position.z = 0.0
|
||||
|
||||
|
||||
func _wire_endpoints() -> void:
|
||||
if attach_start != null:
|
||||
# node_b first: assigning node_a while node_b is still segments[0] would
|
||||
# briefly join the body to itself.
|
||||
joints[0].node_b = attach_start.get_path()
|
||||
joints[0].node_a = segments[0].get_path()
|
||||
if attach_end != null and not fixed_end_point:
|
||||
var end_joint := PinJoint3D.new()
|
||||
add_child(end_joint)
|
||||
end_joint.position = curve_points[-1] + _origin_offset
|
||||
end_joint.node_a = segments[-1].get_path()
|
||||
end_joint.node_b = attach_end.get_path()
|
||||
end_joint.set_param(PinJoint3D.PARAM_BIAS, joint_bias_or_stiffness)
|
||||
end_joint.set_param(PinJoint3D.PARAM_IMPULSE_CLAMP, max_impulse)
|
||||
joints.append(end_joint)
|
||||
Reference in New Issue
Block a user