class TemporaryEntity(Entity): defaults = { 'width': 48, 'height': 48, } attributes = { 'image_file': 'app/assets/corpse.png', 'chunk': None, 'timer': 60 } def init(self, **kwargs): self.image = load_image(self.image_file) self.sprite = Sprite(self.image, self.x, self.y, batch=self.batch, group=self.group) self.chunk.objects.append(self) clock.schedule_once(self.delete, self.timer) def delete(self, dt): if self in self.chunk.objects: self.chunk.objects.remove(self) self.sprite.delete() def update(self): pass def draw(self): self.sprite.draw()
class Scroll(object): def __init__(self, text, pos, batch): self.text = text x, y = self.pos = pos group = OrderedGroup(1) self.label = Label(text, font_name=FONT_NAME, font_size=28, color=COLOUR, x=x + 20, y=y + 15, group=group, batch=batch) w = self.label.content_width + 40 self.bg = ScrollBG(x, x + w, y, batch=batch, group=OrderedGroup(0)) self.left = Sprite(scroll_left, x=x, y=y, group=group, batch=batch) self.right = Sprite(scroll_right, x=x + w, y=y, group=group, batch=batch) def delete(self): self.label.delete() self.left.delete() self.right.delete() self.bg.delete()
class MapSpriteV2(SpriteV2, ABC): def __init__(self, logger, parent_viewport, map_id): super().__init__(logger, parent_viewport) self.map_id = map_id self.rotation = 0 self.x = 0 self.y = 0 @final def on_update(self): if not self.sprite and self.is_located_inside_viewport(): self.sprite = PygletSprite(self.texture, x=self.x, y=self.y, batch=self.batch, group=self.group, usage=self.usage, subpixel=self.subpixel) self.sprite.opacity = self.opacity self.sprite.rotation = self.rotation elif self.is_located_outside_viewport() and self.sprite: self.sprite.delete() self.sprite = None @final def on_position_update(self, x=0, y=0, rotation=0): self.x = x self.y = y self.rotation = rotation if self.sprite: self.sprite.update(x=self.x, y=self.y, rotation=self.rotation) @final def is_located_outside_viewport(self): return self.x - self.texture.anchor_x \ - (MAP_CAMERA.offset_x + self.parent_viewport.x2) / MAP_CAMERA.zoom \ > SPRITE_VIEWPORT_EDGE_OFFSET_LIMIT_X \ or self.x + self.texture.width - self.texture.anchor_x \ - (MAP_CAMERA.offset_x + self.parent_viewport.x1) / MAP_CAMERA.zoom \ < -SPRITE_VIEWPORT_EDGE_OFFSET_LIMIT_X \ or self.y - self.texture.anchor_y \ - (MAP_CAMERA.offset_y + self.parent_viewport.y2) / MAP_CAMERA.zoom \ > SPRITE_VIEWPORT_EDGE_OFFSET_LIMIT_Y \ or self.y + self.texture.height - self.texture.anchor_y \ - (MAP_CAMERA.offset_y + self.parent_viewport.y1) / MAP_CAMERA.zoom \ < -SPRITE_VIEWPORT_EDGE_OFFSET_LIMIT_Y @final def is_located_inside_viewport(self): return not self.is_located_outside_viewport()
def value(self, value): new = min(self.display_max, value) delta = new - self.value self._value = value while delta > 0: sprite = Sprite(self.__class__.icon_img, y=self.y) self.icons.append(sprite) self.add_sprite(sprite, self.__class__.sprite_group) self.x = self.x delta -= 1 while delta < 0: sprite = self.icons.pop() sprite.delete() delta += 1
class StockLabelElement(pyglet.text.document.InlineElement): """Ammunition image representing first character of a StockLabel. Center of ammunition image will be vertically alligned with center of the StockLabel's text that follows it. """ def __init__(self, image: pyglet.image.Texture, separation=2): """ ++image++ Image representing ammunition type. ++separation++ distance between edge of image and subsequent text, in pixels. """ image = copy(image) image.anchor_x = 0 image.anchor_y = 0 self.image = image self.height = image.height super().__init__(ascent=0, descent=0, advance=image.width + separation) def place(self, layout, x: int, y: int): """Position ammunition image. +layout+ StockLabel object to which this StockLabelElement was inserted. Layout should have anchor_y set to 'bottom' and 'content_valign' set to 'center'. +x+ Left edge of box reserved for in-line element and in which ammunition image is to be positioned. +y+ Baseline level, on which layout text sits. """ # Defines y at level so center of in-line image alligns vertically # with center of subsequent text, for which requires: # layout.anchor_y is'bottom' and layout_content_valign is 'center' y = layout.y + (layout.content_height // 2) - (self.image.anchor_y + (self.height // 2)) self._sprite = Sprite(self.image, x, y, batch=layout.batch, group=layout.top_group) self._sprite.draw() def remove(self, layout): """Remove image from in-line element.""" self._sprite.delete()
class SpriteItem(GameItem): '''GameItem that is rendered as a single sprite''' images = None render_layer = 2 # birds and feathers def __init__(self, x=0, y=0): GameItem.__init__(self) self.x = x self.y = y self.rotation = 0 self.sprite = None self.frame_idx = 0 def add_to_batch(self, batch, groups): self.sprite = Sprite( self.images[self.frame_idx], batch=batch, group=groups[self.render_layer] ) def remove_from_batch(self, batch): self.sprite.batch = None self.sprite.delete() def animate(self): self.sprite._x = self.x self.sprite._y = self.y self.sprite._rotation = degrees(self.rotation) image = self.images[self.frame_idx] if self.sprite.image != image: self.sprite.image = image else: self.sprite._update_position()
class MuzzleFlash: def __init__(self, pos, rot, game): self.pos = pos self.rot = rot self.time = 0 self.sprite = Sprite(game.muzzle_flash_img, batch=game.effects_batch) self.sprite.update(rotation=rot) self.sprite.image.anchor_x = self.sprite.width / 3 self.sprite.image.anchor_y = self.sprite.height / 2 self.dead = False self.sprite.x = pos.x self.sprite.y = pos.y def update(self, dt): self.time += dt if self.time > MUZZLE_FLASH_LIFESPAWN: self.sprite.delete() self.dead = True
class Object(SpriteWrapper): sprite_img = None sprite_group = '' def __init__(self, x, y, hb=None): super().__init__() cls = self.__class__ self.rect = primitives.Rect(0, 0, cls.sprite_img.width, cls.sprite_img.height) self.hb = hb self.sprite = Sprite(cls.sprite_img) self.add_sprite(self.sprite, cls.sprite_group) self.x, self.y = x, y @property def x(self): return self.rect.centerx @x.setter def x(self, value): self.rect.centerx = value if isinstance(self.hb, primitives.Circle): self.hb.x = value elif isinstance(self.hb, primitives.Rect): self.hb.centerx = value self.sprite.x = value @property def y(self): return self.rect.centery @y.setter def y(self, value): self.rect.centery = value if isinstance(self.hb, primitives.Circle): self.hb.y = value elif isinstance(self.hb, primitives.Rect): self.hb.centery = value self.sprite.y = value @property def left(self): return self.rect.left @left.setter def left(self, value): self.rect.left = value @property def right(self): return self.rect.right @right.setter def right(self, value): self.rect.right = value @property def top(self): return self.rect.top @top.setter def top(self, value): self.rect.top = value @property def bottom(self): return self.rect.bottom @bottom.setter def bottom(self, value): self.rect.bottom = value @property def center(self): return self.rect.center @center.setter def center(self, value): self.rect.center = value def delete(self): self.sprite.delete() def collide(self, other): if isinstance(other, Object): return self.hb.collide(other.hb) elif isinstance(other, Group): x = [] for object in other: if self.collide(object): x.append(object) return x else: raise NotImplementedError
class InfoRow(object): """Row of Labels collectively providing player information. Provides information on: Lives, as images of player ship, one image per life remaining. Ammunition stock levels, as series of StockLabels associated with each of player's weapons. Radiation level, as RadiationMonitor.gauge associated with player Score, as created score label. Information positioned right-to-left if player is blue, or from left-to-right if red. METHODS --remove_a_life()-- Remove the life furthest from the screen edge. --update_score_label()-- Update score label --delete()-- Delete all objects that comprise InfoRow """ _text_colors = {'blue': BLUE, 'red': RED} _radiation_symbol = load_image('radiation_20.png', anchor='origin') def __init__(self, window: pyglet.window.Window, batch: pyglet.graphics.Batch, control_sys, num_lives: int, level_label: Label): """ ++window++ Window to which InfoRow to be displayed. ++batch++ Batch to which InfoRow objects to be added. ++control_sys++ .game_objects.ControlSystem of player for whom providing information. ++num_lives++ Number of lives player starts with. ++level_label++ Label that expresses current level. """ self._win = window self._info_row_base = self._win.height - 30 self._batch = batch self._control_sys = control_sys self._color = self._control_sys.color self._text_color = self._text_colors[self._color] self._num_lives = num_lives self._lives = [] self._level_label = level_label # Current position of _x, updated --_advance_x()-- as set objects self._x = self._win.width if self._color == 'blue' else 0 self._set_lives() self._set_stocks_labels() self._set_radiation_gauge() self._create_score_label() def _advance_x(self, pixels: int): """Move _x by +pixels+ pixels in the direction that labels are being sequentially placed. """ pixels *= -1 if self._color == 'blue' else 1 self._x += pixels def _get_object_x(self, obj: Union[Sprite, StockLabel]): """Return 'x' coordinate to place object at required separation on from last object placed ASSUMING +obj+ is anchored to bottom left and --_x-- positioned at the required spacing on from the last object placed. """ if self._color == 'blue': width = obj.content_width if isinstance(obj, StockLabel) \ else obj.width return self._x - width else: return self._x def _set_object(self, obj: Union[Sprite, StockLabel], x: Optional[int] = None, y: Optional[int] = None, batch: Optional[pyglet.graphics.Batch] = None, sep: int = 0): """Position and batch +obj+. Position and batch according to passed parameters, or according to default behaviour otherwise. NB Default behaviour ASSUMES +obj+ anchored to bottom left corner. """ if sep is not 0: self._advance_x(sep) obj.batch = self._batch if batch is None else batch obj.y = self._info_row_base if y is None else y obj.x = self._get_object_x(obj) if x is None else x width = obj.content_width if isinstance(obj, StockLabel)\ else obj.width self._advance_x(width) # Leave _x at end of info row def _set_lives(self): for i in range(self._num_lives): img = copy(self._control_sys.ShipCls[self._color].img) img.anchor_x = 0 img.anchor_y = 0 life = Sprite(img) life.scale = 0.36 self._lives.append(life) self._set_object(life, sep=3) def remove_a_life(self): """Remove the life image furthest from the screen edge.""" self._lives.pop() def _set_stocks_labels(self): for weapon in self._control_sys.weapons: label = weapon.stock_label label.set(style_attrs={'color': self._text_color, 'bold': True}) self._set_object(label, sep=10) label.positioned() def _set_radiation_gauge(self): self._set_object(self._control_sys.radiation_monitor.gauge, sep=15) self._rad_symbol = Sprite(self._radiation_symbol) self._set_object(self._rad_symbol, sep=5) def _score_label_x_coordinate(self) -> int: """Returns x coordinate for score label to position to side of level label. """ direction = 1 if self._color == 'blue' else -1 dist = (self._level_label.content_width // 2) + 34 x = self._level_label.x + (dist * direction) return x def _create_score_label(self): self._score_label = Label('0', x=self._score_label_x_coordinate(), y=self._win.height, font_size=30, bold=True, batch=self._batch, anchor_x='center', anchor_y='top') self._score_label.set_style('color', self._text_color) def update_score_label(self, score: int): """Update score label to +score+.""" self._score_label.text = str(score) def delete(self): """Delete all objects that comprise InfoRow.""" for life in self._lives: life.delete() for weapon in self._control_sys.weapons: weapon.stock_label.delete() self._score_label.delete() self._control_sys.radiation_monitor.gauge.delete() self._rad_symbol.delete()
class Player(InertialObject): def __init__(self, name="", hull_image=None, engine_image=None, x=0.0, y=0.0, thrust=200.0, maneuvering_thrust=360.0, weapon_projectile_image=None, weapon_projectile_speed=700.0, *args, **kwargs): super(Player, self).__init__(img=hull_image, name=name, vulnerable=True, x=x, y=y, *args, **kwargs) self.engineflame = Sprite(img=engine_image, x=x, y=y, *args, **kwargs) self.engineflame.visible = False self.thrust = thrust self.maneuvering_thrust = maneuvering_thrust self.weapon_projectile_image = weapon_projectile_image self.weapon_projectile_speed = weapon_projectile_speed self.center_x = x self.center_y = y self.key_handler = key.KeyStateHandler() def fire(self): angle_radians = math.radians(self.rotation) ship_radius = self.image.width/2 + 5.0 bullet_x = self.x + math.sin(angle_radians) * ship_radius bullet_y = self.y + (math.cos(angle_radians) * ship_radius) new_bullet = Projectile(name="bullet", img=self.weapon_projectile_image, x=bullet_x, y=bullet_y, batch=self.batch) new_bullet.velocity_x = ( self.velocity_x + math.sin(angle_radians) * self.weapon_projectile_speed ) new_bullet.velocity_y = ( self.velocity_y + math.cos(angle_radians) * self.weapon_projectile_speed ) self.new_objects.append(new_bullet) def update(self, dt): rotational_dv = self.maneuvering_thrust * dt propulsive_dv = self.thrust * dt modified_rot_speed = self.rotation_speed if self.key_handler[key.RIGHT]: modified_rot_speed += rotational_dv if self.key_handler[key.LEFT]: modified_rot_speed -= rotational_dv if self.key_handler[key.DOWN]: direction = 1.0 if self.rotation_speed < 0: direction = -1.0 abs_rotation = modified_rot_speed * direction modified_rot_speed = direction * (abs_rotation - rotational_dv) if (direction * modified_rot_speed) < 0: modified_rot_speed = 0.0 if self.key_handler[key.SPACE]: self.fire() # interpolate accelerated rotation change self.rotation_speed = (modified_rot_speed + self.rotation_speed) / 2.0 modified_vx = self.velocity_x modified_vy = self.velocity_y if self.key_handler[key.UP]: angle_radians = math.radians(self.rotation) modified_vx += math.sin(angle_radians) * propulsive_dv modified_vy += math.cos(angle_radians) * propulsive_dv self.engineflame.visible = True else: self.engineflame.visible = False # iterpolate accelerated velocity change self.velocity_x = (modified_vx + self.velocity_x) / 2.0 self.velocity_y = (modified_vy + self.velocity_y) / 2.0 super(Player, self).update(dt) # update subsprites self.engineflame.x = self.x self.engineflame.y = self.y self.engineflame.rotation = self.rotation self.rotation_speed = modified_rot_speed self.velocity_x = modified_vx self.velocity_y = modified_vy if self.key_handler[key.C]: self.x = self.center_x self.y = self.center_y self.velocity_x = 0.0 self.velocity_y = 0.0 self.rotation = 0.0 self.rotation_speed = 0.0 def delete(self): self.engineflame.delete() super(Player, self).delete()
class MobileEntity(Entity): defaults = { 'collidable': True, 'mobile': True, 'width': 50, 'height': 50, 'stat_modifiers': { 'str': 0, 'dex': 0, 'wis': 0, 'int': 0, 'con': 0, 'lck': 0 } } attributes = { 'dead': False, 'image_file': 'app/assets/zombie.png', 'moving_to': None, 'chunk': None, 'sprinting': False, 'in_combat': False, #'in_combat_with': [], 'attack_cooldown': 0, 'projectile_cooldown': 0, 'damage_text_color': (255, 255, 255, 255), 'chunk_container': 'npcs' } overwrite = {} def __init__(self, **kwargs): self.pre_init(**kwargs) super(MobileEntity, self).__init__(**kwargs) def init(self, **kwargs): self.in_combat_with = [] self.image = load_image(self.image_file) self.sprite = Sprite(self.image, self.x, self.y) self.hp_image = load_image('app/assets/healthbar.png') self.hp_sprite = Sprite(self.hp_image, self.x + 1, self.y + self.height + 5) self.set_sprite_render(self.group) self.stats = Stats(modifiers=self.stat_modifiers) entity_container = getattr(self.chunk, self.chunk_container) if self.chunk and self not in entity_container: entity_container.append(self) self.post_init(**kwargs) def pre_init(self, **kwargs): self.attributes = {**self.attributes, **self.overwrite} def post_init(self, **kwargs): pass def set_sprite_render(self, group): self.sprite.batch = self.chunk.draw_batch self.sprite.group = group self.hp_sprite.batch = self.chunk.draw_batch self.hp_sprite.group = group @property def speed(self): walk_speed = self.stats.dex // 3 if self.sprinting: return walk_speed * config.sprint_modifier return walk_speed def check_state(self): if self.dead: self.on_death() elif self.in_combat: if not self.in_combat_with: self.in_combat = False else: self.do_combat() else: self.do_idle() def on_death(self): print(f'{self.name} died!') if self.in_combat: for entity in self.in_combat_with: if self in entity.in_combat_with: entity.in_combat_with.remove(self) self.remove_from_chunk() self.sprite.delete() self.hp_sprite.delete() self.after_death() def remove_from_chunk(self): if self.chunk: container = getattr(self.chunk, self.chunk_container) if self in container: container.remove(self) def after_death(self): pass def do_combat(self): pass def do_idle(self): pass def check_bounds(self): if (self.x + self.width) > config.window_width: self.out_of_bounds('e') elif self.x < 0: self.out_of_bounds('w') elif (self.y + self.height) > config.window_height: self.out_of_bounds('n') elif self.y < 0: self.out_of_bounds('s') def out_of_bounds(self, direction): pass def on_collision(self, obj): x_intersect = calc_1D_intersect(*self.x_1D, *obj.x_1D) y_intersect = calc_1D_intersect(*self.y_1D, *obj.y_1D) if x_intersect < y_intersect: if self.x < obj.x: self.x = obj.x - self.width else: self.x = obj.x + obj.width else: if self.y < obj.y: self.y = obj.y - self.height else: self.y = obj.y + obj.height self.after_collision(obj) def after_collision(self, obj): pass def attack(self): if not self.attack_cooldown: atk_range = 75 for entity in self.chunk.attackable_objects: if entity is not self: if distance(*self.coord, *entity.coord) < atk_range: self.in_combat = True if entity not in self.in_combat_with: self.in_combat_with.append(entity) self.do_damage(entity, d6(num=self.stats.str // 2)) self.attack_cooldown = 50 def do_damage(self, target, dmg): target.take_damage(self, dmg) def take_damage(self, source, dmg): dmg_text = Label(f'{dmg}', x=randint((self.x - 5) // 1, (self.x + self.width + 5) // 1), y=self.y + self.height + 15, font_name='courier new', color=self.damage_text_color, bold=True, batch=self.chunk.draw_batch, group=self.chunk.foreground) clock.schedule_once(lambda df: dmg_text.delete(), 0.5) if (source is not None) and (source is not self): self.in_combat = True if source not in self.in_combat_with: self.in_combat_with.append(source) self.stats.hp -= dmg if self.stats.hp < 1: self.dead = True print( f'{self.name} ({self.stats.hp}/{self.stats.base_hp}) took {dmg} damage from {source.name}!' ) def fire_proj(self): velocity = 10 p_x = self.cursor_coord.x - self.center.x p_y = self.cursor_coord.y - self.center.y d = distance(self.center.x, self.center.y, self.cursor_coord.x, self.cursor_coord.y) r = velocity / d r_p = 30 / d if not self.projectile_cooldown: proj = Projectile(owner=self, chunk=self.chunk, x=self.center.x + p_x * r_p, y=self.center.y + p_y * r_p, damage=d20(num=self.stats.int // 3), velocity_x=p_x * r, velocity_y=p_y * r, batch=self.chunk.draw_batch, group=self.chunk.foreground) self.chunk.objects.append(proj) self.projectile_cooldown = 10 def update_cooldowns(self): if self.attack_cooldown > 0: self.attack_cooldown -= 1 if self.projectile_cooldown > 0: self.projectile_cooldown -= 1 def move(self): if self.moving_to: dist_from_target = np.sqrt((self.x - self.moving_to.x)**2 + (self.y - self.moving_to.y)**2) if dist_from_target <= self.speed: self.x = self.moving_to.x self.y = self.moving_to.y else: dist_ratio = self.speed / dist_from_target self.x = (1.0 - dist_ratio) * self.x + dist_ratio * self.moving_to.x self.y = (1.0 - dist_ratio) * self.y + dist_ratio * self.moving_to.y def update_sprites(self): self.sprite.update(x=self.x, y=self.y) self.hp_sprite.update(x=self.x + 1, y=self.y + self.height + 5, scale_x=self.stats.hp / self.stats.base_hp) def update(self): self.check_state() if not self.dead: self.update_cooldowns() self.move() self.check_bounds() self.update_sprites() else: self.after_death() def draw(self): self.sprite.draw()
class Character(object): def __init__(self, x, y, facing=Direction.NORTH): self.sprite = Sprite(self.Sprite.faces[facing], x, y) self.facing = facing self.movement_queue = [] self.movement_ticks = 0 self.current_health = self.health def draw(self): self.sprite.draw() def delete(self): self.sprite.delete() @property def x(self): return self.sprite.x @x.setter def x(self, nx): self.sprite.x = nx @property def y(self): return self.sprite.y @y.setter def y(self, ny): self.sprite.y = ny @property def color(self): return self.sprite.color @color.setter def color(self, ncolor): self.sprite.color = ncolor def look(self, direction): self.facing = direction self.sprite.image = self.Sprite.faces[direction] def move_to(self, x, y, duration=1): self.movement_queue.append((x, y, duration)) def update(self, dt): if self.movement_queue: if self.movement_ticks == 0: self.last_stop = self.x, self.y ex, ey, time = self.movement_queue[0] sx, sy = self.last_stop if ex < sx and ey < sy: self.look(Direction.WEST) elif ex < sx: self.look(Direction.NORTH) elif ey < sy: self.look(Direction.SOUTH) else: self.look(Direction.EAST) self.movement_ticks += dt ex, ey, time = self.movement_queue[0] sx, sy = self.last_stop self.x += (ex - sx) * (dt / time) self.y += (ey - sy) * (dt / time) if self.movement_ticks >= time: self.x, self.y = int(ex), int(ey) del self.movement_queue[0] self.movement_ticks = 0 else: self.look(self.facing) class Sprite(namedtuple("CharacterSprite", "x y facing moving_to internal_clock")): # # @property # def image(self): # return self.faces[self.facing] # # def update(self, dt): # return self.__class__(self.x, self.y, self.facing, self.moving_to, self.internal_clock + dt) # # def move(self, new_x, new_y): pass
class Player(Object): sprite_img = None sprite_group = 'player' hb_img = None hb_group = 'player_hb' _die_invuln = 3 def __init__(self, x, y, hb=None): super().__init__(x, y, hb=hb) self._focus = 0 self.speed_multiplier = 500 self.focus_multiplier = 0.5 self.shooting = 0 self.shot_rate = 20 self.shot_state = 0 self.bullets = BulletGroup() self.invuln = 0 self.v = Vector(0, 0) self.hbsprite = None @property def x(self): return super().x @x.setter def x(self, value): super(Player, self.__class__).x.fset(self, value) if hasattr(self, 'hbsprite') and self.hbsprite is not None: self.hbsprite.x = value @property def y(self): return super().y @y.setter def y(self, value): super(Player, self.__class__).y.fset(self, value) if hasattr(self, 'hbsprite') and self.hbsprite is not None: self.hbsprite.y = value @property def speed(self): if self.focus: return self.speed_multiplier * self.focus_multiplier else: return self.speed_multiplier @property def focus(self): return int(self._focus) @focus.setter def focus(self, value): f = self.focus v = bool(value) if f != v: self._focus = v if v: cls = self.__class__ self.hbsprite = Sprite(cls.hb_img, self.x, self.y) self.add_sprite(self.hbsprite, cls.hb_group) else: self.hbsprite.delete() self.hbsprite = None def die(self): if self.invuln > 0: return 1 else: self.invuln += Player._die_invuln return 0 def on_key_press(self, symbol, modifiers): if symbol == key.LSHIFT: self.focus = 1 elif symbol == key.Z: self.shooting = 1 def on_key_release(self, symbol, modifiers): if symbol == key.LSHIFT: self.focus = 0 elif symbol == key.Z: self.shooting = 0 def update(self, dt): # movement self.x += self.speed * self.v.x * dt self.y += self.speed * self.v.y * dt # bound movement if self.right > GAME_AREA.right: self.right = GAME_AREA.right elif self.left < GAME_AREA.left: self.left = GAME_AREA.left if self.bottom < GAME_AREA.bottom: self.bottom = GAME_AREA.bottom elif self.top > GAME_AREA.top: self.top = GAME_AREA.top # invuln if self.invuln > 0: self.invuln -= dt # bullet generation if self.shooting: self.shot_state += dt self.update_fire(dt) # bullet update self.bullets.update(dt) self.add_sprites(self.bullets) def update_fire(self, dt): pass
class RectangleProgressBarV2(UIObject, ABC): def __init__(self, logger, parent_viewport): super().__init__(logger, parent_viewport) self.inactive_image = None self.inactive_sprite = None self.active_image = None self.active_sprite = None self.current_percent = 0 @abstractmethod def get_position(self): pass @abstractmethod def get_scale(self): pass @final @is_not_active def on_activate(self): super().on_activate() if not self.inactive_sprite: self.inactive_sprite = Sprite(self.inactive_image, x=self.viewport.x1, y=self.viewport.y1, batch=BATCHES['ui_batch'], group=GROUPS['button_background']) self.inactive_sprite.scale = self.get_scale() self.inactive_sprite.opacity = self.opacity if self.current_percent == 0: image_region = self.active_image.get_region( self.active_image.height // 2, self.active_image.height // 2, 1, 1) else: image_region = self.active_image.get_region( 0, 0, self.current_percent * self.active_image.width // 100, self.active_image.height) if not self.active_sprite: self.active_sprite = Sprite(image_region, x=self.viewport.x1, y=self.viewport.y1, batch=BATCHES['ui_batch'], group=GROUPS['button_text']) else: self.active_sprite.image = image_region self.active_sprite.scale = self.get_scale() self.active_sprite.opacity = self.opacity @final @window_size_has_changed def on_window_resize(self, width, height): super().on_window_resize(width, height) self.viewport.x1, self.viewport.y1 = self.get_position() self.viewport.x2 = self.viewport.x1 + int( self.inactive_image.width * self.get_scale()) self.viewport.y2 = self.viewport.y1 + int( self.inactive_image.height * self.get_scale()) if self.inactive_sprite: self.inactive_sprite.position = (self.viewport.x1, self.viewport.y1) self.inactive_sprite.scale = self.get_scale() if self.active_sprite: self.active_sprite.position = (self.viewport.x1, self.viewport.y1) self.active_sprite.scale = self.get_scale() @final def on_update_opacity(self, new_opacity): self.opacity = new_opacity if self.opacity <= 0: if self.inactive_sprite: self.inactive_sprite.delete() self.inactive_sprite = None if self.active_sprite: self.active_sprite.delete() self.active_sprite = None else: if self.inactive_sprite: self.inactive_sprite.opacity = self.opacity if self.active_sprite: self.active_sprite.opacity = self.opacity @final def on_update_progress_bar_state(self, current_value, maximum_value): if maximum_value == 0: self.current_percent = 0 else: self.current_percent = int(current_value / maximum_value * 100) if self.current_percent > 100: self.current_percent = 100 if self.is_activated: if self.current_percent == 0: self.active_sprite.image = self.active_image.get_region( self.active_image.height // 2, self.active_image.height // 2, 1, 1) else: self.active_sprite.image = self.active_image.get_region( 0, 0, self.current_percent * self.active_image.width // 100, self.active_image.height)
class Grenade: def __init__(self, game, type, owner=False): self.game = game self.type = type self.explode = False self.tossed = False self.duration = 0 self.opacity = 255 self.sent = False self.owner = owner def throw(self, pos, vel, rot, o=False): self.distance = 0 self.pos = pos self.vel = vel.rotate(-rot) if self.type == "grenade": self.sprite = Sprite(self.game.granade_img, pos.x, pos.y, batch=self.game.main_batch) elif self.type == "smoke": self.sprite = Sprite(self.game.smoke_img, pos.x, pos.y, batch=self.game.main_batch) self.sprite.image.anchor_x = self.sprite.width / 2 self.sprite.image.anchor_y = self.sprite.height / 2 self.explode_sprite = None self.hit_box = GRENADE_HIT_BOX.copy() self.hit_box.x = pos.x - self.hit_box.width / 2 self.hit_box.y = pos.y - self.hit_box.height / 2 self.tossed = True if o is True: self.game.grenades.append(self) self.game.o_grenades.append({ "pos": { "x": pos.x, "y": pos.y }, "vel": { "x": vel.x, "y": vel.y }, "rot": rot, "type": self.type }) def collide_with_walls(self, dir): if dir == "x": for wall in self.game.walls: if (self.hit_box.x + self.hit_box.width > wall.pos.x and self.hit_box.y + self.hit_box.height > wall.pos.y ) and (self.hit_box.x < wall.pos.x + wall.width and self.hit_box.y < wall.pos.y + wall.height): if wall.center.x > self.hit_box.get_center().x: self.hit_box.x = wall.pos.x - self.hit_box.width elif wall.center.x < self.hit_box.get_center().x: self.hit_box.x = wall.pos.x + wall.width self.vel.x = -self.vel.x elif dir == "y": for wall in self.game.walls: if (self.hit_box.x + self.hit_box.width > wall.pos.x and self.hit_box.y + self.hit_box.height > wall.pos.y ) and (self.hit_box.x < wall.pos.x + wall.width and self.hit_box.y < wall.pos.y + wall.height): if wall.center.y > self.hit_box.get_center().y: self.hit_box.y = wall.pos.y - self.hit_box.height elif wall.center.y < self.hit_box.get_center().y: self.hit_box.y = wall.pos.y + wall.height self.vel.y = -self.vel.y def draw_hit_box(self): glBegin(GL_LINES) glVertex2i(int(self.hit_box.x), int(self.hit_box.y)) glVertex2i(int(self.hit_box.x), int(self.hit_box.y + self.hit_box.height)) glVertex2i(int(self.hit_box.x), int(self.hit_box.y + self.hit_box.height)) glVertex2i(int(self.hit_box.x + self.hit_box.width), int(self.hit_box.y + self.hit_box.height)) glVertex2i(int(self.hit_box.x + self.hit_box.width), int(self.hit_box.y + self.hit_box.height)) glVertex2i(int(self.hit_box.x + self.hit_box.width), int(self.hit_box.y)) glVertex2i(int(self.hit_box.x + self.hit_box.width), int(self.hit_box.y)) glVertex2i(int(self.hit_box.x), int(self.hit_box.y)) glEnd() def update(self, dt): if self.distance <= GRENADE_DISTANCE and self.tossed: # check hit box collisions self.hit_box.x += self.vel.x * dt self.collide_with_walls("x") self.hit_box.y += self.vel.y * dt self.collide_with_walls("y") self.pos.x = self.hit_box.x + self.hit_box.width / 2 self.pos.y = self.hit_box.y + self.hit_box.height / 2 self.sprite.x = self.pos.x self.sprite.y = self.pos.y self.distance += self.vel.magnitude() * dt else: if self.explode is False: self.sprite.delete() self.vel.multiply(0) self.explode = True if self.type == "grenade": self.explode_sprite = Animation( self.game.explosion_anim, self.pos.x, self.pos.y, batch=self.game.effects_batch) elif self.type == "smoke": self.explode_sprite = Animation( self.game.smoke, self.pos.x, self.pos.y, batch=self.game.effects_batch) self.duration = SMOKE_DURATION self.explode_sprite.x = self.pos.x - self.explode_sprite.width / 2 self.explode_sprite.y = self.pos.y - self.explode_sprite.height / 2 if self.type == "smoke" and self.explode is True: if self.duration <= 0: if self.opacity <= 0: self.explode_sprite.deleted = True self.explode_sprite.opacity = self.opacity if self.opacity > 0: self.opacity -= int(300 * dt) else: self.duration -= dt
class Player(physicalobject.PhysicalObject): """Physical player object""" def __init__(self,*args, **kwargs): super().__init__(img=resources.player_image, *args, **kwargs) del kwargs['screen_size'] # remove screen_size for sprite usage self.engine_sprite = Sprite(img=resources.engine_image, *args, **kwargs) self.engine_sprite.visible = False self.engine_sound = resources.engine_sound self.engine_player = pyglet.media.Player() self.engine_player.queue(self.engine_sound) self.engine_player.volume = 0 self.engine_player.eos_action = pyglet.media.Player.EOS_LOOP self.shield = shield.Shield(self.batch) self.shield_sound = resources.shield_sound # vector mechanics self.thrust = 100.0 self.recoil = 200.0 # rotational mechanics self.max_rotation = 250 self.rotation_speed = 0 self.rotation_force = 2.0 self.rotation_resistance = 2.5 self.ship_radius = self.image.width/2 self.key_handler = key.KeyStateHandler() self.event_handlers += [self, self.key_handler] self.score_dif = -25 self.reacts_to_bullets = False self.bullet_speed = 750.0 self.bullet_sound = resources.bullet_sound self.loaded = True def update(self, dt): super().update(dt) right = self.key_handler[key.RIGHT] left = self.key_handler[key.LEFT] def _apply_resistance(modifier=0): """Slows down ship rotation""" if self.rotation_speed < 0.0: self.rotation_speed += (modifier + self.rotation_resistance) * dt if self.rotation_speed > 0.0: self.rotation_speed -= (modifier + self.rotation_resistance) * dt if left and right: modifier = self.rotation_force _apply_resistance(modifier/2) elif right: self.rotation_speed += self.rotation_force * dt elif left: self.rotation_speed -= self.rotation_force * dt else: _apply_resistance() if self.rotation > -360: self.rotation += 360 elif self.rotation < 360: self.rotation -= 360 # rotate ship self.rotation += self.rotation_speed if self.key_handler[key.UP]: angle_radians = -math.radians(self.rotation) force_x = math.cos(angle_radians) * self.thrust * dt force_y = math.sin(angle_radians) * self.thrust * dt self.velocity_x += force_x self.velocity_y += force_y self.engine_sprite.rotation = self.rotation self.engine_sprite.x = self.x self.engine_sprite.y = self.y self.engine_sprite.visible = True # smooth sound == self.engine_player.play() if self.engine_player.volume < 1.8: self.engine_player.volume += 1.2 * dt else: self.engine_sprite.visible = False if self.engine_player.volume > 0.01: self.engine_player.volume -= 1.5 * dt else: self.engine_player.pause() # =============== if self.invulnerable: self.shield.up = True else: self.shield.up = False self.shield.x = self.x self.shield.y = self.y self.shield.update(dt) if self.key_handler[key.SPACE]: # fires has long has space is held self.fire(dt) """Methods that control shield invulnerability Can take 1 argument, deltatime(dt), invulnerability leght""" def set_invulnerable(self, dt=5.0): self.shield_sound.play() self.invulnerable = True # time to vulnerable pyglet.clock.schedule_once(self.set_vulnerable, dt) def set_vulnerable(self, dt): self.invulnerable = False def fire(self, dt): # fires only if loaded if self.loaded: angle_radians = -math.radians(self.rotation) force_x = math.cos(angle_radians) * self.recoil * dt force_y = math.sin(angle_radians) * self.recoil * dt self.velocity_x -= force_x self.velocity_y -= force_y angle_radians = -math.radians(self.rotation) ship_radius = self.ship_radius bullet_x = self.x + math.cos(angle_radians) * ship_radius bullet_y = self.y + math.sin(angle_radians) * ship_radius new_bullet = bullet.Bullet(screen_size=self.screen_size, x=bullet_x, y=bullet_y, batch=self.batch) bullet_vx = (self.velocity_x + math.cos(angle_radians) * self.bullet_speed) bullet_vy = (self.velocity_y + math.sin(angle_radians) * self.bullet_speed) new_bullet.velocity_x = bullet_vx new_bullet.velocity_y = bullet_vy self.bullet_sound.play() self.new_objects.append(new_bullet) # unloading gun self.loaded = False pyglet.clock.schedule_once(self.reload, 0.1) def reload(self, dt): self.loaded = True #def on_key_press(self, symbol, modifiers): ## single short fire # if symbol == key.SPACE: # self.fire() def delete(self): self.shield.delete() self.engine_sprite.delete() self.engine_player.delete() super().delete()
class GameUI(GameObject): """In game UI that shows score etc.""" def __init__(self, *, player: Player, space: pymunk.Space, ui_batch=None, background_batch=None): super().__init__() self.player = player self.space = space self.background_batch = background_batch self.danger_sprite = Sprite(img=resources.danger_image, batch=background_batch, x=128, y=128) self.danger_sprite.visible = False # Internal score modified by the property below self._score = 0 # Show the score self.score_label = Label('', font_name='m5x7', font_size=48, x=WIDTH - 10, y=HEIGHT, anchor_x='right', anchor_y='top', batch=ui_batch) # "call" the property setter below self.score = self._score def tick(self, dt: float): # Don't show danger sprite by default self.danger_sprite.visible = False # Okay so to find out where to possibly show the danger sprite # We need to do a ray cast from the center of the map to the player # And when we hit a wall, that is where it would be shown if it is close enough to the player # The reason we need this, is that the logic for snapping from the players position to a wall position # without raycasting in the case of corners (where two walls meet) is quite complicated so this is simply easier # get some preliminary start and end points start = Vec2d(WIDTH, HEIGHT) / 2 end = (self.player.pos - start) # We can't divide by 0, so make sure the player has moved away from the center if end.get_length_sqrd() > 0: # Then set the length to be the diagonal of the screen, so that the ray will always hit a wall somewhere end.length = math.sqrt(WIDTH**2 + HEIGHT**2) # Remove the start as we don't want vector relative to the center of the screen but rather proper coords end += start # Raycast, finding only wall sensors results = self.space.segment_query( start, end, 1, pymunk.ShapeFilter(mask=CollisionType.WallSensor)) # If we hit something if results: # Find our distance to it wall_result = results[0] distance = (self.player.pos - wall_result.point).length # And if our distance is less than half of danger_sprite's width/height if distance < self.danger_sprite.width / 2: # Show the sprite with increasing opacity as player nears wall self.danger_sprite.visible = True self.danger_sprite.opacity = min( valmap(distance, self.danger_sprite.width / 2, 0, 0, 255), 255) self.danger_sprite.x = wall_result.point.x self.danger_sprite.y = wall_result.point.y @property def score(self): return self._score @score.setter def score(self, value): self._score = value # Update the label text with the new score self.score_label.text = f'Score: {self._score}' def delete(self): self.score_label.delete() self.danger_sprite.delete() super().delete()
class Enemy(Creature): def __init__(self, tex_name, tile_width, tile_height, game_state, xpos=0, ypos=0, group=None, health=3, move_pattern=unlucky, move_params=(), name='enemy', gold=1, exp=1, hitsound='bandit_hit', damage=1): super().__init__(tex_name, tile_width, tile_height, game_state, xpos=xpos, ypos=ypos, group=group, damage=damage, health=health, name=name, hitsound=hitsound) self.is_guard = 'guard' in tex_name if type(move_pattern) == str: self.move_pattern = patterns[move_pattern](self, *move_params) else: self.move_pattern = move_pattern(self, *move_params) self.game.enemies.add(self) self.stats[G] = gold self.stats[EXP] = exp self.healthbar = Sprite(create(24, 4, SolidColorImagePattern(RED)), x=self.x, y=self.y, batch=self.batch, group=self.game.groups[1]) def move(self): dx, dy = next(self.move_pattern) new_x = self.xpos + dx new_y = self.ypos + dy collision = [ e for e in (self.game.enemies - {self}) | {self.game.pc} if new_x == e.xpos and new_y == e.ypos ] if collision != [] and collision[0] is self.game.pc: self.attack(collision[0]) elif (collision == [] and 0 < new_x < self.game.width - 1 and 0 < new_y < self.game.height - 1 and self.game.map[new_y][new_x] in tile.TRAVERSABLE): self.xpos += dx self.ypos += dy self.update_pos() def on_damage(self, damage, source: Creature): super().on_damage(damage, source) if self.stats[HP] <= 0: self.game.enemies.remove(self) if source == self.game.pc: source.stats[G] += self.stats[G] source.stats[EXP] += self.stats[EXP] self.delete() return None self.healthbar.scale_x = self.stats[HP] / self.stats[HPMAX] self.healthbar.draw() def update_pos(self): super().update_pos() self.healthbar.x = self.x self.healthbar.y = self.y def delete(self): self.healthbar.delete() return super().delete() @staticmethod def from_json(path, game, xp=0, yp=0): with open(path, 'r') as json: base_stats = loads(''.join(json.readlines())) if xp == yp == 0: xp, yp = get_clear_tile(game) ret = Enemy(tile_height=24, tile_width=24, game_state=game, xpos=xp, ypos=yp, **base_stats) for stat in LEVELED_STATS: ret.stats[stat] *= 1 + LEVELING_FACTOR * \ game.stage * (2 * game.difficulty + 1) for stat in RANDOMIZED_STATS: ret.stats[stat] *= normalvariate(1, VAR) for stat in LEVELED_STATS | RANDOMIZED_STATS: ret.stats[stat] = int(ret.stats[stat])
class Player(PhysicalObject): """ Player class """ def __init__(self, *args, **kwargs): super(Player, self).__init__(img=player_image, *args, **kwargs) # Name self.name = 'Player' # Bullets self._num_bullets = 0 # Thrust & rotate speed self.thrust = 20000.0 self.rotate_speed = 200.0 # Flame image self.engine_sprite = Sprite(img=engine_image, *args, **kwargs) self.engine_sprite.visible = False # Obtain key state handler self.key_handler = key.KeyStateHandler() def update(self, dt): """ Override super update method :param dt: :return: """ super(Player, self).update(dt) # ----- Rotate ----- # if self.key_handler[key.LEFT]: self.rotation -= self.rotate_speed * dt if self.key_handler[key.RIGHT]: self.rotation += self.rotate_speed * dt # ----- Forward or backward ----- # if self.key_handler[key.UP]: self.__run(dt) elif self.key_handler[key.DOWN]: self.__run(-dt) else: self.__stop() # ----- Speed up and slow down ----- # if self.key_handler[key.J]: self.__speed_up() if self.key_handler[key.K]: self.__slow_down() def on_key_press(self, symbol, modifiers): if symbol == key.SPACE: self._do_shoot_bullet() def _do_shoot_bullet(self): if self.can_shoot(): self.shoot_bullet(700) self.add_shoot() def __run(self, dt): """ Speed up """ # 正方向定义不同,加负号 angle_radians = -math.radians(self.rotation) force_x = math.cos(angle_radians) * self.thrust * dt force_y = math.sin(angle_radians) * self.thrust * dt self.velocity_x = force_x self.velocity_y = force_y # Ignite engine flame self.__ignite_engine_flame() def __stop(self): self.velocity_x = 0 self.velocity_y = 0 # Ignite engine flame self.__stop_engine_flame() def __speed_up(self): self.thrust += 4000 def __slow_down(self): """ Slow down """ self.thrust -= 4000 if self.thrust <= 2000: self.thrust = 2000 def __ignite_engine_flame(self): self.engine_sprite.visible = True self.engine_sprite.rotation = self.rotation self.engine_sprite.x = self.x self.engine_sprite.y = self.y def __stop_engine_flame(self): self.engine_sprite.visible = False def can_shoot(self): return self._num_bullets <= 1 def add_shoot(self): self._num_bullets += 1 def remove_shoot(self): self._num_bullets -= 1 def delete(self): """ Operations when destroyed :return: """ self.engine_sprite.delete() super(Player, self).delete()
class Window(pyglet.window.Window): def __init__(self, *args, **kwargs): super(Window, self).__init__(*args, **kwargs) self.score = 0 self.difficulty = self.score + 50 self.lives = 3 self.time = 0.5 self.ships = pyglet.graphics.Batch() self.rocks = pyglet.graphics.Batch() self.missiles = pyglet.graphics.Batch() self.started = False self.rock_trigger = 0 # Images self.background = pyglet.image.load('static/images/nebula_blue.s2014.png') self.rock_img = pyglet.image.load('static/images/asteroid_blue.png') utils.center_image_anchor(self.rock_img) ship_sequence = pyglet.image.load('static/images/double_ship.png') self.ship_imgs = pyglet.image.ImageGrid(ship_sequence, 1, 2) utils.center_image_grid_anchors(self.ship_imgs) self.splash_img = pyglet.image.load('static/images/splash.png') # Sounds self.thruster_snd = pyglet.media.load('static/sounds/rocket.ogg', streaming=False) self.explosion_snd = pyglet.media.load('static/sounds/explosion.ogg', streaming=False) self.background_music = pyglet.media.load('static/sounds/space1.mp3', streaming=False) self.background_music.play() # Sprite Groups self.rock_group = set() self.missile_group = set() # Spites self.ship = PlayerSprite(self.ship_imgs, self.thruster_snd, 400, 250, 0, 0, 270, 35, self.ships, self.missiles) self.splash = Sprite(self.splash_img, 200, 125) # Screen Text self.text_lives = pyglet.text.Label('Lives=' + str(self.lives), font_name='Times New Roman', font_size=36, x=10, y=10) self.text_score = pyglet.text.Label('Score=' + str(self.score), font_name='Times New Roman', font_size=36, x=10, y=60) # Keymaps self.key_downs = {key.UP:self.accel, key.LEFT:self.left, key.RIGHT:self.right, key.SPACE:self.fire, key.ESCAPE:pyglet.app.exit} self.key_ups = {key.UP:self.decel, key.LEFT:self.right, key.RIGHT:self.left} pyglet.clock.schedule_interval(self.update, 1/60.0) # update at 60Hz def game_reset(self): self.started = False self.rock_group = set() self.ship = PlayerSprite(self.ship_imgs, self.thruster_snd, 400, 250, 0, 0, 270, 35, self.ships, self.missiles) self.lives = 3 self.score = 0 # intro screen # self.splash.draw() def display_score(self): self.text_lives = pyglet.text.Label('Lives=' + str(self.lives), font_name='Times New Roman', font_size=36, x=10, y=10) self.text_score = pyglet.text.Label('Score=' + str(self.score), font_name='Times New Roman', font_size=36, x=10, y=60) def start_game(self): #Function to start game on initial mouse click self.splash.delete() self.started = True def accel(self): self.ship.thrusters = True def decel(self): self.ship.thrusters = False def left(self): self.ship.angle_vel -= 5 def right(self): self.ship.angle_vel += 5 def fire(self): self.ship.shoot(self.missile_group) def on_mouse_press(self, x, y, button, modifiers): self.start_game() def on_key_press(self, symbol, modifiers): for key in self.key_downs: if key == symbol: self.key_downs[symbol]() def on_key_release(self, symbol, modifiers): for key in self.key_ups: if key == symbol: self.key_ups[symbol]() def put_rock(self): rock_position = utils.random_position(WIDTH, HEIGHT) rock = MovingSprite (self.rock_img, rock_position[0], rock_position[1], sound=self.explosion_snd, diff=self.difficulty, radius=40, batch=self.rocks) self.rock_group.add(rock) def trigger_put_rock(self): self.rock_trigger += 1 if self.rock_trigger > 60 and len(self.rock_group) < 10: self.put_rock() self.rock_trigger = 0 # TODO Implement Sheilds def on_draw(self): self.clear() self.background.blit(0, 0) self.ships.draw() self.rocks.draw() self.missiles.draw() self.text_lives.draw() self.text_score.draw() if not self.started: self.splash = Sprite(self.splash_img, 200, 125) self.splash.draw() def update(self, dt): if self.started: self.trigger_put_rock() for rock in self.rock_group: rock.update(WIDTH, HEIGHT) self.ship.update(WIDTH, HEIGHT) local_missiles = set(self.missile_group) for missile in local_missiles: if not missile.update(WIDTH, HEIGHT): self.missile_group.remove(missile) missile.delete() if group_collide(self.rock_group, self.ship): self.lives -= 1 self.explosion_snd.play() if group_group_collide(self.missile_group, self.rock_group): self.score += 10 self.difficulty += self.score + 50 self.explosion_snd.play() self.display_score() if self.lives < 0: self.game_reset()
class Window(pyglet.window.Window): """This class renders the game data within a pyglet window.""" def __init__(self, my_controller, size): super(Window, self).__init__(700, 700, fullscreen=False, caption='') # Link the view to the controller self.controller = my_controller # Gamplay information passed by the controller self.data = { 'size' : size, 'stones' : [[None for x in range(size)] for y in range(size)], 'territory': [[None for x in range(size)] for y in range(size)], 'color' : None, 'game_over': False, 'score' : [0, 20] } # Set default background color pyglet.gl.glClearColor(0.5,0.5,0.5,1) # Load background image and stones self.image_background = BACKGROUND self.image_black_stone = BLACK_STONE self.image_white_stone = WHITE_STONE # Center black and white stones self.init_resources() # Initialize the display self.init_display() def init_resources(self): """Center black and white stones for proper visualization Attributes updated by this function: self.image_black_stone self.image_white_stone """ def center_image(image): """Set an image's anchor point to its center Arguments: image (pyglet.resource.image) - image to center Attributes updated by this function: image """ image.anchor_x = image.width/2 image.anchor_y = image.height/2 center_image(self.image_black_stone) center_image(self.image_white_stone) def init_display(self): """Gather all graphical elements together and draw them simutaneously. Attributes updated by this function: self.batch self.grp_back self.grp_grid self.grp_label self.grp_stones self.grp_territory self.background self.grid self.info self.score_black self.black_label_stone self.score_white self.white_label_stone self.player_color self.current_player_stone self.button_pass self.button_newgame """ # Creating a batch to display all graphics self.batch = pyglet.graphics.Batch() # Graphic groups (groups of lower index get drawn first) self.grp_back = pyglet.graphics.OrderedGroup(0) self.grp_grid = pyglet.graphics.OrderedGroup(1) self.grp_label = pyglet.graphics.OrderedGroup(2) self.grp_stones = pyglet.graphics.OrderedGroup(3) self.grp_territory = pyglet.graphics.OrderedGroup(4) # Initially load all graphic groups. self.init_back() self.init_grid() self.init_label() def init_back(self): """Load the background. Attributes updated by this function: self.background """ # Display background image self.background = Sprite(self.image_background, batch=self.batch, group=self.grp_back) def init_grid(self): """Load the grid. Attributes updated by this function: self.grid """ # Display grid self.grid = Grid(x=self.width/2, y=self.height/2, width=self.width*MAX_GRID_SIZE, height=self.height*MAX_GRID_SIZE, batch=self.batch, group=self.grp_grid, n=self.data['size']) def init_label(self): """Load all labels and buttons. Attributes updated by this function: self.info self.score_black self.black_label_stone self.score_white self.white_label_stone self.player_color self.current_player_stone self.button_pass self.button_newgame """ # Game Information Display label_y = 670 # y position of scores and next turn labels label_font_size = 12 label_text_color = (0, 0, 0, 255) # Controller-Info Panel # The Text of this label is directly changed inside the controller self.info = Label(x=10, y=10, text="Let's start!", color=label_text_color, font_size=label_font_size, batch=self.batch, group=self.grp_label) # Score-Label Label(x=10, y=label_y, text='Score:', color=label_text_color, font_size=label_font_size, bold=True, batch=self.batch, group=self.grp_label) # SCORES BLACK PLAYER self.score_black = Label(x=100, y=label_y, text=str(self.data['score'][1]), color=label_text_color, font_size=label_font_size, batch=self.batch, group=self.grp_label) self.black_label_stone = Sprite(self.image_black_stone, batch=self.batch, group=self.grp_label, x=0, y=0) self.black_label_stone.scale = LITTLE_STONE_SIZE self.black_label_stone.set_position(80, label_y + self.black_label_stone.height/4) # SCORES WHITE PLAYER self.score_white = Label(x=170, y=label_y, text=str(self.data['score'][0]), color=label_text_color, font_size=label_font_size, batch=self.batch, group=self.grp_label) self.white_label_stone = Sprite(self.image_white_stone, batch=self.batch, group=self.grp_label, x=0, y=0) self.white_label_stone.scale = LITTLE_STONE_SIZE self.white_label_stone.set_position(150, label_y + self.white_label_stone.height/4) # CURRENT PLAYER STONE self.player_color = Label(x=550, y=label_y, text="Your color: ", color=label_text_color, font_size=label_font_size, bold=True, batch=self.batch, group=self.grp_label) # INITIAL PLAYER STONE self.current_player_stone = Sprite(self.image_black_stone, batch=self.batch, group=self.grp_label, x=0, y=0) self.current_player_stone.scale = LITTLE_STONE_SIZE self.current_player_stone.set_position(660, label_y + self.current_player_stone.height/4) # Game Buttons # Button that can be pressed to pass on current round self.button_pass = Button(pos=(600,40), text='Pass', batch=self.batch) # New-Game Button self.button_newgame = Button(pos=(480,40), text='New Game') def update(self, *args): """This function does all the calculations when the data gets updated. For other games that require permanent simulations you would add the following line of code at the end of __init__(): pyglet.clock.schedule_interval(self.update, 1/30) Attributes updated by this function: self.batch_stones self.stone_sprites self.image_black_stone self.image_white_stone self.batch_territory self.score_black self.score_white self.current_player_stone """ # Game Information Updates # Scores of each player self.update_stones() self.update_territories() self.update_scores() self.update_current_player() # If the new size in the data is different than the current size if self.data['size'] != self.grid.size: self.init_display() def update_stones(self): """Update the black and white stones on the game board. Attributes updated by this function: self.batch_stones self.stone_sprites self.image_black_stone self.image_white_stone """ # Display the stones on the regular batch self.batch_stones = self.batch self.stone_sprites = [] # Center black and white stones def center_image(image): """Sets an image's anchor point to its center""" image.anchor_x = image.width/2 image.anchor_y = image.height/2 center_image(self.image_black_stone) center_image(self.image_white_stone) # Place all stones on the grid # Scale stone images scaling = self.grid.field_width / self.image_black_stone.width # Limit max size of stones if scaling > MAX_STONE_SCALING: scaling = MAX_STONE_SCALING # Iterate trough all data stones and place the corresponding black or # white stone on the grid for i in range(0, self.data['size']): for j in range(0, self.data['size']): if self.data['stones'][j][i] != None: # Get x and y grid coordinates x_coord, y_coord = self.grid.get_coords(i, j) # Get the stone color to place stone_color = self.image_black_stone if self.data['stones'][j][i] == BLACK else None stone_color = self.image_white_stone if self.data['stones'][j][i] == WHITE else stone_color # Place the stone on the grid if stone_color: _s = Sprite(stone_color, batch=self.batch_stones, group=self.grp_stones, x=x_coord, y=y_coord) _s.scale = scaling self.stone_sprites.append(_s) def update_territories(self): """Update the black and white territories on the board. Attributes updated by this function: self.batch_territory """ # Display the territory an the regular batch # Display the stones on the regular batch self.batch_territory = self.batch rad = 5 # Iterate trough all territory indicators and place the corresponding # black or white circle on the grid or above stones for i in range(0, self.data['size']): for j in range(0, self.data['size']): if self.data['territory'][j][i] != None: x_coord, y_coord = self.grid.get_coords(i, j) if self.data['territory'][j][i] == BLACK: Circle(x_coord, y_coord, color=BLACK_TERRITORY, r=rad, batch=self.batch_territory, group=self.grp_territory) elif self.data['territory'][j][i] == WHITE: Circle(x_coord, y_coord, color=WHITE_TERRITORY, r=rad, batch=self.batch_territory, group=self.grp_territory) def update_scores(self): """Update scores for BLACK and WHITE. Attributes updated by this function: self.score_black self.score_white """ self.score_black.text = str(self.data['score'][1]) self.score_white.text = str(self.data['score'][0]) def update_current_player(self): """Update stone of current player. Attributes updated by this function: self.current_player_stone """ # Remve the last current player stone self.current_player_stone.delete() # If its the BLACK players turn if self.data['color']: self.current_player_stone = Sprite(self.image_black_stone, batch=self.batch, group=self.grp_label, x=0, y=0) self.current_player_stone.scale = LITTLE_STONE_SIZE self.current_player_stone.set_position(660, 670 + self.current_player_stone.height/4) # If its the WHITE players turn else: self.current_player_stone = Sprite(self.image_white_stone, batch=self.batch, group=self.grp_label, x=0, y=0) self.current_player_stone.scale = LITTLE_STONE_SIZE self.current_player_stone.set_position(660, 670 + self.current_player_stone.height/4) def on_draw(self): """Draw the interface. Attributes updated by this function: self self.batch self.button_newgame """ # Clear out old graphics self.clear() # Drawing the batch (which does not contain any graphics yet) self.batch.draw() # Check if Game is over, if True, draw the New Game Button if(self.data['game_over']): self.button_newgame.draw() def on_mouse_press(self, mousex, mousey, button, modifiers): """Function called on any mouse button press. Arguments: mousex : x-coord of the click mousey : y-coord of the click button : modifiers : The buttons are saved as constants in pyglet.window.mouse, the modifiers under pyglet.window.key E.g. >>> if button == pyglet.window.mouse.LEFT: pass Look at the documentation for more information: >>> import pyglet >>> help(pyglet.window.mouse) """ # Check for clicks on New Game Button only when game is Over if(self.data['game_over']): if (mousex, mousey) in self.button_newgame: self.controller.new_game() if button == pyglet.window.mouse.LEFT: # Mark territory if the game is over pos = self.grid.get_indices(mousex, mousey) if pos != None: self.controller.mark_territory(pos) # Handle clicks during game # Check if pass-button was pressed if (mousex, mousey) in self.button_pass: self.controller.passing() # Grid position clicked (only if above buttons) elif button == pyglet.window.mouse.LEFT and mousey > 60: # Place a stone at clicked position pos = self.grid.get_indices(mousex, mousey) if pos != None: self.controller.play(pos) def on_key_press(self, symbol, modifiers): """Function that gets called on any key press (keyboard). Arguments: symbol : symbol that was pressed modifiers : modifiers (e.g. l-shift, r-shift, ctrl, ...) You can compare symbol/modifiers to the constants defined in pyglet.window.key: E.g. >>> if symbol == pyglet.window.key.A: pass For more information look on the help page: >>> import pyglet >>> help(pyglet.window.key) """ pass def receive_data(self, data): """Receive data from the controller and update view. Attributes updated by this function: self.data """ self.data.update(data) self.update() def new_game(self, data): """Receive data from the controller and start a new game. Attributes updated by this function: self.data """ # Initialize the display self.data.update(data) self.init_display() self.update()
class Sprite2D(fixture2d.Fixture2D): def __init__(self, img, x, y, batch=default_batch, group=None): self._sprite = Sprite(img, x=x, y=y, batch=batch, group=group) super().__init__(x, y) def update(self, dt): super().update(dt) self._sprite.update(x=self._pos.x, y=self._pos.y, rotation=self._rot, scale_x=self._sx, scale_y=self._sy) def delete(self): super().delete() self._sprite.delete() def _create_physical_body(self): return pymunk.Body(self._mass, self._inertia, self._body_type) def _create_physical_shape(self): return pymunk.Poly(self._body, [(-self.width / 2, self.height / 2), (self.width / 2, self.height / 2), (self.width / 2, -self.height / 2), (-self.width / 2, -self.height / 2)]) def _get_inertia_for_shape(self): return pymunk.moment_for_poly(self._mass, [(-self.width / 2, self.height / 2), (self.width / 2, self.height / 2), (self.width / 2, -self.height / 2), (-self.width / 2, -self.height / 2)]) def _get_bounding_box(self): return AABB.computeAABB(self.x, self.y, self.width, self.height, self._rot) @property def width(self): return self._sprite.width @property def height(self): return self._sprite.height @property def batch(self): return self._sprite.batch @batch.setter def batch(self, value): self._sprite.batch = value @property def image(self): return self._sprite.image @image.setter def image(self, value): self._sprite.image = value @property def group(self): return self._sprite.group @group.setter def group(self, value): self._sprite.group = value @property def opacity(self): return self._sprite.opacity @opacity.setter def opacity(self, value): self._sprite.opacity = value @property def color(self): return self._sprite.color @color.setter def color(self, value): self._sprite.color = value @property def visible(self): return self._sprite.visible @visible.setter def visible(self, value): self._sprite.visible = value