class PostProcessingStep(object): def __init__(self, width, height, flip_y=True): super().__init__() self._fbo = FrameBuffer(width, height) self._drawable = QuadDrawable() self._drawable.size = Vector2(width, height) self._drawable.flip_y = flip_y self._drawable.pos = Vector2(0, height) self._drawable.invalidate_matrices() self._drawable.texture = self._fbo.texture def draw(self, screen): self._drawable.draw(screen) @property def fbo(self): return self._fbo @property def drawable(self): return self._drawable @drawable.setter def drawable(self, drawable): self._drawable = drawable
class SideTrail(Entity): def __init__(self, ship, offset_x, offset_y, offset_angle): self._ship = ship self.offset_x = offset_x self.offset_y = offset_y self.offset_angle = offset_angle self.side_trail_dimensions = Vector2(56 * self._ship.size, 11 * self._ship.size) self.side_trail = QuadDrawable(0, 0, self.side_trail_dimensions.x, self.side_trail_dimensions.y) self.side_trail.texture = Texture.load_from_file( 'resources/images/ship/side_trail.png') self.side_trail.anchor = Vector2(56 * self._ship.size, 5 * self._ship.size) self.side_trail.angle = offset_angle self.update(0, 0) def draw(self, screen): self.side_trail.draw(screen) def update(self, game_speed, axis): if axis > 0: self.side_trail.size = self.side_trail_dimensions self.side_trail.pos = PHYSICS_SCALE * ( self._ship._physicsShip.body.transform * (self.offset_x / PHYSICS_SCALE, self.offset_y / PHYSICS_SCALE)) self.side_trail.angle = self.offset_angle + self._ship._quad.angle else: self.side_trail.size = Vector2(0, 0)
class Bullet(Entity): def __init__(self, bullet_mgr, world): super().__init__(world, 0, 0, 0) # The visual representation of the bullet. self.owner = None self._world = world self._quad = QuadDrawable() self._quad.texture = Texture.load_from_file( 'resources/images/bullet.png') self._quad.size = Vector2(self._quad.texture.width, self._quad.texture.height) self._quad.anchor_to_center() # Attach physics only in the initialize method self.bullet_radius = min(self._quad.size.x, self._quad.size.y) / PHYSICS_SCALE / 2 self._angle = None self.bullet_mgr = bullet_mgr def initialize(self, x, y, direction, speed, owner): self.owner = owner self._physics = PhysicsBullet(self, self._world.physicsWorld, x / PHYSICS_SCALE, y / PHYSICS_SCALE, self.bullet_radius, owner) # Physics object corresponding to the bullet # self._physics.body.userData = {'type': 'bullet', 'obj': self, 'owner': owner} # self._physics.body.position = (x / PHYSICS_SCALE, y / PHYSICS_SCALE) self._physics.body.angle = math.atan2(-direction.x, direction.y) force_dir = b2Vec2(float(direction.x), float(direction.y)) force_pos = self._physics.body.GetWorldPoint(localPoint=(0.0, 0.0)) self._physics.body.ApplyLinearImpulse(force_dir * speed, force_pos, True) def draw(self, screen): self._quad.draw(screen) def update(self, screen): pos = self._physics.body.position if pos.x < 0 or pos.x > self._world.bounds.w / PHYSICS_SCALE or \ pos.y < 0 or pos.y > self._world.bounds.h / PHYSICS_SCALE: # Bullet is outside the screen self.remove_bullet() else: # Update the position of the bullet pos *= PHYSICS_SCALE self._quad.pos = Vector2(pos[0], pos[1]) self._quad.angle = self._physics.body.angle pos /= PHYSICS_SCALE def collide(self, other, began=False, **kwargs): # Don't do anything if a bullet is hitting a bullet. if isinstance(other, type(self)): return # If a bullet is hit by anything else, recycle it. if not began: self.remove_bullet() def remove_bullet(self): # Mark the associated physical object for deletion self.bullet_mgr.mark_for_recycle(self)
class StageDemo(StageSky): def __init__(self, width, height): super().__init__(width, height) texture = Texture.load_from_file('resources/images/logo.png') self._logo = QuadDrawable(0, 0, texture.width, texture.height) self._logo.anchor = Vector2(texture.width / 2, texture.height / 2) self._logo.texture = texture def update(self, game_speed): super().update(game_speed) self._logo.pos = Vector2(self.width / 2, self.height / 2) def draw_foreground(self, surface, window_x, window_y): super().draw_foreground(surface, window_x, window_y) self._logo.draw(surface)
class Trail(Entity): def __init__(self, ship, offset_x, offset_y): self._isBoosting = False self._ship = ship self.offset_x = offset_x self.offset_y = offset_y self.engine_trail_dimensions = Vector2(130 * self._ship.size, 344 * self._ship.size) self.engine_trail = QuadDrawable(0, 0, self.engine_trail_dimensions.x, self.engine_trail_dimensions.y) self.engine_trail.texture = Texture.load_from_file( 'resources/images/ship/trail.png') # Don't ask why 173... self.engine_trail.anchor = Vector2(65 * self._ship.size, 173 * self._ship.size) self.engine_trail.shader = ShaderProgram.from_files( vert_file='resources/shaders/base.vert', frag_file='resources/shaders/rgba.frag') self.update(0, 0, False) def draw(self, screen): self.engine_trail.shader.bind() self.engine_trail.shader.set_uniform_1f( 'mul_r', 0 if self._isBoosting else 0.49) self.engine_trail.shader.set_uniform_1f( 'mul_g', .9 if self._isBoosting else 0.332) self.engine_trail.shader.set_uniform_1f( 'mul_b', .9 if self._isBoosting else 0.059) self.engine_trail.draw(screen) def update(self, game_speed, trigger_intensity, boost): self._isBoosting = boost """`trigger_intensity` is a number from 0 to 1 representing how much a player is pressing the controller's right trigger. """ self.engine_trail.size = self.engine_trail_dimensions * ( 1 if boost else trigger_intensity) self.engine_trail.pos = Vector2( self._ship._quad.pos.x + self.offset_x, self._ship._quad.pos.y + self.offset_y, ) self.engine_trail.angle = self._ship._quad._angle
class Stage1(Stage): def __init__(self, width, height): super().__init__(width, height) self.quad = QuadDrawable(0, 0, width, height) self.quad.texture = Texture.load_from_file('resources/images/bg.png') def get_width(self): return self.width def update(self, game_speed): pass def draw_background(self, surface, window_x, window_y): self.quad.draw(surface) def draw_foreground(self, surface, window_x, window_y): pass
class HealthBar(Entity): def __init__(self, ship, world): super().__init__(world, ship.position.x, ship.position.y) self._ship = ship self._quad = QuadDrawable(0, 0, ship._dim.x, 5) self._quad.texture = Texture.load_from_file( 'resources/images/health.png') def update(self, game_speed): self._quad.size = Vector2( self._ship._dim.x * self._ship.ship_state.energy / ShipState.MAX_ENERGY, self._quad.size.y, ) self._quad.pos = self._position + Vector2(-self._ship._dim.x / 2, -self._ship._dim.y / 3) def draw(self, screen): self._quad.draw(screen)
class Laser: def __init__(self, x, y, length, angle): # self.quadb = QuadDrawable(x, y, 36/8, 100/8, angle) # self.quadb.texture = Texture.load_from_file('resources/images/burst_b.png') self.quadm = QuadDrawable(x + 36 / 8, y, length, 100 / 8, angle) self.quadm.texture = Texture.load_from_file('resources/images/burst_m.png') # self.quade = QuadDrawable(x+36/8+100-4, y, 64/8, 100/8, angle) # self.quade.texture = Texture.load_from_file('resources/images/burst_e.png') self.should_be_removed = False def update(self, screen): self.quadm.size = Vector2(self.quadm.scale.x, self.quadm.scale.y * 0.9) if self.quadm.scale.x < 0.01: self.should_be_removed = True self.quadm.size = Vector2(0, 0) def draw(self, screen): # self.quadb.draw(screen) self.quadm.draw(screen)
class Sprite: DEBUG = False def __init__(self, frames_store): self._frames_store = frames_store self._x = 0 self._y = 0 self._flags = 0 self._angle = 0 self._scale = Vector2(1, 1) # Collision detection self._attack_box = None self._hit_box = None # Frames and animations self._frame = None self._animation = None self._animation_name = None self._animation_frame_index = None self._animation_frame_delay = 0 self._animation_speed = 1 self._animating = False # Drawing self._drawable = QuadDrawable() def draw(self, screen): if self._frame is None: return self._drawable.pos = Vector2( self._x, self._y) # - camera.offset.x, self._y - camera.offset.y) self._drawable.draw(screen) # DEBUG boxes if Sprite.DEBUG: # TODO: !!! pass # anchor_x = self.frame.rect['x'] + self.frame.anchor['x'] - window_x # anchor_y = self.frame.rect['y'] + self.frame.anchor['y'] - window_y # pygame.draw.rect(surface, (255, 255, 255), pygame.Rect(anchor_x, anchor_y, 1, 1), 1) # if self.hit_box and self.hit_box.w > 0 and self.hit_box.h > 0: # pygame.draw.rect(surface, (0, 200, 0), self.hit_box.move(-window_x, -window_y), 1) # if self.attack_box and self.attack_box.w > 0 and self.attack_box.h > 0: # pygame.draw.rect(surface, (200, 0, 0), self.attack_box.move(-window_x, -window_y), 1) def set_frame(self, frame_name): self.stop_animation() self._animation = None self._frame = self._frames_store.get_frame(frame_name) def stop_animation(self): self._animation_frame_delay = 0 self._animation_frame_index = 0 self._animating = False def play_animation(self, animation_name, flags=0, speed=1): if (self._flags & FramesStore.FLAG_LOOP_ANIMATION) > 0 and \ self._flags == flags and animation_name == self._animation_name: return self._animating = True self._animation_speed = speed self._animation_name = animation_name self._flags = flags self._set_animation_frame(0) def skip_to_last_animation_frame(self): if not self._animating: return self._animating = False self._set_animation_frame(len(self._animation.frames) - 1) def update(self, game_speed): self._update_collision_boxes() if not self._animating: return if self._animation_frame_delay <= 0: self.next_animation_frame() return else: self._animation_frame_delay -= game_speed * self._animation_speed return def next_animation_frame(self): new_animation_frame_index = self._animation_frame_index + 1 if new_animation_frame_index > len(self._animation.frames) - 1: if not (self._flags & FramesStore.FLAG_LOOP_ANIMATION) > 0: self._animating = False return else: new_animation_frame_index = 0 self._set_animation_frame(new_animation_frame_index) def previous_animation_frame(self): new_animation_frame_index = self._animation_frame_index - 1 if new_animation_frame_index < 0: new_animation_frame_index = len(self._animation.frames) - 1 self._set_animation_frame(new_animation_frame_index) def _set_animation_frame(self, frame_index): self._animation = self._frames_store.get_animation( self._animation_name) self._animation_frame_index = frame_index self.animation_frame = self._animation.frames[ self._animation_frame_index] new_frame = self._animation.frames[frame_index] self._animation_frame_delay = new_frame.delay self._frame = self._frames_store.get_frame(new_frame.frame_name) # Override animation flip if the frame is also flipped flags = self._flags if self.animation_frame.flip_x: flags |= FramesStore.FLAG_FLIP_X if self.animation_frame.flip_y: flags |= FramesStore.FLAG_FLIP_Y # Updates the drawable self._drawable.texture = self._frame.image self._drawable.scale = Vector2(self._frame.rect.w, self._frame.rect.h).dot(self._scale) self._drawable.anchor = self._frame.anchor.dot(self._scale) self._drawable.flip_x = (flags & FramesStore.FLAG_FLIP_X > 0) self._drawable.flip_y = (flags & FramesStore.FLAG_FLIP_Y > 0) def _update_collision_boxes(self): if not self._animating: self._attack_box = None self._hit_box = None # TODO: flip_y should be handled as well animation_frame = self._animation.frames[self._animation_frame_index] flip_x = ((self._flags & FramesStore.FLAG_FLIP_X) > 0) ^ animation_frame.flip_x if self._frame.hit_box: self._hit_box = self._frame.hit_box.copy() if flip_x: self._hit_box.x = -self._hit_box.x - self._hit_box.width self._hit_box.move_ip(self._x, self._y) else: self._hit_box = None if self._frame.attack_box: self._attack_box = self._frame.attack_box.copy() if flip_x: self._attack_box.x = -self._attack_box.x - self._attack_box.width self._attack_box.move_ip(self._x, self._y) else: self._attack_box = None @property def x(self): return self._x @x.setter def x(self, value): self._x = value @property def y(self): return self._y @y.setter def y(self, value): self._y = value @property def angle(self): return self._angle @angle.setter def angle(self, value): self._angle = value self._drawable.angle = value @property def scale(self): return self._scale @scale.setter def scale(self, value): self._scale = value self._drawable.scale *= value @property def hit_box(self): return self._hit_box @property def attack_box(self): return self._attack_box @property def animating(self): return self._animating @property def shader(self): return self._drawable.shader @shader.setter def shader(self, shader): self._drawable.shader = shader
class Ship(Entity): def __init__( self, world, bullet_mgr, controllers, x, y, z=0, angle=0, color='standard', ): super().__init__(world, x, y, z) self.world = world self._dim = Vector2(130 * SCALE, 344 * SCALE) self._angle = angle self._physicsShip = PhysicsShip( self, world.physicsWorld, x / config.PHYSICS_SCALE, y / config.PHYSICS_SCALE, angle=angle, ) # Used by ship components to scale themselves self.size = SCALE self._quad = QuadDrawable(0, 0, self._dim.x, self._dim.y) self._quad.pos = self._position self._quad.anchor = self._dim.__div__(2.0) texture_file = SHIP_TEXTURES.get(color, SHIP_TEXTURES['standard']) self._quad.texture = Texture.load_from_file(texture_file) self._quad.shader = ShaderProgram.from_files(vert_file='resources/shaders/base.vert', frag_file='resources/shaders/rgba.frag') self.controllers = controllers self.pilotController = controllers[0] if len(controllers) else None self.shieldController = controllers[1] if len(controllers) > 1 else None self.turretController = controllers[2] if len(controllers) > 2 else None self.shields = [ Shield(self, world), Shield(self, world), ] self.turret_right = Turret(self, bullet_mgr, offset_x=-59 * SCALE, offset_y=2 * SCALE) self.turret_left = Turret(self, bullet_mgr, offset_x=59 * SCALE, offset_y=2 * SCALE) self.ship_state = ShipState(self) self.trail = Trail(self, 0, 0) self.side_trail_left = SideTrail(self, 28 * SCALE, 40 * SCALE, -45) self.side_trail_right = SideTrail(self, -25 * SCALE, 40 * SCALE, 225) # self._healthbar = HealthBar(self, world) def update(self, game_speed): self._physicsShip.update_forces(self.pilotController) for c in self.controllers: c.update() # if c.is_button_pressed(GameController.BUTTON_DIR_PAD_UP): # if self.turretController == c: # self.turretController = None # if self.shieldController == c: # self.shieldController = None # self.pilotController = c # elif c.is_button_pressed(GameController.BUTTON_DIR_PAD_DOWN): # if self.pilotController == c: # self.pilotController = None # if self.shieldController == c: # self.shieldController = None # self.turretController = c # elif c.is_button_pressed(GameController.BUTTON_DIR_PAD_LEFT): # if self.turretController == c: # self.turretController = None # if self.pilotController == c: # self.pilotController = None # self.shieldController = c if self.pilotController and self.ship_state.state == ShipState.LIVE: boost = self.pilotController.is_button_down(GameController.BUTTON_A) trigger_intensity = self.pilotController.get_axis(GameController.AXIS_TRIGGER_RIGHT) or 0.0 self.trail.update(game_speed, trigger_intensity, boost) axis_intensity = self.pilotController.get_axis(GameController.AXIS_LEFT_X) or 0.0 self.side_trail_left.update(game_speed, axis_intensity) self.side_trail_right.update(game_speed, -axis_intensity) if self.shieldController and self.ship_state.state == ShipState.LIVE: shield0_input_values = ( self.shieldController.get_axis(GameController.AXIS_LEFT_X) or 0.0, self.shieldController.get_axis(GameController.AXIS_LEFT_Y) or 0.0, 0.0, ) shield1_input_values = ( self.shieldController.get_axis(GameController.AXIS_RIGHT_X) or 0.0, self.shieldController.get_axis(GameController.AXIS_RIGHT_Y) or 0.0, 0.0, ) else: shield0_input_values = (0.0, 0.0, 0.0) shield1_input_values = (0.0, 0.0, 0.0) self.shields[0].update(game_speed, shield0_input_values) self.shields[1].update(game_speed, shield1_input_values) if self.turretController and self.ship_state.state == ShipState.LIVE: turret_left_x, turret_left_y = ( self.turretController.get_axis(GameController.AXIS_LEFT_X) or 0.0, self.turretController.get_axis(GameController.AXIS_LEFT_Y) or 0.0, ) turret_right_x, turret_right_y = ( self.turretController.get_axis(GameController.AXIS_RIGHT_X) or 0.0, self.turretController.get_axis(GameController.AXIS_RIGHT_Y) or 0.0, ) threshold = 0.2 turret_left_fire = (self.turretController.get_axis(GameController.AXIS_TRIGGER_LEFT) or 0.0) > threshold turret_right_fire = (self.turretController.get_axis(GameController.AXIS_TRIGGER_RIGHT) or 0.0) > threshold else: turret_left_x, turret_left_y = (0, 0) turret_right_x, turret_right_y = (0, 0) turret_left_fire = turret_right_fire = False self.turret_left.update( game_speed, turret_left_x, turret_left_y, turret_left_fire, is_right_wing=False, ) self.turret_right.update( game_speed, turret_right_x, turret_right_y, turret_right_fire, is_right_wing=True, ) self._angle = self._physicsShip.body.angle + math.pi pos = self._physicsShip.body.position * config.PHYSICS_SCALE self._position = Vector2(pos[0], pos[1]) self._quad.pos = self._position self._quad.angle = self._angle self.ship_state.update( time_passed_ms=(game_speed * config.GAME_FRAME_MS), ) # self._healthbar.update(game_speed) def draw(self, screen): if self.is_live(): for shield in self.shields: shield.draw(screen) self.trail.draw(screen) self.side_trail_left.draw(screen) self.side_trail_right.draw(screen) if self.has_recent_damage(): energy_ratio = self.ship_state.energy / self.ship_state.MAX_ENERGY damage_ratio = 1.0 - energy_ratio self._quad.shader.bind() self._quad.shader.set_uniform_1f('mul_r', 0.0) self._quad.shader.set_uniform_1f('mul_g', 0.2 + 0.8 * damage_ratio) self._quad.shader.set_uniform_1f('mul_b', 0.2 + 0.8 * damage_ratio) else: self._quad.shader.bind() self._quad.shader.set_uniform_1f('mul_r', 0.0) self._quad.shader.set_uniform_1f('mul_g', 0.0) self._quad.shader.set_uniform_1f('mul_b', 0.0) self.turret_left.draw(screen) self.turret_right.draw(screen) # Important: this has to be drawn AFTER the trails and turrets (to # be positioned on top of them) self._quad.draw(screen) def destroy_ship(self): pos = self._physicsShip.body.position * config.PHYSICS_SCALE x, y = pos self.world.asteroids.append(Asteroid( self.world, x, y - 30, Vector2(random() * 30 - 15, random() * -100), random() * 3.0, 'resources/images/derelict/part_01.png', config.SHIP_SCALE )) self.world.asteroids.append(Asteroid( self.world, x + 30, y + 30, Vector2(random() * 60 + 30, random() * 60 + 30), random() * 3.0, 'resources/images/derelict/part_02.png', config.SHIP_SCALE )) self.world.asteroids.append(Asteroid( self.world, x - 30, y + 30, Vector2(random() * -60 - 30, random() * 60 + 30), random() * 3.0, 'resources/images/derelict/part_03.png', config.SHIP_SCALE )) self.world.asteroids.append(Asteroid( self.world, x, y - 30, Vector2(random() * 3.0 - 1.5, random() * -10), random() * 3.0, 'resources/images/people/pilot.png', config.SHIP_SCALE )) self.world.asteroids.append(Asteroid( self.world, x + 30, y + 30, Vector2(random() * 6.0 + 3.0, random() * 6.0 + 3.0), random() * 3.0, 'resources/images/people/gunner.png', config.SHIP_SCALE )) self.world.asteroids.append(Asteroid( self.world, x - 30, y + 30, Vector2(random() * -6.0 - 3.0, random() * 6.0 + 3.0), random() * 3.0, 'resources/images/people/technician.png', config.SHIP_SCALE )) def is_live(self): return self.ship_state.state == ShipState.LIVE def has_recent_damage(self): return self.ship_state.has_recent_damage def collide(self, other, intensity=10.0, **kwargs): # TODO: Calculate the damage: # Collision between shield and bullet (sensor) # Collision between shield and everything else self.ship_state.damage(energy=10.0) def heal(self, amount): self.ship_state.heal(amount)
class Font: def __init__(self): self._font_faces = {} self._page_textures = {} self._character_program = ShaderProgram.from_sources( vert_source=self.vert_shader_base, frag_source=self.frag_shader_texture) self._quad = QuadDrawable() self._quad.shader = self._character_program def load_bmfont_file(self, filename): base_dir = dirname(filename) font_def = BMFontDef(filename) self._font_faces[font_def.size] = font_def self._page_textures[font_def.size] = [] for file in font_def.page_files: path = base_dir + '/' + file self._page_textures[font_def.size].append( Texture().load_from_file(path)) def draw_string(self, screen, font_size, string, x, y, scale=1): font = self._font_faces[font_size] for char in string: c = font.get_char(char) self._quad.texture = self._page_textures[font_size][c.page_index] self._quad.size = Vector2(c.width, c.height) * scale self._quad.pos = Vector2(x + c.offset_x * scale, y + c.offset_y * scale) self._character_program.bind() self._character_program.set_uniform_2f('area_pos', c.x / font.page_width, c.y / font.page_height) self._character_program.set_uniform_2f('area_size', c.width / font.page_width, c.height / font.page_height) self._quad.draw(screen) x += c.advance_x * scale def draw_char(self, screen, font_size, char, x, y, scale=1): font = self._font_faces[font_size] c = font.get_char(char) s = self._character_program self._quad.texture = self._page_textures[font_size][c.page_index] self._quad.size = Vector2(c.width, c.height) * scale self._quad.pos = Vector2(x, y + c.offset_y * scale) self._quad.shader = s s.bind() s.set_uniform_matrix4('projection', screen.projection_matrix.m) s.set_uniform_2f('area_pos', c.x / font.page_width, c.y / font.page_height) s.set_uniform_2f('area_size', c.width / font.page_width, c.height / font.page_height) self._quad.draw(screen) return c.advance_x vert_shader_base = """ #version 330 core uniform mat4 model; uniform mat4 projection; uniform vec2 area_pos; uniform vec2 area_size; layout(location=0) in vec2 vertex; layout(location=1) in vec2 uv; out vec2 uv_out; void main() { vec2 texture_out = uv; if (gl_VertexID == 0) { texture_out = area_pos; } else if (gl_VertexID == 1) { texture_out = vec2(area_pos.x, area_pos.y+area_size.y); } else if (gl_VertexID == 2) { texture_out = vec2(area_pos.x+area_size.x, area_pos.y+area_size.y); } else if (gl_VertexID == 3) { texture_out = vec2(area_pos.x+area_size.x, area_pos.y); } vec4 vertex_world = model * vec4(vertex, 1, 1); gl_Position = projection * vertex_world; uv_out = texture_out; } """ frag_shader_texture = """
class StageSky(Stage): def __init__(self, width, height): super().__init__(width, height) self.quad = QuadDrawable(0, 0, width, height) self.quad.texture = Texture.load_from_file('resources/images/bg.png') number_of_planets = 9 self.planets = [] number_of_clouds_background = 5 number_of_clouds_foreground = 5 self.clouds_background = [] self.clouds_foreground = [] # Generate the list of planets self.generate_planets(number_of_planets, width, height) # Generate the list of clouds self.generate_clouds_background(number_of_clouds_background, width, height) self.generate_clouds_foreground(number_of_clouds_foreground, width, height) def get_width(self): return self.width def update(self, game_speed): for planet in self.planets: planet.update(game_speed) for cloud in self.clouds_foreground: cloud.update(game_speed) for cloud in self.clouds_background: cloud.update(game_speed) def draw_background(self, surface, window_x, window_y): self.quad.draw(surface) for planet in self.planets: planet.draw(surface) for cloud in self.clouds_background: cloud.draw(surface) def draw_foreground(self, surface, window_x, window_y): for cloud in self.clouds_foreground: cloud.draw(surface) def generate_planets(self, number_of_planets, width, height): planet_picture_list = [f for f in listdir('resources/images/planets') if isfile(join('resources/images/planets', f))] number_per_areas = number_of_planets // 4 for x in range(0, number_per_areas): p = Planet(width / 2, height / 2, width, height, planet_picture_list) self.planets.append(p) for x in range(number_per_areas, number_per_areas * 2): p = Planet(width / 2, 0, width, height, planet_picture_list) self.planets.append(p) for x in range(number_per_areas * 2, number_per_areas * 3): p = Planet(0, height / 2, width, height, planet_picture_list) self.planets.append(p) for x in range(number_per_areas * 3, number_per_areas * 4): p = Planet(0, 0, width, height, planet_picture_list) self.planets.append(p) def generate_clouds_background(self, number_of_clouds, width, height): planet_picture_list = [f for f in listdir('resources/images/clouds') if isfile(join('resources/images/clouds', f))] for x in range(0, number_of_clouds): cloud = Cloud(width, height, planet_picture_list) self.clouds_background.append(cloud) def generate_clouds_foreground(self, number_of_clouds, width, height): planet_picture_list = [f for f in listdir('resources/images/clouds') if isfile(join('resources/images/clouds', f))] for x in range(0, number_of_clouds): cloud = Cloud(width, height, planet_picture_list) self.clouds_foreground.append(cloud)
class TMXMap(object): def __init__(self, filename): self._tmx_data = pytmx.TiledMap(filename, invert_y=True, image_loader=self._image_loader) self._size = self._tmx_data.width * self._tmx_data.tilewidth, self._tmx_data.height * self._tmx_data.tileheight self._layer_offsets = [ Vector2(0, 0) for _ in range(0, len(self._tmx_data.layers)) ] self._drawable = QuadDrawable() @staticmethod def _image_loader(filename, colorkey, **kwargs): if colorkey: logger.error('colorkey not implemented') image = Texture.load_from_file(filename) def load_image(rect=None, flags=None): if rect: try: x, y, w, h = rect y = image.height - y - h tile = image.get_region(x, y, w, h) except: logger.error('cannot get region %s of image', rect) raise else: tile = image if flags: logger.error('tile flags are not implemented') return tile return load_image @property def width_in_pixels(self): return self._size[0] @property def height_in_pixels(self): return self._size[1] def draw(self, screen): self.draw_layers_range(screen, 0, len(self._tmx_data.layers)) def draw_layers_range(self, screen, start, how_many): for index in range(start, start + how_many): layer = self._tmx_data.layers[index] if layer.visible == 0: continue offset_x = layer.offsetx + self._layer_offsets[index].x offset_y = layer.offsety + self._layer_offsets[index].y if isinstance(layer, pytmx.TiledTileLayer): # for x, y, image in layer.tiles(): pass elif isinstance(layer, pytmx.TiledObjectGroup): for obj in layer: if hasattr(obj, 'points'): # draw_lines(poly_color, obj.closed, obj.points, 3) pass elif obj.image: self._drawable.texture = obj.image self._drawable.scale = Vector2(obj.width, obj.height) self._drawable.pos = Vector2(obj.x - offset_x, obj.y - offset_y) self._drawable.draw(screen) # obj.image.blit(obj.x - offset_x, (Gfx.screen_height - obj.y) - offset_y, 0, w, h) else: # draw_rect(rect_color, (obj.x, obj.y, obj.width, obj.height), 3) pass elif isinstance(layer, pytmx.TiledImageLayer): if layer.image: pass def set_layer_offset(self, layer_index, x, y): self._layer_offsets[layer_index].x = x self._layer_offsets[layer_index].y = y def all_objects_as_dict(self, layer_name): # Note: there shouldn't be duplicate or missing names if layer_name not in self._tmx_data.layernames: return {} objects = dict() layer = self._tmx_data.layernames[layer_name] for obj in layer: objects[obj.name] = obj return objects def all_objects_as_list(self, layer_name): if layer_name not in self._tmx_data.layernames: return [] return list(self._tmx_data.layernames[layer_name])
class Shield(Entity): def __init__(self, ship, world): super().__init__(ship._world, 0, 0) self._ship = ship self._quad = QuadDrawable(0, 0, 0, 0) self._quad.texture = Texture.load_from_file( 'resources/images/shield_arc.png') self._quad.shader = ShaderProgram.from_files( vert_file='resources/shaders/base.vert', frag_file='resources/shaders/rgba.frag') self._quad.size = Vector2(self._quad.texture.width, self._quad.texture.height) * 0.67 self._quad.anchor = Vector2(0, self._quad.size.y / 2) self._physicsShield = PhysicsShield( self, ship._physicsShip, world.physicsWorld, center=self._ship._physicsShip.body.position, radius=(self._quad.size.x / PHYSICS_SCALE) * 1.1, ) self._collision_timer = 0 self._rad1 = ship._dim.y / 2.9 self._rad2 = ship._dim.y / 2.9 self._angle = 0 self._angle_speed = 1 self._enable = False self._charge = 0 self.shield_state = ShieldState(self) self.update(0, (0.0, 0.0, 0.0)) def calc_angle(self, x, y): if INERTIA: mag = math.sqrt(x * x + y * y) self._angle_speed = max(1, self._angle_speed * mag) if mag > 0.001: theta = math.atan2(y, x) self._angle_speed = min(self._angle_speed * 1.01, 6) else: return self._angle delta = theta - self._angle delta = (delta + math.pi) % (2 * math.pi) - math.pi step = delta * self._angle_speed / 8.0 return self._angle + step else: return math.atan2(y, x) def update(self, game_speed, input_values): x, y, trigger = input_values if input_values == (0.0, 0.0, 0.0): # If the shields aren't being used, don't display them self._enable = False else: self._enable = True self.update_angle_position(x, y) self._physicsShield.body.position = self._ship._physicsShip.body.position self.shield_state.advance_time(time_passed_ms=(game_speed * config.GAME_FRAME_MS), ) self._collision_timer -= game_speed * config.GAME_FRAME_MS def update_angle_position(self, x, y): self._angle = self.calc_angle(x, y) self._quad.pos = self._ship._position self._quad.angle = self._angle def draw(self, screen): if not self._enable: return self._quad.shader.bind() if self._collision_timer > 0: self._quad.shader.set_uniform_1f('mul_r', 0) self._quad.shader.set_uniform_1f('mul_g', 0.8) self._quad.shader.set_uniform_1f('mul_b', 0.8) else: self._quad.shader.set_uniform_1f('mul_g', 0) self._quad.shader.set_uniform_1f('mul_b', 1) self._quad.shader.set_uniform_1f('mul_r', 1) # if self.shield_state.is_healthy: self._quad.draw(screen) def collide(self, other, intensity=0.0, began=False, **kwargs): if not self._enable: return body = kwargs['body'] other_body = kwargs['other_body'] # Collision between shield and everything else self.shield_state.damage(energy=10.0) # Heal the ship so it has a purpose -- take less damage # self._ship.heal(5.0) if began: incoming_pos = other_body.position vector = incoming_pos - body.position direction = b2Vec2(vector.x, vector.y) direction.Normalize() incoming_angle = math.atan2(direction.y, direction.x) incoming_angle = (incoming_angle + math.pi * 2) % (math.pi * 2) shield_angle = (self._angle + math.pi * 2) % (math.pi * 2) shield_angle2 = (self._angle + math.pi * 2) % (math.pi * 2) + ( math.pi * 2) shield_angle3 = (self._angle + math.pi * 2) % (math.pi * 2) - ( math.pi * 2) if (shield_angle - HALF_ARC_DEGREES < incoming_angle < shield_angle + HALF_ARC_DEGREES) or \ (shield_angle2 - HALF_ARC_DEGREES < incoming_angle < shield_angle2 + HALF_ARC_DEGREES) or \ (shield_angle3 - HALF_ARC_DEGREES < incoming_angle < shield_angle3 + HALF_ARC_DEGREES): self._collision_timer = 400
class Asteroid(Entity): def __init__( self, world, x, y, speed, torque, asset='resources/images/asteroides/asteroid_01.png', scale=1, ): super().__init__(world, x, y, 0) # Slightly smaller than the image texture = Texture.load_from_file(asset) image_size = min(texture.width, texture.height) radius = ((image_size / PHYSICS_SCALE) / 2) * 0.8 self._quad = QuadDrawable(x, y, texture.width * scale, texture.height * scale) self._quad.anchor_to_center() self._quad.texture = texture self._quad.shader = ShaderProgram.from_files(vert_file='resources/shaders/base.vert', frag_file='resources/shaders/rgba.frag') self._physicAsteroid = PhysicsAsteroid( self, world.physicsWorld, center=Vector2(x / PHYSICS_SCALE, y / PHYSICS_SCALE), radius=radius, speed=speed, torque=torque, ) def update(self, game_speed): self._physicAsteroid.update_forces() self._quad.pos = self._physicAsteroid.body.position * PHYSICS_SCALE self._quad.angle = self._physicAsteroid.body.angle pass def draw(self, screen): self._quad.shader.bind() self._quad.shader.set_uniform_1f('mul_r', 0.3) self._quad.shader.set_uniform_1f('mul_g', 0.3) self._quad.shader.set_uniform_1f('mul_b', 0.3) self._quad.draw(screen) pass def collide(self, other, **kwargs): pass def destroy(self): goes_to_left = self._physicAsteroid.body.linearVelocity.x < 0 goes_to_right = not goes_to_left goes_to_top = self._physicAsteroid.body.linearVelocity.y < 0 goes_to_bottom = not goes_to_top return (goes_to_left and self._physicAsteroid.body.position.x < 0 - self._physicAsteroid.shape.radius) or \ (goes_to_right and (self._physicAsteroid.body.position.x * PHYSICS_SCALE > SCREEN_WIDTH + self._physicAsteroid.shape.radius)) or \ (goes_to_top and (self._physicAsteroid.body.position.y < 0 - self._physicAsteroid.shape.radius)) or \ (goes_to_bottom and (self._physicAsteroid.body.position.y * PHYSICS_SCALE > SCREEN_HEIGHT + - self._physicAsteroid.shape.radius))
class World: SCENE_NONE = 0 SCENE_TITLE = 1 SCENE_GAME = 2 SCENE_GAME_OVER = 4 def __init__(self, bounds, controllers, stage, debug=0): self.scene = self.SCENE_TITLE self.game_over_timer = 0 self.stage = stage self.game_over_quad = QuadDrawable( SCREEN_WIDTH / 2 - 496 / 2, SCREEN_HEIGHT / 2 - 321 / 2, 496, 321, ) self.game_over_quad.texture = Texture.load_from_file( 'resources/images/game_over.png') self.bounds = bounds self.debug = debug self.window_x = 0 self.window_y = 0 self.physicsWorld = b2World(gravity=(0, 0), contactListener=ContactListener()) # Physical bodies should be deleted outside the simulation step. self.physics_to_delete = [] self._shapes = Shapes() self.controllers = controllers self.bullet_mgr = bullet_mgr = BulletManager(self) self.players = [] self.entities = [bullet_mgr] def batch(iterable, n=1): l = len(iterable) for ndx in range(0, l, n): yield iterable[ndx:min(ndx + n, l)] quadrant = 0 for cs in batch(controllers, 3): x = 300 + (SCREEN_WIDTH - 600) * (quadrant & 1) + 100 * random.uniform(-1, 1) y = 200 + (SCREEN_HEIGHT - 400) * (quadrant >> 1 & 1) + 50 * random.uniform(-1, 1) ship = Ship( self, bullet_mgr, controllers=cs, x=x, y=y, # angle=math.degrees(math.atan2(y, x)) - 180 color=SHIP_TEXTURES[list(SHIP_TEXTURES.keys())[quadrant]], ) self.players.append(ship) self.entities.append(ship) quadrant += 1 self.asteroids = [] # self.generate_asteroid() def restart_game(self): # This is not enough, you need to re-init players self.__init__( bounds=self.bounds, controllers=self.controllers, stage=self.stage, debug=self.debug, ) def begin(self): self.scene = self.SCENE_GAME # for character in self.characters: # character.begin() def update(self, game_speed): time_delta = game_speed * config.GAME_FRAME_MS alive = 0 for p in self.players: if p.is_live(): alive += 1 if alive <= 1 and self.game_over_timer <= 0: self.game_over_timer = 5000 if self.game_over_timer > 0 and self.game_over_timer < time_delta: self.restart_game() return self.game_over_timer -= time_delta if self.game_over_timer <= 0: self.game_over_timer = 0 self.stage.update(game_speed) for e in self.asteroids: e.update(game_speed) for e in self.entities: e.update(game_speed) if random.randint(0, 10000) < 100: self.generate_asteroid() self.check_asteroids() # Check position of physical objects for e in self.entities: if not isinstance(e, Ship): continue pos = e._physicsShip.body.position force_dir = Vector2() if pos.x < 0: force_dir = Vector2(1, 0) elif pos.x > self.bounds.w / PHYSICS_SCALE: force_dir = Vector2(-1, 0) elif pos.y < 0: force_dir = Vector2(0, 1) elif pos.y > self.bounds.h / PHYSICS_SCALE: force_dir = Vector2(0, -1) intensity = 100 force_dir *= intensity force_apply_pos = e._physicsShip.body.GetWorldPoint( localPoint=(0.0, 0.0)) e._physicsShip.body.ApplyLinearImpulse((force_dir.x, force_dir.y), force_apply_pos, True) def draw(self, screen): self.stage.draw_background(screen, self.window_x, self.window_y) for e in self.asteroids: e.draw(screen) for e in self.entities: e.draw(screen) self.stage.draw_foreground(screen, self.window_x, self.window_y) if config.PHYSICS_DEBUG_DRAW_BODIES: self._draw_physics_bodies(screen) if self.game_over_timer > 0: self.game_over_quad.draw(screen) def _draw_physics_bodies(self, screen): for body in self.physicsWorld.bodies: for fixture in body.fixtures: if isinstance(fixture.shape, b2PolygonShape): vertices = [(body.transform * v) * PHYSICS_SCALE for v in fixture.shape.vertices] vertices = [(v[0], v[1]) for v in vertices] # Add the first point again to close the polygon vertices.append(vertices[0]) self._shapes.draw_polyline(screen, vertices, Color(1, 0, 0, 1)) elif isinstance(fixture.shape, b2CircleShape): center = (body.transform * fixture.shape.pos) * PHYSICS_SCALE radius = fixture.shape.radius * PHYSICS_SCALE self._shapes.draw_circle(screen, center.x, center.y, radius, Color(1, 0, 0, 1), start_angle=body.angle) def game_over(self): self.scene = self.SCENE_GAME_OVER def generate_asteroid(self): # Picks a random movement direction direction = Vector2() angle = random.random() * math.pi * 2 direction.x = math.cos(angle) direction.y = math.sin(angle) # Places the asteroid outside of the screen position = Vector2() position.x = SCREEN_WIDTH / 2 + direction.x * (SCREEN_WIDTH / 1.5) position.y = SCREEN_HEIGHT / 2 + direction.y * (SCREEN_HEIGHT / 1.5) speed = -Vector2(direction.x, direction.y) * 1000 * random.random() torque = 1 * random.random() asteroid = Asteroid(self, position.x, position.y, speed=speed, torque=torque) self.asteroids.append(asteroid) def check_asteroids(self): for asteroid in self.asteroids: if asteroid.destroy(): self.asteroids.remove(asteroid)