Pygame for possible trajectories planning for a car and make the car follow one of that path/trajectory - python

I have to create a visualization where I have to create a car (I considered to represent it as a rectangle) and develop trajectories for that car depending on its lateral & longitudinal acceleration. My problem is I am unable to create trajectories. I am doing this as a part of own project. Hence I have no idea if I am in the right direction or not. Guide me if I am wrong.
Problem1 : How do I show predicted path trajectories (beginner step to create only 8 trajectories and not many) ? Is a possible way to also create trajectories also like a curve using Pygame? If not, can I use a mix pf pygame and tkinter to create a mix of curved and straight line trajectories?
Problem2 : Make my rectangular car run along the chosen trajectory (curved line or straight line) or make it follow the path defined in trajectory.
Solutions Tried:
I have taken a rectangular sprite as a car with an idea that I can make it move around the desired trajectory. I am successful with sprite creation and moving it. But I am unable to show display any trajectory curve and make the car follow the path. I am not sure if I have to take trajectories also as Sprite or only car as Sprite is sufficient and trajectories as lines/curves?
Code of the above same explanation is as follows:
My Sprite Class :
class Vehicle(pygame.sprite.Sprite):
def __init__(self,x,y,slip_angle,length=4):
super().__init__()
width = 50
height = 50
self.image = pygame.Surface([width,height])
#Create a car
self.image.fill(RED)
self.image.set_colorkey(RED)
pygame.draw.rect(self.image, WHITE, [0, 0, width, height])
self.rect = self.image.get_rect()
#Parameters of car
self.position = position = Vector2(x, y)
self.velocity = Vector2(0.0, 0.0)
self.slip_angle = slip_angle
self.length = length
self.max_velocity = 20
self.brake_deceleration = 10
self.free_deceleration = 2
self.initial_velocity = 0
self.long_acceleration = 0.0
self.lat_acceleration = 0.0
self.acceleration = 0.0
self.steering = 0.0
def update(self,dt):
self.velocity += (self.acceleration * dt, 0)
self.velocity.x = max(-self.max_velocity, min(self.velocity.x, self.max_velocity))
if self.steering:
turning_radius = self.length / sin(radians(self.steering))
angular_velocity = self.velocity.x / turning_radius
else:
angular_velocity = 0
self.position += self.velocity.rotate(-self.angle) * dt
self.slip_angle += degrees(angular_velocity) * dt
Trajectory Class:
class trajectory(object):
def __init__(self,x,y):
self.x = x
self.y = y
def draw():
x_value = []
time = []
for acc in range(10):
for dt in range(0,10,0.05):
x = vehicle1.initial_velocity + (vehicle1.velocity*dt) + (0.5 * vehicle1.long_acceleration * dt* dt)
x_value.append(x)
time.append(dt)
coordinates = (x_value,time)
canvas_1 = Canvas(root,700,600,background='pink')
canvas_1.grid(row=0,column=1)
x1 = coordinates[acc]
y1 = coordinates[time]
canvas_1.create_line(x1,y1)
#pygame.draw.line(screen,GREEN,list(coordinates),(700, 600))
def update(self):
#self.angle = vehicle1.slip_angle*pi / 180
self.velocity_h = vehicle1.velocity*cos(angle)
self.velocity_v = vehicle1.velocity*sin(angle)
Pygame Main Logic :
#main Logic
proceed = True
#Capturing events till exit
while proceed:
for event in pygame.event.get():
if event.type == pygame.QUIT:
proceed = False
vehicle1.update(dt)
sprites_list.update(dt)
screen.fill(WHITE)
#screen.pygame.Surface.fill(color, rect=None, special_flags=0)
pygame.draw.line(screen, BLACK, [0, 0], [700, 300], 5)
# self.screen.blit(rotated, [20,30])
sprites_list.draw(screen)
pygame.display.flip()
clock.tick(60)
pygame.quit()
I thank you for your advices and support. Happy to accept any guidance

A simple re-working of your trajectory.draw() should sort it out. I noticed the initial x and y passed to the constructor don't seem to be used. Is it intended that the trajectory is always from ( 0, 0 ) or suchlike?
def draw( screen, initial_vel, vel, accel ):
coordinates = []
for acc in range(10):
for dt in range( 0, 10, 0.05 ):
x = initial_vel + ( vel * dt ) + ( 0.5 * accel * dt * dt )
coordinates.append( ( x, dt ) )
if ( len( coordinates ) > 1 ):
PINK = ( 255, 192, 203 )
pygame.draw.lines( screen, PINK, False, coordinates )
Pass your Window and Vehicle Velocities as a parameters when calling trajectory.draw(), rather than relying on global variables.
my_trajectory.draw( screen, vehicle1.initial_velocity, vehicle1.velocity, vehicle1.long_acceleration )

Related

My screen glitches out when I try to run my subprogram code

I'm working on a subprogram code that will make this happy face bounce around the screen and turn different colours. For some reason, the screen turns into that black glitchy screen and when I press exit at the top the face shows for a quick second before the program shuts down. I can't figure out why this is, here is my code and I've included a picture of what happens at first when I run it:
""" Program to show a very basic function
Most of the program is exactly the same as other programs we have done
The main difference is the grouping of code into a function called
drawHappy() to draw a few shapes together
In the main loop we "call" this function whenever we want to draw this
group of shapes
"""
# import the necessary modules
import pygame
import sys
import math
import random
from random import randint
# initialize pygame
pygame.init()
# set the size for the surface (screen)
# note this screen is resizable by the user
screen = pygame.display.set_mode((800, 600), pygame.RESIZABLE)
# set the caption for the screen
pygame.display.set_caption("Happy Face")
#screen width and height
screenW = screen.get_width()
screenH = screen.get_height()
# define colours you will be using
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
BLACK = (0, 0, 0)
YELLOW = (255, 255, 0)
# funtion to draw a the "happy face"
# it has 4 parameters passed to it xPos, yPos, radius, and colour
# notice all the shapes are drawn "relative" to the xPos and yPos and the radius
def drawHappy(xPos,yPos,r,colour):
pygame.draw.circle(screen,colour,(xPos,yPos),r,1)
eyeRadius = int(1/6*r)
eyeX = int(xPos-1/3*r)
eyeY = int(yPos- 1/3*r)
pygame.draw.circle(screen,colour,(eyeX,eyeY),eyeRadius,1)
eyeX = int(xPos + 1/3*r)
pygame.draw.circle(screen,colour,(eyeX,eyeY),eyeRadius,1)
wMouth = 1.5*r
xMouth = xPos - 3/4*r
yMouth = yPos - 3/4*r
pygame.draw.arc(screen,colour,(xMouth,yMouth,wMouth,wMouth),math.pi,2*math.pi,1)
randomR = randint(1,300)
r = randomR
randomX = randint(r, 800-r)
randomY = randint(r, 600-r)
dx = 0
dy = 0
x = 100
y = 100
speed = 3
x2 = randomX
y2 = randomY
dx2 = speed
dy2 = -speed
colour_list = [YELLOW, BLACK, BLUE, RED, GREEN]
randomcolour = random.choice(colour_list)
colour = RED
# set up clock to control frames per second
clock = pygame.time.Clock()
FPS = 120
# set main loop to True so it will run
main = True
# main loop
while main:
for event in pygame.event.get(): # check for any events (i.e key press, mouse click etc.)
if event.type == pygame.QUIT: # check to see if it was "x" at top right of screen
main = False # set the "main" variable to False to exit while loop
clock.tick(FPS)
screen.fill(WHITE)
oldx = x
oldy = y
x += dx
y += dy
if x >= 800-r or x <= 0+r:
x = oldx
if y >= 600-r or y <= 0+r:
y = oldy
x2 += dx2
y2 += dy2
if x >= 800-r or x <= 0+r:
dx2 = -dx2
randomcolour = random.choice(colour_list)
colour = randomcolour
if y2 >= 600-r or y2 <= 0+r:
dy2 = -dy2
randomcolour = random.choice(colour_list)
colour = randomcolour
# "call" the function "drawHappy()" to draw the happy face
# this is where we would normally do a pygame.draw or a screen.blit()
# we are "passing" the function 4 values to use(x,y,radius, colour)
# it will use these to know where to draw the happy face
drawHappy(x2,y2,r,colour)
pygame.display.flip()
# quit pygame and exit the program (i.e. close everything down)
pygame.quit()
sys.exit()
First of all, you need to call your draw function inside the loop. Your current code shows only a glimpse of "drawing" because it gets executed once you exit the main loop.
So, put your drawHappy() inside of main loop:
while main:
for event in pygame.event.get(): # check for any events (i.e key press, mouse click etc.)
if event.type == pygame.QUIT: # check to see if it was "x" at top right of screen
main = False # set the "main" variable to False to exit while loop
drawHappy(x2,y2,r,colour)
pygame.display.update()
clock.tick(FPS)
screen.fill(WHITE)
Now you will get a random size "smiley" on the screen, But now it will move on exit only, for the same reason it wouldn't display earlier. Next thing is to make it bounce (move). For this you'll need some kind of update of the coordinates, just like you did in the last part of your code, except they also need to be updated during the loop, not after it.
I suggest making a Class because then it will be easier to manipulate the object.
Also, I found it easier to separate draw and update_coordinates code into separate functions and them call them from main loop for example.
Hope this helps, and if you need more help, ask.
Here, I made a quick solution using parts of your code, there is plenty room for improvement especially for update_smiley_position() method where you can control how "smiley" moves.
Also, if you need multiple objects, a list should be passed instead of single object.
import pygame as pg
import math
import random
pg.init()
clock = pg.time.Clock()
window = pg.display.set_mode((800, 600), pg.RESIZABLE)
pg.display.set_caption("Happy Face")
SCREEN_W = window.get_width()
SCREEN_H = window.get_height()
class Smiley:
def __init__(self, x, y, r, color):
self.x = x
self.y = y
self.r = r
self.color = color
self.create_smiley()
def create_smiley(self):
self.eye_radius = int(1/6 * self.r)
self.eye_x1 = int(self.x - 1/3 * self.r)
self.eye_x2 = int(self.x + 1/3 *self.r)
self.eye_y = int(self.y - 1/3 *self.r)
self.mouth_width = 1.5 * self.r
self.mouth_x = self.x - self.r * 0.75
self.mouth_y = self.y - self.r * 0.75
def draw_smiley(self, win):
pg.draw.circle(win, self.color, (self.x, self.y), self.r, 1)
pg.draw.circle(win, self.color, (self.eye_x1, self.eye_y), self.eye_radius, 1)
pg.draw.circle(win, self.color, (self.eye_x2, self.eye_y), self.eye_radius, 1)
pg.draw.arc(win, self.color, (self.mouth_x, self.mouth_y, self.mouth_width, self.mouth_width), math.pi, 2*math.pi, 1)
def update_smiley_position(self):
if self.x >= SCREEN_H - self.r or self.x <= 0 + self.r:
self.x = random.randint(100, 400)
else:
self.x += 5
if self.y >= SCREEN_W - self.r or self.y <= 0 + self.r:
self.y = random.randint(100, 400)
else:
self.y -= 5
self.create_smiley()
def draw(win, smiley):
win.fill(pg.Color("white"))
smiley.draw_smiley(win)
smiley.update_smiley_position()
pg.display.update()
def main_loop(win, smiley):
clock.tick(30)
for event in pg.event.get():
if event.type == pg.QUIT:
return False
draw(win, smiley)
return True
r = random.randint(1, 300)
x = random.randint(r, SCREEN_W - r)
y = random.randint(r, SCREEN_H - r)
smiley = Smiley(x, y, r, pg.Color("red"))
while main_loop(window, smiley):
pass
pg.quit()

Generate enemies around the player from all directions randomly

The enemy are being generated from above the screen and then move toward player in the middle, I want to generate enemies randomly around the screen from all directions but not inside the screen directly and proceed to move towards the player and also enemy sprites are sometimes joining combining and moving together how to repel the enemy sprites.
I have tried changing x,y coordinates of enemy objects using a random range but sometimes they generate objects inside the play screen, I want enemies to generate outside the playing window.
class Mob(pg.sprite.Sprite):
def __init__(self):
pg.sprite.Sprite.__init__(self)
self.image = pg.image.load('enemy.png').convert_alpha()
self.image = pg.transform.smoothscale(pg.image.load('enemy.png'), (33, 33))
self.image_orig = self.image.copy()
self.radius = int(29 * .80 / 2)
self.rect = self.image.get_rect()
self.rect.x = random.randrange(width - self.rect.width)
self.rect.y = random.randrange(-100, -40)
self.speed = 4
self.rot = 0
self.rot_speed = 5
self.last_update = pg.time.get_ticks()
def rotate(self):
now = pg.time.get_ticks()
if now - self.last_update > 50:
self.last_update = now
self.rot = (self.rot + self.rot_speed) % 360
new_image = pg.transform.rotozoom(self.image_orig, self.rot, 1)
old_center = self.rect.center
self.image = new_image
self.rect = self.image.get_rect()
self.rect.center = old_center
def update(self):
self.rotate()
dirvect = pg.math.Vector2(rotator.rect.x - self.rect.x,
rotator.rect.y- self.rect.y)
if dirvect.length_squared() > 0:
dirvect = dirvect.normalize()
# Move along this normalized vector towards the player at current speed.
if dirvect.length_squared() > 0:
dirvect.scale_to_length(self.speed)
self.rect.move_ip(dirvect)
if self.rect.top > height + 10 or self.rect.left < -25 or self.rect.right > width + 20:
self.rect.x = random.randrange(width - self.rect.width)
self.rect.y = random.randrange(-100, -40)
self.speed = random.randrange(1, 4)
[UPDATE]
This the remaining code:
import math
import random
import os
import pygame as pg
import sys
pg.init()
height = 650
width = 1200
os_x = 100
os_y = 45
os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (os_x, os_y)
screen = pg.display.set_mode((width, height), pg.NOFRAME)
screen_rect = screen.get_rect()
background = pg.image.load('background.png').convert()
background = pg.transform.smoothscale(pg.image.load('background.png'), (width, height))
clock = pg.time.Clock()
running = True
font_name = pg.font.match_font('Bahnschrift', bold=True)
def draw_text(surf, text, size, x, y, color):
[...]
class Mob(pg.sprite.Sprite):
[...]
class Rotator(pg.sprite.Sprite):
def __init__(self, screen_rect):
pg.sprite.Sprite.__init__(self)
self.screen_rect = screen_rect
self.master_image = pg.image.load('spaceship.png').convert_alpha()
self.master_image = pg.transform.smoothscale(pg.image.load('spaceship.png'), (33, 33))
self.radius = 12
self.image = self.master_image.copy()
self.rect = self.image.get_rect(center=[width / 2, height / 2])
self.delay = 10
self.timer = 0.0
self.angle = 0
self.distance = 0
self.angle_offset = 0
def get_angle(self):
mouse = pg.mouse.get_pos()
offset = (self.rect.centerx - mouse[0], self.rect.centery - mouse[1])
self.angle = math.degrees(math.atan2(*offset)) - self.angle_offset
old_center = self.rect.center
self.image = pg.transform.rotozoom(self.master_image, self.angle, 1)
self.rect = self.image.get_rect(center=old_center)
self.distance = math.sqrt((offset[0] * offset[0]) + (offset[1] * offset[1]))
def update(self):
self.get_angle()
self.display = 'angle:{:.2f} distance:{:.2f}'.format(self.angle, self.distance)
self.dx = 1
self.dy = 1
self.rect.clamp_ip(self.screen_rect)
def draw(self, surf):
surf.blit(self.image, self.rect)
def shoot(self, mousepos):
dx = mousepos[0] - self.rect.centerx
dy = mousepos[1] - self.rect.centery
if abs(dx) > 0 or abs(dy) > 0:
bullet = Bullet(self.rect.centerx, self.rect.centery, dx, dy)
all_sprites.add(bullet)
bullets.add(bullet)
There's not much informations to go by here, but you probably need to check the x and y range your play window has and make sure the random spawn coordinates you generate are outside of it:
In your init:
# These are just example min/max values. Maybe pass these as arguments to your __init__ method.
min_x = min_y = -1000
max_x = max_y = 1000
min_playwindow_x = min_playwindow_y = 500
max_playwindow_x = max_playwindow_y = 600
self.x = (random.randrange(min_x, min_playwindow_x), random.randrange(max_playwindow_x, max_x))[random.randrange(0,2)]
self.y = (random.randrange(min_y, min_playwindow_y), random.randrange(max_playwindow_y, max_y))[random.randrange(0,2)]
This solution should work in basically any setup. For x and y it generates a tuple of values outside the playing window. Then a coinflip decides on the value. This will only spawn mobs that are diagonally outside the playing field, but it will always generate valid random coordinates.
Another approach would be just generating as many random variables as needed to get a valid pair like this:
while min_playingwindow_x <= self.x <= max_playingwindow_x and
min_playingwindow_y <= self.y <= max_playingwindow_y:
# While within screen(undesired) calculate new random positions
self.x = random.randrange(min_x, max_x)
self.y = random.randrange(min_y, max_y)
This can be really slow however if your valid amount of positions is (for example) only 1% of the total positions.
IF you need something really fleshed out, you need to know the corners of both your map and the rectangle that is actually displayed, which is I assume smaller than the entire map(otherwise you cannot spawn enemies outside your view.
(0,0)
+----------------------+
| A |
|-----+-----------+----|
| D | W | B |
|-----+-----------+----|
| C |
+----------------------+(max_x, max_y)
In this diagram W is the window that is acutally visible to the player, and A,B,C,D together are the part of your map that is not currently visible. Since you only want to spawn mobs outside the player's view, you'll need to make sure that the coordinates you generate are inside your map and outside your view:
def generate_coordinates_outside_of_view(map_width=1000, map_height=1000, view_window_top_left=(100, 100),
view_width=600, view_height=400):
"""
A very over the top way to generate coordinates outside surrounding a rectangle within a map almost without bias
:param map_width: width of map in pixels (note that 0,0 on the map is top left)
:param map_height: height of map in pixels
:param view_window_top_left: top left point(2-tuple of ints) of visible part of map
:param view_width: width of view in pixels
:param view_height: height of view in pixels
"""
from random import randrange
# generate 2 samples for each x and y, one guaranteed to be random, and one outside the view for sure.
x = (randrange(0, map_width), (randrange(0, view_window_top_left[0]),
randrange(view_window_top_left[0] + view_width, map_width))[randrange(0, 2)])
y = (randrange(0, map_height), (randrange(0, view_window_top_left[1]),
randrange(view_window_top_left[1] + view_height, map_height))[randrange(0, 2)])
# now we have 4 values. To get a point outside our view we have to return a point where at least 1 of the
# values x/y is guaranteed to be outside the view.
if randrange(0, 2) == 1: # to be almost completely unbiased we randomize the check
selection_x = randrange(0, 2)
selection_y = randrange(0, 2) if selection_x == 1 else 1
else:
selection_y = randrange(0, 2)
selection_x = randrange(0, 2) if selection_y == 1 else 1
return x[selection_x], y[selection_y]
HTH

Adding gravity to a bouncing ball using vectors

I have a gravity vector (in the form [r, theta]) which I add to my ball's velocity vector. For some reason, the ball doesn't return to the same height after bouncing, but instead slowly loses height sporadically. I am guessing there's some rounding error or something in a calculation I'm using, but I can't isolate the issue.
Here is my code. You need both files and pygame to run it. Sorry if it's a little confusing. I can comment anything some more if you want.
I added a marker whenever the ball reaches its max height so you guys what I mean. I want the ball to return to exactly the same height every time it bounces.
I took a little bit of unnecessary code out. The full program is under the pastebin links.
https://pastebin.com/FyejMCmg - PhysicsSim
import pygame, sys, math, tools, random, time
from pygame.locals import *
clock = pygame.time.Clock()
lines = []
class Particle:
def __init__(self,screen,colour, mass, loc, vel):
self.screen = screen
self.colour = colour
self.mass = mass
self.x = loc[0]
self.y = loc[1]
self.location = self.x,self.y
self.speed = vel[0]
self.angle = vel[1]
def update(self):
global lines
# add gravity
self.speed,self.angle = tools.add_vectors2([self.speed,self.angle], tools.GRAVITY)
# update position
dt = clock.tick(60)
self.x += self.speed * tools.SCALE * math.cos(self.angle) * dt
self.y -= self.speed * tools.SCALE * math.sin(self.angle) * dt
self.location = int(self.x),int(self.y)
# border checking
do = False
n=[]
if ((self.y+self.mass) > tools.SCREEN_HEIGHT):
self.y = tools.SCREEN_HEIGHT-self.mass
n = [0,1]
do = True
# adds position to array so max height so max height can be recorded
if (self.speed==0):
lines.append([self.screen, self.location, self.mass])
# bounce
if do:
#init, convert everything to cartesian
v = tools.polarToCartesian([self.speed, self.angle])
#final -> initial minus twice the projection onto n, where n is the normal to the surface
a = tools.scalarP(2*abs(tools.dotP(v,n)),n) #vector to be added to v
v = tools.add_vectors(v,a)
self.angle = tools.cartesianToPolar(v)[1] # does not set magnitude
# drawing
pygame.draw.circle(self.screen, self.colour, self.location, self.mass, 0)
# draws max height line
def draw_line(l):
screen = l[0]
location = l[1]
radius = l[2]
pygame.draw.line(screen, tools.BLACK, [location[0] + 15, location[1]-radius],[location[0] - 15, location[1]-radius])
def main():
pygame.init()
DISPLAY = pygame.display.set_mode(tools.SCREEN_SIZE,0,32)
DISPLAY.fill(tools.WHITE)
particles = []
particles.append(Particle(DISPLAY, tools.GREEN, 10, [100,100], [0,0]))
done = False
while not done:
global lines
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
DISPLAY.fill(tools.WHITE)
for i in particles:
i.update()
for l in lines:
draw_line(l)
pygame.display.update()
main()
https://pastebin.com/Epgqka31 - tools
import math
#colours
WHITE = (255, 255, 255)
BLUE = ( 0, 0, 255)
GREEN = ( 0, 255, 0)
RED = ( 255, 0, 0)
BLACK = ( 0, 0, 0)
COLOURS = [WHITE,BLUE,GREEN,RED,BLACK]
#screen
SCREEN_SIZE = SCREEN_WIDTH,SCREEN_HEIGHT = 1000,700
#vectors
GRAVITY = [5.0, 3*math.pi/2] # not 9.8 because it seems too high
SCALE = 0.01
# converts polar coordinates to cartesian coordinates in R2
def polarToCartesian(v):
return [v[0]*math.cos(v[1]), v[0]*math.sin(v[1])]
# converts cartesian coordinates to polar coordinates in R2
def cartesianToPolar(v):
return [math.sqrt(v[0]**2 + v[1]**2), math.atan2(v[1],v[0])]
# dots two cartesian vectors in R2
def dotP(v1, v2):
return v1[0]*v2[0] + v1[1]*v2[1]
# multiplies cartesian vector v by scalar s in Rn
def scalarP(s,v):
v_=[]
for i in v:
v_.append(s*i)
return v_
# returns the sum of two cartesian vectors in R2
def add_vectors(v1, v2):
return [v1[0]+v2[0], v1[1]+v2[1]]
# returns the sum of two polar vectors in R2, equations from https://math.stackexchange.com/questions/1365622/adding-two-polar-vectors
def add_vectors2(v1,v2):
r1,r2,t1,t2 = v1[0],v2[0],v1[1],v2[1]
return [math.sqrt(r1**2 + r2**2 + 2*r1*r2*math.cos(t2-t1)), t1 + math.atan2(r2*math.sin(t2 - t1), r1 + r2*math.cos(t2 - t1))]
Your time interval, dt = clock.tick(60), is not a constant. If you change it to dt = 60 your program runs as expected.
Have a look a the Verlet Algorithm and implement it in your code. You are on the right track!

Earth pulling Sun in 2d Physics Engine

I'm creating a basic physics engine in python and I've managed to create a simple "planet" class to handle the maths. I've also created a basic working scenario when a small planet orbits a larger one - if the small planet has zero mass. However, when I give the small planet any mass it slowly pulls the sun to the right in an arc shape. It's easier to see in this image:
.
I understand what's going on - the earth is pulling the sun right on one side and stopping it but not pulling it back on the other - but I have no idea how to fix it, short of giving the sun a hard-coded velocity. I've checked a few other sources but nothing helps - in fact this physics sim has the same problem (uncheck system centered).
Does this happen in the real world, or is it a glitch with this type of simulation? Either way, is there some sort of hack to prevent this from happening?
My planet class (all the methods are pretty straightforward):
import math
G = 6
class Planet():
def __init__(self, x = 0, y = 0, vx = 0, vy = 0, mass = 1, radius = 1, colour = "#ffffff"):
self.x = x
self.y = y
self.vx = vx
self.vy = vy
self.mass = mass
self.radius = radius
self.colour = colour
def draw(self, canvas):
canvas.create_oval(self.x - self.radius, self.y - self.radius, self.x + self.radius, self.y + self.radius, fill = self.colour)
def move_next(self):
self.x += self.vx
self.y += self.vy
def apply_gravity(self, other):
if other is self: #make sure you're not checking against yourself.
return 1
x = other.x - self.x
y = other.y - self.y
r = math.hypot(x, y)
if r == 0: #make sure they're not on each other.
return 1
gravity = G * (other.mass) / (r ** 2) #calculate the force that needs to be applied
self.vx += (x / r) * gravity #normalise the x component of vector and multiply it by the force
self.vy += (y / r) * gravity #normalise the y component of vector and multiply it by the force
And the "main" file:
from tkinter import *
import time
import planet
FPS = 1/60
window = Tk()
canvas = Canvas(window, width = 640, height = 400, bg = "#330033")
canvas.pack()
earth = planet.Planet(x = 320, y = 100, vx = 10, mass = 1, radius = 10, colour = "lightblue") #Because the earth's mass is so low, it won't be AS noticable, but it still pulls the sun a few pixels per rotation.
sun = planet.Planet(x = 320, y = 200, mass = 2e3, radius = 20, colour = "yellow")
planets = [earth, sun]
while True:
canvas.delete(ALL)
for planet in planets:
for other in planets:
planet.apply_gravity(other)
planet.move_next()
planet.draw(canvas)
window.update()
time.sleep(FPS)

Pygame - Gravity Methods

I'm in the process of making a simple game in pygame. Its looking to be a platformer RPG. But that is neither final or relevant per this question. So far i have very little functionality in the game. Its just a skeleton at this point if that. My question is kind of two fold:
Whats the best (in terms of performance and flexibility) way to add gravity to classes in pygame?
What are the best practices for adding gravity in general? For example, do you just simply do a "if keyPressed == k_W then subtract 2pixels per tick from player-y for 20 ticks" or something with velocity in the up or negative-y direction?
I've seen other posts on adding gravity to games after the fact, where adding it really wasn't thought about during initial development. I want to add it in as early as possible so instead of adding gravity to other things, i can add other things to gravity. I'm going to continue to read up on this, so if you prefer to point me in the direction of some online resources, I'd much appreciate that as well!
Quick dislaimer: I do not know multiple ways to incorporate gravity, so I can not say which is "best". But, if you're fighting the performance battle in Python, you've probably fighting the wrong battle.
For gravity, you can use a vector system. Say a character jumps off the ground and has initial velocity of [5, -15] (negative y because positive y is down!), you can move your character's rect by this velocity every frame to simulate movement. To throw gravity into this, you need to add 9.8 to your y velocity component value every second. So 1 second in, the velocity will be about [5, -5]. This will have your character slow to a stop, and begin moving down.
For key pressed movement, I recommend using booleans. An example, upon pressing k_U , a variable that says you are moving up becomes True. Then, if this variable is True, you move him, say, [0, -5]. Upon keyup, set variable to false. Do this for north/east/south/west, and then you have a movement system in 4 directions, that moves you while you hold the key down.
I used the 1/2 mg^2 equation in this code, which has a snow like effect:
import math, sys, random, time
import pygame, inputbox
from pygame.locals import *
class flake:
def __init__(self, xpos, ypos, mass, color, drift):
self.xpos = xpos
self.ypos = ypos
self.mass = mass
self.rect = pygame.Rect(xpos, ypos, 2, 2)
self.checked = False
self.color = color
self.drift = drift
size = width, height = 510, 700
BLACK = (0,0,0)
WHITE = (255, 255, 255)
GREY = (128,128,128)
DARKGREY = (169,169,169)
SILVER = (192,192,192)
LIGHTGREY = (211,211,211)
LIGHTESTGREY = (220,220,220)
pygame.init()
screen = pygame.display.set_mode(size)
background = pygame.Surface(screen.get_size())
background = background.convert()
background.fill(BLACK)
def init():
global theSnow, snowColours, clock, startrange
theSnow = []
snowColours = []
snowColours.append(WHITE)
snowColours.append(GREY)
snowColours.append(DARKGREY)
snowColours.append(SILVER)
snowColours.append(LIGHTGREY)
snowColours.append(LIGHTESTGREY)
for c in range(2000):
mass = 0.0
mass = float(random.randint(1,8) / 100.0)
xpos = random.randint(0,width)
ypos = random.randint(0,5)
ypos = -ypos
drift = ypos/10.0
colour = snowColours[random.randint(0,5)]
f = flake(xpos, ypos, mass, colour, drift)
theSnow.append(f)
print "flake x = " + str(f.xpos) + " y = " + str(f.ypos) + " mass = " + str(f.mass)
startrange = 200
clock = pygame.time.Clock()
def run():
global theSnow, clock
global startrange
newrange = 0
while True:
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
sys.exit()
keys=pygame.key.get_pressed()
if keys[K_q]:
return
g = 3
for count in range(startrange):
yinc = 0.0
yuncertainty = float(random.randint(1,5)/10.0)
yinc = float(0.5 * theSnow[count].mass * (g*g)) + yuncertainty
theSnow[count].ypos += yinc
xuncertainty = random.randint(1,10)
if xuncertainty > 4:
theSnow[count].xpos += theSnow[count].drift
else:
theSnow[count].xpos -= theSnow[count].drift
theSnow[count].rect = pygame.Rect(theSnow[count].xpos, theSnow[count].ypos, 2,2)
if not theSnow[count].checked:
if theSnow[count].ypos > 30:
for c in range(newrange, startrange):
print " c= " + str(c)
theSnow[c].checked = True
if startrange < 2000:
startrange += 100
newrange = startrange - 100
print " newrange = " + str(newrange)
print " startrange = " + str(startrange)
update()
pygame.time.wait(10)
#clock.tick(10)
def update():
global theSnow, startrange
background.fill(BLACK)
for count in range(startrange):
pygame.draw.rect(background, theSnow[count].color, theSnow[count].rect)
screen.blit(background, (0, 0))
pygame.display.flip()
if __name__ == "__main__":
init()
run()

Categories