class Engine: def __init__(self): self.updateRate = 1/300. # real-time between physics ticks in seconds. the lower this is the more accurete the physics is self.initialSpeed = 0.3 #self.timestep = self.updateRate * self.initialSpeed # how much game time each simulation represents # groups of entities self.groups = { 'all' : set(), 'updating' : set(), # all that have a update function } self.entityAddQueue = [] self.entityDelQueue = [] # layers specify the order in which they are drawn. (ordered back to front) self.drawLayerNames = [ 'background', 'game', 'foreground', # UI ones from here on # UI Entities will be drawn in camera space 'UI_pauseMenu', 'UI_debug', ] # A dict from drawLayerNames to a Batch of entities. they are mutually exclusive self.drawLayers = { name : Batch() for name in self.drawLayerNames } self.drawCalls = { name : [] for name in self.drawLayerNames } self.levelStartTime = time.time() self.levelTime = 0. # TODO proper pausing (maybe move to gameState or some level class) self.accumulatedFrameTime = 0. # Window config = gl.Config( sample_buffers=1, samples=4 # antialiasing ) self.window = Window( config = config, #fullscreen = True, vsync = False, style = Window.WINDOW_STYLE_BORDERLESS, ) # opengl flags gl.glEnable(gl.GL_BLEND) #enables transparency # gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) # mouse position self.windowCenter = Vec2d(self.window.get_size()) / 2 self.mousePos = Vec2d(self.windowCenter) @self.window.event def on_mouse_motion(x, y, dx, dy): self.mousePos.x = x self.mousePos.y = y @self.window.event def on_mouse_drag(x, y, dx, dy, buttons, modifiers): self.mousePos.x = x self.mousePos.y = y # DEBUG: drop us into a debug shell when @self.window.event def on_key_press(symbol, modifiers): if symbol==key.QUOTELEFT and modifiers & key.MOD_CTRL: liveInspect(self) elif symbol==key.UP: self.initialSpeed *= 2. elif symbol==key.DOWN: self.initialSpeed *= 0.5 self.fps_display = clock.ClockDisplay() # camera self.camera = Camera() # shedule our main loop so we don't need to manually deal with time clock.schedule(self.run) # our main game loop def run(self, dt): self.timestep = self.updateRate * (self.initialSpeed*2**(self.levelTime*0.2)) ## UPDATE ## # timestep ala http://gafferongames.com/game-physics/fix-your-timestep/ if dt > .25: # avoid spiral of death (updating taking longer than framerate) dt = .25 self.accumulatedFrameTime += dt while self.accumulatedFrameTime >= self.updateRate: self.accumulatedFrameTime -= self.updateRate self.levelTime = time.time() - self.levelStartTime shots = [] for entity in self.groups['updating']: # update all entities, this should not change the state shot = entity.update(self.timestep) if shot: # self.entityAddQueue += response.get("add Entities", []) # self.entityDelQueue += response.get("del Entities", []) shots.append((entity, shot)) # this will apply the actions generated by update for entity, shot in shots: self.entityAddQueue.append(physics.shoot(entity, shot)) self._processRemoving() self._processAdding() self.space.step(self.timestep) # this will do the physics and apply all actions self._processRemoving() self._processAdding() if enablePhysicsTesting: # DEBUG: test for physics consistency. Disabled as bouncing off Walls changes momentum # momentum = sum([ e.body.mass * e.body.velocity for e in self.blobs.itervalues() ]) assertClose(self.physCheck_mass, sum([ e.body.mass for e in self.blobs.itervalues() ])) assertClose(self.physCheck_area, sum([ math.pi * e.radius**2 for e in self.blobs.itervalues() ])) # assertClose(self.physCheck_mometum.x, momentum.x) # assertClose(self.physCheck_mometum.y, momentum.y) ## DRAW ## gl.glClearColor(0,0,0, 0) gl.glClear(gl.GL_COLOR_BUFFER_BIT) gl.glLoadIdentity() self.camera.track() # does camera work (such as what it focuses on) for name in self.drawLayerNames: for func in self.drawCalls[name]: func() shift = Vec2d() if name.startswith('UI') else None with self.camera.shiftView(shift): self.drawLayers[name].draw() self.fps_display.draw() #self.window.flip() def loadLevel(self, levelName): # Initialize physics self.blobs = {} # a dict from blob.id to blob self.highestBlobId = 0 self.space = physics.createSpace(self) self.spaceView = SpaceView(self.space) # self.levelCreator = resources.loadEntities self.levelCreator = levelCreator.createLevel # load the entities for e in self.levelCreator(levelName): self.addEntity(e) self._processAdding() self._processRemoving() self.physCheck_mass = sum([ e.body.mass for e in self.blobs.itervalues() ]) self.physCheck_area = sum([ math.pi * e.radius**2 for e in self.blobs.itervalues() ]) self.physCheck_mometum = sum([ e.body.mass * e.body.velocity for e in self.blobs.itervalues() ]) def addEntity( self, e): self.entityAddQueue.append(e) def removeEntity(self, e): self.entityDelQueue.append(e) def _processAdding(self): while len(self.entityAddQueue): e = self.entityAddQueue.pop(0) self.groups["all"].add(e) if Entities.isEntityKind_updating(e): self.groups['updating'].add(e) if Entities.isEntityKind_physics(e): self.space.add(e.shapes) for shape in e.shapes: self.spaceView.shapeToEntity[shape] = e if e.body is not self.space.static_body: self.space.add(e.body) if Entities.isEntityKind_visible(e): if hasattr(e, 'initGraphics'): e.initGraphics(self.drawLayers[e.drawLayer]) if hasattr(e, 'draw'): self.drawCalls[e.drawLayer].append(e.draw) if isinstance(e, Blob): self.highestBlobId += 1 e.controller = e.controller(e, self.blobs, self.spaceView) # initialize the controller e.id = self.highestBlobId self.blobs[e.id] = e def _processRemoving(self): while len(self.entityDelQueue): e = self.entityDelQueue.pop(0) if e in self.groups['all']: self.groups['all'].remove(e) else: # print "was told to delete entity but it was not in the 'all' group: " + repr(e) # DEBUG continue if Entities.isEntityKind_updating(e): self.groups['updating'].remove(e) if Entities.isEntityKind_physics(e): self.space.remove(e.shapes) for shape in e.shapes: del self.spaceView.shapeToEntity[shape] if e.body is not self.space.static_body: self.space.remove(e.body) if Entities.isEntityKind_visible(e): # self.drawLayers[e.drawLayer].remove(e) if hasattr(e, 'draw'): self.drawCalls[e.drawLayer].remove(e.draw) if hasattr(e, 'sprite'): e.sprite.delete() # for vertexList in e.vertexLists: # vertexList.delete() if isinstance(e, Blob): del self.blobs[e.id]