Sign in to follow this  

fixed timestep euler physics

This topic is 3492 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'm working on a basic asteroids clone, learning how to implement a fixed timestep, so that I can use Euler without it exploding. ( My ref is: gaffer.org/fix-your-timestep) I have a few questions, and my code is below. [1] If I want to run my simulation at 100fps , is it correct to set dt to 10? Since I'm using pygame.time.get_ticks(), where the tut was using (I think? seconds). [ 1000ms / 100fps = 10ms ? ] [2] How do I calculate my speeds? ie: I want to set player's velocity to 200 pixels/second. Do I do something like:?
def get_adjusted_vel( speed_in_pixels ):
	# speed_in_pixels was: 200
	# adjusted_vel : is the vel I set to get 200px/sec movement ?
	adjusted_vel = speed_in_pixels / dt # dt was 10 if q #1 was correct
[2.B]Does that also work for accel? ( ie: accel of 200px/sec ?) [3] on the gaffer.org/fix-your-timestep tutorial, it calls integrate() with 3 args; state, t, dt. Do I only need t if I were to use the RK4 integrator instead of Euler? [4] How low should I set dt? 100fps? ( Or it doesn't matter as long as .integrate() takes less time to complete than my timestep: dt? ) [5] Is there anything else wrong with my code? ( in the fixed timestep physics part )
#game.py
__author__ = "Jake b"
VERSION = "0.3"
WINDOW_TITLE = "fixed-timestep (euler): v%s [ %s ]" % (VERSION, __author__)

try:
	import sys
	import pygame
	from pygame.locals import *
	from pygame.sprite import RenderPlain	
	from actor import Actor, Ship, Asteroid
except ImportError, err:	
	print "Couldn't load module. %s" % (err)
	sys.exit(2)

class FPSText():
	"""helper to render FPS text"""
	def __init__(self, game, color="white"):		
		self.screen = game.screen
		self.clock = game.clock
		self.font = pygame.font.Font(None, 18)
		self.image = None
		self.rect = pygame.Rect(0,0,0,0)
		self.text = "FPS: "
		self.color = pygame.color.Color(color)
		
	def draw(self): # todo: only re-render every 1 second
		self.text = "FPS: %d" % self.clock.get_fps()		
		self.image = self.font.render( self.text, False, self.color )
		self.rect = self.image.get_rect()
		dest_rect = pygame.Rect( 15, 15, 0, 0 )		
		self.screen.blit( self.image, dest_rect )

class GameMain():
	"""game Main entry point. handles intialization of game and graphics.
	
	methods:
		__init__(width, height) : just init
		
		main_loop() : the main loop
		handle_events() : main events loop
		draw() : main render
		update() : physics framerate logic, calls .integrate()
		integrate() : physics				
	members:
		dt : physics fixed-timestep ( defined as milliseconds ? )
		fps : FPSText() object
		actor_sprites : RenderPlain() sprite group, holds all sprites
		player : player Ship() object
		asteroids : Asteroid()s objects	
		screen : screen display surface		
	"""
	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 )
		
		# 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 )		
		self.player = Ship(self)
		self.asteroids = []
		for i in range(15):
			self.asteroids.append( Asteroid(self) )
		
		# physics stuff		
		self.dt = 10. # 10 milliseconds ( or 1/100th of sec )
		self.t = 0. # not needed for Euler?		
		self.timeCur = pygame.time.get_ticks()
		self.accumulator = 0.
	
	def main_loop(self):
		"""Game() main loop"""
		while not self.bDone:
			self.handle_events()
			self.update()
			self.draw()

			self.clock.tick(30) # delay and caps display FPS to 30

	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"""
		# newTime = time()
		# deltaTime = newTime - curTime
		# curTime = newTime		
		self.timeNew = pygame.time.get_ticks()
		self.timeDelta = self.timeNew - self.timeCur
		self.timeCur = self.timeNew
		
		# jake note: is this clamping of .timeDelta correct?
		# 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 )
			self.t += self.dt
			self.accumulator -= self.dt
	
	def handle_events(self):
		"""do regular events."""
		# handle events, and pass to renderer
		events = pygame.event.get()
		for event in events:
			if event.type == pygame.QUIT: sys.exit()
			elif event.type == KEYDOWN:
				if (event.key == K_ESCAPE): self.bDone = True

	def draw(self):
		"""render screen"""
		# clear
		self.screen.fill( pygame.color.Color("black") )		
		self.fps.draw()
		# sprites
		self.actor_sprites.draw( self.screen )		
		# flip
		pygame.display.flip()

# == entry point ==
if __name__ == "__main__":
	# entry point	
	main_window = GameMain()
	main_window.main_loop()	

#actor.py
__author__ = "Jake b"
VERSION = "0.1"

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. for now, renders Rect()
	
	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()		
		draw() : [todo:] main render, derived from this		
		spawn() : add to sprite group
		kill() rem from sprite group		
		_get_topleft_offset() : get offset for rect.topleft based on loc
		
	members:
		screen : actual screen root surface		
		loc, vel, accel, mass : self explainatory
		max_accel, max_vel : speed caps
	"""
	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.sprite_w, self.sprite_h = sprite_w, sprite_h
		# note: caps not used yet, todo:
		self.set_max_vel( 1000 )
		self.set_max_accel( 200 )
		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
			
	# get/set loc, vel, accel, mass, max_vel, max_accel
	def get_loc(self): return self.loc.copy()
	def set_loc(self, loc): self.loc = loc
	def get_vel(self): return self.vel.copy()
	def set_vel(self, vel): self.vel = vel
	def get_accel(self): return self.accel.copy()
	def set_accel(self, accel): self.accel = accel
	def get_mass(self): return self.mass
	def set_mass(self, mass): self.mass = mass
	def get_max_vel(self): return self.max_vel
	def set_max_vel(self, max_vel): self.max_vel = max_vel
	def get_max_accel(self): return self.max_accel
	def set_max_accel(self, max_accel): self.max_accel = max_accel
	
	def handle_events(self, events):
		"""derive to handle a copy of events from mainloop."""
		pass
		
	def handle_input(self):
		"""derive to handle key input not handled by events"""
		pass
	
	def draw(self):
		"""derived classes can override this. note: RenderSrcRect() does not call .draw()"""
		pass
	
	def add_accel(self, force):
		self.accel += ( force / self.mass )
		
	def update(self, dt):
		"""update physics, dt = the fixed-timestep"""
		self.loc += self.vel * dt
		self.vel += self.accel * dt
		
		# update loc; keeponscreen;
		self.rect.topleft = self._get_topleft_offset()		
		self._keep_onscreen()
		
	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) # call parent; rem 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):
		"""get the topleft offset of sprite based on Actor.loc(), and
		Actor.sprite_w, Actor.sprite_h"""
		# 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.set_max_vel( 1000 )
		self.set_max_accel( 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.)
		
class Asteroid(Actor):
	"""Asteroid specific"""
	def __init__( self, game, loc=Vector2(0.,0.), vel=Vector2(0.,0.),
		accel=Vector2(0.,0.), mass=1., sprite_w=50, sprite_h=50, color="blue" ):
		"""Overwrite size and other stuff."""
		Actor.__init__(self, game, loc, vel, accel, mass, sprite_w, sprite_h,
			color )		
		self.set_max_vel( 10 ) # todo: I expected this to mean 10px / sec but not working
		self.set_max_accel( 200 )
		
		self.spawn()
	
	def spawn(self):
		"""spawn random loc, random vel, and random direction"""
		Actor.spawn(self)
		w,h = self.game.screen.get_size()		
		
		# rand loc
		self.loc = random.randint(0, w), random.randint(0,h)		
		# rand dir
		angle = radians( random.randint( 0, 360 ) )
		v = Vector2( cos(angle), sin(angle) )
		v.normalize()		
		# rand vel
		mag = random.randint( self.max_vel/10, self.max_vel )		
		# finally; set rand dir, and rand vel.
		v *= mag		
		self.vel = v		
	
	def kill(self):
		Actor.kill(self)
		

thanks, -- monkey

Share this post


Link to post
Share on other sites
Quote:
Original post by ninmonkeys
[1] If I want to run my simulation at 100fps , is it correct to set dt to 10? Since I'm using pygame.time.get_ticks(), where the tut was using (I think? seconds). [ 1000ms / 100fps = 10ms ? ]

[2] How do I calculate my speeds? ie: I want to set player's velocity to 200 pixels/second. Do I do something like:?
*** Source Snippet Removed ***[2.B]Does that also work for accel? ( ie: accel of 200px/sec ?)

If you want to work in "units per second" inside your code, you should use a dt of 0.01 (seconds), not 10 (milliseconds).
pygame.time.get_ticks() is returning values in milliseconds, so divide the return value by 1000 so that all of your values are in seconds.

The only major problem with [2] is that the '/' needs to be a '*'.
Then your [2] code will work, but speed_in_pixels is actually speed_in_pixels_per_millisecond (because dt is in milliseconds, not in seconds).
If you instead measure dt in seconds, and you measure your speed in pixels per second, then your code for [2] is ok.

i.e. You just need to ensure that all your units match up:
pixels_to_move_this_update = speed_in_pixels_per_second * dt_in_seconds

Share this post


Link to post
Share on other sites
Quote:
Original post by HodgmanIf you want to work in "units per second" inside your code, you should use a dt of 0.01 (seconds), not 10 (milliseconds). pygame.time.get_ticks() is returning values in milliseconds, so divide the return value by 1000 so that all of your values are in seconds.
I set dt = 0.01 ( seconds) and self.timeNew = pygame.time.get_ticks() / 1000.

But then my .integrate() is never called. ( So I stuck with ms since I was already using that. )

Quote:
Original post by HodgmanThe only major problem with [2] is that the '/' needs to be a '*'.
Then your [2] code will work, but speed_in_pixels is actually speed_in_pixels_per_millisecond (because dt is in milliseconds, not in seconds).
...
i.e. You just need to ensure that all your units match up:
pixels_to_move_this_update = speed_in_pixels_per_second * dt_in_seconds


The following code apears right to me, Is my get_adjusted_speed() and integrate() code correct?

I was getting confused because you said pixels_to_move_this_update but I think I'm supposed to return pixels_per_ms, unless my update() is wrong.
def get_adjusted_speed( self, speed_in_px_per_sec ):
"""input: speed per seconds.
output: speed per ms.

I thought this function had to use dt to define the new speed,
so I would get the same speed per sec if dt is ever changed. but, it
seems like I don't use dt here, else: update() is wrong ?
"""

# speed[per ms] = speed[per sec]/1000.
speed_in_px_per_ms = speed_in_px_per_sec / 1000.
return speed_in_px_per_ms

def update(self, dt):
"""update physics, dt = the fixed-timestep"""
# note: preview is not showing the pluseuals operator, but its there.
self.loc += self.vel * dt
self.vel += self.accel * dt

def integrate(self, dt):
"""physics update, integrate."""
# update Actor()s
self.actor_sprites.update( dt )

Share this post


Link to post
Share on other sites

This topic is 3492 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this