extends Node3D ## Playable Box3D/Jolt/GodotPhysics pinball testbed. ## Z = left flipper, / = right flipper, Space = plunger, R = reset ball. const FLIP_SPEED := 25.0 const REST_SPEED := 8.0 const PLUNGE_IMPULSE := 0.32 # ~4 m/s on the 80 g ball const BUMPER_IMPULSE := 0.12 @onready var ball: RigidBody3D = $Ball @onready var left_joint: HingeJoint3D = $LeftFlipperJoint @onready var right_joint: HingeJoint3D = $RightFlipperJoint var _ball_start: Transform3D func _ready() -> void: _ball_start = ball.transform print("physics engine: ", ProjectSettings.get_setting("physics/3d/physics_engine")) $Drain.body_entered.connect(_on_drain) $BumperArea.body_entered.connect(_on_bumper) if OS.get_environment("PINBALL_AUTOTEST") == "1": _autotest() ## Headless check that the real table flippers flip: hold both motors at flip ## speed for half a second and report how far each flipper swung. func _autotest() -> void: set_physics_process(false) var lf: RigidBody3D = $LeftFlipper var rf: RigidBody3D = $RightFlipper var l0 := lf.rotation.y var r0 := rf.rotation.y left_joint.set_param(HingeJoint3D.PARAM_MOTOR_TARGET_VELOCITY, FLIP_SPEED) right_joint.set_param(HingeJoint3D.PARAM_MOTOR_TARGET_VELOCITY, -FLIP_SPEED) for i in 30: await get_tree().physics_frame print("AUTOTEST left_swing_deg=%.1f right_swing_deg=%.1f" % [ rad_to_deg(angle_difference(l0, lf.rotation.y)), rad_to_deg(angle_difference(r0, rf.rotation.y))]) get_tree().quit(0) func _physics_process(_delta: float) -> void: var left := Input.is_physical_key_pressed(KEY_Z) var right := Input.is_physical_key_pressed(KEY_SLASH) left_joint.set_param(HingeJoint3D.PARAM_MOTOR_TARGET_VELOCITY, FLIP_SPEED if left else -REST_SPEED) right_joint.set_param(HingeJoint3D.PARAM_MOTOR_TARGET_VELOCITY, -FLIP_SPEED if right else REST_SPEED) if Input.is_physical_key_pressed(KEY_SPACE) and _ball_in_lane(): ball.apply_central_impulse(Vector3(0, 0, -PLUNGE_IMPULSE)) if Input.is_physical_key_pressed(KEY_R): _reset_ball() func _ball_in_lane() -> bool: return ball.position.x > 0.21 and ball.position.z > 0.30 \ and ball.linear_velocity.length() < 0.05 func _on_drain(body: Node3D) -> void: if body == ball: _reset_ball.call_deferred() func _on_bumper(body: Node3D) -> void: if body != ball: return var bumper: StaticBody3D = $Bumper var dir := ball.global_position - bumper.global_position dir.y = 0.0 if dir.length() < 0.001: dir = Vector3(0, 0, 1) ball.apply_central_impulse(dir.normalized() * BUMPER_IMPULSE) func _reset_ball() -> void: ball.linear_velocity = Vector3.ZERO ball.angular_velocity = Vector3.ZERO ball.transform = _ball_start