Path-finding in 2D space - python

I am creating a game where I want an enemy to path track onto the player - who can move in any direction on the 2D plane. At first, I tried...
self.bat_x += (player_rect.centerx - self.rect.centerx) / 60
self.bat_y += (player_rect.centery - self.rect.centery) / 60
Here the path-tracking works fine. I divide each value by 60 so that the enemy doesn't just appear and stick on to my player / to slow the movement of the enemy down. However, the further away the enemy is, the faster it is. The closer the bat gets, the slower the bat gets. This is because, using the x-axis for example, when the distance between the player and the enemy is smaller, player_rect.centerx - self.rect.centerxis smaller so less gets added to self.bat_x. Is there a way so that the path-finding still works but the speed is constant? Or does anyone know a different path-finding method and how to implement it?

Pythagoras is your friend
x = player_rect.centerx - self.rect.centerx
y = player_rect.centery - self.rect.centery
norm = (x**2 + y**2)**0.5
const = 1/60
self.bat_x += const * x / norm
self.bat_y += const * y / norm

One way would be using the locations of the player and enemy to find the slope/angle of the line connecting them.
Considering that enemy is at (x1, y1) and player is at (x2, y2).
Then
angle = arctan((y2 - y1)/x2-x1))
Note that x2 - x1 could be zero, so take care of that case.
After finding the line, you could use polar coordinates to find the next position
For eg
x += speed * sin(angle)
Y += speed * cos(angle)

Related

How can i make my randomly moving object move in a specific angle in pygame

Full Code
self.VEL = 3
self.RAND = numpy.linspace(-self.VEL, +self.VEL, 100) # calculating value between -2 and 2, a total of 100 values
-----------------------------------------
if self.new_line:
self.deltax = random.choice(self.RAND)
self.deltay = random.choice(self.RAND)
self.new_line = False
self.move(self.deltax, self.deltay)
-----------------------------------------
def move(self, deltax, deltay):
self.x += deltax
self.y += deltay
I'm currently building an ant simulator in python with pygame. The ants start in the middle and move on from there randomly. The movement works like this. Every few milliseconds self.new_line is set to True. Then a new self.deltax and self.deltay are calculated. Now, as long as self.new_line is False my ant will move with self.x += deltax and self.y += deltay per iteration. When self.new_line is True again the ant will start a new line with new values for self.deltax and self.deltay. The values for deltax and deltay are between -2 and +2 meaning the ant could move in every angle.
Visualization
Questions:
However i want my ants only to move in a specific angle. Like This
How can i do this?
If you run the program with on ant you will see that the speed of the ant changes with new lines. How can make my ant to have one consistent speed?
How can i make my ant bounce of walls in a specific angle?
Thanks for your help.
These are multiple questions. The intent is to ask only one question. I will only answer 2 of them.
Use pygame.math.Vector2 and rotate() to generate a direction vector with constant length and angle in a specified range:
if self.new_line:
angle = random.randint(-45, 45)
direction_vector = pygame.math.Vector2(0, -self.VEL).rotate(angle)
self.deltax = direction_vector.x
self.deltay = direction_vector.y

How to predict trajectory of ball in a ping pong game, for AI paddle prediction?

This might be a more mathematical question, but I'm trying to get my head around how I can program an unbeatable AI for a ping pong game. From what I have read so far, it would be to simulate the trajectory of a ball when it is moving in the direction towards the AI Paddle.
In this game I have a ball and I can read its x and y position on the board, and then read it again in the next iteration which will allow me to calculate the velocity in the x and y direction.
But I'm not sure how to program how and where the ball will reach the AI paddle's goal position, and consider how many times the ball will bounce off the walls will requires me to use some geometry. But I can't get my head around it and how I will be programming it.
So far what I have thought of is the variables I've been given: the size of the table in x and y direction, the position of the ball "currently" and before in order to get its velocity in x and y direction. My first assumption is to find out a way to calculate whether the ball will hit the walls or the AI goal side instead?
There is a more direct way of doing this instead of repeated "raycasting":
def predict(x, y, vx, vy, h, b):
"""
:param x: ball x position
:param y: ball y position
:param vx: ball x velocity
:param vy: ball y velocity
:param h: the field height
:param b: the y position the prediction is for
:return: ball x position at y = b
"""
m = vy / vx # slope
c = -x * m + y # y-intercept
val = (m * b + c) % (2 * h)
return min(val, 2 * h - val)
Now, step by step
m = vy / vx # slope
c = -x * m + y # y-intercept
val = (m * b + c)
A simple linear function showing the ball's current path.
This works, but only if the ball never hits a side wall.
A Model
Imagine there were fields with the same height on both sides of the original one, stretching into infinity.
Now 'the number of bounces' has become 'the number of cells the ball travels'.
Additionally, if the number of bounces is even, the distance from the lower border of the cell it hits to the point of impact is the same as the height the actual ball would hit at in the real cell.
Therefore
(m * b + c) % (2 * h)
To cover odd bounces as well, you need to mirror the graph around h.
Here is a graphic explanation:
And since the irrelevant graph is the one with values above h, you take the minimum.
Possible Problems
In some languages, the % is a remainder operator, though not python.
If the predictions are negative in some cases add this.
val = ((m * b + c) % (2 * h) + 2 * h) % (2 * h)
This function depends on 'accurate' collision.
So if the bounces are handled in a way similar to this,
if y not in range(0, y_max):
vy *= -1
the predictions will be slightly off.
If you can change the core game, use
if y < 0:
y *= -1
vy *= -1
elif y > y_max:
y = 2 * y_max - y
vy *= -1
A divide by zero exception will be thrown if vx is 0, but since the ball will never hit a wall in this case, this should be handled by the ball movement logic.
Snippets are cool, but functions are better. I can't prove this works, but it seems to.
float pong_get_ball_endpoint(float xpos, float ypos, float xspeed, float yspeed)
{
// In the following, the fabs() mirrors it over the bottom wall. The fmod wraps it when it exceeds twice
// the top wall. If the ball ends up in the top half of the double height section, we reflect it back
auto deltaX = (xspeed > 0) ? (BAT2_X - xpos) : -(xpos - BAT1_X); // How far from ball to opponent bat
auto slope = yspeed / xspeed; // Rise over run, ie: deltaY per X
float newY = fmod(fabs(ypos + deltaX * slope), (2 * MATRIX_HEIGHT)); // New Y, but wrappped every 2*height
if (newY > MATRIX_HEIGHT) // If in top half, reflect to bottom
newY = 2 * MATRIX_HEIGHT - newY;
return newY;
}

How do I have circle bounce back up after gravity takes it down?

I have a ball and once it hits the floor, I want it to bounce back up to say half the original bounce, until it can't bounce anymore. I added a pass where the ball just stops at the bottom. How do I make it bounce back up?
kickoff_location = [WIDTH/2 - ball_size[0] / 2, 210]
gravity = 2
ball_bounce = False
if not(ball_bounce):
if kickoff_location[1] < player_one_pos[1] + (player_width / 2) + ball_size[0]:
kickoff_location[1] += gravity
elif kickoff_location[1] == player_one_pos[1] + (player_width / 2) + ball_size[0]:
kickoff_location[1] -= gravity / 2
pass
You need a speed vector for your ball, ie. vel = [0, 0] # [dX, dY]
You need simple gravity acceleration constant, ie. grav = 0.1
You need enviroment resistance constant, ie. resist = 0.8
You need mentioned elasticity factor, ie. elast = 0.5
Now, in every update cycle you:
update Y part of velocity by gravity: vel[1] += grav
update both velocity parts by resistance: vel[0] *= resist ; vel[1] *= resist
check for collisions, vertical and horizontal separately. If collision is vertical (floor/ceiling) reverse Y speed and modify it by elasticity: vel[1] = -vel[1] * elast. The same for horizontal collisions, but for X part of velocity.
update ball position by speed vector: ball_loc[0] += vel[0] ; ball_loc[1] += vel[1]
And tinker with constants to get realistic ball movement...

Colliding shapes in python

My plan is to create a "simple" 2-player minigame game (for ex. sumo and race).
My goal would be to efficiently implement collisions (my current code can deal only with simple wall physics and movement of the objects) with square, circle (and triangle?) shaped objects that can be either part of the environment (for ex. "rocks" or immovable obstacles) or part of the user controlled items (for ex. "cars" or pushable obstacles). It would be also nice to know how mass could also be accounted for in the collisions.
There are two aspects I need help with:
Different types of dynamic collisions between two moving objects (that have a mass and 2D vector)
(the physics part not the detection).
Making sure everything that needs to collide, collides fast enough (so that my slow computer could still render more than 40-60 frames per second) , and according to the specific rules (or if it would be possible then according to one rule?). So that it would not also be hard to manage the objects that need to be collided (add, remove, modify and so on).
Or should I just implement two types of collision for ex. static + dynamic circle and dynamic + dynamic circle?
def checkcollisions(object1, object2):
# x is the current x position
# y is the current y position
# angle is the current vector angle (calculated from x and y with pythagoros
# speed is the length of the vector
dx = object1.x - object2.x
dy = object1.y - object2.y
dist = hypot(dx, dy)
if dist < object1.radius + object2.radius:
angle = atan2(dy, dx) + 0.5 * pi
total_mass = object1.mass + object2.mass
'''''http://www.petercollingridge.co.uk/pygame-physics-simulation/mass'''''
if (0.79 <= object1.angle < 2.36 or 0.79-2*pi <= object1.angle < 2.36-2*pi) or (3.93 <= object1.angle < 5.5 or 3.93-2*pi <= object1.angle < 5.5-2*pi) and ((0.79 <= object2.angle < 2.36 or 0.79-2*pi <= object2.angle < 2.36-2*pi) or (3.93 <= object2.angle < 5.5 or 3.93-2*pi <= object2.angle < 5.5-2*pi)):
(object2angle, object2speed) = vectorsum((object2.angle, object2.speed*(object2.mass-object1.mass)/total_mass), (angle+pi, 2*object1.speed*object1.mass/total_mass))
(object1angle, object1speed) = vectorsum((object1.angle, object1.speed*(object1.mass-object2.mass)/total_mass), (angle, 2*object2.speed*object2.mass/total_mass))
else:
'''''https://en.wikipedia.org/wiki/Elastic_collision'''''
CONTACT_ANGLE = angle
x = (((object1.speed * cos(object1.angle - CONTACT_ANGLE) * (object1.mass-object2.mass)+ 2*object2.mass*object2.speed*cos(object2.angle - CONTACT_ANGLE))/total_mass)*cos(CONTACT_ANGLE))+object1.speed*sin(object1.angle - CONTACT_ANGLE)*cos(CONTACT_ANGLE + 0.5 * pi)
y = (((object1.speed * cos(object1.angle - CONTACT_ANGLE) * (object1.mass-object2.mass)+ 2*object2.mass*object2.speed*cos(object2.angle - CONTACT_ANGLE))/total_mass)*cos(CONTACT_ANGLE))+object1.speed*sin(object1.angle - CONTACT_ANGLE)*sin(CONTACT_ANGLE + 0.5 * pi)
object1angle = pi/2 - atan2(y, x)
object1speed = hypot(x, y)
x = (((object2.speed * cos(object2.angle - CONTACT_ANGLE)*(object2.mass-object1.mass)+2*object1.mass*object1.speed*cos(object1.angle - CONTACT_ANGLE))/total_mass)*cos(CONTACT_ANGLE))+object2.speed*sin(object2.angle - CONTACT_ANGLE)*cos(CONTACT_ANGLE + 0.5 * pi)
y = (((object2.speed * cos(object2.angle - CONTACT_ANGLE)*(object2.mass-object1.mass)+2*object1.mass*object1.speed*cos(object1.angle - CONTACT_ANGLE))/total_mass)*cos(CONTACT_ANGLE))+object2.speed*sin(object2.angle - CONTACT_ANGLE)*sin(CONTACT_ANGLE + 0.5 * pi)
object2angle = pi/2 - atan2(y, x)
object2speed = hypot(x, y)
(object2.angle, object2.speed) = (object2angle, object2speed)
(object1.angle, object1.speed) = (object1angle, object1speed)
object1.speed *= 0.999
object2.speed *= 0.999
overlap = 0.5*(object1.radius + object2.radius - dist+1)
object1.x += sin(angle)*overlap
object1.y -= cos(angle)*overlap
object2.x -= sin(angle)*overlap
object2.y += cos(angle)*overlap
'''''http://www.petercollingridge.co.uk/pygame-physics-simulation/mass'''''
def vectorsum(vectorx, vectory): # Every array's first number is the degree from 0, the second is speed
x = sin(vectory[0]) * vectory[1] + sin(vectorx[0]) * vectorx[1]
y = cos(vectory[0]) * vectory[1] + cos(vectorx[0]) * vectorx[1] # Calculating new vectors from anle and lenght
angle = pi / 2 - atan2(y, x) # Calculating the degree
speed = hypot(x, y) # Calculating the speed
return angle, speed
(I'm just a beginner with Python (or English) so keep that in mind please.)
Collision detection is very easy in pygame. Take a look at using pygame.sprite. They have several functions to detect collisions. (spritecollide, groupcollide, etc) If you have some complex collision interaction generally you use the rect or circle to see if they collide, then only do your complex calculations on those. Though for most games you do not need to have the cost of perfect collision detection, close enough is good enough.
As far was what happens when you collide, that is more physics then programming. Some concepts to keep in mind are: momentum conservation, elastic vs inelastic collisions, deflection angle. "How to building a 2D physics engine" is a bit too broad for SO question. Maybe look at how-to-create-a-custom-2d-physics-engine-oriented-rigid-bodies

Working around a pygame trigonometry error

I'm making a pygame game designed so that bullets will shoot in the direction of the mouse. I'm using
a Class to define the bullets in a list like this:
class Bullet:
def __init__(self,pos,speed,size):
self.pos = pos
self.speed = speed
self.size = size
def move(self):
self.pos[0] = int(self.pos[0] + self.speed[0])
self.pos[1] = int(self.pos[1] + self.speed[1])
I'm using this trigonometry function to get the vector of the angle in which I'm going to be shooting bullets.
def getUnitVector(x1, y1, x2, y2):
delx = x2 - x1
dely = y2 - y1
m = math.sqrt(delx * delx + dely * dely)
unit = (delx / m, dely / m)
return unit
level = [
I'm not using angles because I have to work around a pygame rounding error.
these are the variables I'm plugging into the function.
mousex, mousey = pygame.mouse.get_pos()
startx = 50
starty = 400
aim = getUnitVector(startx, starty, mousex, mouse
This how i'm handling the aim and making the bullets shoot from the start x,y
if pygame.mouse.get_pressed()[0]:
if reload>10:
bx = BULLETSPEED * aim[0]
by = BULLETSPEED * aim[1]
bullet = Bullet([startx,starty], [bx,by],10)
bullets.append(bullet)
reload=0
reload = reload + 1
I just want to let you know. I'm working on a school assignment and I will be learning more in depth about vectors and trig next unit so I don't really want to spend too much time learning this stuff right now :L . Also if you know any active python forums that might be more helpful in answer this question please comment. I cant find any.
Thank you for your time.
I might just build a work-around by only allowing it to shoot if the mouse is within 20 pixels or something so the error is minimized.
The bullet class should have a rectangle attribute so that the bullet's direction of travel runs through the bullet's center.
Doing this would first require reading the Pygame documentation here: Pygame.Rect Docs
Centering the bullet rectangle on the direction of travel could be implemented in a movement method as such:
def move(self):
self.dir = self.get_direction(self.target) # get direction
if self.dir: # if there is a direction to move
self.trueX += (self.dir[0] * self.speed) # calculate speed from direction to move and speed constant
self.trueY += (self.dir[1] * self.speed)
self.rect.center = (round(self.trueX),round(self.trueY)) # apply values to bullet.rect.center
More information can be found in this helpful sprite movement example:Pygame Sprite Movement

Categories

Resources