Godot Version
4.5
Question
I am having difficulties getting the trajectory to draw correctly when it collides with an object, especially at the edges. The trajectory (yellow line) is the path that a ball will follow after being launched from a cannon.
In the image below the trajectory shows that the ball will pass the object as if it isn’t going to collide with it. However, when testing the ball will hit this object. This is the case when aiming near the edges of most objects.
extends Node2D
@export var ball_pool: NodePath
@export var trajectory_line: Line2D
@export var launch_speed: float = 900.0
@export var trajectory_points: int = 50
@export var use_high_arc: bool = false
@export var ball_radius: float = 6.0
var pool: Node
var remaining_balls: int = 999
var gravity: float
var space_state: PhysicsDirectSpaceState2D
func _ready():
pool = get_node(ball_pool)
gravity = ProjectSettings.get_setting("physics/2d/default_gravity")
func _process(_delta):
space_state = get_world_2d().direct_space_state
update_trajectory(get_global_mouse_position())
func _input(event):
if event.is_action_pressed("shoot"):
shoot_ball(get_global_mouse_position())
func get_launch_angles(start: Vector2, target: Vector2, speed: float) -> Array:
var dx = target.x - start.x
var dy = start.y - target.y
var v2 = speed * speed
var g = gravity
if abs(dx) < 1e-6:
if dy > v2 / (2 * g):
return []
return [3.0 * PI / 2.0]
var disc = v2 * v2 - g * (g * dx * dx + 2.0 * dy * v2)
if disc < 0:
return []
var sqrt_d = sqrt(disc)
var low = atan((v2 - sqrt_d) / (g * dx))
var high = atan((v2 + sqrt_d) / (g * dx))
if dx < 0:
low += PI
high += PI
return [low, high]
func angle_to_velocity(angle: float, speed: float) -> Vector2:
return Vector2(cos(angle) * speed, -sin(angle) * speed)
func update_trajectory(target_pos: Vector2):
trajectory_line.clear_points()
var angles = get_launch_angles(global_position, target_pos, launch_speed)
if angles.is_empty():
return
var angle = angles[1] if use_high_arc else angles[0]
var velocity = angle_to_velocity(angle, launch_speed)
var pos = global_position
var vel = velocity
var dt = 0.016
var shape = CircleShape2D.new()
shape.radius = ball_radius
for i in range(trajectory_points):
trajectory_line.add_point(pos - global_position)
var next_vel = vel + Vector2(0, gravity * dt)
var next_pos = pos + vel * dt
var query = PhysicsShapeQueryParameters2D.new()
query.shape = shape
query.transform = Transform2D(0, pos)
query.motion = next_pos - pos
query.margin = 0.001
var results = space_state.cast_motion(query)
if results.size() > 0 and results[0] < 1.0:
var collision_pos = pos + (next_pos - pos) * results[0]
trajectory_line.add_point(collision_pos - global_position)
break
vel = next_vel
pos = next_pos
if pos.y > get_viewport_rect().size.y + 100:
break
func shoot_ball(target_pos: Vector2):
if remaining_balls <= 0:
return
var angles = get_launch_angles(global_position, target_pos, launch_speed)
if angles.is_empty():
return
var ball = pool.get_ball()
if ball:
ball.global_position = global_position
var angle = angles[1] if use_high_arc else angles[0]
ball.linear_velocity = angle_to_velocity(angle, launch_speed)
remaining_balls -= 1
