Games
Playing Gods : First Steps
Tue, 08/16/2011 - 12:39 — beermanAll's going well so far with the Playing Gods project. After a thoroughly productive (and somewhat beery :D ) series of playthroughs of the board game last weekend to fully clarify the rules, I've got most of a prototype of the logic hacked together in python, and I'm happy enough with it that I'm ready to make a start on the game proper.
For that, the current plan is to use Unity3D for the front-end graphics, backed up with an AppEngine web service for tracking the game state and co-ordinating the players. I'm looking at getting the back-end working first, with a basic HTML UI for testing, then look at adding the pretty stuff later.
So far that's working out well, and I'm hoping to have most of the state tracking logic in place with AppEngine over the next week or two.
Conveniently, this upcoming weekend is the next Ludum Dare 48 hour game making competition, so I'm planning to use that as an excuse to familiarise myself with the Unity engine in preparation for when I get to work on the front-end.
Gun Bastard progress update
Sat, 07/02/2011 - 17:59 — beermanIn s staggering break with tradition, I've been using my weekend to actually get some work done! No, I don't really believe it either...
I started the day off by knocking up a particle system. After hooking it up make the enemies explode when shot it looked a little something like this:
As you might have noticed, it's a bit on the excessive side - I accidentally set the explosions to repeat every beat instead of only firing once. Looks like I'll be able to get plenty of stuff on screen at once though :)
Once I was satisfied I could chuck particles around without any problems, I thought I should see about making things a bit prettier. After fixing the particle size and colour to match the game sprites, I added a motion trail effect to my explosion particles and ended up with this:
Not quite up to OddBob's standards yet, but a definite improvement.
With that done, I spent a bit of time refactoring things to keep the codebase nice and tidy. One thing that was a bit ugly about Sheep Snaggers 2 was the huge chunks of boilerplate code I was using to spawn my game entities and attach the relevant behaviours to them. It works, but as the project gets bigger it's not really sustainable and I ended up with a whole bunch of near-identical 'Spawner' classes for the different entities.
So, to make my life easier in future, I've built myself a generic entity spawner system which takes a folder full of entity definitions (basically a list of behaviours to attach to the game object) and can then generate entities by name. So instead of having
enemy = GameObject( x = 0, y = 0, name="enemy" ) SpriteBehaviour.Attach(enemy, texture="alien1", scale=2 ) PhysicsBehaviour.Attach(enemy) EnemyBehaviour.Attach(enemy) #etc.
enemy = EntitySpawner.Spawn("enemy")
Brand new Bastard
Thu, 06/30/2011 - 22:17 — beermanWith SS2 finally fixed up and shipped, I've been thinking about what to work on next. While I'm still pretty keen to make a crack at some of the ideas in there, I can't help but think I should really finish up Gun Bastard first.
With that in mind I took a quick look at the old codebase, went 'uurhg!', and promptly binned the lot and started again ('cos that's just how we roll in indie-land).
What I've got so far looks like this:
Doesn't look like much so far, but the new stuff is a lot easier to work with.
Inspired by this article I thought it might be cool to have the AI synced to an external timer, so I hacked up a basic events system that I could drive with my metronome class. Right now all it does is make the enemies change direction every beat but I'm planning on making it the core of the game AI and hopefully hooking it up to the audio system so that events happen in time with the soundtrack. If it works out, it should be awesome :D
SS2 : Update
Sun, 06/19/2011 - 16:49 — beermanThe updated version of Sheep Snaggers 2 is now available for download.
As well as the performance fix for the schoolboy error in the original release, I also corrected a major oversight in the original game.
One of the first questions I was asked when demoing it at Retrovision was "why can't you shoot Markie up the arse?" Seems obvious in retrospect, but somehow during development it never occured to me!
This shameful faux-pas has now been corrected - although in the interests of balance you'll have to deal with Markie's newly acquired rear defenses...
Yes. Yes I did go there.
SpritePacker.py
The SpritePacker class takes a folder of sprites and generates the following:
- texture.png : single texture with all sprites in the folder packed into it
- texmap.py : python file containing a dictionary which maps the sprite names to texture co-ordinates
Example texmap.py file using various Sheep Snaggers sprites:
texmap = { 'kisschicklogo' : [0, 0, 500, 300], 'ming' : [501, 0, 202, 162], 'ship' : [704, 0, 32, 32], 'alien' : [737, 0, 32, 32], 'alien_shadow' : [770, 0, 32, 32], 'fighter' : [803, 0, 32, 32], 'sheep' : [836, 0, 32, 32], 'star' : [869, 0, 20, 20], 'star2' : [890, 0, 20, 20], 'bullet' : [911, 0, 4, 4], 'beerman_logo' : [0, 301, 477, 305], }
Full source code of SpritePacker.py:
#Beercave Gamews Beerware license v1.0 #This code is free to use for anything you want. #If you find it useful, I'd appreciate it if you buy me a beer :) #See http://www.beercave.co.uk/content/beerware for details #quick and dirty spritesheet packer #based on http://www.blackpawn.com/texts/lightmaps/default.html import os, pygame, glob from operator import itemgetter class SpritePacker(): def __init__(self, **kwargs): #defaults for input/output folders and texture size self.folder = kwargs.get("folder", "input") self.outfolder = kwargs.get("outfolder", "output") self.texturew = kwargs.get("texturew", 1024) self.textureh = kwargs.get("textureh", 1024) def Pack(self): patterns = ["*.gif", "*.png", "*.bmp"] #check for files in folder print os.path.abspath(self.folder) result = [] for pattern in patterns: pattern = os.path.join(self.folder, pattern) result.extend(glob.glob(pattern)) if len(result) == 0: print "No files in input folder!" return #build a new texture print "processing %d files:" % len(result) texture = pygame.Surface((self.texturew, self.textureh), pygame.SRCALPHA, 32) NodeTree = Node(texture, 0, 0, self.texturew, self.textureh) #sort our sprites by size sprites = [] for filepath in result: head, tail = os.path.split(filepath) filename = tail spritename, ext = os.path.splitext(tail) surface = pygame.image.load(filepath) x,y = surface.get_rect().size size = x*y #area should work as a proxy for size unless we get some badly out of proportion sprites sprites.append([filepath, spritename, size]) sprites.sort(key = itemgetter(2), reverse=True) print sprites for filepath, spritename, size in sprites: surface = pygame.image.load(filepath) print "Inserting %s - %s" %(spritename, surface) NodeTree.Insert(surface, spritename) #save the finished texture to outfolder outfile = os.path.join(self.outfolder, "texture.png") mapfile = os.path.join(self.outfolder, "texmap.py") pygame.image.save(texture, outfile) print "texture written to %s" % outfile #now dump the list of nodes map = NodeTree.Dump(True) + "}" with open(mapfile, 'w') as f: f.write(map) print "texture map written to %s" % mapfile class Node(): def __init__(self, texture, x, y, w, h): print "Creating node %d, %d, %d, %d" %(x,y,w,h) self.texture = texture self.bottom = None self.right = None self.rect = [x,y,w,h] self.imagerect = [0,0,0,0] self.image = None def Insert(self, surface, name): if self.image != None: print "node at %s contains %s. Adding to child node" % (self.rect, self.image) result = self.AddChild(surface, name) if not result: print "Could not add %s!" %name return result x,y,myw,myh = self.rect rect = surface.get_rect() print "image size : (%d,%d)" % rect.size w,h = rect.size #add some padding around sprites h += 1 w += 1 if w > myw or h > myh: print "Node too small!" return False print "adding to node at %s" % self.rect self.texture.blit(surface, (x,y)) self.image = name self.imagerect = [x,y,w-1,h-1] print self.imagerect #generate ChildNodes dw = myw - w dh = myh - h if dw > dh: self.right = Node(self.texture, x+w, y, dw, myh) self.bottom = Node(self.texture, x, y+h, w, dh) else: self.right = Node(self.texture, x+w, y, dw, h) self.bottom = Node(self.texture, x, y+h, myw, dh) return True def AddChild(self, surface, name): if not self.right.Insert(surface, name): return self.bottom.Insert(surface, name) return True #dump structure #{"filename" : [x,y,w,h], #} def Dump(self, first = False): #print "Dumping node" if self.image == None: return "" result = "'%s' : %s,\n" % (self.image, self.imagerect) if first: result = "texmap = {\n%s" %result result += self.right.Dump() result += self.bottom.Dump() return result packer = SpritePacker() packer.Pack()
This code is released under the Beerware license. It's completely free to use, but if you like it, why not buy me a beer? :)
*All donations are covered by the Beercave Games Beerware Pledge.
Input.py
This is a wrapper class to simplify control handling in pygame when using multiple controller methods, as developed for Sheep Snaggers 2 and originally described here.
Rather than your game logic having to deal with all the controllers directly, it allows you to define named controls which can map to multiple controllers at once.
Simple example:
input = Input() while running: input.Poll() ship.x += input.GetControl('THRUST') ship.y += input.GetControl('CLIMB') if input.GetControl('FIRE') ship.fire() if input.GetControl('QUIT'): running = False render()
Full source code:
#Beercave Gamews Beerware license v1.0 #This code is free to use for anything you want. #If you find it useful, I'd appreciate it if you buy me a beer :) #See http://www.beercave.co.uk/content/beerware for details import pygame from pygame.locals import * #wrapper class for all input methods #maps controllers to named controls, eg 'FIRE', 'JUMP' etc #keeps all that nasty control handling code out of the game classes #call GetControl(CONTROL_NAME) to check the control state in game #clamp value to a min/max range def clamp(sourceval, minval, maxval): return max(min(sourceval, maxval), minval) class Input(): def __init__(self, **kwargs): #mappings for keydowns, key states and axis positions #format is controlname : [list of [source, (arguments...)]] #multiple inputs can all map to the same control #key/button press events (1,0) #identifies if a key/button was pressed this frame #['keyevent'], (KEY_ID,)] where KEY_ID is the pygame key id #['buttonevent', (STICK_ID, BUTTON_ID)] #['mouseevent', (BUTTON_ID)] #stickevent tracks whether the joystick was moved in a given #direction this frame. Threshold parameter allows for #filtering out of small movements #['stickevent', (STICK_ID,AXIS_ID,DIRECTION,THRESHOLD)] #key/button states (1,0) #identifies if a key/button is currently pressed #['keystate'], (KEY_ID,)] #['buttonstate', (STICK_ID, BUTTON_ID)] #axis mappings #track a joystick axis #can also map keyboard controls to a virtual axis so #the game code doesn't need to know whether it's #dealing with keys or a controller #['keyaxis'], (+KEY_ID, -KEY_ID)] #+KEY_ID is mapped to the positive axis direction #-KEY_ID is mapped to the negative axis direction #['stickaxis', (STICK_ID, AXIS_ID,)] #use '-stickaxis' to invert axis values self.controlMap = { 'QUIT' : [['keyevent', (K_ESCAPE,)]], 'PAUSE' : [['keyevent', (K_p,)]], 'START' : [ ['keyevent', (K_SPACE,)], ['keyevent', (K_RETURN,)], ['buttonevent', (0,0)] ], 'TEST' : [['keyevent', (K_SPACE,)]], 'OPTIONS' : [['keyevent', (K_o,)]], 'SCREENSHOT' : [['keyevent', (K_RCTRL,)]], 'THRUST' : [ ['keyaxis', (K_RIGHT, K_LEFT)], ['stickaxis', (0,0)] ], 'FIRE' : [['keyevent', (K_LCTRL,)], ['keyevent', (K_z,)], ['mouseevent', (1,)], ['mouseevent', (2,)], ['mouseevent', (3,)], ['buttonevent', (0,0)] ], 'FIRE2' : [ ['keyevent', (K_LSHIFT,)], ['keyevent', (K_c,)], ['buttonevent', (0,1)] ], 'TURN' : [ ['keyevent', (K_x,)], ['buttonevent', (0,2)] ], 'CLIMB' : [ ['keyaxis', (K_UP, K_DOWN)], ['-stickaxis', (0,1,)] ], 'STICK1' : [ ['-stickaxis', (0,0,)] ], 'STICK2' : [ ['-stickaxis', (0,1,)] ], 'UP' : [ ['keyevent', (K_UP, )], ['stickevent', (0,1,-1,.5)] ], 'DOWN' : [ ['keyevent', (K_DOWN, )], ['stickevent', (0,1,1,.5)] ], 'RIGHT' : [ ['keyevent', (K_RIGHT, )], ['stickevent', (0,0,1,.5)] ], 'LEFT' : [ ['keyevent', (K_LEFT, )], ['stickevent', (0,0,-1,.5)] ], 'FLINGX' : [['mousedir', (0,)]], 'FLINGY' : [['mousedir', (1,)]] } self.Mouse = [0,0] #state for all keys self.keys = pygame.key.get_pressed() #did event happen this frame for all keys self.keyevents = [0] *323 #all key events this frame self.keyspressed = [] #all mouse events this frame self.mouseevents = [0] * 4 #button press events this frame self.buttonevents = [[0]*20] #state of all joysticks in the system self.stickStates = [] self.InitSticks() self.gesturetime = 0 self.gesturex = 0 self.gesturey = 0 #update the input states #your game engine should call this every frame #for mouse control to work pass time elapsed this frame as dt def Poll(self, dt=0): keyevents = pygame.event.get(pygame.KEYDOWN) mouseevents = pygame.event.get(pygame.MOUSEBUTTONDOWN) stickevents = pygame.event.get(pygame.JOYAXISMOTION) buttonevents = pygame.event.get(pygame.JOYBUTTONDOWN) #clear the events list from the previous frame self.keyevents = [0] *323 self.mouseevents = [0] * 4 sticks = self.stickCount for i in range(0,sticks): state = self.stickStates[i] axes = state['numaxes'] buttons = state['numbuttons'] state['axisevents'] = [0] * axes state['buttonevents'] = [0] * buttons state['axes'] = [0.] * axes #move all pressed keys to an array - will save #iterating over the list every time we check #for a keypress for event in keyevents: self.keyevents[event.key] = True #all keyevents this frame #exposing this is useful for situations where we want to know #all the keys that were pressed - ie for handling text input self.keyspressed = keyevents self.keys = pygame.key.get_pressed() for event in mouseevents: self.mouseevents[event.button] = True for event in stickevents: self.stickStates[event.joy]['axisevents'][event.axis] = event.value for event in buttonevents: self.stickStates[event.joy]['buttonevents'][event.button] = True self.CheckMouse(dt) self.CheckSticks() pygame.event.clear() #check the system for attached joysticks and initialise def InitSticks(self): sticks = pygame.joystick.get_count() self.stickCount = sticks self.stickStates = [] for i in range(0,sticks): state = {} state['stick'] = stick =pygame.joystick.Joystick(i) stick.init() state['name'] = stick.get_name() state['numbuttons'] = buttons = stick.get_numbuttons() state['numaxes'] = axes = stick.get_numaxes() state['buttonstates'] = [0] * buttons state['axes'] = [0.] * axes state['axisevents'] = [0] * axes state['buttonevents'] = [0] * buttons self.stickStates.append(state) #poll all joysticks for state def CheckSticks(self): sticks = self.stickCount for i in range(0,sticks): state = self.stickStates[i] stick = state['stick'] buttons = state['numbuttons'] axes = state['numaxes'] for k in range(0, axes): state['axes'][k] = stick.get_axis(k) #poll mouse for state def CheckMouse(self,dt): #basic mouse gestures relx,rely = pygame.mouse.get_rel() #clear the axes so we don't report a movement every frame self.Mouse = [0,0] #check we've moved a decent amount if relx*relx+rely*rely > 10: self.gesturetime += dt self.gesturex += relx self.gesturey += rely elif self.gesturetime > 0: #a gesture just ended #change the velocity self.Mouse = [self.gesturex, -self.gesturey] self.gesturetime = 0 self.gesturex = 0 self.gesturey = 0 return #call this to get the state of a defined control def GetControl(self, name): mappings = self.controlMap.get(name, None) result = 0. for map in mappings: source, args = map if source == 'keyevent': key, = args result += self.GetKeyEvent(key) if source == 'buttonevent': stick,button = args result += self.GetButtonEvent(stick, button) if source == 'mouseevent': button, = args result += self.GetMouseEvent(button) if (source == 'keystate'): key, = args result += self.GetKeyState(key) if (source == 'keyaxis'): key1,key2 = args result += self.GetKeyAxis(key1, key2) if (source == 'stickaxis'): stick, axis = args val = self.GetStickAxis(stick, axis) result += val if (source == '-stickaxis'): stick, axis = args val = -self.GetStickAxis(stick, axis) result += val if (source == 'stickevent'): stick, axis, direction, threshold = args result += self.GetStickEvent(stick, axis, direction, threshold) if (source == 'mousedir'): axis, = args #mouse axis overrules alternatives if in use return self.GetMouseAxis(axis) return clamp(result, -1, 1) #functions to handle different control mappings def GetKeyEvent(self, key): return self.keyevents[key] def GetButtonEvent(self, stick, button): if self.stickCount <= stick: return False return self.stickStates[stick]['buttonevents'][button] def GetStickEvent(self, stick, axis, direction, threshold): if self.stickCount <= stick: return False value = self.stickStates[stick]['axisevents'][axis] return value * direction > threshold def GetMouseEvent(self, button): return self.mouseevents[button] def GetKeyState(self, key): return self.keys[key] def GetKeyAxis(self, key1, key2): keys = self.keys val1 = 1. if self.keys[key1] else 0. val2 = -1. if self.keys[key2] else 0. return val1 + val2 def GetStickAxis(self, stick, axis): if self.stickCount <= stick: return 0. state = self.stickStates[stick] pos = state['axes'][axis] return pos def GetMouseAxis(self, axis): return self.Mouse[axis]/1000.
This code is released under the Beerware license. It's completely free to use, but if you like it, why not buy me a beer? :)
*All donations are covered by the Beercave Games Beerware Pledge.
Now what?
Thu, 06/02/2011 - 21:59 — beermanSo with Sheep Snaggers 2 all done with (pending bug fixes passing testing), I guess it's time to find a new project to play with. Which of course brings me to the eternal quandary of the indie developer :
Which one of my countless unrealised ideas do I work on next?
Found the problem
Wed, 06/01/2011 - 20:40 — beermanI finally got around to sitting down and taking a look at the performance problems with Sheep Snaggers 2 tonight. A 5 minute run with profiling switched on pointed me right to the middle of my font handling routine where I found this:

So while I was probably right about how inefficient I'm being with my texture usage, it looks like the bigger problem is that I was looking in the wrong bloody cache, with the end result that every time I was rendering a character I was generating a brand new texture - oops!
To make things worse, since the score counter was using that font routine, it was rendering at least half a dozen new character textures every single frame - hardly surprising that I was chewing up VRAM at a rate of knots.
I still need to find out how it runs on one of the RV homebrew laptops, but based on the profiler results for the fixed version I reckon it should be ok, in which case I can delay optimising things for a future project and get back to the important business of making it possible to shoot Mecha Markie up the arse!
Post Mortem : Sheep Snaggers 2
Tue, 05/24/2011 - 22:56 — beermanI released Sheep Snaggers 2 on Friday at Retrovision 2011, to what was overall a pretty good reception. All being well, I'm hoping for a fairly positive writeup in the Retrogamer review of the event.
The high score competition I had running over the weekend was popular, though made somewhat more difficult than anticipated by the fairly low-end laptops available to play it on - I didn't pay a great deal of attention to performance optimisation so when it landed on a machine with 32MB of shared video RAM there were... issues.
Luckily I managed to source an alternative machine which left the game playable with only occasional slowdown, but I'm not entirely happy. At the end of the day it's only a bloody Defender clone; there's really no excuse for it to not run on damn near any hardware you can throw at it, so I'm planning to spend a little time sharpening up the performance before I move on to my next project.
Retrogamer's article on the event should be hitting the stands around the middle of next month, so that seems like a reasonable short term deadline to get a version 1.1 together and iron out some of the speed issues before that hopefully points some more attention this way
So, the plan.
I'll probably need to spend some time profiling to be certain, but I'm pretty sure the biggest problem is that I'm being incredibly lazy with my texture memory usage. I've got limited spritesheet support in there for some of the animations, but for the most part it's one texture per sprite. Worse, for some of the font routines I'm generating one texture per combination of character/font/size.
Since my development system has 96 cores and a gig of video RAM, that's not really something that's been an issue for me, but I should probably look into addressing it. First on the list there should be packing all my graphics into as few textures as possible, which may mean finally venturing into writing my own development tools. Nothing fancy, just something that takes a folder of PNG files and turns them into packed images and a handy code file with all the texture coordinates in - something along the lines of:
{
#use the original filename as a key to the texture dictionary
#cuts down on the amount changes needed to the existing code
#and makes it easy to cross reference with the original unpacked sprites
"alien1.png" : {
"texture" : "texturemap1.png",
"tex_shape" : [0,1,1,0],
"shape" : [-50,50,50,-50]
}
}
With that done, it won't need a massive amount of work to update my drawing routines to load sprites from the packed textures rather than individual files.
The next (and possibly much bigger) problem is the fonts. Right now I have a kind of hybrid system that sometimes generates textures for individual characters (e.g. in the high score counter) and sometimes for entire phrases (like the "SHEEPIE SAVE" message). It would probably help if I defined these up-front rather than building them on the fly as the game runs just because I'm lazy. One texture per font and a couple of special cases for the floating messages shouldn't really be all that much work.
Hopefully, by the time I've got those out of the way I should have managed to come up with a few ideas to play with for my next project.
SS2 : Right down to the wire
Thu, 05/19/2011 - 22:39 — beermanFinally finished coding on Sheep Snaggers 2. Been a bit of a hectic week, not helped by me being struck down by the dreaded Man Flu.
I spent most of today alternately between dozing on the couch (that damn man Flu again), deciding I was all done, and thinking "crap, I missed a bit" and coding frantically to implement piddly unimportant features like having more than one life... All done now though, and I'm pretty pleased with the end result.
The final build is uploading now. I'll be putting the download link live sometime tomorrow after the official launch at Retrovision 2011. Watch this space!


