[ This is based off of my code to my previous post about
fixed-timestep physics.]
My acceleration is somehow affected by my display framerate. I'm supposed to be using a fixed time-based integrator. And my physics fps is constant at 100fps ( regardless of display fps. )
If I set display fps = 30, and physics fps = 100, it takes
about 4 seconds for my ships acceleration to reach the max_vel(0.75). But if I uncap fps ( about 320fps ), it takes my ships acceleration
only 1 second to reach max vel!
[1] Do you see where my problem is? The most likely functions are:
Actor.update(), GameMain.update(), GameMain().adjusted_speed(), Actor.accelerate(), Actor.add_accel(), Ship().handle_input()
[2] My GameMain.adjusted_speed() doesn't use dt. I'm not sure if it should? ( Even if it should, I think my .accel problem is somewhere else. )
# game.py
#!/usr/bin/python
__author__ = "Jake b"
VERSION = "0.6"
WINDOW_TITLE = "fixed-timestep (euler): v%s [ %s ]" % (VERSION, __author__)
try:
import sys
import pygame
from pygame.locals import *
from pygame.sprite import RenderPlain
import random
from euclid import Vector2, Vector3
from actor import Actor, Ship, Asteroid
except ImportError, err:
print "Couldn't load module. %s" % (err)
sys.exit(2)
class GameMain():
"""game Main entry point. handles intialization of game and graphics.
methods:
update() : physics framerate logic, calls .integrate()
integrate() : physics
members:
dt : physics fixed-timestep ( defined as milliseconds )
actor_sprites : RenderPlain() sprite group, holds all sprites
player : player Ship() object
asteroids : Asteroid()s objects
"""
bDone = False
def __init__(self, width=800, height=600):
"""Initialize PyGame"""
pygame.init()
self.width, self.height = width, height
self.screen = pygame.display.set_mode(( self.width, self.height ))
pygame.display.set_caption( WINDOW_TITLE )
self.dt = 10. #.01 # 10 milliseconds ( or 1/100th of sec )
self.t = 0.
self.timeCur = pygame.time.get_ticks()
self.accumulator = 0.
# sprite group, all Actors(), Ship()s, and Asteroid()s will join this:
self.actor_sprites = RenderPlain()
self.clock = pygame.time.Clock()
self.fps = FPSText( self )
# Actor() instances
self.player = Ship(self)
# spawn asteroids
self.asteroids = []
self.next_round()
def adjusted_speed( self, speed_in_px_per_sec ):
"""input: speed per seconds. output: speed per ms. """
speed_in_px_per_ms = speed_in_px_per_sec / 1000.#speed[ms] = speed[sec]/1000.
return speed_in_px_per_ms
def main_loop(self):
"""Game() main loop"""
while not self.bDone:
self.handle_events()
self.update()
self.draw()
# toggle: FPS cap = .fps_limit, or no cap to FPS
if self.bLimitFPS: self.clock.tick( self.fps_limit )
else: self.clock.tick()
def integrate(self, dt):
"""physics update, integrate."""
self.actor_sprites.update( dt ) # update Actor()s
def update(self):
"""integrate / update physics. using fixed-timestep.
based off of: http://www.gaffer.org/game-physics/fix-your-timestep"""
self.timeNew = pygame.time.get_ticks()
self.timeDelta = self.timeNew - self.timeCur
self.timeCur = self.timeNew
# clamp timeDelta so it doesn't reach insanely high numbers if window
# loses focus for extended period of time. capped at 250ms
if self.timeDelta > 250: self.timeDelta = 250
self.accumulator += self.timeDelta
while self.accumulator >= self.dt:
self.integrate( self.dt ) # not sure why tut args are (t, dt) ?
self.t += self.dt
self.accumulator -= self.dt
def handle_events(self):
"""do regular events. Also sends copies to Ship(), etc..."""
# ...
def draw(self):
"""render screen"""
# clear; render; update;
self.screen.fill( pygame.color.Color("black") )
self.actor_sprites.draw( self.screen )
pygame.display.flip()
def next_round(self):
"""spawn asteroids for next_round."""
# ...
if __name__ == "__main__":
# entry point
main_window = GameMain()
main_window.main_loop()
# actor.py
# actor.py
__author__ = "Jake b"
VERSION = "0.6"
try:
import random
from math import *
import pygame
from pygame.locals import *
from euclid import Vector2
except ImportError, err:
print "Couldn't load module. %s" % (err)
sys.exit(2)
class Actor(pygame.sprite.Sprite):
"""Base actor class. Handles physics, and rendering.
methods:
__init__(loc, vel, accel) : just init
update(dt) : physics
handle_events(events) : main events loop
handle_input() : handle key input not handled in .handle_events()
accelerate(magnitude) : accelerate along normal_foreward
decelerate(magnitude) : accelerate along -normal_foreward
normal_foreward() : Vector2() : returns normal_foreward
truncate( Vector2, magnitude ) : Vector2() : truncates vector to magnitude
spawn() : add to sprite group
kill() rem from sprite group
draw() : [todo:] main render, derived from this
_get_topleft_offset() : Rect() : get offset for rect.topleft based on loc
_keep_onscreen() : keep on screen ( wrapping boundries )
members:
screen : actual screen root surface
loc, vel, accel, max_vel, max_force: Vector2() self explainatory quanities
rotation, mass, sprite_w, sprite_h = scalars
"""
def __init__( self, game, loc=Vector2(0.,0.), vel=Vector2(0.,0.),
accel=Vector2(0.,0.), mass=1., sprite_w=15, sprite_h=15, color="blue" ):
"""init loc, vel, accel."""
pygame.sprite.Sprite.__init__(self)
self.game = game
self.loc = loc
self.vel = vel
self.accel = accel
self.mass = mass
self.rotation = radians(-90)
self.max_vel = self.game.adjusted_speed( 1000 )
self.max_force = self.game.adjusted_speed( 200 )
self.sprite_w, self.sprite_h = sprite_w, sprite_h
# placeholder: for now render a Rect() instead of a sprite
self.image = pygame.Surface([sprite_w, sprite_h])
self.image.fill( pygame.color.Color(color) )
self.rect = self.image.get_rect()
self.rect.topleft = self._get_topleft_offset()
self.spawn() # add to sprite group
def accelerate(self, magnitude):
"""accelerate along foreward_normal"""
accel = self.normal_foreward()
accel *= magnitude
self.add_accel( accel )
# old: self.accel += accel ( use function so it gets truncate()ed )
def decelerate(self, magnitude):
"""decelerate, (or: accelerate along -normal_foreward)"""
self.accelerate( -magnitude )
def add_accel(self, force):
"""add a Vector2() force to the .accel Vector2()"""
force = self.truncate( force, self.max_force )
self.accel += force / self.mass
def update(self, dt):
"""update physics, dt = the fixed-timestep
note: sets self.accel = Vector2(0,0) after every .update()
"""
self.loc += self.vel * dt
self.vel = self.truncate( self.vel + self.accel*dt, self.max_vel )
# reset accel every physics update()
self.accel = Vector2(0., 0.)
# update .rect to with .loc
self._keep_onscreen()
self.rect.topleft = self._get_topleft_offset()
def truncate( self, vector, magnitude ):
"""truncates Vector2()/Vector3() so that vector.magnitude() <= magnitude"""
if vector.magnitude() > magnitude:
vector.normalize()
vector *= magnitude
return vector
def normal_foreward(self):
"""get the normal_foreward for this Actor()"""
foreward = Vector2( cos( self.rotation ), sin( self.rotation ) )
return foreward.normalize()
def spawn(self):
"""add Actor() to sprite group, move to proper location, etc..."""
self.game.actor_sprites.add( self ) # add to sprite group
def kill(self):
"""kill player/asteroid/whatever"""
pygame.sprite.Sprite.kill(self) # remove from sprite group
def _keep_onscreen(self):
"""keep self onscreen, wrapping on edges"""
w,h = self.game.screen.get_size()
if self.loc.x < 0: self.loc.x = w
if self.loc.x > w: self.loc.x = 0
if self.loc.y < 0: self.loc.y = h
if self.loc.y > h: self.loc.y = 0
def _get_topleft_offset(self):
# loc is based on center of sprite, so subtract to get topleft
x = self.loc.x - ( self.sprite_w / 2 )
y = self.loc.y - ( self.sprite_h / 2 )
return (x, y)
class Ship(Actor):
"""Ship() specific"""
def __init__( self, game, loc=Vector2(0.,0.), vel=Vector2(0.,0.),
accel=Vector2(0.,0.), mass=1., sprite_w=30, sprite_h=30, color="red" ):
"""Overwrite size and other stuff."""
Actor.__init__(self, game, loc, vel, accel, mass, sprite_w, sprite_h,
color )
self.max_vel = self.game.adjusted_speed( 750 )
self.max_force = self.game.adjusted_speed( 200 )
self.spawn()
def spawn(self):
"""spawn random loc, random vel, and random direction"""
Actor.spawn(self)
w,h = self.game.screen.get_size()
self.loc = w/2, h/2
self.vel = Vector2(0., 0.)
self.accel = Vector2(0., 0.)
def handle_input(self):
"""derive to handle key input not handled by events"""
keys = pygame.key.get_pressed()
# reset accel
# just incase .handle_input() is called more than once per each .update()
# but this either doesn't help, or barely noticeably helps ( still 3sec error )
self.accel = Vector2(0., 0.)
# up
if keys[K_UP]: self.accelerate( self.game.adjusted_speed( 1 ) )
# down
if keys[K_DOWN]: self.decelerate( self.game.adjusted_speed( 1 ) )
class Asteroid(Actor):
"""Asteroid specific"""
# ...
thanks,
--
monkey