Topic was automatically imported from the old Question2Answer platform.
Asked By
newold
Hey. How i can do this?
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.
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
I try it, but i get wrong results:
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
newold | 2019-10-01 18:14
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.
gmaps | 2019-10-02 10:33
I can’t make this work.
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
newold | 2019-10-02 14:35
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:
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).
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)