correct usage of acceleration ( howto fix )

Started by
6 comments, last by LilBudyWizer 15 years, 10 months ago
[ 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
Advertisement
How do I fix my code? Do you need more information? Or is it too much?

The problem is that my Ship() accelerates at different speeds based on my FPS. If fps = 30, it takes 4 seconds to reach max acceleration. If fps = 320, it takes 1 second to reach max acceleration. (Both display framerates are tied to physics updates framerate 100fps )

Here its stripped down as small as I think it can go:

game.py
class GameMain():	def adjusted_speed( self, speed_in_px_per_sec ):		"""input: speed per seconds. output: speed per ms.		[ Used anywhere I set vel, or add accels. ]		warning!: maybe the problem is I need to have self.dt in this function		somehow ?? """		speed_in_px_per_ms =  speed_in_px_per_sec / 1000.#speed[ms] = speed[sec]/1000.		return speed_in_px_per_ms	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		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 integrate(self, dt):		"""physics update, integrate."""		self.actor_sprites.update( dt )	 # update Actor()s


Actor.py
class Ship(Actor):	"""Ship() specific"""	def __init__( self ):		self.max_vel = self.game.adjusted_speed( 750 )		self.max_force = self.game.adjusted_speed( 200 )	def handle_input(self):		keys = pygame.key.get_pressed()		# reset accel to prevent adding .add_accel() more than once per update()		# just incase .handle_input() is called more than once per each .update()			# but this does not fix my accel error.		self.accel = Vector2(0., 0.)		# up		if keys[K_UP]: self.accelerate( self.game.adjusted_speed( 1 ) )class Actor(pygame.sprite.Sprite):	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() because I .add_accel() every frame.		self.accel = Vector2(0., 0.)	def accelerate(self, magnitude):		"""accelerate along foreward_normal"""		accel = self.normal_foreward()		accel *= magnitude		self.add_accel( accel )	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 normal_foreward(self):		"""get the normal_foreward for this Actor()"""		foreward = Vector2( cos( self.rotation ), sin( self.rotation ) )		return foreward.normalize()	def truncate( self, vector, magnitude ):		"""truncates Vector2()/Vector3() so that vector.magnitude() <= magnitude"""		if vector.magnitude() > magnitude:			vector.normalize()			vector *= magnitude		return vector


Edit: I *think* that my physics updates / fps is constant, but maybe i'm measuring wrong
def draw(self):	"""draw display FPS and physics FPS"""	self.text = "FPS: %d, physicsFPS: %d avg [ %d cur step ]" % (		self.clock.get_fps(), self.game.physics_fps_avg, self.game.physics_fps )			def update(self):	"""update time"""	# ... snip ...		# physicsFPS, calc. fps and fps_avg for physics update framerate	if self.timeNew - self.physics_timeLast >= 1000.:		self.physics_timeLast = self.timeNew		self.physics_fps_avg += self.physics_fps		self.physics_fps_avg /= 2		self.physics_fps = 0.			while self.accumulator >= self.dt:			self.integrate( self.dt )			self.t += self.dt			self.accumulator -= self.dt			self.physics_fps += 1 # physicsFPS		# ... snip ...
You know I was getting weird FPS in my game, and from what you described sounds like your rendering, or updating your scene depending on how many FPS your system can handle. By all means this is a method, but what if your FPS isn't at a constant rate? You'll get the problem that is occurring right now.

Do you have some kind of antivirus program on your system ?

If so try turning that off, and killing other high resource programs and see if that doesn't change it.

If at all you could always just have a max acceleration that you get after something is achieved, and until that achievement have a constant speed like your max speed except maybe half the speed, and just keep that in an update managed by a boolean value.

maybe like

bool max_speed;bool IsRunning;int speed = 5;int max_speed = 10;while(IsRunning){       if(max_speed) ship_speed = max_speed;       else ship_speed = speed;}


That is how I would fix your problem. That way your speed of the ship has nothing to do with how many FPS your system is able to push out, or will be one speed over another one second or later, but at a constant speed unless certain achievements are meet.


Good Luck ;)
Quote:Original post by aleisterbukowski
You know I was getting weird FPS in my game, and from what you described sounds like your rendering, or updating your scene depending on how many FPS your system can handle. By all means this is a method, but what if your FPS isn't at a constant rate? You'll get the problem that is occurring right now.
My video and physics framerates should be properly decoupled, so regardless of the video FPS, the physics updates at the same speed. But, they are not disconnected properly, or i'm doing .update() incorrectly, or i'm doing .accelerate() incorrectly?

Quote:Original post by aleisterbukowskiDo you have some kind of antivirus program on your system ? If so try turning that off, and killing other high resource programs and see if that doesn't change it.
I disabled Antivirus, and checked the process manager for any thing else. Turning everything off I can, it didn't fix the acceleration problem.

Quote:Original post by aleisterbukowski
If at all you could always just have a max acceleration that you get after something is achieved, and until that achievement have a constant speed like your max speed except maybe half the speed, and just keep that in an update managed by a boolean value.

maybe like

*** Source Snippet Removed ***

That is how I would fix your problem. That way your speed of the ship has nothing to do with how many FPS your system is able to push out, or will be one speed over another one second or later, but at a constant speed unless certain achievements are meet.
I really need to solve this problem. I can't work on other physics demos until I get this basic [particle kinematics?] working correctly.

I thought that this would be a easy problem for most of the users in this forum because they solve problems a lot more complicated than this. Or am I missing something that makes it harder?

To make it easier to look at, here's the file for download.
fixed-timestep_acceleration_broke.zip
Well, I appreciate your urgency and frustration, but you're basically asking people to debug your program from a listing. I don't even do that with my own programs, I use an interactive debugger.

Ok, you're talking about a fixed timestep to your physics update. It takes an exact number of iterations to hit that max speed. How many iterations is that? How long did that take down to the tenth, if not hundredth, of a second? Chances are it's either getting there either 3.2 or 6.4 times as fast indicating your doing either a minimum of one or two updates per frame rather than a minimum of zero. With 30 fps you are mostly going to do 2-4 updates per frame. Some other program hogs the cpu you might shoot as high as 10 or even higher updates of a single frame. At 320 fps you should be doing one UPDATE per 2-4 frames.
Keys to success: Ability, ambition and opportunity.
(1) Do commercial games use what I'm trying to achieve? Meaning does their physics updates always occur at the same frequency, regardless of the video FPS which can spike? Or am I assuming wrong, and they instead just target a static video FPS ?

Quote:Original post by LilBudyWizer
Well, I appreciate your urgency and frustration, but you're basically asking people to debug your program from a listing. I don't even do that with my own programs, I use an interactive debugger.

Ok, you're talking about a fixed timestep to your physics update. It takes an exact number of iterations to hit that max speed. How many iterations is that? How long did that take down to the tenth, if not hundredth, of a second? Chances are
I took stats of how long it took to accelerate to full speed on each of the framerate options: 30fps, 60fps, unlimited fps.

I found that at 30fps and 60fps it always takes 75 iterations to hit max speed. The time to reach max accel was in the range: 1.22 secs to 1.226 secs . Running at 120fps the iterations would vary, from 90-92 at .747 secs to .776 secs.
Running at variable / unlimited fps the iterations would vary from 158-164 at .743 secs to .823 secs.

== running at 60fps ==[test1]        time: 1220 ms [ 1.220000 secs ]        iters: physics ( 75 ) frames ( 348 )        avg fps: physics ( 61.475410 ) avg fps: frames ( 285.245902 )[test2]        time: 1226 ms [ 1.226000 secs ]        iters: physics ( 75 ) frames ( 698 )        avg fps: physics ( 61.174551 ) frames ( 569.331158 )[test3]        time: 1225 ms [ 1.225000 secs ]        iters: physics ( 75 ) frames ( 975 )        avg fps: physics ( 61.224490 ) frames ( 795.918367 )== running at 30fps ==[test1]        time: 2535 ms [ 2.535000 secs ]        iters: physics ( 75 ) frames ( 1598 )        avg fps: physics ( 29.585799 ) frames ( 630.374753 )[test2]        time: 2523 ms [ 2.523000 secs ]        iters: physics ( 75 ) frames ( 2178 )        avg fps: physics ( 29.726516 ) frames ( 863.258026 )[test3]        time: 2535 ms [ 2.535000 secs ]        iters: physics ( 75 ) frames ( 2530 )        avg fps: physics ( 29.585799 ) frames ( 998.027613 )== running at max speed (jumps from 180ish to 220ish) ==[test1]        time: 756 ms [ 0.756000 secs ]        iters: physics ( 159 ) frames ( 535 )        avg fps: physics ( 210.317460 ) frames ( 707.671958 )[test2]        time: 745 ms [ 0.745000 secs ]        iters: physics ( 148 ) frames ( 677 )        avg fps: physics ( 198.657718 ) frames ( 908.724832 )[test3]        time: 745 ms [ 0.745000 secs ]        iters: physics ( 153 ) frames ( 885 )        avg fps: physics ( 205.369128 ) frames ( 1187.919463 )


Quote:it's either getting there either 3.2 or 6.4 times as fast indicating your doing either a minimum of one or two updates per frame rather than a minimum of zero. With 30 fps you are mostly going to do 2-4 updates per frame. Some other program hogs the cpu you might shoot as high as 10 or even higher updates of a single frame. At 320 fps you should be doing one UPDATE per 2-4 frames.
If your execution time for 75 iterations varies depending on the display framerate, then obviously the problem is that you're not updating your physics at a constant 100fps like you think you are. Your numbers don't really make much sense... Your data looks like you're changing the physics rate to 30fps or 60fps, not the display, but what you're saying is the opposite.

And yes, many commercial game engines including Source use fixed time step for physics.
Let's take a simple example. Let's say it was just an object moving in a straight line at a fixed speed, say 60 mph. That's 88 feet per second. If your timestep was 0.01s then that's 0.88 feet per timestep. Each update you move the object 0.88 feet in it's direction of travel. How many times you call it determines how far the object moves.

What's it's speed? 60 mph. It has nothing to do with the frequency with which you call the routine. Each call updates the position for 0.01s. It doesn't matter whether you do 10, 100 or 1000 calls a second. The only differance the rate of the calls make is the scale of the game time. 10 calls a second means 1s of clock time is 0.1s of game time. Similarly 100 means 1s is 1s and for 1000 1s is 10s.

How fast is the object moving on the screen? That depends upon your scales. One is the scale of the feet to pixels and the other is game time to clock time. Say it's 100 pixels per mile for the linear scale. So if the object is moving 60 mph it will move 100 pixels per minute. That minute is game time. If game time is moving 10 times faster than the clock then it's going to move 1000 pixels per minute by the clock. The object is moving at 60 mph irregardless of how fast it's moving across the screen.

That's because you set it to move at 60 mph. Each call advances the game clock by 0.01s and moves the object by 0.88 ft. It's no differant than a map. Two geographical positions are a certain distance apart irregardless of the scale the map is drawn at. The scale of the map just determines how you convert distances on the map to distances in the real world.

Assuming all your input is the same then the number of calls determines the positions of the objects. If 100 iterations doesn't produce the same positions irregardless of the rate of the calls then you're mixing up your times. You're getting clock time mixed up with game time. The game time is only advanced by the update. It is advanced by a fixed amount per update, i.e. the fixed timestep. The game loop determines how many times the update is called based upon the elapsed clock time and the time scale. If the last pass took 0.1s, your timestep is 0.01s and your time scale is 1:1 then you need to call update ten times this pass.

As a generaly rule you need some type catch up. If you can't complete 100 updates per second then attempting to will just mean your frame rate gets slower and slower. So you might have an alternative update that can do 0.1s timesteps instead of 0.01s. Not as accurate, but better to lose accuracy than to drop to one frame every five minutes.
Keys to success: Ability, ambition and opportunity.

This topic is closed to new replies.

Advertisement