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.
Related
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.
As part of a Pygame physics engine I'm working on, I would like to have it so each particle accelerates by 9.81 pixels per real time second, instead of every single frame, which is the way it currently works:
self.y_acceleration = 9.81
self.y_velocity += self.y_acceleration
self.y += self.y_velocity * delta_time
I already have use this code to create a timer:
current_time = time.time()
delta_time = current_time - previous_time
previous_time = current_time
timer += delta_time
You need to calculate the motion per frame as a function of the frame rate.
pygame.time.Clock.tick returns the number of milliseconds since the last call. If you call it in the application loop, this is the number of milliseconds that have elapsed since the last frame. Multiply the object speed by the elapsed time per frame to get constant motion regardless of FPS.
Define the distance in pixels that the player should move per second (pixels_per_second) when self.y_velocity is 1. Then calculate the distance per frame in the application loop:
clock = pygame.time.Clock()
pixels_per_second = 500 # 500 pixels/second - just for example
run = True
while run:
# [...]
delta_time = clock.tick(100)
pixel_per_frame = pixels_per_second * delta_time / 1000
self.y += self.y_velocity * pixel_per_frame
# [...]
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
)
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
I'm making a top down car racing game and I want to make the car rotate when you press the left and right keys (I’ve already done that part), the sprite's rotation is stored in a variable as degrees. I'd like to be able to make it move according to acceleration in the direction it is facing. I can figure out the acceleration part myself, it's just figuring out what pixel exactly is in that direction. Can anyone give me some simple code to help with this?
Here are the contents of the class that are relevant:
def __init__(self, groups):
super(Car, self).__init__(groups)
self.originalImage = pygame.image.load(os.path.join("Data", "Images", "Car.png")) #TODO Make dynamic
self.originalImage.set_colorkey((0,255,0))
self.image = self.originalImage.copy() # The variable that is changed whenever the car is rotated.
self.originalRect = self.originalImage.get_rect() # This rect is ONLY for width and height, the x and y NEVER change from 0!
self.rect = self.originalRect.copy() # This is the rect used to represent the actual rect of the image, it is used for the x and y of the image that is blitted.
self.velocity = 0 # Current velocity in pixels per second
self.acceleration = 1 # Pixels per second (Also applies as so called deceleration AKA friction)
self.topSpeed = 30 # Max speed in pixels per second
self.rotation = 0 # In degrees
self.turnRate = 5 # In degrees per second
self.moving = 0 # If 1: moving forward, if 0: stopping, if -1: moving backward
self.centerRect = None
def update(self, lastFrame):
if self.rotation >= 360: self.rotation = 0
elif self.rotation < 0: self.rotation += 360
if self.rotation > 0:
self.image = pygame.transform.rotate(self.originalImage.copy(), self.rotation)
self.rect.size = self.image.get_rect().size
self.center() # Attempt to center on the last used rect
if self.moving == 1:
self.velocity += self.acceleration #TODO make time based
if self.velocity > self.topSpeed: self.velocity = self.topSpeed # Cap the velocity
Trigonometry: The formula to get your coordinate is:
# cos and sin require radians
x = cos(radians) * offset
y = sin(radians) * offset
You use velocity for offset. (This means a negative velocity will drive backwards).
so:
def rad_to_offset(radians, offset): # insert better func name.
x = cos(radians) * offset
y = sin(radians) * offset
return [x, y]
loop_update is something like:
# vel += accel
# pos += rad_to_offset( self.rotation, vel )
math.cos, math.sin: uses radians, so
storing rotations as radians is simpler. If you want to define speed / etc as degrees, you still can.
# store radians, but define as degrees
car.rotation_accel = radians(45)
car.rotation_max_accel = radians(90)
I can't really do better than pointing you to this tutorial (*). In particular, the first part explains how to do rotation and make the sprites move in certain directions.
(*) Shameless plug :-) but very relevant to the question.