I'm working on building a physics-based pinball flipper and I'm trying to use RigidBody physics rather than manually animating or tweening the motion. Basically I'd like to apply a torque to the flipper to rotate it when the flipper button is flipped, and apply a torque in the other direction when it's released. So far so good, but I'd like to add "end stops" to this motion, Ideally without having to get other physics bodies involved.
The reason I would like to do this with physics is so that things like bounciness, and the strength of the flip and return mechanisms can be tuned to get a realistic acceleration for the flipper when it starts, and so that it reacts to the ball hitting it.
So far what I have is an input signal handler which sets a boolean, and in the integrate_forces callback if that variable is set, a torque will be applied in the forward rotation direction, and if not in the backward rotation direction. Things get messy when the resting position or the maximum rotated position are reached. Ideally when either of those two states occur, the rotational velocity should become zero, but not immediately. The "end stop" would apply an opposite torque to the flipper causing it to come to a stop, and it might even bounce a small amount.
That's what I'd like to have happen, but currently it's a flickering teleporting mess. Does anyone have ideas on how this could be accomplished more cleanly?
Here is the code for my flipper:
extends RigidBody3D
@export_enum("left", "right") var flipper_side: String = "left"
@export var flipper_torque: float = 100
@export var flip_angle: float = 25.0
@export var stop_margin: float = 1
var flipper_action: String = "left_flipper"
var resting_transform: Transform3D
var resting_rotation: Vector3
var default_damp: float
var flipping: bool = false
func flip():
# constant_torque = Vector3()
# add_constant_torque(Vector3(0, flipper_torque, 0) * global_transform.basis)
flipping = true
func retract():
# constant_torque = Vector3()
# add_constant_torque(Vector3(0, -flipper_torque, 0) * global_transform.basis)
flipping = false
# Called when the node enters the scene tree for the first time.
func _ready():
flipper_action = "left_flipper" if flipper_side == "left" else "right_flipper"
resting_transform = self.transform
resting_rotation = self.rotation_degrees
default_damp = angular_damp
retract()
# the flipper should be stopped within a small range from the rest position
func at_retract_stop():
var angle = rotation_degrees.y
var boolean = angle < resting_rotation.y
return boolean
func at_flip_stop():
var angle = rotation_degrees.y
return angle > flip_angle + resting_rotation.y
func _input(event):
if Input.is_action_just_pressed(flipper_action):
flip()
if Input.is_action_just_released(flipper_action):
retract()
func _physics_process(delta):
pass
func _integrate_forces(state):
var flipper_angle_fraction = (rotation_degrees.y - resting_rotation.y) / flip_angle
var local_angular_velocity = angular_velocity * global_transform.basis
var current_torque: Vector3 = Vector3()
if flipping:
var solenoid_accel_factor = 5
var torque_magnitude = flipper_torque + solenoid_accel_factor * flipper_angle_fraction
current_torque.y = torque_magnitude
if at_flip_stop():
if local_angular_velocity.y > 0:
current_torque = current_torque - Vector3(0,current_torque.length()+1,0)
angular_damp = 100
else:
current_torque = Vector3()
angular_damp = default_damp
else:
var spring_accel_factor = 4
var torque_magnitude = flipper_torque + spring_accel_factor * flipper_angle_fraction
current_torque.y = -torque_magnitude
if at_retract_stop():
if local_angular_velocity.y < 0:
current_torque = current_torque + Vector3(0,current_torque.length()+1,0)
angular_damp = 100
else:
current_torque = Vector3()
angular_damp = default_damp
var transformed_torque = current_torque * global_transform.basis
state.set_constant_torque(transformed_torque)
# lock angular velocity to the flipper's local Y axis
self.rotation_degrees.x=0
self.rotation_degrees.z=0
if rotation_degrees.y < resting_rotation.y:
self.rotation_degrees.y = resting_rotation.y
elif rotation_degrees.y > resting_rotation.y + flip_angle:
self.rotation_degrees.y = resting_rotation.y + flip_angle
state.integrate_forces()