Search code examples
pythongame-physicspygletpymunk

Using pymunk gravity with pyglet


This code displays the image assassin1.png on a black screen standing still at position (50, 80).

The goal is to induce the pymunk gravity on that image so that it falls down. I followed the pymunk tutorial which is written using pygame and tried to adapt that. I don't know why my code isn't applying gravity to the my image. Could someone tell me what I am doing wrong and what I should change to make it work properly?

import pyglet
import pymunk

class PymunkSpace(object):
    def assassin_space(self, space):
        self.space = space
        self.mass = 91
        self.radius = 14
        self.inertia = pymunk.moment_for_circle(self.mass, 0, self.radius) 
        self.body = pymunk.Body(self.mass, self.inertia) 
        self.body.position = 50, 80 
        self.shape = pymunk.Circle(self.body, self.radius) 
        self.space.add(self.body, self.shape) 
        return self.shape 

class Assassin(pyglet.sprite.Sprite):
    def __init__(self, batch, img, x, y):
        pyglet.sprite.Sprite.__init__(self, img, x , y )

class Game(pyglet.window.Window):
    def __init__(self):
        pyglet.window.Window.__init__(self, width = 315, height = 220)
        self.batch_draw = pyglet.graphics.Batch()
        self.a_space = PymunkSpace().assassin_space(space)
        self.player1 = Assassin(batch = self.batch_draw, img = pyglet.image.load("assassin1.png"), x = self.a_space.body.position.x ,y = self.a_space.body.position.y )

    def on_draw(self):
        self.clear()
        self.batch_draw.draw()
        self.player1.draw()
        space.step(1/50.0) 

if __name__ == "__main__":
    space = pymunk.Space() 
    space.gravity = (0.0, -900.) 
    window = Game()
    pyglet.app.run()

Solution

  • The problem is that you set the location of your Sprite once:

    self.player1 = Assassin(batch = self.batch_draw, img = pyglet.image.load("assassin1.png"), x = self.a_space.body.position.x ,y = self.a_space.body.position.y )
    
    class Assassin(pyglet.sprite.Sprite):
      def __init__(self, batch, img, x, y):
          pyglet.sprite.Sprite.__init__(self, img, x , y )
    

    but this location never gets updated.

    Another problem is that in the pygame example, space.step(1/50.0) is called every iteration of the main loop, while your's only gets called when the window is drawn.

    So, first, you should ensure space.step gets called every frame. Do so by using a clock:

    class Game(pyglet.window.Window):
        def __init__(self):
            ...
            pyglet.clock.schedule(self.update)
    
        def on_draw(self):
            self.clear()
            self.batch_draw.draw()
            self.player1.draw()
    
        def update(self, dt):
            space.step(dt)
    

    The next step is ensuring the location of the sprite is keeped in sync with the pyglet object.

    Change your Assassin class:

    class Assassin(pyglet.sprite.Sprite):
        def __init__(self, batch, img, space):
            self.space = space
            pyglet.sprite.Sprite.__init__(self, img, self.space.body.position.x , self.space.body.position.y)
    
        def update(self):
            self.x = self.space.body.position.x
            self.y = self.space.body.position.y
    

    Create your Assassin instance like this:

    class Game(pyglet.window.Window):
        def __init__(self):
            pyglet.window.Window.__init__(self, width = 315, height = 220)
            self.batch_draw = pyglet.graphics.Batch()
            self.a_space = PymunkSpace().assassin_space(space)
            self.player1 = Assassin(batch = self.batch_draw, img = pyglet.image.load("assassin1.png"), space = self.a_space)
            pyglet.clock.schedule(self.update)
    

    and call self.player1.update() in your new update method:

    class Game(pyglet.window.Window):
        def __init__(self):
            ...
    
        def on_draw(self):
            ...
    
        def update(self, dt):
            self.player1.update()
            space.step(dt)
    

    (And you should probably get rid of some unneeded instance members, but that's another topic)