def __init__(self, size, name): self.width, self.height = size.xy self.name = name # for debugging only self.data = array.array("c", chr(0) * self.width * self.height * 4) stride = self.width * 4 self.surface = cairo.ImageSurface.create_for_data( self.data, cairo.FORMAT_ARGB32, self.width, self.height, stride ) self.texture = glGenTextures(1) self.cr = cairo.Context(self.surface) self.pango = pangocairo.CairoContext(self.cr) self.layout = self.pango.create_layout() # force subpixel rendering self.font_options = cairo.FontOptions() self.font_options.set_antialias(cairo.ANTIALIAS_SUBPIXEL) self.cr.set_font_options(self.font_options) glBindTexture(GL_TEXTURE_2D, self.texture) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) v = np.array([0, 0, 0, 0, 1, size.x, 0, 0, 1, 1, size.x, size.y, 0, 1, 0, 0, size.y, 0, 0, 0], np.float32) i = np.array([0, 1, 2, 3], np.uint32) self.vbo = VBO(GL_QUADS, v, i)
def __init__(self, pos): self.pos = pos self.vel = Vector2f(0,0) self.texture = Image('texture/player.png', filter=GL_NEAREST) self.in_air = False self.jumping = 0 self.hp = Player.max_hp self.hp_ratio = 0.8 self.dir = 1 self.cave_visited = False self.cave_quest = False self.have_ham = False self.have_cheese = False self.have_bread = False self.have_sandwich = False self.is_killed = False self.derp = False v = np.array([ 0,1,0, 0,1, 1,1,0, 1,1, 1,3,0, 1,0, 0,3,0, 0,0, ], np.float32) i = np.array([0,1,2,3], np.uint32) self.vbo = VBO(GL_QUADS, v, i)
def __init__(self, filename): self.filename = os.path.join('data', filename) with open(self.filename) as fp: data = json.load(fp) self.width = data['width'] self.height = data['height'] self.tile_width = data['tilewidth'] self.tile_height = data['tileheight'] # hardcoded dx = self.tile_width / 128.0 dy = self.tile_height / 128.0 tile_div = 128 / self.tile_width # load tilemap self.texture = Image(data['tilesets'][0]['image'], filter=GL_NEAREST) n = len(data['layers'][0]['data']) ver = np.zeros((n*4, 5), np.float32) for i, tile in enumerate(data['layers'][0]['data']): x = i % self.width y = -(i / self.width) tile -= 1 # start with 0 index tx = tile % tile_div ty = tile / tile_div ver[i*4+0] = (x , y , 0, tx*dx, ty*dy+dy) ver[i*4+1] = (x+1, y , 0, tx*dx+dx, ty*dy+dy) ver[i*4+2] = (x+1, y+1, 0, tx*dx+dx, ty*dy) ver[i*4+3] = (x , y+1, 0, tx*dx, ty*dy) ver = ver.flatten() ind = np.array(range(n*4), np.uint32) self.vbo = VBO(GL_QUADS, ver, ind) self.grid = np.array(data['layers'][0]['data'], np.uint32) # load objects self.obj1 = list(self.twiddle(data['layers'][1]['objects'])) self.obj2 = list(self.twiddle(data['layers'][2]['objects'])) self.obj3 = list(self.twiddle(data['layers'][3]['objects'])) self.pickups = []
class HUD: def __init__(self, size, name): self.width, self.height = size.xy self.name = name # for debugging only self.data = array.array("c", chr(0) * self.width * self.height * 4) stride = self.width * 4 self.surface = cairo.ImageSurface.create_for_data( self.data, cairo.FORMAT_ARGB32, self.width, self.height, stride ) self.texture = glGenTextures(1) self.cr = cairo.Context(self.surface) self.pango = pangocairo.CairoContext(self.cr) self.layout = self.pango.create_layout() # force subpixel rendering self.font_options = cairo.FontOptions() self.font_options.set_antialias(cairo.ANTIALIAS_SUBPIXEL) self.cr.set_font_options(self.font_options) glBindTexture(GL_TEXTURE_2D, self.texture) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) v = np.array([0, 0, 0, 0, 1, size.x, 0, 0, 1, 1, size.x, size.y, 0, 1, 0, 0, size.y, 0, 0, 0], np.float32) i = np.array([0, 1, 2, 3], np.uint32) self.vbo = VBO(GL_QUADS, v, i) def clear(self, color=(0, 0, 0, 0)): self.cr.save() self.cr.set_source_rgba(color[0], color[1], color[2], color[3]) self.cr.set_operator(cairo.OPERATOR_SOURCE) self.cr.paint() self.cr.restore() @classmethod def create_font(cls, font="Sans", size=12, raw=None): if raw is None: raw = "%s %f" % (font, size) return pango.FontDescription(raw) def text(self, text, font, color=(0, 0, 0, 1), alignment=pango.ALIGN_LEFT, justify=False, width=None): cr = self.cr cr.set_source_rgba(*color) self.layout.context_changed() self.layout.set_font_description(font) if width: self.layout.set_width(int(width * pango.SCALE)) self.layout.set_alignment(alignment) self.layout.set_justify(justify) self.layout.set_markup(text) self.pango.show_layout(self.layout) return self.layout.get_pixel_extents() def rectangle(self, x, y, w, h, color=(0, 0, 0, 1)): self.cr.save() self.cr.set_source_rgba(color[0], color[1], color[2], color[3]) self.cr.rectangle(x, y, w, h) self.cr.fill() self.cr.restore() def __enter__(self): self.cr.save() return self def __exit__(self, type, value, traceback): if type is None: glBindTexture(GL_TEXTURE_2D, self.texture) glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, self.width, self.height, 0, GL_BGRA, GL_UNSIGNED_BYTE, self.data.tostring() ) def draw(self): glBindTexture(GL_TEXTURE_2D, self.texture) glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, self.width, self.height, 0, GL_BGRA, GL_UNSIGNED_BYTE, self.data.tostring() ) self.vbo.draw()
class Map(object): def __init__(self, filename): self.filename = os.path.join('data', filename) with open(self.filename) as fp: data = json.load(fp) self.width = data['width'] self.height = data['height'] self.tile_width = data['tilewidth'] self.tile_height = data['tileheight'] # hardcoded dx = self.tile_width / 128.0 dy = self.tile_height / 128.0 tile_div = 128 / self.tile_width # load tilemap self.texture = Image(data['tilesets'][0]['image'], filter=GL_NEAREST) n = len(data['layers'][0]['data']) ver = np.zeros((n*4, 5), np.float32) for i, tile in enumerate(data['layers'][0]['data']): x = i % self.width y = -(i / self.width) tile -= 1 # start with 0 index tx = tile % tile_div ty = tile / tile_div ver[i*4+0] = (x , y , 0, tx*dx, ty*dy+dy) ver[i*4+1] = (x+1, y , 0, tx*dx+dx, ty*dy+dy) ver[i*4+2] = (x+1, y+1, 0, tx*dx+dx, ty*dy) ver[i*4+3] = (x , y+1, 0, tx*dx, ty*dy) ver = ver.flatten() ind = np.array(range(n*4), np.uint32) self.vbo = VBO(GL_QUADS, ver, ind) self.grid = np.array(data['layers'][0]['data'], np.uint32) # load objects self.obj1 = list(self.twiddle(data['layers'][1]['objects'])) self.obj2 = list(self.twiddle(data['layers'][2]['objects'])) self.obj3 = list(self.twiddle(data['layers'][3]['objects'])) self.pickups = [] @staticmethod def twiddle(src): for obj in src: t = obj['type'] if t == 'food': item = Food(**obj) elif t == 'kebab': item = Schebab(**obj) elif t == 'key': item = QuestItem(**obj) else: raise ValueError, 'unknown type %s' % t yield item def draw(self, *args, **kwargs): Shader.upload_model(Matrix.identity()) self.texture.texture_bind() self.vbo.draw(*args, **kwargs) def tile_at(self, pos): x = int(pos.x) y = -int(pos.y) if x < 0 or y < 0: return -1 i = y * self.width + x try: return self.grid[i] except IndexError: return -1 def update(self): self.pickups = [x for x in self.pickups if not x.killed]
class Player(object): weight = 70.0 max_hp = 100 def __init__(self, pos): self.pos = pos self.vel = Vector2f(0,0) self.texture = Image('texture/player.png', filter=GL_NEAREST) self.in_air = False self.jumping = 0 self.hp = Player.max_hp self.hp_ratio = 0.8 self.dir = 1 self.cave_visited = False self.cave_quest = False self.have_ham = False self.have_cheese = False self.have_bread = False self.have_sandwich = False self.is_killed = False self.derp = False v = np.array([ 0,1,0, 0,1, 1,1,0, 1,1, 1,3,0, 1,0, 0,3,0, 0,0, ], np.float32) i = np.array([0,1,2,3], np.uint32) self.vbo = VBO(GL_QUADS, v, i) def update(self, dt, map): acc = 0.0 acc -= 9.8 * dt if self.jumping > 0: acc += self.jumping / Player.weight * dt self.jumping -= 25.0 self.vel.y += acc * dt # handle vertical self.in_air = True t1 = map.tile_at(self.pos + Vector2f(0, self.vel.y)) t2 = map.tile_at(self.pos + Vector2f(0.999, self.vel.y)) if walkable(t1) and walkable (t2): self.pos.y += self.vel.y else: if self.vel.y < -0.4: game.land.play() self.in_air = False self.vel.y = 0 self.pos.y = math.floor(self.pos.y) if not walkable(map.tile_at(self.pos+Vector2f(0,0.1))): self.pos.y += 1 # handle horizontal t1 = map.tile_at(self.pos + Vector2f(self.vel.x, 0.01)) t2 = map.tile_at(self.pos + Vector2f(self.vel.x + 1.0, 0.01)) if walkable(t1) and walkable(t2): self.pos.x += self.vel.x elif self.vel.x < 0: self.pos.x = math.floor(self.pos.x) elif self.vel.x > 0: self.pos.x = math.ceil(self.pos.x) # flip direction if self.vel.x > 0: self.dir = 1 elif self.vel.x < 0: self.dir = -1 # kill players falling down the hole if not self.is_killed and self.pos.y < -30 and self.pos.x >= 130 and self.pos.x <= 135: game.message('Player was killed while jumping down a hole') game.over() self.is_killed = True if not self.is_killed and self.hp <= 0.0001: game.message('player got lost in the woods and died') game.over() self.is_killed = True # subtract health d = (self.pos - Vector2f(53,-8)).length() d2 = (self.pos - Vector2f(354,-18)).length() d3 = (self.pos - Vector2f(200,-48)).length() d4 = (self.pos - Vector2f(384,-87)).length() if d >= 14.0 and d2 >= 14.0 and d3 >= 17.0 and d4 >= 20.0: self.hp -= 1.7 * dt else: self.hp += (Player.max_hp / 100.0) * dt self.hp = min(max(self.hp, 0.0), Player.max_hp) self.hp_ratio = float(self.hp) / Player.max_hp # test if possible to make sandwhich (without sudo) if d < 2 and self.have_cheese and self.have_ham and self.have_bread and not self.have_sandwich: game.message('Sandwich made') self.have_sandwich = True # check cave dc = (self.pos - Vector2f(354,-18)).length() if dc < 2.0 and not self.derp: if not self.cave_visited: if self.have_sandwich: game.message('Dward: om nom nom, thank you for the sandwich') game.message('Quest finished: "Make me a sandwich".') self.derp = True else: game.message('Dwarf: Hey there, could you fetch me a sandwich?') self.cave_visited = True if not self.cave_quest: game.message('Quest started: "Make me a sandwich".') self.cave_quest = True elif dc > 30.0: self.cave_visited = False def draw(self): model = Matrix.identity() # translate model[3,0] = self.pos.x + (self.dir == -1 and 1.0 or 0.0) model[3,1] = self.pos.y model[0,0] = self.dir Shader.upload_model(model) self.texture.texture_bind() self.vbo.draw() def jump(self): if not self.in_air: self.vel.y += 18.0 / Player.weight self.jumping = 900.0 def unjump(self): self.jumping = 0 def frobnicate(self, stuff): min2 = self.pos + Vector2f(0.25,0) max = self.pos + Vector2f(0.75,2) for obj in stuff: omin = obj.pos omax = obj.pos + Vector2f(1,1) if max.x < omin.x or min2.x > omax.x: continue if max.y < omin.y or min2.y > omax.y: continue self.hp = min(self.hp + obj.hp, Player.max_hp) obj.kill()
def init(self, size, fullscreen=False): flags = OPENGL|DOUBLEBUF if fullscreen: flags |= FULLSCREEN pygame.display.set_mode(size.xy, flags) pygame.display.set_caption('Super Chainsaw Food Adventure') i = pygame.display.Info() self.size = Vector2i(i.current_w, i.current_h) glMatrixMode(GL_MODELVIEW) glEnable(GL_TEXTURE_2D) glDisable(GL_CULL_FACE) glEnable(GL_BLEND) glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA) self.stage = 1 self.projection = Matrix.perspective(75, self.size, 0.1, 100) self.ortho = Matrix.ortho(self.size) v = np.array([ 0,0,0, 0,0, 1,0,0, 1,0, 1,1,0, 1,1, 0,1,0, 0,1, ], np.float32) i = np.array([0,1,2,3], np.uint32) self.quad = VBO(GL_QUADS, v, i) # parallax self.parallax_rep = 25 v = np.array([ 0,0,0, 0,1, 1,0,0, self.parallax_rep, 1, 1,1,0, self.parallax_rep,0, 0,1,0, 0,0, ], np.float32) i = np.array([0,1,2,3], np.uint32) self.repquad = VBO(GL_QUADS, v, i) self.parallax = Image('texture/sky.png', wrap=GL_REPEAT) self.parallax2 = Image('texture/sky2.png', wrap=GL_REPEAT) self.fbo = FBO(self.size, format=GL_RGB8, depth=True) self.shader = Shader('derp') self.passthru = Shader('passtru') self.herp = Shader('herp') self.map = Map('map.json') self.player = Player(Vector2f(55,-9)) self.clock = pygame.time.Clock() self.hud = HUD(Vector2i(500,100)) self.hpmeter = HUD(Vector2i(20, 500)) self.font = self.hud.create_font(size=16) self.font2 = self.hud.create_font(size=12) self.land = pygame.mixer.Sound('data/sound/land.wav') self.ding = pygame.mixer.Sound('data/sound/ding.wav') self.eat = pygame.mixer.Sound('data/sound/eat.wav') self.wind = pygame.mixer.Sound('data/sound/wind.wav') self.wind.play(loops=-1) self.set_stage(1) self.killfade = None self.killfade2 = 1.0 # fade amount self.textbuf = [] self.texttime = -10.0 self.message('<b>Welcome adventurer!</b>\nYou can start exploring the world but beware of wandering away too far.') self.message('When outside of lights your <i>HP</i> will drain and you will get lost in the woods.') self.message('Eat food to temporary increase your <i>HP</i>.') self.message('Quest started: "Find the chainsaw".') self.message('Quest started: "Frobnicate something".') with self.hud: self.hud.clear((0,1,1,1))
class Game(object): def __init__(self): self._running = False self.camera = Vector2f(0,5) def init(self, size, fullscreen=False): flags = OPENGL|DOUBLEBUF if fullscreen: flags |= FULLSCREEN pygame.display.set_mode(size.xy, flags) pygame.display.set_caption('Super Chainsaw Food Adventure') i = pygame.display.Info() self.size = Vector2i(i.current_w, i.current_h) glMatrixMode(GL_MODELVIEW) glEnable(GL_TEXTURE_2D) glDisable(GL_CULL_FACE) glEnable(GL_BLEND) glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA) self.stage = 1 self.projection = Matrix.perspective(75, self.size, 0.1, 100) self.ortho = Matrix.ortho(self.size) v = np.array([ 0,0,0, 0,0, 1,0,0, 1,0, 1,1,0, 1,1, 0,1,0, 0,1, ], np.float32) i = np.array([0,1,2,3], np.uint32) self.quad = VBO(GL_QUADS, v, i) # parallax self.parallax_rep = 25 v = np.array([ 0,0,0, 0,1, 1,0,0, self.parallax_rep, 1, 1,1,0, self.parallax_rep,0, 0,1,0, 0,0, ], np.float32) i = np.array([0,1,2,3], np.uint32) self.repquad = VBO(GL_QUADS, v, i) self.parallax = Image('texture/sky.png', wrap=GL_REPEAT) self.parallax2 = Image('texture/sky2.png', wrap=GL_REPEAT) self.fbo = FBO(self.size, format=GL_RGB8, depth=True) self.shader = Shader('derp') self.passthru = Shader('passtru') self.herp = Shader('herp') self.map = Map('map.json') self.player = Player(Vector2f(55,-9)) self.clock = pygame.time.Clock() self.hud = HUD(Vector2i(500,100)) self.hpmeter = HUD(Vector2i(20, 500)) self.font = self.hud.create_font(size=16) self.font2 = self.hud.create_font(size=12) self.land = pygame.mixer.Sound('data/sound/land.wav') self.ding = pygame.mixer.Sound('data/sound/ding.wav') self.eat = pygame.mixer.Sound('data/sound/eat.wav') self.wind = pygame.mixer.Sound('data/sound/wind.wav') self.wind.play(loops=-1) self.set_stage(1) self.killfade = None self.killfade2 = 1.0 # fade amount self.textbuf = [] self.texttime = -10.0 self.message('<b>Welcome adventurer!</b>\nYou can start exploring the world but beware of wandering away too far.') self.message('When outside of lights your <i>HP</i> will drain and you will get lost in the woods.') self.message('Eat food to temporary increase your <i>HP</i>.') self.message('Quest started: "Find the chainsaw".') self.message('Quest started: "Frobnicate something".') with self.hud: self.hud.clear((0,1,1,1)) def running(self): return self._running @event(pygame.QUIT) def quit(self, event=None): self._running = False @event(pygame.KEYDOWN) def on_keypress(self, event): if event.key == 113 and event.mod & KMOD_CTRL: # ctrl+q return self.quit() if event.key == 27: # esc return self.quit() if event.key == 119: self.player.jump() @event(pygame.KEYUP) def on_keyrelease(self, event): if event.key == 119: self.player.unjump() def poll(self): global event_table for event in pygame.event.get(): func = event_table.get(event.type, None) if func is None: continue func(self, event) def update(self): if self.killfade is not None: t = pygame.time.get_ticks() / 1000.0 s = (t - self.killfade) / 2.5 self.killfade2 = 1.0 - s if s > 1.0: self.quit() # so player keeps falling dt = 1.0 / self.clock.tick(60) self.player.vel.y = 0 self.player.update(dt, self.map) return key = pygame.key.get_pressed() self.player.vel.x = 0 if key[97 ]: self.player.vel.x = -0.15 if key[100]: self.player.vel.x = 0.15 if key[260]: self.camera.x -= 0.1 if key[262]: self.camera.x += 0.1 if key[258]: self.camera.y -= 0.1 if key[264]: self.camera.y += 0.1 dt = 1.0 / self.clock.tick(60) self.player.update(dt, self.map) self.player.frobnicate(self.map.pickups) self.map.update() def render(self): glClearColor(1,0,1,1) glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT) with self.hpmeter as hud: hud.clear((0.3,0,0,1)) hud.cr.identity_matrix() hud.rectangle(0,0, hud.width, hud.height * self.player.hp_ratio, (0,0.3,0,1)) hud.cr.translate(18,0) hud.cr.rotate(math.pi*0.5) hud.text(' Energy: %d / %d' % (int(math.ceil(self.player.hp/10)) * 10, Player.max_hp), self.font2, color=(1,0.8,0,1)) with self.hud: self.hud.clear((0,0,0,0)) self.hud.cr.identity_matrix() t = pygame.time.get_ticks() / 1000.0 s = (t - self.texttime) / 4.0 if s > 1.0: if len(self.textbuf) > 0: self.texttime = pygame.time.get_ticks() / 1000.0 self.text = self.textbuf.pop(0) else: a = min(1.0-s, 0.2) * 5 self.hud.cr.translate(0,25) self.hud.text(self.text, self.font, color=(1,0.8,0,a), width=self.hud.width, alignment=ALIGN_CENTER) view = Matrix.lookat( self.player.pos.x, self.player.pos.y+7, 15, self.player.pos.x, self.player.pos.y+7, 0, 0,1,0) with self.fbo as frame: frame.clear(0,0.03,0.15,1) Shader.upload_projection_view(self.projection, view) Shader.upload_player(self.player) self.shader.bind() # parallax background pm1 = Matrix.identity() pm1[3,0] = self.player.pos.x * 0.35 - 20 pm1[3,1] = self.player.pos.y * 0.5 - 20 pm1[0,0] = 42.0 * self.parallax_rep pm1[1,1] = 42.0 self.parallax.texture_bind() Shader.upload_model(pm1) self.repquad.draw() Shader.upload_projection_view(self.projection, view) self.map.draw() # entities for obj in self.map.pickups: obj.draw(self.quad) self.player.draw() # parallax 2 pm1 = Matrix.identity() pm1[3,0] = self.player.pos.x * -2.0 + 100 pm1[3,1] = self.player.pos.y * 0.5 - 45 * 3 + 10 pm1[0,0] = 45.0 * self.parallax_rep * 3 pm1[1,1] = 45 * 3 self.parallax2.texture_bind() Shader.upload_model(pm1) self.repquad.draw() mat = Matrix.identity() mat[0,0] = self.size.x mat[1,1] = self.size.y Shader.upload_projection_view(self.ortho, Matrix.identity()) Shader.upload_model(mat) self.fbo.bind_texture() self.herp.bind() self.quad.draw() # messagebox mat = Matrix.identity() mat[3,0] = self.size.x / 2 - self.hud.width / 2 mat[3,1] = self.size.y - self.hud.height Shader.upload_model(mat) self.hud.draw() # hpmeter mat = Matrix.identity() mat[3,1] = self.size.y / 2 - self.hpmeter.height / 2 Shader.upload_model(mat) self.hpmeter.draw() Shader.unbind() pygame.display.flip() def run(self): self._running = True while self.running(): self.poll() self.update() self.render() def message(self, text): self.textbuf.append(text) def set_stage(self, n): if n == 1: self.map.pickups.extend(self.map.obj1) elif n == 2: self.map.pickups.extend(self.map.obj2) elif n == 3: self.map.pickups.extend(self.map.obj3) def over(self): self.killfade = pygame.time.get_ticks() / 1000.0