extends RigidBody3D class_name Dart #ray cast @onready var point = $RayCast3D #states var in_flight: bool = false var dragging: bool = false #drag var drag_plane: Plane var drag_offset: Vector3 = Vector3.ZERO var target_pos: Vector3 = Vector3.ZERO # velocity tracking for release throw var position_history: Array[Vector3] = [] var time_history: Array[float] = [] const HISTORY_LENGTH = 6 # drag "stiffness" tuning @export var drag_follow_speed: float = 20.0 # how fast it chases the mouse @export var max_drag_speed: float = 42.0 # clamp so it doesn't go insane @export_range(-180,180) var angle_of_plane: float = 15.0 @export var throw_force_multiplier: float = 0.8 # scale raw throw velocity down if it's too fast @export var max_throw_speed: float = 64.0 # hard cap so fast flicks don't kill the arc @export var flight_gravity_scale: float = 4.0 # higher = more visible arc @export var nose_align_speed: float = 1.0 # how fast dart rotates to face velocity @export var dart_forward_axis: Vector3 = Vector3.FORWARD func _ready(): freeze_mode = RigidBody3D.FREEZE_MODE_STATIC freeze = true func start_drag(camera: Camera3D, mouse_pos: Vector2, hit_point: Vector3): dragging = true in_flight = false freeze = false # keep it physics-active — don't freeze gravity_scale = 0.0 # optional: ignore gravity while held so it doesn't sag linear_damp = 5.0 # optional: a bit of damping for stability while dragged angular_damp = 10.0 #drag_plane = Plane(camera.global_transform.basis.z, hit_point) var base_normal = camera.global_transform.basis.z var tilt = base_normal.rotated(camera.global_transform.basis.x, deg_to_rad(angle_of_plane)) # tilt adjustable around camera's right axis drag_plane = Plane(tilt.normalized(), hit_point) drag_offset = global_position - hit_point target_pos = global_position #fixes a bug where dart drops until mouse is moved.m position_history.clear() time_history.clear() func update_drag(camera: Camera3D, mouse_pos: Vector2): if not dragging: return var ray_origin = camera.project_ray_origin(mouse_pos) var ray_dir = camera.project_ray_normal(mouse_pos) var intersection = drag_plane.intersects_ray(ray_origin, ray_dir) if intersection: target_pos = intersection + drag_offset func _align_dart_to_velocity(delta: float): if linear_velocity.length() < 0.5: return var vel_dir = linear_velocity.normalized() var current_forward = global_transform.basis * dart_forward_axis var rotation_axis = current_forward.cross(vel_dir) if rotation_axis.length() < 0.001: return rotation_axis = rotation_axis.normalized() var angle = current_forward.angle_to(vel_dir) var rotate_amount = min(angle, nose_align_speed * delta) global_transform.basis = global_transform.basis.rotated(rotation_axis, rotate_amount) global_transform.basis = global_transform.basis.orthonormalized() func _physics_process(delta): if dragging: # chase the target position with velocity instead of teleporting var to_target = target_pos - global_position var desired_velocity = to_target * drag_follow_speed if desired_velocity.length() > max_drag_speed: desired_velocity = desired_velocity.normalized() * max_drag_speed linear_velocity = desired_velocity angular_velocity = angular_velocity.lerp(Vector3.ZERO, 0.2) # optional: damp spin while dragging # track actual position for momentum calc on release position_history.append(global_position) time_history.append(Time.get_ticks_msec() / 1000.0) if position_history.size() > HISTORY_LENGTH: position_history.pop_front() time_history.pop_front() elif point.is_colliding(): var collider = point.get_collider() if collider and collider.is_in_group("dartable"): in_flight = false dragging = false await get_tree().create_timer(0.01).timeout freeze_mode = RigidBody3D.FREEZE_MODE_STATIC freeze = true elif in_flight: # Align dart nose to face velocity direction (tip points the way it's flying) if linear_velocity.length() > 0.5: _align_dart_to_velocity(delta) else: in_flight = false angular_velocity = Vector3.ZERO # If your dart tip points along +Z, use global_position + linear_velocity # If it points along -Z, negate the direction below var look_target = global_position + linear_velocity.normalized() var up_ref = Vector3.UP # Avoid gimbal lock when flying straight up/down if abs(linear_velocity.normalized().dot(Vector3.UP)) > 0.99: up_ref = Vector3.FORWARD var target_basis = Basis.looking_at(linear_velocity.normalized(), up_ref) global_transform.basis = global_transform.basis.slerp( target_basis, nose_align_speed * delta ) func release_drag(): if not dragging: return dragging = false in_flight = true gravity_scale = flight_gravity_scale angular_damp = 0.0 linear_damp = 0.0 # reset to whatever your normal dart damping is var velocity = calculate_throw_velocity() * throw_force_multiplier if velocity.length() > max_throw_speed: velocity = velocity.normalized() * max_throw_speed linear_velocity = velocity # angular_velocity left as-is from physics, or compute your own spin here func calculate_throw_velocity() -> Vector3: if position_history.size() < 2: return linear_velocity # fallback to whatever physics already had var start_pos = position_history[0] var end_pos = position_history[position_history.size() - 1] var start_time = time_history[0] var end_time = time_history[time_history.size() - 1] var dt = end_time - start_time if dt <= 0.0: return linear_velocity return (end_pos - start_pos) / dt