Pyglet and ecs Part 1: Small Beginnings

I am not going to go through the specifics of setting up Python/Pyglet/IDE’s and ecs, but here are the links to all the appropriate sources:

  • Python (I’m using 2.7)
  • ecs (I used pip to install it, here is an easy install for pip)
  • Pyglet I am using the development version (pyglet 1.2 alpha1), and used pip on the command line to install as per instructions on the bottom of their download page.

QuickStart Guide for Pyglet: If you’ve never used Pyglet I would advise looking through this, just to get a feel for how Pyglet works. They can probably explain the basics better than I can!

The IDE I am (experimentally) using is PyCharm, though I have used Aptana Studio standalone, and both do a very good job.

This tutorial is based heavily upon this tutorial, but I have tried to go about extracting the game into components and systems.

So, to the code. First thing’s first we need to do the generic “setup” of a Pyglet application.

#main.py

#I shorten the imported name
import pyglet as pyg

#give our window a size
window = pyg.window.Window(800,600)
#give our window a name!
window.set_caption("PyggieSpaceWars")

if __name__ == '__main__':
    pyg.app.run() #tell it to run!

When this piece of code is run it will produce a blank window with the caption we set in the top window bar. Pyglet has the window exit stuff set up already so we can close the window.

Now, we want to be able to add some images, for that we’ll need to set up our resources.

#game/resources.py
from pyglet.gl import *
import pyglet as pyg

def set_anchor(image, anchor=None):
    if anchor == None:
        image.anchor_x = image.width/2
        image.anchor_y = image.height/2
    else:
        image.anchor_x = anchor[0]
        image.anchor_y = anchor[1]

pyg.resource.path = ["../data"]
pyg.resource.reindex()

#look for an image in the data folder, in images called ship.png
player_image = pyg.resource.image("images/ship.png")

set_anchor(player_image)

#openGL flags for scaling
glEnable(GL_TEXTURE_2D)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)

In this file we have defined our resource path to be in data, this means when we do the subsequent calls to pyg.resource.image(“someimage.png”) the “path” we set will be the origin point for these calls. So bear that in mind when setting and organising your image files.

picture of the file structure as an example

The file structure of this tutorial.

In pyglet image files have an “anchor” point, around which any rotations are performed. Primarily we’ll probably want this to be around the centre of the image. So we define that clause, otherwise we can give the image a specific anchor location. If you don’t set the anchor point it is defaulted to the bottom left corner of the image.

Using some OpenGL flags we tell pyglet how we want it to scale out images. (I don’t completely understand what these flags do, I can guess, but the end result is what I want!) I found the solution here and so because everything in the game will be scaled with no blurring this is the method I’ve chosen!

Now, for our first components!

#game/components.py

from ecs import Component

class Transform(Component):
""" The positional and rotational data of an entity"""
def __init__(self, x=0, y=0, rotation=0):
    self.x = x
    self.y = y
    self.rotation = rotation

class Renderable(Component):
    """The visual representation of an entity. """
    def __init__(self, sprite):
        self.sprite = sprite

These two components are fairly self explanatory, Transform is the “where” and Renderable is the “what” (visually).

Now onto our first system, this is going to be the RenderingSystem to draw all our sprites onto the screen.

#game/systems.py
from ecs import System

class RenderingSystem(System):
    def __init__(self, world):
        System.__init__(self)
        self.world = world #we'll talk about this soon.

    def update(self, dt):
        """ this method must be overwritten in each system"""

        #this is in effect what we would put into the on_draw event in pyglet.
        self.world.window.clear()
        self.world.batch.draw()

When creating a new system we must be sure to call the super class’ constructor in it’s own constructor, making sure to explicitly pass self. Every System needs to have an update method which will be given the delta time(or time since last update/tick) this can be then used in calculations to keep the game frames per second independent. All our Renderable components have a sprite and these will be passed the main batch – then upon the batch.draw() call here it will draw the sprites. And prior to that we will need to clear the screen using window.clear()

I create a class called World in which I store things that there are only one of, it’s my means to pass things like the reference to the main batch/ game window etc. The Artemis Entity System also has a class like World (called EntityWorld) and it’s how the different managers can talk to each other, that is also how our World class will be used in future.

#main.py

import pyglet as pyg
from game.systems import RenderingSystem

class World():
    window = pyg.window.Window(800,600)
    batch = pyg.graphics.Batch()
    em = EntityManager()
    sm = SystemManager(em)

    def __init__(self):
        self.window.set_caption("PyggieSpaceWars")

world = World() #initialise this

if __name__ == '__main__':
    #create a rendering system and add it to the systems manager.
    r_system = RenderingSystem(world)
    world.sm.add_system(r_system)

    #schedule updates for 60 fps of the systems manager
    #which in turn will update all registered systems.
    pyg.clock.schedule_interval(world.sm.update, 1/60.0)

    pyg.app.run()

We have moved the Window initialisation to the World class along with the caption setting. In the main block we create an instance of the RenderingSystem and add the system to the system manager. The update method for each system is called in the order they are added to the manager. We then schedule the calling of the system managers update method at a rate of 60 fps.

blank_screen

blank screen!

When run, this will still produce just a black screen. So lets change that – finally!

#entity_factory.py

from pyglet.sprite import Sprite
from game.components import Transform, Renderable
from game.resources import player_image

def create_player(world, x, y):
    #we use the entity manager to create an entity.
    e = world.em.create_entity()

    player_ship = Sprite(player_image, x, y, batch = world.batch)
    player_ship.scale = 2

    world.em.add_component(e, Renderable(player_ship))
    world.em.add_component(e, Transform(x, y)

    return e

The Renderable component holds an instance of pyglet’s Sprite class. and we need to provide it with the image we loaded in resources.py we also scale it by two, and this is where the OpenGL flags will come in and prevent the image from being blurred when the image is scaled. The sprite also needs to be given the batch it will be drawn by, so using our handy link through world we give it that one.

#system.py

class AnimationSystem(System):
    def __init__(self):
        System.__init__(self)

    def update(self, dt):
        for entity, renderable_component in self.entity_manager.pairs_for_type(Renderable):
        #we will naively assume all renderables have a transform component.
        transform_component = self.entity_manager.component_for_entity(entity, Transform)

        renderable_component.sprite.x = transform_component.x
        renderable_component.sprite.y = transform_component.y

This is named AnimationSystem, however, I’m not sure I feel comfortable with that yet, but it will do for now. It’s purpose at the moment is to move the sprite to the location held by the Transform component.

Now for us to use the create_player and AnimationSystem we have to edit our main.py file again

#main.py

if __name__ == '__main__':
    #add the animation system to the system manager first, so it will be updated first.
    world.sm.add_system(AnimationSystem())
    #Rendering System being added to system manager
    r_system = RenderingSystem(world, world.batch)
    world.sm.add_system(r_system)

    #create our player entity in the center of the screen
    entity_factory.create_player(world, world.window.width/2, world.window.height/2)

    pyg.clock.schedule_interval(world.sm.update, 1/60.0) # update at a rate of 60 fps
    pyg.app.run()

When running this program now, you should now have this:

Capture

our ship drawing in the center

That’s the end for this part!

 

Spaceship image found here: http://opengameart.org/content/spaceship-9
Source code for this part: here

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s