I am trying to make a ball bounce forever, at the same height always, as it would if no energy was lost in the bounce.
I don't know why, depending on what I do it bounces higher as time goes by, or it bounces lower with every jump. My expectation is that in every bounce it should always reach the same height, not higher or lower.
This is my code (the code is in pyglet, but it is really easy to read even if you don't know the library). Here, a square is dropped from the middle of the screen, marked by an arrow, and I expect the cube to bounce in the floor and go exactly to the original height when coming back:
import pyglet
class Polygon(object):
def __init__(self, vertices, color, velocity=0, acceleration=-600):
self.vertices = vertices
self.y_idx = 1
self.num_vertices = int(len(self.vertices) // 2)
self.colors = color * self.num_vertices
self.velocity = velocity
self.acceleration = acceleration
self.bottom_edge = 0
def draw(self):
self.vertex_list = pyglet.graphics.vertex_list(self.num_vertices,
("v2f", self.vertices),
("c3B", self.colors))
self.vertex_list.draw(pyglet.gl.GL_POLYGON)
def move_by_offset(self, offset):
for i in range(1, len(self.vertices), 2):
self.vertices[i] += offset # only modify y values
def bounce(self, dt):
if self.vertices[self.y_idx] < self.bottom_edge:
self.velocity = abs(self.velocity)
return True
return False
def update(self, dt):
# move
self.move_by_offset(self.velocity * dt)
# check if bounce
self.bounce(dt)
# accelerate
self.velocity += self.acceleration * dt
class GameWindow(pyglet.window.Window):
def __init__(self, objects=None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.objects = objects
def update(self, dt):
for obj in self.objects:
obj.update(dt)
def on_draw(self):
self.clear()
for obj in self.objects:
obj.draw()
class Game(object):
def __init__(self, w=400, h=400, title="My game", resizable=False):
self.w = w
self.h = h
objects = [
# square
Polygon(vertices=[w/2-20, h/2, w/2-20, h/2+40, w/2+20, h/2+40, w/2+20, h/2],
color=[0, 128, 32], # green
velocity=0,
acceleration=-6000),
# arrow, marks exactly how high the square should bounce
Polygon(vertices=[w/2, h/2, w/2+40, h/2+20, w/2+30, h/2, w/2+40, h/2-20],
color=[255, 255, 0], # yellow
velocity=0,
acceleration=0)
]
self.window = GameWindow(objects, self.w, self.h, title, resizable)
def update(self, dt):
self.window.update(dt)
if __name__ == "__main__":
game = Game(resizable=False)
pyglet.clock.schedule_interval(game.update, 1/120)
pyglet.app.run()
I tried different update orders, like modifying speed according to acceleration before changing velocity after bounce, or even not accelerating at all after bounce (which seems to work the best) but still the bounce is not accurate and changes heights constantly:
def update2(self, dt):
# move
self.move_by_offset(self.velocity * dt)
# accelerate
self.velocity += self.acceleration * dt
# check if bounce
self.bounce(dt)
def update3(self, dt):
# move
self.move_by_offset(self.velocity * dt)
# check if bounce
bounced = self.bounce(dt)
if not bounced:
# accelerate (only if no bounce happened)
self.velocity += self.acceleration * dt
I even tried something more complex: creating 2 dt's, one before and one after bounce, and doing 2 acceleration updates, but this did not work either.
Can you guys help me? What is the way to program game physics for such a simple scenario?
Numerical integration is hard! Since you can easily solve the one-dimensional ballistic equation exactly, do so: compute
y1=y0+v0*dt+g*dt*dt/2
v1=v0+g*dt
This is the velocity Verlet method in the trivial case of a constant acceleration. If y1<0
, you can solve the quadratic equation to find out when it bounced and restart the integration from that point (with the velocity negated).
If you want to incorporate more complicated physics while still being numerically accurate, consider the centering of your velocity variable. Better accuracy can be had by staggering it—defining it at points in time that are halfway between the points at which position is defined gives the similar leapfrog method.
A very different approach for conservative forces is to define a total energy for the ball and move it according to how much of it must be kinetic based on its height. Even then, you have to include the above correction with dt*dt
to avoid numerical problems near the maximum altitude.