0 votes

Hey. How i can do this?

enter image description here

i want draw a line with first point in A, then calculate a line that passes through the position of the mouse and describe the entire route including bounces against the obstacles as in the image. The line has a fixed size, but I can't figure out how to do this.

Any help?

in Engine by (184 points)

1 Answer

+2 votes
Best answer

Since you are already using physics, just use Raycast query when the mouse moves. Get the vector pointing from A to mouse loctaion (position of A - mouse location). multiply it by some large value, e.g. length of diagonal of the field and raycast a point from A to that far point. The result will be a collision with a normal. You mirror (EDITED) your vector against the normal and get your next raycasting direction. YOu use the collision location the same way you used A. Multipy the normal so you get a very far point and raycast from current collision location to that far point. You get another collision location and it's normal, and you repeat the steps until you've reached the max number of steps, or exit condition happens (e.g. collision with the bottom line). Prettiest way would be to implement it as a recursion.

Pseudocode would look like this:

0. raycast_count = 0
1.  start point = A, end_point = (Mouse_loc - A) * 100
2. raycast from A to end_point, raycast_count += 1, Draw rectangle from A to raycast.result.position
3. If raycast.result is bottom line, finish
4. Else Go to number 2, start_point = raycast.result.location,  end_point = raycast.result.normal.rotate(angle_to_normal) * 100 + raycast.result.location
by (707 points)
selected by

I try it, but i get wrong results:

enter image description here

Black line in the picture is drawn with the points obtained with a raycast but I expected to get the green line.

I use this code to get that points:

func get_preview_path(a, b):
# Node $Player call this method in his parent
        # a = start_point
        # b = end_point (current mouse position)
        a += $Player.position
        var points = []
        var start_point = a
        var end_point = b * diagonal # diagonal = diagonal of screen
        var l = 0 # counter for steps for the loop
        var last_collider = null

while l < diagonal: # length of diagonal of screen
points.append(start_point)
raycast.clear_exceptions()
if last_collider != null: raycast.add_exception(last_collider)
raycast.position = start_point
raycast.cast_to = end_point
raycast.force_raycast_update()
if raycast.is_colliding():
var p = raycast.get_collision_point()
var n = (raycast.get_collision_normal() * diagonal + p)
start_point = p
end_point = n
 l += 30
last_collider = raycast.get_collider()
else:
break

I wrote that you should use Raycast2D, but what I really meant was raycast 2d query, sorry about that. Although both should work, Raycast2D is usually used when we want to do only one raycast which is fixed to it's parent's location/rotation, character aim for example. In your case, it could be used only to retrieve the first point. But since you need unknown number of raycasts (Technically it could be infinite) it's better to use Raycast querying.

So, you don't need Raycast2D, you just need to initialize space state like this:

var space_state = get_world_2d().direct_space_state

And then check the collisions with this function

var result = space_state.intersect_ray(point_a, point_b)

It should work like a charm, If it doesn't leave another comment. I edited the answer to include link to raycast query instead of Raycast2D.

I can't make this work.

enter image description here

i change my code at this:

func get_preview_path(a, b):
a += $Player.position
var points = []
var start_point = a
var end_point = b * diagonal
var l = 0
var last_collider = null


# use World2D
var space_state = get_world_2d().direct_space_state 
var result

while l < diagonal:
    points.append(start_point)
    result = space_state.intersect_ray(start_point, end_point, [last_collider])
    if result:
        print([result.position, result.normal])
        start_point = result.position
        end_point = result.normal * diagonal + start_point
        last_collider = result.collider
    else:
        break
    l += 12


return (points)

i only can get the first point as before using the raycast, rest of points are wrong

Aah, I see, you should use the angle between previous vector and normal to mirror the vector across the normal and get next direction. Something like this:

    var angle_to_normal = (start_point - end_point).angle_to(result.normal)
    start_point = result.position
    end_point = result.normal.rotate(angle_to_normal) * diagonal + start_point

It should give you a similar result, you want. But the normals seem odd in your case... Can you print them and comment them here? I think they should be perpendicular to the wall, but they're not. In documentation it says:

normal: The object’s surface normal at the intersection point.

And the surface normal of a wall is always perpendicular.

I suggest you try mirroring it, if it's not the same as ball bounce, post another question with "collision normals don't reflect surface normal".

I also see another issue you will be dealing with... One raycast would not be enough, as it may go beside a wall, but the ball would hit it because it's wider, so the ball would bounce, but ray would continue. One option would be to use more than one raycast (3-5, or so that the distance between the raycast is bigger than the smallest object), and check which one is the shortest (and use it's normal). Alternative is to try actually simulating another ball, with higher speed, it should be simpler than raycasting, and could produce better results, but... everytime the mouse moves, you shoot another ball, high speed, just to get the angles and draw it's position. The issue with this approach is that with very high speeds, it may happen that collisions are not properly detected, because the ball could be in las vegas instead of colliding with your wall.

There also exists the Shape query 2D. First set all the Shape query parameters, so motion and shape (your ball and it's starting direction). And use cast_motion function. It seems to me that it doesn't return any normals, nor does it simulate anything. But you might need it, because with raycasts, it's difficult to determine collisions with edges (maybe by recognizing when neighbouring normals point in different directions).

Thanks for your help. It works at last:

enter image description here

I only need this code to make it work

var angle_to_normal = (start_point - end_point).angle_to(result.normal)
start_point = result.position
end_point = result.normal.rotate(angle_to_normal) * diagonal + start_point

This is the final code:

func get_preview_path(a, b):
a += $Player.position
var points = []
var start_point = a
var end_point = b * diagonal
var l = 0
var last_collider = null

var space_state = get_world_2d().direct_space_state 
var result
var normal
var angle_to_normal

while l < diagonal:
    points.append(start_point)
    result = space_state.intersect_ray(start_point, end_point, [last_collider])
    if result:
        normal = result.normal
        angle_to_normal = (start_point - end_point).angle_to(normal)
        start_point = result.position
        end_point = normal.rotated(angle_to_normal) * diagonal + start_point
        last_collider = result.collider
        if last_collider.is_in_group("wall_bottom"):
            break
    else:
        break
    l += 30


return (points)
Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to webmaster@godotengine.org with your username.