Pygame clock function returns a strange value - python

Basically, I've wanted to get back into Python, so I decided to make a small game in pygame, where there is a bouncy ball which you need to keep bouncing in the air. The problem is that when I use the functions clock.tick() and clock.get_time(), get_time should return the time passed in milliseconds, but it passes time in milliseconds*10.
My code:
GRAVITY = 10
def move(self, delta):
self.x+= (self.vx * delta)
self.y+= (self.vy * delta)
def speed(self, delta):
self.vy += (GRAVITY * delta)
clock.tick()
while True:
clock.tick()
delta = (clock.get_time() / 100) #should be /1000
ball.move(delta)
ball.speed(delta)
It works smoothly like in real world when its /100, but works really slow then its /1000.

I think part of the problem might be truncation from dividing by "1000" instead of "1000.0". You can verify clock.tick is working with this:
import pygame
pygame.init()
i = 0
clock = pygame.time.Clock()
while i < 10: # Just run a few cycles to check the output
res = clock.tick(1) # Caps framerate to 1 fps
print res # This should print out 1000
i += 1
Note that clock.tick already returns the delta and is normally used to cap the framerate. If left uncapped, you might have a really high FPS, giving a small delta and, when divided by an int, isn't larger than 1 so you rarely get a number at all.
Also, make sure you have all your units right, that you really want to be converting to seconds

Related

pygame.Rect objects keep getting stuck at top = 0 and don't move when .move or .move_ip is used

This is a small part of some more complicated code but basically. I've narrowed down the problem to this one bit of code.
I'm using the formula s = ut + 1/2 at^2 to get the distance traveled in one instance to time, in this case, 0.001 seconds which will then further be used to draw the rects on a window.
import pygame
run = True
clock = pygame.time.Clock()
ex_s = pygame.Rect((500, 625, 50, 50))
start_height = ex_s.top
print(start_height, "m")
g = -9.8
v = 400
t = 0.001
while run:
ex_s.top -= (v * t) + ((g / 2) * (t ** 2))
print(ex_s.top - start_height)
if (ex_s.top - start_height) * -1 <= -10:
run = False
clock.tick(30)
I want the rectangles to move upwards/downwards based on their velocity.
The rectangles move up and get stuck whenever the top reaches y = 0. This doesn't happen if the value of "t" is increased to o.o1 or above but I need it to be 0.001 for the precision. The problem is also solved when the value of v is increased by an order of magnitude, changing g does nothing.
I tried using both move and move_ip and the y coordinate did not change at all.
I've also tried manually pushing the objects above y = 0 when they hit it but it doesn't do anything. The fact that the problem changes with decimal places has led me to believe it could be something to do with the integer coordinate system of pygame but I don't see how this would cause things to move first and then get stuck only at 0.
See Pygame doesn't let me use float for rect.move, but I need it. The problem is that a pygame.Rect object can only store integer data. So when you add a value less than 1.0 to the coordinate of a rectangle, than the position stays the same. You need to store the position of the rectangle in separate variables and update the position of the rectangle using these variables.
ex_s = pygame.Rect((500, 625, 50, 50))
ex_s_y = ex_s.top
# [...]
while run:
# [...]
# change position
ex_s_y = .....
ex_s.top = round(ex_s_y)
You didn't change the time t variable, and your formula for the motion is wrong. Also your initial time, initial velocity and g are wrong.
If you want to use an exact solution for the position, do like this.
dt = 0.05
t = 0
g = 9.8 # It's positive by definition.
v = -100 # It's upward.
while run:
y = 625 + v*t + 0.5*g*t**2 # The acceleration is +g, downward.
t += dt
ex_s.top = y
...
If you want to solve the equation of motion approximately and iteratively, do like this inside the loop.
...
y = 625
while run:
v += g * dt
y += v * dt
t += dt
ex_s.top = y
...
The above method is called sympletic Euler method. There're other more accurate methods, such as Runge-Kutta method.

Simulating Momentum in a Game Engine

Just for fun, I've been working on a simple platformer engine in Pygame over break. Today I tried to add some momentum code so that the player doesn't instantly stop, but I've been running into a problem. My momentum code does not slow the player down at a constant rate over all possible FPS rates. Here is my code:
def getXDisplacement(self):
pressed = pygame.key.get_pressed()
dt = self.xTimer.elapsedTime()
self.momentum = self.momentum * ( (1-self.momentumDecayRate) ** dt)
self.xDisplacement = ((self.rightKey.ifActive(pressed)-self.leftKey.ifActive(pressed)) + self.momentum)
self.momentum = self.xDisplacement
self.xDisplacement *= dt
self.xDisplacement = numpy.clip(self.xDisplacement, -700*dt, 700*dt)
self.xTimer.reset()
To slow down the FPS, I'll add a print() function to the top of the getXDisplacement() function. I also comment out the numpy.clip() function towards the bottom to see how total speed is affected. With a slower FPS rate, the player accelerates much more slowly and doesn't quite reach the same speed as at higher FPS rates. I can't imagine why an exponential decay function wouldn't work here. Am I using it improperly, or is the problem more complicated than I think? For reference, here is the link to the full program: https://github.com/2Amateurs/SimplePygamePlatformer.
You can use the pygame clock to limit the maximum FPS:
clock = pygame.time.Clock()
# in your game loop
while True:
clock.tick(FPS)
If you're certain your game won't run slower than say 30 FPS, this is already sufficient. If not, you can use the clock to calculate the delta time between the last and this frame.
clock = pygame.time.Clock()
# in your game loop
while True:
delta_time = clock.tick_busy_loop()
getXDisplacement(delta_time)
def getXDisplacement(self, dt):
pressed = pygame.key.get_pressed()
self.momentum = self.momentum * ( (1-self.momentumDecayRate) ** dt)
self.xDisplacement = ((self.rightKey.ifActive(pressed)-self.leftKey.ifActive(pressed)) + self.momentum)
self.momentum = self.xDisplacement
self.xDisplacement *= dt
self.xDisplacement = numpy.clip(self.xDisplacement, -700*dt, 700*dt)
tick_busy_loop is not the only way, also take a look at Clock.get_time.

Turtle slowing down for larger drawings

I have created some code in order to simulate sierpinski's triangle. For this, in my code, I set it so that it can simulate any number of points on the triangle, but I've noticed that increasing this to a large number, it ends up with the turtle taking an amount of time that increases as it goes on.
I did a sample output with 100 000 dots and this is what I got
0% done
Time since last update: 0.0019948482513427734
5.0% done
Time since last update: 1.2903378009796143
10.0% done
Time since last update: 1.8589198589324951
15.000000000000002% done
Time since last update: 2.325822114944458
20.0% done
Time since last update: 2.9351391792297363
25.0% done
Time since last update: 3.4773638248443604
30.0% done
Time since last update: 4.152036190032959
35.0% done
Time since last update: 4.7314231395721436
40.0% done
Time since last update: 5.260996103286743
44.99999999999999% done
Time since last update: 5.988528490066528
49.99999999999999% done
Time since last update: 6.804485559463501
54.99999999999999% done
Time since last update: 7.768667221069336
60.0% done
Time since last update: 8.379971265792847
65.0% done
Time since last update: 8.995774745941162
70.0% done
Time since last update: 15.876121282577515
75.00000000000001% done
Time since last update: 17.292492151260376
80.00000000000001% done
Time since last update: 29.57323122024536
85.00000000000001% done
Time since last update: 65.96741080284119
90.00000000000003% done
Time since last update: 148.21749567985535
95.00000000000003% done
the code in question to run the main loop of the program is this
t = turtle.Turtle()
t.penup()
curr_time = time()
for x in range(iterations):
#The code will take a while should the triangle be large, so this will give its % completion
if x / iterations > percent_done:
print(percent_done * 100, r"% done", sep='')
percent_done += 0.05
window.update()
print(f"Time since last update:\t{time() - curr_time}")
curr_time = time()
c = determine_point(t, init_c)
make_point(t, c)
determine_point and make_point do not iterate at all, so I can't find a reason for turtle to slow down this considerably. Why does turtle slow down as more points are being created? (screen tracer has already been set to (0,0) and the code itself works as intended
make point:
def make_point(turt: turtle.Turtle, point):
'''
Basically does turt.goto(*point) where point is a list of length 2, then places a dot at that place
I did not use goto because I forgot what the function was, and instead of doing a 2 second google search to find out,
I did a 15 second google search to find out how to set the angle of the turtle, and use trigonometry and pythagorean
theorem in order to move the turt to the right position
'''
if point[0] != turt.xcor():
y = point[1] - turt.ycor()
x = point[0] - turt.xcor()
if x > 0:
turt.setheading(math.degrees(math.atan(y / x)))
else:
turt.setheading(math.degrees(math.pi + math.atan(y / x)))
else:
turt.setheading(0 if point[1] < turt.ycor() else 180)
turt.fd(pythag(turt.xcor(), turt.ycor(), point[0], point[1]))
turt.dot(3)
determine_point:
def determine_point(turt: turtle.Turtle, initial_cords):
'''
Returns the midpoint between the turt's current coordinates and a random one of the of the 3 starting points
'''
coord = initial_cords[random.randint(0, 2)]
result = []
result.append((coord[0] - turt.xcor()) / 2 + turt.xcor())
result.append((coord[1] - turt.ycor()) / 2 + turt.ycor())
return result
pythag:
def pythag(a, b, x, y):
'''
Does pythagorean theorem to find the length between 2 points
'''
return math.sqrt((x - a) ** 2 + (y - b) ** 2)
Although your code doesn't require more resources on each iteration, your program does because it is built atop libraries that do. This is true of both turtle and tkinter.
Although we think of turtle dot() as putting up dead ink, as opposed to stamp which can be selectively removed, to the underlying tkinter graphics, everything is live ink and adds to its (re)display lists.
I couldn't completely remove the increase in time for each iteration, but by optimizing the code, I believe I've reduced it to no longer being an issue. Some of my optimizations: put turtle into radians mode to avoid calls to math.degrees(); reduce calls into turtle, eg. by getting x and y in one step via position() rather than calling xcor() and ycor(); turn off turtle's undo buffer as it keeps a growing list of graphic commands:
from turtle import Screen, Turtle
from time import time
from random import choice
from math import sqrt, atan, pi
iterations = 100_000
init_c = [(300, -300), (300, 300), (-300, -300)]
def make_point(t, point):
'''
Basically do turtle.goto(*point) where point is a list of length 2,
then places a dot at that place
I did not use goto() because I forgot what the function was, and instead
of doing a 2 second google search to find out, I did a 15 second google
search to find out how to set the angle of the turtle, and use trigonometry
and pythagorean theorem in order to move the turtle to the right position
'''
x, y = t.position()
if point[0] != x:
dx = point[0] - x
dy = point[1] - y
if dx > 0:
t.setheading(atan(dy / dx))
else:
t.setheading(pi + atan(dy / dx))
else:
t.setheading(0 if point[1] < y else pi)
t.forward(pythag(x, y, point[0], point[1]))
t.dot(2)
def determine_point(t, initial_cords):
'''
Return the midpoint between the turtles's current coordinates
and a random one of the of the 3 starting points
'''
coord = choice(initial_cords)
x, y = t.position()
return [(coord[0] - x) / 2 + x, (coord[1] - y) / 2 + y]
def pythag(a, b, x, y):
'''
Do pythagorean theorem to find the length between 2 points
'''
return sqrt((x - a) ** 2 + (y - b) ** 2)
screen = Screen()
screen.tracer(False)
turtle = Turtle()
turtle.hideturtle()
turtle.setundobuffer(None)
turtle.radians()
turtle.penup()
fraction_done = 0
curr_time = time()
for iteration in range(iterations):
# The code will take a while should the triangle be large, so this will give its % completion
if iteration / iterations > fraction_done:
screen.update()
print(fraction_done * 100, r"% done", sep='')
fraction_done += 0.05
print(f"Time since last update:\t{time() - curr_time}")
curr_time = time()
make_point(turtle, determine_point(turtle, init_c))
screen.update()
screen.exitonclick()
CONSOLE
% python3 test.py
0% done
Time since last update: 0.024140119552612305
5.0% done
Time since last update: 1.2522082328796387
10.0% done
Time since last update: 1.619455099105835
15.000000000000002% done
Time since last update: 1.9793877601623535
20.0% done
...
Time since last update: 9.460317373275757
85.00000000000001% done
Time since last update: 10.161489009857178
90.00000000000003% done
Time since last update: 10.585438966751099
95.00000000000003% done
Time since last update: 11.479820966720581

setting the pace of a character

I want to define a function to set the speed to characters in my game, I understand there is a formula for this:
rate * time = distance
1. Establish a rate of movement in whatever units of measure you want (such as pixels per millisecond).
2. Get the time since the last update that has passed (elapsed time).
3. Establish the direction of movement .
I have tryied to define a method which implements this:
def speed(self, speed):
clock = pygame.time.Clock()
milliseconds = clock.tick(60) # milliseconds passed since last frame
seconds = milliseconds / 1000.0
speed = seconds * (self.dx+self.dy)
But when I call this method to change the speed of my character, nothing happens.
Any suggestions?
You need to use the self keyword so set a class attribute in a method. But in your method you use speed as a parameter which I don't believe is how you want to use it.
def speed(self, speed):
clock = pygame.time.Clock()
milliseconds = clock.tick(60) # milliseconds passed since last frame
seconds = milliseconds / 1000.0
speed = seconds * (self.dx+self.dy)
This method takes a speed and then sets it equal to something and then it goes out of of scope with out making a change. To set an objects speed attribute with a method you would:
def set_speed(self):
clock = pygame.time.Clock()
milliseconds = clock.tick(60) # milliseconds passed since last frame
seconds = milliseconds / 1000.0
self.speed = seconds * (self.dx+self.dy)
self is the way to reference an object from inside one of it's methods. self.speed = 10 equates to doing my_object.speed = 10 outside the method. Using this method:
class Character:
def __init__():
self.speed = 0 # your character class needs to have a speed attribute somewhere
hero = Character() # creates an object that represents your character
hero.set_speed() # call the method to set the speed attribute of the character class
I don't know exactly how you have everything set up, but here is a rough example using 2-tuples (x, y) for position and speed (I think you might have self.x, self.y for my self.position and self.dx, self.dy for my self.speed, but the principles are the same):
def Character(object):
def __init__(self, position):
self.position = position # e.g. (200, 100)
self.speed = (0, 0) # start stationary
def move(self, elapsed):
"""Update Character position based on speed and elapsed time."""
# distance = time * rate
# position = old position + distance
self.position = (int(self.position[0] + (elapsed * self.speed[0])),
int(self.position[1] + (elapsed * self.speed[1])))
Note that you don't need to work out how far a Character should travel for a given speed when you set self.speed, just when you try to move the Character. You can directly access character.speed; having a "setter" method (e.g. set_speed(self, speed)) is unpythonic.
Now you could call this:
hero = Character(start_loc)
framerate = 60
clock = pygame.time.Clock()
...
while True: # main gameplay loop
elapsed = clock.tick(60) # update world time and get elapsed
...
hero.speed = (2, 0) # travelling horizontally in the positive x-direction
hero.move(elapsed) # update the hero's position
Note that speed here would be in units of pixels per millisecond.

Simple 2D gravity with pygame; Value Guidelines?

I am trying to simulate gravity in a simple 2D window using pygame. It's very simple stuff (a dot rising and falling again) and I understand the mechanics, i.e. speed as a vector and the y portion being consistently diminished by a value g representing gravity during each run through the mainloop and the subsequent update of the position of the dot.
It's all working fine, but I am having trouble choosing the correct values to be inserted. It is all trial and error at the moment. Is there a good rule of thumb on how to determine which numbers to use, to achieve a semi-realistic looking trajectory?
I inserted a minimal example below, with the following values important to my question:
window = (640, 480) # pixels
initial_speed = 20 # pixels per update along the y axis
gravity = 0.4 # deduction on initial_speed per update
Now, why do these numbers happen to make the illusion work? I tried first to use formulae I learnt in physics class years and years ago, but, with or without unit conversions, the simulation was just not right. Most of the time, I didn't even see the ball, yet the above values, found through trial and error work.
Thanks for all your help in advance. If you need more information, just post a comment and I will try to oblige.
Here is the minimal example. Note the vector2D library was conveniently borrowed from the pygame website (follow this link)
#!/usr/bin/env python
import pygame
from pygame.locals import *
from vector2D import Vec2d
pygame.init()
GRAVITY = 0.4
class Dot(pygame.sprite.Sprite):
def __init__(self, screen, img_file, init_position, init_direction, speed):
pygame.sprite.Sprite.__init__(self)
self.screen = screen
self.speed = Vec2d(speed)
self.base_image = pygame.image.load(img_file).convert_alpha()
self.image = self.base_image
# A vector specifying the Dot's position on the screen
self.pos = Vec2d(init_position)
# The direction is a normalized vector
self.direction = Vec2d(init_direction).normalized()
def blitme(self):
""" Blit the Dot onto the screen that was provided in
the constructor.
"""
self.screen.blit(self.image, self.pos)
def update(self):
self.speed.y -= GRAVITY
displacement = Vec2d(
self.direction.x * self.speed.x,
self.direction.y * self.speed.y
)
self.pos += displacement
def main():
DIMENSION = SCREEN_WIDTH, SCREEN_HEIGHT = 640, 480
BG_COLOUR = 0,0,0
# Creating the screen
window = screen = pygame.display.set_mode(
(SCREEN_WIDTH, SCREEN_HEIGHT), 0, 32)
screen = pygame.display.get_surface()
clock = pygame.time.Clock()
dot = Dot(screen, "my/path/to/dot.jpg", (180, SCREEN_HEIGHT),
(0, -1), (0, 20))
mainloop = True
while mainloop:
# Limit frame speed to 50 FPS
time_passed = clock.tick(50)
for event in pygame.event.get():
if event.type == pygame.QUIT:
mainloop = False
# Redraw the background
screen.fill(BG_COLOUR)
dot.update()
dot.blitme()
pygame.display.flip()
if __name__ == '__main__':
main()
def update(self):
self.speed.y -= GRAVITY
displacement = Vec2d(
self.direction.x * self.speed.x,
self.direction.y * self.speed.y
)
self.pos += displacement
You call Dot.update() 50 times every second. The equation you're using,
delta v_y = - g #* (1 second)
represents the change in velocity every second, but it gets called 50 times every second. This means your velocities will lose 50*GRAVITY m/s ever second and this is why you are forced to make your gravity so weak.
So, I would recommend adding time_passed as an argument for Dot.update, and change the speed changing statement to
def update(self, time_passed):
self.speed.y -= GRAVITY * time_passed/1000. #time converted from millisecond to seconds
This will make the units more intuitive. Switch GRAVITY back to 10 and use realistic velocities.
EDIT:
Additionally, the displacement vector also needs to contain time, or else the displacement will depend on FPS. If you have x frames per second, then each update occurs after t = 1000/x milliseconds. This is the amount of time Dot spends "moving" in each update. Think about how we approximate Dot's motion; it gets an update, calculates its new velocity and travels at that speed for t seconds. However, in your implementation, the displacement vector is independent of time. Change the update method to be something like:
def update(self, time):
time /= 1000.
self.speed.y -= GRAVITY * time
displacement = Vec2d(
self.direction.x * self.speed.x * time,
self.direction.y * self.speed.y * time
)
self.pos += displacement
Now, according to kinematics, we'll get something like
max height = v0**2 / 2g
so initial velocities of 10-20m/s will only produce max heights of 5-20m. If you don't want to work with velocities of 50-200m/s, then you could add a scaling factor in the displacement vector, something like
displacement = Vec2d(
self.direction.x * self.speed.x * time * scaling_factor,
self.direction.y * self.speed.y * time * scaling_factor
)

Categories

Resources