The enemy in my game will not move towards the left when player is on the left but will move to the right. Then when the player is on the right the enemy will move to the player.
code for enemy:
extends KinematicBody2D
var run_speed = 100
var velocity = Vector2.ZERO
var collider = null
func _physics_process(delta):
velocity = Vector2.ZERO
if collider:
velocity = position.direction_to(collider.position) * run_speed
velocity = move_and_slide(velocity)
func _on_DetectRadius_body_entered(body):
collider = body
func _on_DetectRadius_body_exited(body):
collider = null
I suspect you are using an Area2D and it is detecting something other than the player character. I remind you can use collision_mask and collision_layer to narrow what objects are detected. For example, are you sure the Area2D is not detecting the enemy itself? For a quick check you can have Godot print the collider to double check it is what you expect.
Furthermore, notice that wen an object leaves the Area2D it will set collider to null regardless if there is still some object inside the Area2D or not. I remind you that an alternative approach is using get_overlapping_bodies.
And, of course, you can query each body you get to see if it is the player character (for example by checking its node group, name, class, etc...). I go over filtering in more detail in another answer.
If you are getting the player character, there is another possible source of problems: position vs global_position. The code you have like this:
position.direction_to(collider.position)
Is correct if both the enemy and the collider it got have the same parent. In that case their positions are in the same space. Otherwise, you may want to either work on global coordinates:
global_position.direction_to(collider.global_position)
Or you can bring the collider position to local coordinates:
position.direction_to(to_local(collider.global_position))
And of course, double check that the Area2D is positioned correctly. You can enable "Visible Collision Shapes" on the "Debug" menu, which will show it when running the game from the editor.
I have a trouble with a 2D Godot Project.
I wrote the following code in the KinematicBody2D script:
extends KinematicBody2D
export var speed = 250
var motion = Vector2()
func _physics_process(delta):
motion = Vector2.ZERO
if Input.is_action_just_pressed("ui_left"):
motion.x = -speed
if Input.is_action_just_pressed("ui_right"):
motion.x = speed
if Input.is_action_just_pressed("ui_up"):
motion.y = -speed
if Input.is_action_just_pressed("ui_down"):
motion.y = speed
motion = move_and_slide(motion)
pass # Replace with function body.
The problem is that my player is only moving a few pixels and stops while I'm pressing the W, A, S, D or the arrow keys.
What I've done wrong?
Thank you all!
The function is_action_just_pressed tells you if the named action※ was just pressed. It will not continue returning true if the action is held.
This, combined with the fact that you are erasing motion each physic frame motion = Vector2.ZERO, result in the object moving one physics frame and then it stops.
If you want to know if the action is currently pressed - independently of when it began being pressed - use is_action_pressed instead.
※: The actions you can use with these functions are configured in the Project Settings. You will find them on the "Input Map" tab.
I have a a 2d Line render that creates loops, a Issue I have noticed is that when looping at speed it sometimes doesn't detect, what I want to know is how to stop this from happening, another issue is how to make it more forgiving for example if the line is really close to a previous line I would like it to count that as a loop, and also how would I make the line actually be the mouse pointer instead of having it ghost behind and this might be a issue in the future I currently have it create an area 2d to detect items/objects inside its self I was wondering if there is a better way to accurately detect them inside said loop.
I have a video link I uploaded to show you the issue visually: https://www.youtube.com/watch?v=Jau7YDpZehY
extends Node2D
var points_array = PoolVector2Array()
#var check_array = []
var colision_array = []
var index : int = 0
onready var Line = $Line2D
onready var collision = $Area2D/CollisionPolygon2D
onready var collision_area = $Loop_collider
func _physics_process(delta):
Line.points = points_array # Link the array to the points and polygons
collision.polygon = points_array
if Input.is_action_just_pressed("left_click"): #This sets a position so that the next line can work together
points_array.append(get_global_mouse_position()) # This makes a empty vector and the mouse cords is assigned too it
#points_array.append(Vector2()) #This is the vector that follows the mouse
if Input.is_action_pressed("left_click"): #This checks the distance between last vector and the mouse vector
#points_array[-1] = get_global_mouse_position() # Gets the last position of the array and sets the mouse cords
var mouse_pos = get_global_mouse_position()
var distance = 20
while points_array[-1].distance_to(mouse_pos) > distance:
var last_point = points_array[-1]
var cords = last_point + last_point.direction_to(mouse_pos) * distance
points_array.append(cords)
create_collision()
if points_array.size() > 80: # This adds a length to the circle/line so it wont pass 18 mini lines
points_array.remove(0) #Removes the first array to make it look like it has a length
#check_array = []
colision_array[0].queue_free()
colision_array.remove(0)
if Input.is_action_just_released("left_click"): # This just clears the screen when the player releases the button
points_array = PoolVector2Array()
#check_array = []
for x in colision_array.size():
colision_array[0].queue_free()
colision_array.remove(0)
#index = 0
if points_array.size() > 3: # If the loop is long enough, to detect intersects
if points_array[0].distance_to(get_global_mouse_position()) < 5: # Checks if the end of the loop is near the end, then start new loop
new_loop()
for index in range(0, points_array.size() - 3):
if _segment_collision(
points_array[-1],
points_array[-2],
points_array[index],
points_array[index + 1]
):
new_loop()
break
#if check_array.size() != points_array.size():
# check_array = points_array
#create_collision()
func _segment_collision(a1:Vector2, a2:Vector2, b1:Vector2, b2:Vector2) -> bool:
# if both ends of segment b are to the same side of segment a, they do not intersect
if sign(_wedge_product(a2 - a1, b1 - a1)) == sign(_wedge_product(a2 - a1, b2 - a1)):
return false
# if both ends of segment a are to the same side of segment b, they do not intersect
if sign(_wedge_product(b2 - b1, a1 - b1)) == sign(_wedge_product(b2 - b1, a2 - b1)):
return false
# the segments must intersect
return true
func _wedge_product(a:Vector2, b:Vector2) -> float:
# this is the length of the cross product
# it has the same sign as the sin of the angle between the vectors
return a.x * b.y - a.y * b.x
func new_loop(): # Creates a new loop when holding left click and or loop is complete
var new_start = points_array[-1]
collision.polygon = points_array
points_array = PoolVector2Array()
collision.polygon = []
#check_array = []
points_array.append(new_start)
for x in colision_array.size():
colision_array[0].queue_free()
colision_array.remove(0)
func create_collision(): # Creates collisions to detect when something hits the line renderer
var new_colision = CollisionShape2D.new()
var c_shape = RectangleShape2D.new()
var mid_point = Vector2((points_array[-1].x + points_array[-2].x) / 2,(points_array[-1].y + points_array[-2].y) / 2)
c_shape.set_extents(Vector2(10,2))
new_colision.shape = c_shape
if points_array.size() > 1:
colision_array.append(new_colision)
collision_area.add_child(new_colision)
new_colision.position = mid_point
#new_colision.position = Vector2((points_array[-1].x),(points_array[-1].y))
new_colision.look_at(points_array[-2])
func _on_Area2D_area_entered(area): # Test dummies
print("detect enemy")
func _on_Loop_collider_area_entered(area):
print("square detected")
Diagnosis
Symptom 1
I have noticed is that when looping at speed it sometimes doesn't detect
This is what happens:
When the mouse pointer moved too much between frames, it creates multiple segments, here:
var mouse_pos = get_global_mouse_position()
var distance = 20
while points_array[-1].distance_to(mouse_pos) > _distance:
var last_point = points_array[-1]
var cords = last_point + last_point.direction_to(mouse_pos) * distance
points_array.append(cords)
create_collision()
But the check for collisions is only comparing the last one, here:
for index in range(0, points_array.size() - 3):
if _segment_collision(
points_array[-1],
points_array[-2],
points_array[index],
points_array[index + 1]
):
new_loop()
break
*Remember that [-1] gives the last item, and [-2] gives the second to last.
As a consequence, the intersection can happen on one of the segments that weren't checked.
Symptom 2
how to make it more forgiving for example if the line is really close to a previous line I would like it to count that as a loop
We could check distance from point to segment.
Symptom 3
how would I make the line actually be the mouse pointer instead of having it ghost behind
Currently the segments are all of the same length. This seems to be a limitation of the way you create CollisionShape2D.
Treatment selection
We could address Symptom 1 by checking every segment. Symptom 2 by improving said checking. But we would still need a solution for Symptom 3 that allows variable segment lengths.
If we create a solution that supports variable segment lengths, we would not need to create multiple segments at once, which solves Symptom 1. We would still need to improve the checking to solve Symptom 2.
If we need to improve the way we check collisions and we are rewriting the collisions anyway, we might as well implement something that allows us to detect self intersections.
We are going to transplant a new way to define collision shapes which allows us to make rotated rectangles of the dimensions we want.
Surgery
I ended up rewriting the whole script. Because I'm like that, I guess.
I decided to have the script create its child nodes in the following structure:
Node
├_line
├_segments
└_loops
Here_line will be a Line2D, _segments will hold multiple Area2D, each a segment. And _loops will also hold Area2D, but they are the polygons of the loops traced.
This will be done in _ready:
var _line:Line2D
var _segments:Node2D
var _loops:Node2D
func _ready() -> void:
_line = Line2D.new()
_line.name = "_line"
add_child(_line)
_segments = Node2D.new()
_segments.name = "_segments"
add_child(_segments)
_loops = Node2D.new()
_loops.name = "_loops"
add_child(_loops)
Another decision I took was to consider the way of the data on the application: We are taking positions. The first position is when the click is just pressed. Subsequent positions are when it moves. From those positions we take points to add to both the line and the segments. From the segments we will get the loops. And we will continue in this manner until the click is released.
Well, if whether or not click was just pressed or it is held, it does not matter. Either way, we take the position of the mouse.
Now, _physics_process will look like this:
func _physics_process(_delta:float) -> void:
if Input.is_action_pressed("left_click"):
position(get_global_mouse_position())
# TODO
We also need to handle when the click is released. Let us make a function for that and worry later about it:
func _physics_process(_delta:float) -> void:
if Input.is_action_pressed("left_click"):
position(get_global_mouse_position())
if Input.is_action_just_released("left_click"):
total_purge()
On position we will follow that odd trick of moving the last point to match the most recent position. We need to make sure that there are at least two points. So the first point does not move, and we can safely move the last point.
var _points:PoolVector2Array = PoolVector2Array()
var _max_distance = 20
func position(pos:Vector2) -> void:
var point_count = _points.size()
if point_count == 0:
_points.append(pos)
_points.append(pos)
elif point_count == 1:
_points.append(pos)
else:
if _points[-2].distance_to(pos) > _max_distance:
_points.append(pos)
else:
_points[-1] = pos
Notice we check the distance to the second to last point. We cannot check against the last point because that is the one we are moving.
If the distance is greater than _max_dinstance then we add a new point, otherwise we move the last point.
We also need to add and update segments:
var _points:PoolVector2Array = PoolVector2Array()
var _max_distance = 20
func position(pos:Vector2) -> void:
var point_count = _points.size()
if point_count == 0:
_points.append(pos)
_points.append(pos)
add_segment(pos, pos)
elif point_count == 1:
_points.append(pos)
add_segment(_points[-2], pos)
else:
if _points[-2].distance_to(pos) > _max_distance:
_points.append(pos)
add_segment(_points[-2], pos)
else:
_points[-1] = pos
change_segment(_points[-2], pos)
You know, we worry later about how that works.
We also need to handle the case when there are too many points:
var _points:PoolVector2Array = PoolVector2Array()
var _max_points = 30
var _max_distance = 20
func position(pos:Vector2) -> void:
var point_count = _points.size()
if point_count == 0:
_points.append(pos)
_points.append(pos)
add_segment(pos, pos)
elif point_count == 1:
_points.append(pos)
add_segment(_points[-2], pos)
elif point_count > _max_points:
purge(point_count - _max_points)
else:
if _points[-2].distance_to(pos) > _max_distance:
_points.append(pos)
add_segment(_points[-2], pos)
else:
_points[-1] = pos
change_segment(_points[-2], pos)
We need to update the Line2D, and we need to handle any loops:
var _points:PoolVector2Array = PoolVector2Array()
var _max_points = 30
var _max_distance = 20
func position(pos:Vector2) -> void:
var point_count = _points.size()
if point_count == 0:
_points.append(pos)
_points.append(pos)
add_segment(pos, pos)
elif point_count == 1:
_points.append(pos)
add_segment(_points[-2], pos)
elif point_count > _max_points:
purge(point_count - _max_points)
else:
if _points[-2].distance_to(pos) > _max_distance:
_points.append(pos)
add_segment(_points[-2], pos)
else:
_points[-1] = pos
change_segment(_points[-2], pos)
_line.points = _points
process_loop()
Alright, let us talk about adding and updating segments:
var _width = 5
func add_segment(start:Vector2, end:Vector2) -> void:
var points = rotated_rectangle_points(start, end, _width)
var segment = Area2D.new()
var collision = create_collision_polygon(points)
segment.add_child(collision)
_segments.add_child(segment)
func change_segment(start:Vector2, end:Vector2) -> void:
var points = rotated_rectangle_points(start, end, _width)
var segment = (_segments.get_child(_segments.get_child_count() - 1) as Area2D)
var collision = (segment.get_child(0) as CollisionPolygon2D)
collision.set_polygon(points)
Here _width is the width of the collision polygons we want.
We are either adding an Area2D with a collision polygon (created via function we will worry about later), or we are taking the last Area2D and updating its collision polygon by the same means.
So, how do we get the points for the rotated rectangle?
static func rotated_rectangle_points(start:Vector2, end:Vector2, width:float) -> Array:
var diff = end - start
var normal = diff.rotated(TAU/4).normalized()
var offset = normal * width * 0.5
return [start + offset, start - offset, end - offset, end + offset]
So you take the vector that goes from the start to the end of the segment, and rotate it a quarter turn (a.k.a. 90º). That gives you a vector that is normal (perpendicular) to the segment, which we will use to give it width.
From the starting point, we find the first point of the rectangle by going half width in the normal direction, and we find the second by going the other half width in the opposite direction. Do the same with the ending point and we have the four corners of the rectangle.
And we return them in an order such that they go around the rectangle.
Creating a collision polygon with those points is straight forward:
static func create_collision_polygon(points:Array) -> CollisionPolygon2D:
var result = CollisionPolygon2D.new()
result.set_polygon(points)
return result
Ok, let us talk about purging. I added a function to purge points (of the line) and segments. That is part of the total purge. The other part will be removing the loops:
func total_purge():
purge(_points.size())
purge_loops()
That was easy.
Alright, to purge points and segment we iterate and remove them.
func purge(index:int) -> void:
var segments = _segments.get_children()
for _index in range(0, index):
_points.remove(0)
if segments.size() > 0:
_segments.remove_child(segments[0])
segments[0].queue_free()
segments.remove(0)
_line.points = _points
That check for if segments.size() > 0 is necessary, by the way. Sometimes the purge leaves points without segment, which cause problems later. And this is the simpler solution.
And, of course, we have to update the Line2D.
What about purging loops? Well, you remove them all:
func purge_loops() -> void:
for loop in _loops.get_children():
if is_instance_valid(loop):
loop.queue_free()
Finally we can process the loops. We will be checking the overlapping areas of the segments to find if they intersect with each other.
One caveat: we want to ignore overlaps of adjacent segments (which are bound to happen, and do not constitute loops).
So we iterate over the segments, check the overlapping areas, look for them among the segments (if they are there at all), and if they are not adjacent (the different of their index among the segments must be greater than 1). If all that happens, we have a loop:
func process_loop() -> void:
var segments = _segments.get_children()
for index in range(segments.size() - 1, 0, -1):
var segment = segments[index]
var candidates = segment.get_overlapping_areas()
for candidate in candidates:
var candidate_index = segments.find(candidate)
if candidate_index == -1:
continue
if abs(candidate_index - index) > 1:
push_loop(candidate_index, index)
purge(index)
return
So, when a loops happens we want to do something with it, right? That is what push_loop is for. We also want to remove the points and segments that were part of the loop (or were before the loop), so we call purge.
Only push_loop is left to discuss:
func push_loop(first_index:int, second_index:int) -> void:
purge_loops()
var loop = Area2D.new()
var points = _points
points.resize(second_index)
for point_index in first_index + 1:
points.remove(0)
var collision = create_collision_polygon(points)
loop.add_child(collision)
_loops.add_child(loop)
As you can see, it creates an Area2D, with a collision polygon that corresponds to the loop. I decide to use rezise to remove points that are after the loop, and a for loop to remove the points that are before. So only the points of the loop remain.
Also notice I'm calling purge_loops at the start, that ensures there will only be one loop at a time.
Going back to the symptoms: Symptoms 1 and 3 are solved by that trick of always moving the last point (and updating the segment). And Symptom 2 is addressed by the width of the rectangles. Tweak that value.
In godot i have ParallaxLayer
extends ParallaxLayer
var motion = Vector2(-50, 0)
var start_pos = Vector2()
var speed = -50
# Called when the node enters the scene tree for the first time.
func _ready():
set_mirroring(motion)
pass
func _process(delta):
speed -= 5
set_motion_offset(motion+Vector2(speed,0))
this code make the background scroll but not infinite
I dont know what to do when scrolling to end
the official document say i should use set_mirroring
can somebody tell me how to use this function?
or where should i go for more information?
Mirroring expects a Vector2 value, which is the XY coordinates of the "offset" of the mirror. Typically this value would be the height or width of the image you are using as a background (which usually corresponds with the window size), depending on what direction you want to mirror. The example code below assumes a window size of 1080x1920, with the ParallaxLayer being mirrored on the Y axis.
extends ParallaxLayer
func _ready():
set_mirroring(motion_mirroring)
func _process(delta):
motion_mirroring = Vector2(0,1920)
So i am trying to make a fighting game and i have fixed so i can jump and so on by making my y speed += 1 every frame to make it act like gravity and when i press the jump key the y speed -=14. But when i use higher than -=14 for example -=20 the character clips through the ground.
i am using this kind off collison detection
collisions = pygame.sprite.spritecollide(self, object_list, False)
for col in collisions:
if type(col) == Solid:
if (self.rect.top < col.rect.bottom and self.rect.bottom >
col.rect.bottom):
self.rect.top = col.rect.bottom
if (self.rect.bottom > col.rect.top and self.rect.y < col.rect.top):
self.rect.bottom = col.rect.top
self.yspeed = 0
self.jump = False
I guess that your fighter is falling so fast that it passes the platform in one update of the loop...
There are a few ways to solve this issue, the most simple one is to update its position a few times and check for collisions each time...
#example ...
for i in range(4): # split the change into smaller steps ...
self.rect.y += self.velocity_y /4.0
collisions = pygame.sprite.spritecollide(self, object_list, False)
... #all your collisions checks here ...