Over the years I've posted a number of Python examples in a variety of threads, as such it was suggested it might be more convenient to pool them all into one place for ease of reference rather than digging through all the reams of threads on the forum. So, this thread will contain many of those examples, along with potentially future examples for anyone to reference in the future.
A basic example for key input with Pygame, with the list of keycodes here:
import pygame
from pygame import mixer
import sys
def Example():
#initialize pygame
pygame.init()
#initialize sound mixer
mixer.init()
#create display
window = pygame.display.set_mode([640,480])
#load sound
sound = mixer.Sound('tone5.wav')
#main update loop
while True:
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
#if space is pressed, play sound
if event.key == pygame.K_SPACE:
sound.play()
#if escape is pressed, quit
if event.key == pygame.K_ESCAPE:
pygame.quit()
sys.exit(0)
#update window
pygame.display.update()
Example()
You can also collect text strings by adding a capture for event.unicode:
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
text += event.unicode
print(text)
An example for modular menu based system with simple state handling:
import pygame
import sys
class main(object):
def __init__(self):
#initialize pygame
pygame.init()
#create display
self.window = pygame.display.set_mode([640,480])
#current menu state
self.state = 'title'
#list of menu/game classes
self.menus = {'title':titlescreen(), 'game':game()}
#primary update loop
while True:
#check for key press events
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
key = event
else:
key = None
#update and pass key presses and menu state to active menu
self.state = self.menus[self.state].update(key,self.state)
#update window
pygame.display.update()
#titlescreen menu class
class titlescreen(object):
def __init__(self):
#menu option
self.option = 0
def update(self,event,state):
#if a key has been pressed
if event != None:
#move up in the menu
if event.key == pygame.K_UP:
if self.option > 0:
print(self.option)
self.option -= 1
#move down in the menu
elif event.key == pygame.K_DOWN:
if self.option < 1:
print(self.option)
self.option += 1
#select a menu option
elif event.key == pygame.K_RETURN:
#if option is 0 return game state and play tone
if self.option == 0:
print('game menu')
return 'game'
#quit game
elif self.option == 1:
print('quit')
pygame.quit()
sys.exit(0)
#return current state if no changes
return state
#main game class
class game(object):
def __init__(self):
pass
#DEMO: return to titlescreen
def update(self,key,state):
return 'title'
a = main()
A menu example with sounds:
import pygame
from pygame import mixer
import sys
class main(object):
def __init__(self):
#initialize pygame
pygame.init()
#initialize sound mixer
mixer.init()
#create display
self.window = pygame.display.set_mode([640,480])
#current menu state
self.state = 'title'
#list of menu/game classes
self.menus = {'title':titlescreen(), 'game':game()}
#primary update loop
while True:
#check for key press events
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
key = event
else:
key = None
#update and pass key presses and menu state to active menu
self.state = self.menus[self.state].update(key,self.state)
#update window
pygame.display.update()
#titlescreen menu class
class titlescreen(object):
def __init__(self):
#load sound
self.click = mixer.Sound('sound.wav')
#load sound
self.enter = mixer.Sound('enter.wav')
#load music
self.music = mixer.Sound('music.wav')
#menu option
self.option = 0
#select channel's for better playback control
self.channel0 = pygame.mixer.Channel(0)
self.channel1 = pygame.mixer.Channel(1)
#play background music
self.channel0.queue(self.music)
def update(self,event,state):
#if titlescreen is active and not playing, play
if self.channel0.get_busy == False:
self.channel0.play(self.music)
#queue the music to keep it playing
if self.channel0.get_queue() == None:
self.channel0.queue(self.music)
#if a key has been pressed
if event != None:
#move up in the menu and play tone
if event.key == pygame.K_UP:
if self.option > 0:
self.option -= 1
self.channel1.play(self.click)
#move down in the menu and play tone
elif event.key == pygame.K_DOWN:
if self.option < 1:
self.option += 1
self.channel1.play(self.click)
#select a menu option
elif event.key == pygame.K_RETURN:
#if option is 0 return game state and play tone
if self.option == 0:
self.channel1.play(self.enter)
return 'game'
#quit game
elif self.option == 1:
self.channel1.play(self.enter)
pygame.quit()
sys.exit(0)
#return current state if no changes
return state
#main game class
class game(object):
def __init__(self):
pass
#DEMO: return to titlescreen
def update(self,key,state):
return 'title'
a = main()
A network programming example using python Twisted. Note that PIckle should likely be replaced with JSON, "S" starts the server, "C" connects to it, "Q" quits, and "D" disconnects clients from the server:
from twisted.internet.endpoints import TCP4ServerEndpoint
from twisted.internet.protocol import Protocol
from twisted.internet.protocol import Factory
from twisted.internet.protocol import ClientFactory
from twisted.internet import reactor
from twisted.internet import task
import pickle
import zlib
import random
pyglet.options['debug_gl'] = False
#primary window/game class
class Prototype(pyglet.window.Window):
def __init__(self):
super(Prototype, self).__init__(640, 480, resizable=False, fullscreen=False, caption="Test")
self.clear()
#server side connected user variable, when someone connects the Echo Class will add itself into it.
#then just call self.user.sendData to transmit to the connected user or check self.user.inbox to get data.
self.user = []
self.server = None
self.client = None
#since the Reactor is in control, to properly run the main game loop you have to manually call the clock.
#this variable is to keep track of the last time the clock was ticked.
self.old_dt = clock.tick()
self.fps_display = pyglet.clock.ClockDisplay()
#shutdown program gracefully
def shutdown(self, result):
reactor.stop()
#system error shutdown
def bailout(self, reason):
reason.printTraceback()
reactor.stop()
#main game loop
def update(self):
while not self.has_exit:
#manually dispatch window events (since twisted reactor is in control)
self.dispatch_events()
#Make sure events are timed properly while coiterating with network layer. That and tick clock events like the fps counter and game pacing.
dt = clock.tick()
self.old_dt = self.old_dt + dt
#if enough time has passed, update game state
if self.old_dt >= 0.025:
self.old_dt = 0
self.clear()
for a in self.user:
if len(a.inbox) > 0:
print(a.inbox)
a.inbox = []
self.fps_display.draw()
#draw here and manually flip frame buffers
self.flip()
#return control to twisted Reactor
yield 1
def on_key_release(self,symbol, modifiers):
#start server
if symbol == key.S:
self.server = TCP4ServerEndpoint(reactor, 8007)
self.server.listen(QOTDFactory())
print("server initializing")
#connect to server
if symbol == key.C:
self.client = reactor.connectTCP('localhost', 8007, EchoClientFactory())
print("connecting")
#disconnect client from server side
if symbol == key.D:
for a in range(0,len(self.user),1):
self.user[a].disconnect()
if symbol == key.Q:
if self.client != None:
#disconnect from the client side
print('stopping client')
self.client.disconnect()
self.close()
if symbol == key.SPACE:
for a in self.user:
a.sendData("space pressed")
#this handles two way communication with connected clients
class Echo(Protocol):
#data reciever class
def connectionMade(self):
#add this class to the main program
window.user.append(self)
#stored incoming data
self.inbox = []
def dataReceived(self, data):
data = zlib.decompress(data)
data = pickle.loads(data)
self.inbox.insert(0,data)
def sendData(self,data):
data = pickle.dumps(data)
data = zlib.compress(data)
self.transport.write(data)
def disconnect(self):
self.transport.loseConnection()
#this monitors the port and automatically calls Echo() when someone connects, passing their address to it.
class QOTDFactory(Factory):
def buildProtocol(self, addr):
print(addr, "connected.")
return Echo()
#This class establishes a connection to a server.
class EchoClientFactory(ClientFactory):
def startedConnecting(self, connector):
print('Started to connect.')
def buildProtocol(self, addr):
print('Connected.')
return Echo()
def clientConnectionLost(self, connector, reason):
print('Lost connection. Reason:', reason)
def clientConnectionFailed(self, connector, reason):
print('Connection failed. Reason:', reason)
if __name__ == '__main__':
#initialize main window
window = Prototype()
#port to listen to
task.coiterate(window.update()).addCallback(window.shutdown).addErrback(window.bailout)
#start reactor loop
reactor.run()
Random map generator code:
import pyglet
from pyglet.window import key
from pyglet.gl import *
import numpy
import random
import sys
class Prototype(pyglet.window.Window):
def __init__(self):
super(Prototype, self).__init__(640, 480, resizable=False, fullscreen=False, caption="Test")
self.clear()
self.map = Map_Gen(128,128)
self.map.generate()
pyglet.clock.schedule_interval(self.update, .01)
def update(self,dt):
#draw screen
self.draw()
def draw(self):
self.clear()
self.map.minimap['map'].blit(32,0)
def on_key_press(self,symbol,modifiers):
if symbol == key.ESCAPE:
self.close()
#Map Generator
#/---------------------------------------------------------------------------/
class Map_Gen(object):
def __init__(self,mx=64,my=64,density=0.5,r_density=0.5,node=10):
#Map Dimensions
self.mx = mx
self.my = my
#density of terrain: 0.0 to 1.0
self.density = density
#number of terrain nodes
self.node = node
#random map seed
self.seed = None
#minimap main texture
self.minimap = {'map_image':pyglet.image.create(1,1),'map':pyglet.image.create(1,1)}
#contains the ground layer data that makes up the map
self.map = [] #28
#contains the object layer information IE: vehicles, resources, objects, components, (buildings?),etc.
self.resource = []
#The tileset is an indexed list of tile types based on their shape and purpose relative to terrain placement.
#In this case, the total is 16 tiles with the order being:
#0: Land
#1: Water
#2: Water with lower right corner Land
#3: Water with bottom half Land
#4: Water with lower left Land
#5: Water with right side Land
#6: Water with left side Land
#7: Water with upper right Land
#8: Water with upper half Land
#9: Water with upper left Land
#10: Land with Bottom Right Water
#11: Land with bottom left water
#12: Land with upper right Water
#13: Land with upper left water
#14: Land with Upper Right and Lower Left water
#15: Land with upper left and lower right water
self.tileset = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
#Tile properties determines whether the tiles are passable or solid, the length equals number
#of different types of tile, 16 in all: #0 passable, 1 impassable, 2 overhead
#tiles that have water are impassable.
self.properties = [0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
#The update table is a quick reference 2D array to change adjacent tiles during a change based on
#what tiles are adjacent to the new ground tile. So if the tile above the new ground tile is solid water, it would be
#changed to water with the bottom being land, or self.table[8_direction][current_land_type] = new_land_type.
#update table
self.table = [[0,2,2,3,3,5,12,5,11,14,0,11,12,13,14,13],#upper left
[0,3,3,3,3,13,12,13,0,12,0,0,12,13,12,13],#top
[0,4,3,3,4,13,6,15,10,6,10,0,12,13,12,15],#upper right
[0,6,12,12,6,0,6,10,10,6,10,0,12,0,12,10],#right
[0,9,14,12,6,11,6,8,8,9,10,11,12,0,14,10],#lower right
[0,8,11,0,10,11,10,8,8,8,10,11,8,8,11,10],#bottom
[0,7,5,13,15,5,10,7,8,8,10,11,0,13,11,15],#lower left
[0,5,5,13,13,5,0,5,11,11,0,11,0,13,11,13]]#left
#Toggle map wrap around on/off
self.wrap_toggle = False
#toggle terrain update
self.update = False
#total solid water count
self.w_count = self.mx * self.my
#total shoreline count
self.s_count = 0
#total solid ground count
self.g_count = 0
#wander variables
self.x = 0
self.y = 0
self.counter = 0
#Generate map data and map image
#/-----------------------------------------------------------------------------/
def generate(self,seed=None):
#generate seed
if seed == None:
self.seed = random.randint(-999999999999,999999999999) #move to window?
print("SEED: ", self.seed)
random.seed(self.seed)
#generate water
print("Initializing Environment")
self.map = numpy.zeros((self.my,self.mx),dtype='uint8')
self.map[:] = 1
#generate land masses
print("Generating Land Masses")
dx = random.randrange(0,len(self.map[0])-1,1)#incorporate random positions to help map saturation.
dy = random.randrange(0,len(self.map)-1,1)
direction = 0
count = 0
#randomize direction and change terrain accordingly
while self.g_count < (self.mx*self.my)*self.density:
direction = random.randrange(0,4,1)
#right
if direction == 0 and (dx + 1) < len(self.map[dy])-1:
dx += 1
#left
elif direction == 1 and (dx - 1) > 0:
dx -= 1
#up
elif direction == 2 and (dy + 1) < len(self.map)-1:
dy += 1
#down
elif direction == 3 and (dy - 1) > 0:
dy -= 1
#count number of water/shore/land tiles
#if water
if self.map[dy][dx] == 1:
self.w_count = self.w_count - 1
self.g_count = self.g_count + 1
#if shore
elif self.map[dy][dx] != 0 and self.map[dy][dx] != 1:
self.s_count = self.s_count - 1
self.g_count = self.g_count + 1
#set tile to land and update surrounding tiles with flux
self.map[dy][dx] = 0
self.flux(dx,dy)
#calculate number of nodes and change position based on density threshold NOTE: use self.g_count instead of count?
count = count + 1
if count >= ((self.mx * self.my)*self.density)/(self.density*self.node):
dx = random.randrange(0,len(self.map[0])-1,1)
dy = random.randrange(0,len(self.map)-1,1)
count = 0
#generate minimap
print("Generating Map")
self.minimap['map_image'].width = len(self.map[0])
self.minimap['map_image'].height = len(self.map)
tmp = numpy.zeros((self.my,self.mx,3),dtype='uint8')
tmp = numpy.dstack((self.map,tmp))
#land/water/shore colors
color = [[139,69,19,255], [0,0,255,255], [100,50,10,255]]
for a in range(0,15,1):
if a < 2:
tmp = numpy.where(tmp[::,::,:1]!=a,tmp,color[a])
else:
tmp = numpy.where(tmp[::,::,:1]!=a,tmp,color[2])
tmp = tmp.astype('uint8')
self.minimap['map_image'].set_data('RGBA',self.minimap['map_image'].width*4,tmp.tobytes())
self.minimap['map'] = self.minimap['map_image'].get_texture()
#disable anti-aliasing/bilinear-filtering
glTexParameteri(self.minimap['map'].target, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexParameteri(self.minimap['map'].target, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
#save image
self.minimap['map'].save('tmp.png')
#update adjacent tiles
#----------------------------------------------------------------------------------|
def flux(self,x,y):
#upper left wrap check
if y+1 > len(self.map)-1 and x-1 < 0:
mx = self.mx
my = self.my
elif y+1 <= len(self.map)-1 and x-1 < 0:
mx = self.mx
my = 0
elif y+1 > len(self.map)-1 and x-1 >= 0:
mx = 0
my = self.my
else:
mx = 0
my = 0
if self.map[(y+1)-my][(x-1)+mx] == 1:
self.w_count -= 1
self.s_count += 1
elif self.map[(y+1)-my][(x-1)+mx] == 10:
self.s_count -= 1
self.g_count += 1
#upper left
self.map[(y+1)-my][(x-1)+mx] = self.table[0][self.map[(y+1)-my][(x-1)+mx]]
#up wrap check
if y+1 > len(self.map)-1:
my = self.my
else:
my = 0
#up
if self.map[(y+1)-my][x] == 1:
self.w_count -= 1
self.s_count += 1
elif self.map[(y+1)-my][x] == 8:
self.s_count -= 1
self.g_count += 1
elif self.map[(y+1)-my][x] == 10:
self.s_count -= 1
self.g_count += 1
elif self.map[(y+1)-my][x] == 11:
self.s_count -= 1
self.g_count += 1
self.map[(y+1)-my][x] = self.table[1][self.map[(y+1)-my][x]]
#upper right wrap check
if y+1 > len(self.map)-1 and x+1 > len(self.map[0])-1:
mx = self.mx
my = self.my
elif y+1 > len(self.map)-1 and x+1 <= len(self.map[0])-1:
mx = 0
my = self.my
elif y+1 <= len(self.map)-1 and x+1 > len(self.map[0])-1:
mx = self.mx
my = 0
else:
mx = 0
my = 0
if self.map[(y+1)-my][(x+1)-mx] == 1:
self.w_count -= 1
self.s_count += 1
elif self.map[(y+1)-my][(x+1)-mx] == 11:
self.s_count -= 1
self.g_count += 1
#upper right
self.map[(y+1)-my][(x+1)-mx] = self.table[2][self.map[(y+1)-my][(x+1)-mx]]
#right wrap check
if x+1 > len(self.map[0])-1:
mx = self.mx
else:
mx = 0
if self.map[y][(x+1)-mx] == 1:
self.w_count -= 1
self.s_count += 1
elif self.map[y][(x+1)-mx] == 5:
self.s_count -= 1
self.g_count += 1
elif self.map[y][(x+1)-mx] == 11:
self.s_count -= 1
self.g_count += 1
elif self.map[y][(x+1)-mx] == 13:
self.s_count -= 1
self.g_count += 1
#right
self.map[y][(x+1)-mx] = self.table[3][self.map[y][(x+1)-mx]]
#lower right wrap check
if y-1 < 0 and x+1 > len(self.map[0])-1:
mx = self.mx
my = self.my
elif y-1 >= 0 and x+1 > len(self.map[0])-1:
mx = self.mx
my = 0
elif y-1 < 0 and x+1 <= len(self.map[0])-1:
mx = 0
my = self.my
else:
mx = 0
my = 0
if self.map[(y-1)+my][(x+1)-mx] == 1:
self.w_count -= 1
self.s_count += 1
elif self.map[(y-1)+my][(x+1)-mx] == 13:
self.s_count -= 1
self.g_count += 1
elif self.map[(y-1)+my][(x+1)-mx] == 4:
self.s_count -= 1
self.g_count += 1
#lower right
self.map[(y-1)+my][(x+1)-mx] = self.table[4][self.map[(y-1)+my][(x+1)-mx]]
#bottom wrap check
if y-1 < 0:
my = self.my
else:
my = 0
if self.map[(y-1)+my][x] == 1:
self.w_count -= 1
self.s_count += 1
elif self.map[(y-1)+my][x] == 3:
self.s_count -= 1
self.g_count += 1
elif self.map[(y-1)+my][x] == 12:
self.s_count -= 1
self.g_count += 1
elif self.map[(y-1)+my][x] == 13:
self.s_count -= 1
self.g_count += 1
#bottom
self.map[(y-1)+my][x] = self.table[5][self.map[(y-1)+my][x]]
#lower left wrap check
if y-1 < 0 and x-1 < 0:
mx = self.mx
my = self.my
elif y-1 >= 0 and x-1 < 0:
mx = self.mx
my = 0
elif y-1 < 0 and x-1 >= 0:
mx = 0
my = self.my
else:
mx = 0
my = 0
if self.map[(y-1)+my][(x-1)+mx] == 1:
self.w_count -= 1
self.s_count += 1
elif self.map[(y-1)+my][(x-1)+mx] == 12:
self.s_count -= 1
self.g_count += 1
#lower left
self.map[(y-1)+my][(x-1)+mx] = self.table[6][self.map[(y-1)+my][(x-1)+mx]]
#left wrap check
if x-1 < 0:
mx = self.mx
else:
mx = 0
if self.map[y][(x-1)+mx] == 1:
self.w_count -= 1
self.s_count += 1
elif self.map[y][(x-1)+mx] == 6:
self.s_count -= 1
self.g_count += 1
elif self.map[y][(x-1)+mx] == 10:
self.s_count -= 1
self.g_count += 1
elif self.map[y][(x-1)+mx] == 12:
self.s_count -= 1
self.g_count += 1
#left
self.map[y][(x-1)+mx] = self.table[7][self.map[y][(x-1)+mx]]
if __name__ == '__main__':
window = Prototype()
pyglet.app.run()