class Background(pygame.sprite.Sprite): containers = None # pygame group image = None # surface to display (can be a list of Surface or a single pygame.Surface) def __init__(self, vector_: pygame.math.Vector2, # background speed vector (pygame.math.Vector2) position_: pygame.math.Vector2, # original position (tuple) gl_, # global variables (GL class) layer_: int = -8, # layer used default is -8 (int <= 0) blend_: int = 0, # pygame blend effect (e.g pygame.BLEND_RGB_ADD, or int) event_name_: str = '', # event name (str) timing_=0 ): self.layer = layer_ pygame.sprite.Sprite.__init__(self, self.containers) # change sprite layer if isinstance(gl_.All, pygame.sprite.LayeredUpdates): gl_.All.change_layer(self, layer_) self.images_copy = Background.image.copy() self.image = self.images_copy[0] if isinstance(Background.image, list) else self.images_copy self.rect = self.image.get_rect(topleft=position_) self.position = position_ self.vector = vector_ self.gl = gl_ self.blend = blend_ self.event_name = event_name_ self.id_ = id(self) self.rotation = 0 self.timing = timing_ self.dt = 0 if self.event_name == 'STATION': self.rotation = 0 self.background_object = Broadcast(self.make_rotation_object()) else: self.background_object = Broadcast(self.make_object()) def make_object(self) -> StaticSprite: return StaticSprite(frame_=self.gl.FRAME, id_=self.id_, surface_=self.event_name, layer_=self.layer, blend_=self.blend, rect_=self.rect) def make_rotation_object(self) -> RotateSprite: return RotateSprite(frame_=self.gl.FRAME, id_=self.id_, surface_=self.event_name, layer_=self.layer, blend_=self.blend, rect_=self.rect, rotation_=self.rotation) def process(self): if self.event_name == 'CL1': self.rect.move_ip(self.vector) if self.rect.y > 1023: self.rect.y = randint(-1024, - CL1.get_height()) self.rect.x = randint(-400, 400) self.background_object.update({'frame': self.gl.FRAME, 'rect': self.rect}) self.background_object.queue() elif self.event_name == 'CL2': self.rect.move_ip(self.vector) if self.rect.y > 1023: self.rect.y = randint(-1024, - CL2.get_height()) self.rect.x = randint(-400, 400) self.background_object.update({'frame': self.gl.FRAME, 'rect': self.rect}) self.background_object.queue() elif self.event_name == 'BLUE_PLANET': self.rect.move_ip(self.vector) if self.rect.y > 1023: self.rect.y = randint(-1024, - BLUE_PLANET.get_height()) self.rect.x = randint(-400, 400) self.background_object.update({'frame': self.gl.FRAME, 'rect': self.rect}) self.background_object.queue() elif self.event_name == 'STATION': # below 8192 frames the station is not on sight if self.gl.FRAME < 12280: self.rect.move_ip(self.vector) # no need to rotate station if not on sight if self.rect.colliderect(self.gl.SCREENRECT): centre = self.rect.center self.image = pygame.transform.rotate(self.images_copy.copy(), self.rotation) self.rect = self.image.get_rect(center=centre) self.background_object.update({'frame': self.gl.FRAME, 'rect': self.rect, 'rotation': self.rotation}) self.background_object.queue() self.rotation += 0.2 else: if self.event_name in ('BACK1_S', 'BACK2_S'): self.rect.move_ip(self.vector) if self.rect.y > 1023: self.rect.y = -1024 self.background_object.update({'frame': self.gl.FRAME, 'rect': self.rect}) self.background_object.queue() elif self.event_name == 'BACK3': if self.gl.FRAME < 12288: self.rect.move_ip(self.vector) # if self.gl.FRAME > 12288: # self.rect.y = 0 self.background_object.update({'frame': self.gl.FRAME, 'rect': self.rect}) self.background_object.queue() # Any other background type else: self.rect.move_ip(self.vector) if self.rect.y > 1023: self.rect.y = -1024 self.rect.x = 0 self.background_object.update({'frame': self.gl.FRAME, 'rect': self.rect}) self.background_object.queue() def update(self): if self.timing != 0: # update frequently if self.dt > self.timing: self.process() self.dt = 0 else: self.dt += self.gl.TIME_PASSED_SECONDS # update every frames else: self.process()
class AfterBurner(pygame.sprite.Sprite): containers = None images = None def __init__(self, parent_, gl_, offset_: tuple, timing_: int = 8, blend_: int = 0, layer_: int = 0, texture_name_='EXHAUST'): """ Create an exhaust effect for the player's :param parent_: Player's instance (MirroredPlayer1Class or MirroredPlayer2Class) :param gl_: Class GL (contains all the game constants :param offset_: tuple, offset location of the afterburner sprite (offset from the center) :param timing_: integer; Sprite refreshing time must be > 0 :param blend_: integer; Sprite blending effect, must be > 0 or any of the following BLEND_RGBA_ADD, BLEND_RGBA_SUB, BLEND_RGBA_MULT, BLEND_RGBA_MIN, BLEND_RGBA_MAX BLEND_RGB_ADD, BLEND_RGB_SUB, BLEND_RGB_MULT, BLEND_RGB_MIN, BLEND_RGB_MAX :param layer_: integer; must be <= 0 (0 is the top layer) :param texture_name_: string corresponding to the texture used. """ if parent_ is None: raise ValueError('Positional argument <parent_> cannot be None.') if gl_ is None: raise ValueError('Positional argument <gl_> cannot be None.') if offset_ is None: raise ValueError('Positional argument <offset_> cannot be None.') assert isinstance(offset_, tuple), \ "Positional argument <offset_> is type %s , expecting tuple." % type(offset_) assert isinstance(timing_, int), \ "Positional argument <timing_> is type %s , expecting integer." % type(timing_) assert isinstance(blend_, int), \ "Positional argument <blend_> is type %s , expecting integer." % type(blend_) assert isinstance(layer_, int), \ "Positional argument <layer_> is type %s , expecting integer." % type(layer_) assert isinstance(texture_name_, str), \ "Positional argument <texture_name_> is type %s , expecting str." % type(texture_name_) if self.containers is None: raise ValueError( 'AfterBurner.containers is not initialised.\nMake sure to assign the containers to' ' a pygame group prior instantiation.\ne.g: AfterBurner.containers = ' 'pygame.sprite.Group()') if self.images is None: raise ValueError( "AfterBurner.images is not initialised.\nMake sure to assign a texture to " "prior instantiation.\ne.g: AfterBurner.images = 'EXHAUST'") if timing_ < 0: raise ValueError('Positional argument timing_ cannot be < 0') self.layer = layer_ pygame.sprite.Sprite.__init__(self, self.containers) if isinstance(gl_.All, pygame.sprite.LayeredUpdates): if layer_: gl_.All.change_layer(self, layer_) self.images = AfterBurner.images self.image = self.images[0] if isinstance(self.images, list) else self.images self.parent = parent_ self.offset = offset_ x, y = self.parent.rect.centerx + self.offset[ 0], self.parent.rect.centery + self.offset[1] self.rect = self.image.get_rect(center=(x, y)) self.timing = timing_ self.dt = 0 self.index = 0 self.gl = gl_ self.blend = blend_ self.texture_name = texture_name_ self.id_ = id(self) self.afterburner_object = Broadcast(self.make_object()) Broadcast.add_object_id(self.id_) def delete_object(self) -> DeleteSpriteCommand: """ Send a command to kill an object on client side. :return: DetectCollisionSprite object """ return DeleteSpriteCommand(frame_=self.gl.FRAME, to_delete_={self.id_: self.texture_name}) def make_object(self) -> AnimatedSprite: """ Create a network object (AnimatedSprite) :return: AnimatedSprite """ return AnimatedSprite(frame_=self.gl.FRAME, id_=self.id_, surface_=self.texture_name, layer_=self.layer, blend_=self.blend, rect_=self.rect, index_=self.index) def quit(self) -> None: Broadcast.remove_object_id(self.id_) obj = Broadcast(self.delete_object()) obj.queue() self.kill() def update(self) -> None: """ Update the sprite. :return: None """ if self.dt > self.timing: # checking if MirroredPlayer1Class is still alive if self.parent.alive(): # display animation if self.images is a list. if isinstance(self.images, list): self.image = self.images[self.index % len(self.images) - 1] x, y = self.parent.rect.centerx + self.offset[ 0], self.parent.rect.centery + self.offset[1] self.rect.center = (x, y) if self.rect.colliderect(self.gl.SCREENRECT): self.afterburner_object.update({ 'frame': self.gl.FRAME, 'rect': self.rect, 'index': self.index }) self.afterburner_object.queue() self.dt = 0 self.index += 1 else: self.quit() return else: self.dt += self.gl.TIME_PASSED_SECONDS
class ShootingStar(pygame.sprite.Sprite): image = None # sprite surface (single surface) containers = None # sprite group to use def __init__( self, gl_, # global variables layer_=-4, # layer where the shooting sprite will be display timing_=16, # refreshing rate, default is 16ms (60 fps) surface_name_=''): self.layer = layer_ pygame.sprite.Sprite.__init__(self, self.containers) # change sprite layer if isinstance(gl_.All, pygame.sprite.LayeredUpdates): gl_.All.change_layer(self, layer_) self.images_copy = ShootingStar.image.copy() self.image = self.images_copy[0] if isinstance( ShootingStar.image, list) else self.images_copy self.w, self.h = pygame.display.get_surface().get_size() self.position = pygame.math.Vector2(randint(0, self.w), randint(-self.h, 0)) self.rect = self.image.get_rect(midbottom=self.position) self.speed = pygame.math.Vector2(uniform(-30, 30), 60) self.rotation = -270 - int(degrees(atan2(self.speed.y, self.speed.x))) self.image = pygame.transform.rotozoom(self.image, self.rotation, 1) self.blend = pygame.BLEND_RGB_ADD self.timing = timing_ self.gl = gl_ self.dt = 0 self.surface_name = surface_name_ self.id_ = id(self) self.shooting_star_object = Broadcast(self.make_object()) Broadcast.add_object_id(self.id_) def delete_object(self) -> DeleteSpriteCommand: """ Send a command to kill an object on client side. :return: DetectCollisionSprite object """ return DeleteSpriteCommand(frame_=self.gl.FRAME, to_delete_={self.id_: self.surface_name}) def make_object(self) -> RotateSprite: return RotateSprite(frame_=self.gl.FRAME, id_=self.id_, surface_=self.surface_name, layer_=self.layer, blend_=self.blend, rect_=self.rect, rotation_=self.rotation) def quit(self) -> None: Broadcast.remove_object_id(self.id_) obj = Broadcast(self.delete_object()) obj.queue() self.kill() def update(self): if self.dt > self.timing: if self.rect.centery > self.h: self.quit() return self.rect = self.image.get_rect(center=self.position) self.position += self.speed if self.rect.colliderect(self.gl.SCREENRECT): self.shooting_star_object.update({ 'frame': self.gl.FRAME, 'rect': self.rect, 'rotation': self.rotation }) self.shooting_star_object.queue() self.dt = 0 else: self.dt += self.gl.TIME_PASSED_SECONDS
class Explosion(pygame.sprite.Sprite): images = None containers = None def __init__(self, parent_, pos_, gl_, timing_, layer_, texture_name_, mute_=False): self.layer = layer_ pygame.sprite.Sprite.__init__(self, self.containers) if isinstance(gl_.All, pygame.sprite.LayeredUpdates): if layer_: gl_.All.change_layer(self, layer_) self.images_copy = Explosion.images.copy() self.image = self.images_copy[0] if isinstance( self.images_copy, list) else self.images_copy self.timing = timing_ self.length = len(self.images) - 1 self.pos = pos_ self.gl = gl_ self.position = pygame.math.Vector2(*self.pos) self.rect = self.image.get_rect(center=self.pos) self.dt = 0 self.blend = pygame.BLEND_RGB_ADD self.parent = parent_ self.index = 0 self.id_ = id(self) self.texture_name = texture_name_ self.mute = mute_ # Create the network object self.explosion_object = Broadcast(self.make_object()) # Create sound object self.explosion_sound_object = Broadcast( self.make_sound_object('EXPLOSION_SOUND')) Broadcast.add_object_id(self.id_) def delete_object(self) -> DeleteSpriteCommand: """ Send a command to kill an object on client side. :return: DetectCollisionSprite object """ return DeleteSpriteCommand(frame_=self.gl.FRAME, to_delete_={self.id_: self.texture_name}) def play_explosion_sound(self) -> None: """ Play the sound explosion locally and forward the sound object to the client(s). :return: None """ # play the sound locally self.gl.MIXER.play(sound_=EXPLOSION_SOUND, loop_=False, priority_=0, volume_=1.0, fade_out_ms=0, panning_=True, name_='EXPLOSION_SOUND', x_=self.rect.centerx, object_id_=id(EXPLOSION_SOUND), screenrect_=self.gl.SCREENRECT) # Add the sound object to the queue self.explosion_sound_object.play() def make_sound_object(self, sound_name_: str) -> SoundAttr: """ Create a network sound object :param sound_name_: string; represent the sound name e.g 'EXPLOSION_SOUND" :return: SoundAttr object """ assert isinstance(sound_name_, str), \ "Positional argument <sound_name_> is type %s , expecting string." % type(sound_name_) if sound_name_ not in globals(): raise NameError('Sound %s is not define.' % sound_name_) return SoundAttr(frame_=self.gl.FRAME, id_=self.id_, sound_name_=sound_name_, rect_=self.rect) def make_object(self) -> AnimatedSprite: return AnimatedSprite(frame_=self.gl.FRAME, id_=self.id_, surface_=self.texture_name, layer_=self.layer, blend_=self.blend, rect_=self.rect, index_=self.index) def quit(self) -> None: Broadcast.remove_object_id(self.id_) obj = Broadcast(self.delete_object()) obj.queue() self.kill() def update(self): if self.dt > self.timing: if self.rect.colliderect(self.gl.SCREENRECT): if self.index == 0 and not self.mute: self.play_explosion_sound() self.image = self.images_copy[self.index] self.rect = self.image.get_rect(center=self.rect.center) self.index += 1 if self.index > self.length: self.quit() return self.dt = 0 self.explosion_object.update({ 'frame': self.gl.FRAME, 'rect': self.rect, 'index': self.index, 'blend': self.blend }) self.explosion_object.queue() else: self.quit() return else: self.dt += self.gl.TIME_PASSED_SECONDS
class Debris(pygame.sprite.Sprite): containers = None image = None def __init__(self, asteroid_name_: str, pos_: tuple, gl_: GL, blend_: int = 0, timing_: int = 16, layer_: int = -2, particles_: bool = True): """ Create debris after asteroid explosion or collision :param asteroid_name_: string; Parent name (not used) :param pos_: tuple, representing the impact position (x, y) :param gl_: class GL, global constants :param blend_: integer; Sprite blend effect, must be > 0 or any of the following BLEND_RGBA_ADD, BLEND_RGBA_SUB, BLEND_RGBA_MULT, BLEND_RGBA_MIN, BLEND_RGBA_MAX BLEND_RGB_ADD, BLEND_RGB_SUB, BLEND_RGB_MULT, BLEND_RGB_MIN, BLEND_RGB_MAX :param timing_: integer; Sprite refreshing time in milliseconds, must be >=0 :param layer_: integer; Sprite layer must be <=0 (0 is the top layer) :param particles_: bool; Particles effect after asteroid desintegration """ assert isinstance(asteroid_name_, str), \ "Positional argument <asteroid_name_> is type %s , expecting string." % type(asteroid_name_) assert isinstance(pos_, tuple), \ "Positional argument <pos_> is type %s , expecting tuple." % type(pos_) assert isinstance(timing_, int), \ "Positional argument <timing_> is type %s , expecting integer." % type(timing_) assert isinstance(blend_, int), \ "Positional argument <blend_> is type %s , expecting integer." % type(blend_) assert isinstance(layer_, int), \ "Positional argument <layer_> is type %s , expecting integer." % type(layer_) assert isinstance(particles_, bool), \ "Positional argument <particles_> is type %s , expecting boolean." % type(particles_) if self.containers is None: raise ValueError( 'Debris.containers is not initialised.\nMake sure to assign the containers to' ' a pygame group prior instantiation.\ne.g: Debris.containers = ' 'pygame.sprite.Group()') if self.image is None: raise ValueError( "Debris.image is not initialised.\nMake sure to assign a texture to " "prior instantiation.\ne.g: Debris.image = 'CHOOSE_YOUR_TEXTURE'" ) if timing_ < 0: raise ValueError('Positional argument timing_ cannot be < 0') if blend_ < 0: raise ValueError('Positional argument blend_ cannot be < 0') if layer_ > 0: raise ValueError('Positional argument layer_ cannot be > 0') self.layer = layer_ pygame.sprite.Sprite.__init__(self, self.containers) # change sprite layer if isinstance(gl_.All, pygame.sprite.LayeredUpdates): gl_.All.change_layer(self, layer_) self.image = Debris.image self.rect = self.image.get_rect(center=pos_) self.speed = pygame.math.Vector2(uniform(-10, +10), uniform(-8, +8)) self.damage = randint(5, 15) self.timing = timing_ self.dt = 0 self.fxdt = 0 self.gl = gl_ self.asteroid_name = asteroid_name_ # not used self.blend = blend_ self.layer = layer_ self.life = self.damage self.points = self.life self.rotation = 0 self.scale = 1.0 self.id_ = id(self) self.asteroid_object = Broadcast(self.make_object()) self.vertex_array = [] # todo create instances in the vertex_array (declare in global) # before instanciating debris. # make a copy of the vertex_array and goes through the list changing arguments: # position_, vector_. Assign the list to self.vertex_array if particles_: angle = math.radians(uniform(0, 359)) self.asteroid_particles_fx(position_=pygame.math.Vector2( self.rect.center), vector_=pygame.math.Vector2( math.cos(angle) * randint(5, 10), math.sin(angle) * randint(5, 10)), images_=FIRE_PARTICLES.copy(), layer_=self.layer, blend_=pygame.BLEND_RGB_ADD) Broadcast.add_object_id(self.id_) def delete_object(self) -> DeleteSpriteCommand: """ Send a command to kill an object on client side. :return: DetectCollisionSprite object """ return DeleteSpriteCommand(frame_=self.gl.FRAME, to_delete_={self.id_: self.asteroid_name}) def make_object(self) -> DetectCollisionSprite: """ Create a network sprite object. :return: DetectCollisionSprite object """ return DetectCollisionSprite(frame_=self.gl.FRAME, id_=self.id_, surface_=self.asteroid_name, layer_=self.layer, blend_=self.blend, rect_=self.rect, rotation_=self.rotation, scale_=self.scale, damage_=self.damage, life_=self.life, points_=self.points) def remove_particle(self, p_) -> None: """ Remove the sprite from the group it belongs to and remove the object from the vertex_array :param p_: pygame.sprite.Sprite :return: None """ p_.kill() if p_ in self.vertex_array: self.vertex_array.remove(p_) def display_asteroid_particles_fx(self) -> None: # Display asteroid tail debris. if self.fxdt > self.timing - 8: # 8 ms for p_ in self.vertex_array: p_.image = p_.images[p_.index % p_.length] # load the next surface p_.rect.move_ip(p_.vector) # Move the particle p_.vector *= 0.9999 # particle deceleration / attenuation rect_centre = p_.rect.center # rect centre after deceleration next_image = p_.images[(p_.index + 1) % p_.length] # Load the next image # Decrease image dimensions (re-scale) try: particle_size = pygame.math.Vector2( p_.w - p_.index * 4, p_.h - p_.index * 4) # transform next image (re-scale) p_.images[(p_.index + 1) % p_.length] = \ pygame.transform.scale(next_image, (int(particle_size.x), int(particle_size.y))) # Redefine the rectangle after transformation to avoid a collapsing movement # to the right p_.rect = p_.images[(p_.index + 1) % p_.length].get_rect(center=rect_centre) # delete the particle before exception if particle_size.length() < 25: self.remove_particle(p_) except (ValueError, IndexError): self.remove_particle(p_) if not p_.rect.colliderect( self.gl.SCREENRECT) or p_.vector.length() < 1: self.remove_particle(p_) p_.index += 1 self.fxdt = 0 else: self.fxdt += self.gl.TIME_PASSED_SECONDS def asteroid_particles_fx( self, position_, # particle starting location (tuple or pygame.math.Vector2) vector_, # particle speed, pygame.math.Vector2 images_, # surface used for the particle, (list of pygame.Surface) layer_=0, # Layer used to display the particles (int) blend_=pygame.BLEND_RGB_ADD # Blend mode (int) ) -> None: """ Create bright debris after explosion or asteroid collision """ # Cap the number of particles to avoid lag # if len(self.gl.FIRE_PARTICLES_FX) > 100: # return # Create fire particles when the aircraft is disintegrating sprite_ = pygame.sprite.Sprite() self.gl.All.add(sprite_) # self.gl.FIRE_PARTICLES_FX.add(sprite__) # assign the particle to a specific layer if isinstance(self.gl.All, pygame.sprite.LayeredUpdates): self.gl.All.change_layer(sprite_, layer_) sprite_.layer = layer_ sprite_.blend = blend_ # use the additive mode sprite_.images = images_ sprite_.image = images_[0] sprite_.rect = sprite_.image.get_rect(center=position_) sprite_.vector = vector_ # vector sprite_.w, sprite_.h = sprite_.image.get_size() sprite_.index = 0 sprite_.length = len(sprite_.images) - 1 # assign update method to self.display_fire_particle_fx # (local method to display the particles) sprite_.update = self.display_asteroid_particles_fx self.vertex_array.append(sprite_) def collide(self, player_=None, damage_: int = 0) -> None: """ :return: """ self.quit() ... def hit(self, player_=None, damage_: int = 0) -> None: """ Check asteroid life after laser collision. :param player_: Player instance :param damage_: integer; Damage received :return: None """ self.quit() ... def quit(self) -> None: Broadcast.remove_object_id(self.id_) obj = self.delete_object() broadcast_object = Broadcast(obj) broadcast_object.queue() self.kill() ... def update(self) -> None: """ Update debris sprite :return: """ # Inside the 60FPS Area if self.dt > self.timing: if self.rect.colliderect(self.gl.SCREENRECT): self.rect.move_ip(self.speed) else: self.quit() return self.asteroid_object.update({ 'frame': self.gl.FRAME, 'id_': self.id_, 'rect': self.rect, 'life': self.life }) self.asteroid_object.queue() self.dt = 0 else: self.dt += self.gl.TIME_PASSED_SECONDS
class Asteroid(pygame.sprite.Sprite): containers = None image = None def __init__(self, asteroid_name_: str, gl_, blend_: int = 0, rotation_: int = 0, scale_: int = 1, timing_: int = 8, layer_: int = 0): """ :param asteroid_name_: strings, MirroredAsteroidClass name :param gl_: class GL (contains all the game constant) :param blend_: integer, Sprite blend effect, must be > 0 :param rotation_: integer, Object rotation in degrees :param scale_: integer, Object scale value, default 1 -> no transformation. must be > 0 :param timing_: integer; Refreshing time in milliseconds, must be > 0 :param layer_: integer; Layer used. must be <= 0 (0 is the top layer) """ """ assert isinstance(asteroid_name_, str), \ "Positional argument <asteroid_name_> is type %s , expecting string." % type(asteroid_name_) assert isinstance(blend_, int), \ "Positional argument <blend_> is type %s , expecting integer." % type(blend_) if blend_ < 0: raise ValueError('Positional attribute blend_ must be > 0') assert isinstance(rotation_, (float, int)), \ "Positional argument <rotation_> is type %s , expecting float or integer." % type(rotation_) assert isinstance(scale_, (float, int)), \ "Positional argument <scale_> is type %s , expecting float or integer." % type(scale_) if scale_ < 0: raise ValueError('Positional attribute scale_ must be > 0') assert isinstance(timing_, int), \ "Positional argument <timing_> is type %s , expecting integer." % type(timing_) if timing_ < 0: raise ValueError('Positional attribute timing_ must be >= 0') assert isinstance(layer_, int), \ "Positional argument <layer_> is type %s , expecting integer." % type(layer_) if layer_ > 0: raise ValueError('Positional attribute layer_ must be <= 0') """ self.layer = layer_ pygame.sprite.Sprite.__init__(self, self.containers) # change sprite layer if isinstance(gl_.All, pygame.sprite.LayeredUpdates): gl_.All.change_layer(self, layer_) self.images_copy = Asteroid.image.copy() self.image = self.images_copy[0] if isinstance( Asteroid.image, list) else self.images_copy self.w, self.h = pygame.display.get_surface().get_size() self.appearance_frame = 0 # randint(100, 100240) # When the asteroid start moving if asteroid_name_ == 'MAJOR_ASTEROID': self.speed = pygame.math.Vector2(0, 1) self.rect = self.image.get_rect(center=((self.w >> 1), -self.image.get_height())) # MirroredAsteroidClass life points (proportional to its size) self.life = 5000 self.damage = self.life * 2 else: self.rect = self.image.get_rect(center=(randint(0, self.w), -randint(0, self.h) - self.image.get_height())) # self.rect = self.image.get_rect(center=(self.w // 2, - self.image.get_height())) self.speed = pygame.math.Vector2(0, uniform( +4, +8)) # * round(self.appearance_frame / 4000)) if self.speed.length() == 0: self.speed = pygame.math.Vector2(0, 1) # MirroredAsteroidClass life points (proportional to its size) self.life = randint(self.rect.w, max(self.rect.w, (self.rect.w >> 1) * 10)) # Collision damage, how much life point # will be removed from players and transport in case of collision self.damage = self.life >> 1 self.timing = timing_ self.rotation = rotation_ self.scale = scale_ self.dt = 0 self.gl = gl_ self.asteroid_name = asteroid_name_ self.blend = blend_ # No need to pre-calculate the mask as all asteroid instanciation is # done before the main loop self.mask = pygame.mask.from_surface(self.image) # MirroredAsteroidClass value in case of destruction. self.points = self.life self.layer = layer_ self.index = 0 self.has_been_hit = False self.id_ = id(self) self.asteroid_object = Broadcast(self.make_object()) self.impact_sound_object = Broadcast(self.make_sound_object('IMPACT')) Broadcast.add_object_id(self.id_) def delete_object(self) -> DeleteSpriteCommand: """ Send a command to kill an object on client side. :return: DetectCollisionSprite object """ return DeleteSpriteCommand(frame_=self.gl.FRAME, to_delete_={self.id_: self.asteroid_name}) def make_sound_object(self, sound_name_: str) -> SoundAttr: """ Create a network sound object :param sound_name_: string; represent the sound name e.g 'EXPLOSION_SOUND" :return: SoundAttr object """ assert isinstance(sound_name_, str), \ "Positional argument <sound_name_> is type %s , expecting string." % type(sound_name_) if sound_name_ not in globals(): raise NameError('Sound %s is not define.' % sound_name_) return SoundAttr(frame_=self.gl.FRAME, id_=self.id_, sound_name_=sound_name_, rect_=self.rect) def make_object(self) -> DetectCollisionSprite: """ Create a network sprite object :return: DetectCollisionSprite object """ return DetectCollisionSprite(frame_=self.gl.FRAME, id_=self.id_, surface_=self.asteroid_name, layer_=self.layer, blend_=self.blend, rect_=self.rect, rotation_=self.rotation, scale_=self.scale, damage_=self.damage, life_=self.life, points_=self.points) def create_gems(self, player_) -> None: """ Create collectable gems after asteroid disintegration. :param player_: player causing asteroid explosion :return: None """ if player_ is None: raise ValueError("Argument player_ cannot be none!.") if hasattr(player_, 'alive'): if player_.alive(): number = randint(3, 15) for _ in range(number): if _ < number: MakeGems.inventory = set() MakeGems(gl_=self.gl, player_=player_, object_=self, ratio_=1.0, timing_=8, offset_=pygame.Rect(self.rect.centerx, self.rect.centery, randint(-100, 100), randint(-100, 20))) def make_debris(self) -> None: """ Create sprite debris (different sizes 32x32, 64x64 pixels) :return:None """ if not globals().__contains__('MULT_ASTEROID_64'): raise NameError( "Texture MULT_ASTEROID_64 is missing!" "\nCheck file Texture.py for MULT_ASTEROID_64 assigment. ") if not globals().__contains__('MULT_ASTEROID_32'): raise NameError( "Texture MULT_ASTEROID_32 is missing!" "\nCheck file Texture.py for MULT_ASTEROID_32 assigment. ") size_x, size_y = self.image.get_size() if size_x > 128: aster = MULT_ASTEROID_64 name = 'MULT_ASTEROID_64' else: aster = MULT_ASTEROID_32 name = 'MULT_ASTEROID_32' length = len(aster) - 1 if self.asteroid_name != 'MAJOR_ASTEROID': Debris.containers = self.gl.All, self.gl.ASTEROID for _ in range(8 if size_x > 255 else 6): element = randint(0, length) Debris.image = aster[element] Debris(asteroid_name_=name + '[' + str(element) + ']', pos_=self.rect.center, gl_=self.gl, blend_=0, timing_=16, layer_=-2, particles_=True) else: Debris.containers = self.gl.All, self.gl.ASTEROID aster = MULT_ASTEROID_64 name = 'MULT_ASTEROID_64' length = len(aster) - 1 for _ in range(10): element = randint(0, length) Debris.image = aster[element] Debris(asteroid_name_=name + '[' + str(element) + ']', pos_=(self.rect.centerx + randint(-size_x, size_y), self.rect.centery + randint(-size_x, size_y)), gl_=self.gl, blend_=0, timing_=20, layer_=randint(-2, 0), particles_=False) aster = MULT_ASTEROID_32 name = 'MULT_ASTEROID_32' length = len(aster) - 1 for _ in range(10): element = randint(0, length) Debris.image = aster[element] Debris(asteroid_name_=name + '[' + str(element) + ']', pos_=(self.rect.centerx + randint(-size_x, size_y), self.rect.centery + randint(-size_x, size_y)), gl_=self.gl, blend_=0, timing_=8, layer_=randint(-2, 0), particles_=False) def explode(self, player_) -> None: """ Create an explosion sprite when asteroid life points < 1 :param player_: Player causing asteroid explosion :return: None """ if not globals().__contains__('EXPLOSION1'): raise NameError( "Texture EXPLOSION1 is missing!" "\nCheck file Texture.py for EXPLOSION1 assigment. ") # Create queue sprite Explosion.images = EXPLOSION1 Explosion(self, self.rect.center, self.gl, 8, 0, texture_name_='EXPLOSION1') # self.layer) if not globals().__contains__('HALO_SPRITE12'): raise NameError( "Texture HALO_SPRITE12 is missing!" "\nCheck file Texture.py for HALO_SPRITE12 assigment. ") if not globals().__contains__('HALO_SPRITE14'): raise NameError( "Texture HALO_SPRITE14 is missing!" "\nCheck file Texture.py for HALO_SPRITE14 assigment. ") # Create Halo sprite AsteroidHalo.images = choice([HALO_SPRITE12, HALO_SPRITE14]) AsteroidHalo.containers = self.gl.All AsteroidHalo(texture_name_='HALO_SPRITE12' if AsteroidHalo.images is HALO_SPRITE12 else 'HALO_SPRITE14', object_=self, timing_=8) self.make_debris() if player_ is not None: self.create_gems(player_) self.quit() def hit(self, player_=None, damage_: int = 0) -> None: """ Check asteroid life after laser collision. :param player_: Player instance :param damage_: integer; Damage received :return: None """ assert isinstance(damage_, int), \ "Positional argument <damage_> is type %s , expecting integer." % type(damage_) if damage_ < 0: raise ValueError('positional argument damage_ cannot be < 0') self.life -= damage_ self.has_been_hit = True if self.asteroid_name != 'MAJOR_ASTEROID' else False if self.life < 1: if player_ is not None: if hasattr(player_, 'update_score'): player_.update_score(self.points) self.explode(player_) def collide(self, player_=None, damage_: int = 0) -> None: """ Check asteroid life after collision with players or transport :param player_: Player instance or transport :param damage_: integer; Damage received :return: None """ assert isinstance(damage_, int), \ "Positional argument <damage_> is type %s , expecting integer." % type(damage_) if damage_ < 0: raise ValueError('positional argument damage_ cannot be < 0') if not globals().__contains__('IMPACT1'): raise NameError("Sound IMPACT1 is missing!" "\nCheck file Sounds.py for IMPACT1 assigment. ") if hasattr(self, 'life'): self.life -= damage_ # transfer damage to the asteroid (decrease life) else: raise AttributeError('self %s, %s does not have attribute life ' % (self, type(self))) # play asteroid burst sound locally self.gl.MIXER.play(sound_=IMPACT1, loop_=False, priority_=0, volume_=1.0, fade_out_ms=0, panning_=True, name_='IMPACT1', x_=self.rect.centerx, object_id_=id(IMPACT1), screenrect_=self.gl.SCREENRECT) # broadcast asteroid burst sound self.impact_sound_object.play() # check if asteroid life is still > 0 if self.life < 1: # check who is colliding with the asteroid # if not colliding with transport, transfer score to player. if not type(player_).__name__ == 'Transport': if player_ is not None: # player has collide with asteroid, player1 or player 2 get the points player_.update_score(self.points) else: # Transport does not get points ... # Split asteroid self.make_debris() self.quit() ... def quit(self) -> None: Broadcast.remove_object_id(self.id_) obj = Broadcast(self.delete_object()) obj.queue() self.kill() def update(self) -> None: """ Update asteroid sprites. :return: None """ if self.gl.FRAME > self.appearance_frame: # start to move asteroid when frame number is over # self.appearance_frame (random frame number) self.rect.move_ip(self.speed) # asteroid is moving but not visible yet? # The rectangle bottom edge must be > 0 to start the code below if self.rect.midbottom[1] > 0: # Inside the 60 FPS Area if self.dt > self.timing: # self.image = self.images_copy.copy() if self.has_been_hit: if not globals().__contains__('LAVA'): raise NameError("Texture LAVA not available") self.image.blit(LAVA[self.index % len(LAVA) - 1], (0, 0), special_flags=pygame.BLEND_RGB_ADD) self.index += 1 self.has_been_hit = False # if self.rotation != 0: # self.mask = pygame.mask.from_surface(self.image) self.asteroid_object.update({ 'frame': self.gl.FRAME, 'rect': self.rect, 'life': self.life }) self.asteroid_object.queue() self.dt = 0 else: self.dt += self.gl.TIME_PASSED_SECONDS if self.rect.midtop[1] > self.gl.SCREENRECT.h: self.quit()
class Transport(pygame.sprite.Sprite): containers = None image = None def __init__(self, gl_, timing_, pos_, surface_name_, layer_=0): pygame.sprite.Sprite.__init__(self, self.containers) if isinstance(gl_.All, pygame.sprite.LayeredUpdates): if layer_: gl_.All.change_layer(self, layer_) self.image = Transport.image self.image_copy = self.image.copy() self.rect = self.image.get_rect(center=pos_) self.timing = timing_ self.gl = gl_ self.dt = 0 self.fxdt = 0 self.layer = layer_ self.blend = 0 self.previous_pos = pygame.math.Vector2() # previous position self.max_life = 5000 self.life = 5000 # MirroredTransportClass max hit points self.damage = 10000 # Damage transfer after collision self.mask = pygame.mask.from_surface(self.image) # Image have to be convert_alpha compatible self.pos = pos_ self.index = 0 self.impact = False self.vertex_array = [] self.engine = self.engine_on() self.surface_name = surface_name_ self.id_ = id(self) self.transport_object = Broadcast(self.make_object()) self.impact_sound_object = Broadcast(self.make_sound_object('IMPACT')) half = self.gl.SCREENRECT.w >> 1 self.safe_zone = pygame.Rect(half - 200, half, 400, self.gl.SCREENRECT.bottom - half) self.half_life = (self.max_life >> 1) Broadcast.add_object_id(self.id_) # this is now obsolete since it is done from main loop def delete_object(self) -> DeleteSpriteCommand: """ Send a command to kill an object on client side. :return: DetectCollisionSprite object """ return DeleteSpriteCommand(frame_=self.gl.FRAME, to_delete_={self.id_: self.surface_name}) def make_sound_object(self, sound_name_: str) -> SoundAttr: return SoundAttr(frame_=self.gl.FRAME, id_=self.id_, sound_name_=sound_name_, rect_=self.rect) def make_object(self) -> StaticSprite: return StaticSprite(frame_=self.gl.FRAME, id_=self.id_, surface_=self.surface_name, layer_=self.layer, blend_=self.blend, rect_=self.rect, damage=self.damage, life=self.life, impact=self.impact) def engine_on(self) -> AfterBurner: AfterBurner.images = EXHAUST2 calc_pos = (self.pos[0] - 400, self.pos[1] - 205) # Top left corner position return AfterBurner(self, self.gl, calc_pos, 8, pygame.BLEND_RGB_ADD, self.layer - 1, texture_name_='EXHAUST2') def player_lost(self) -> None: PlayerLost.containers = self.gl.All PlayerLost.DIALOGBOX_READOUT_RED = DIALOGBOX_READOUT_RED PlayerLost.SKULL = SKULL font = freetype.Font('Assets\\Fonts\\Gtek Technology.ttf', size=14) PlayerLost(gl_=self.gl, font_=font, image_=FINAL_MISSION, layer_=0) # todo kill player 1 and 2 game is over def explode(self): Explosion.images = EXPLOSION2 for i in range(10): Explosion(self, (self.rect.centerx + randint(-400, 400), self.rect.centery + randint(-400, 400)), self.gl, 8, self.layer, texture_name_='EXPLOSION2', mute_=False if i > 0 else True) PlayerHalo.images = HALO_SPRITE13 PlayerHalo.containers = self.gl.All PlayerHalo(texture_name_='HALO_SPRITE13', object_=self, timing_=8) self.quit() def collide(self, damage_: int)-> None: """ Asteroid collide with object (e.g Asteroids) :param damage_: int; damage transfer to the transport (damage must be positive) :return: None """ assert isinstance(damage_, int), \ 'Positional arguement damage_, expecting int type got %s ' % type(damage_) if self.alive(): if damage_ is None or damage_ < 0: raise ValueError('positional arguement damage_ cannot be None or < 0.') self.impact = True # variable used for blending, (electric effect on the transport's hull ) self.index = 0 self.life -= damage_ # Transport life decrease # Play an impact sound locally self.gl.MIXER.play(sound_=IMPACT1, loop_=False, priority_=0, volume_=1.0, fade_out_ms=0, panning_=True, name_='IMPACT1', x_=self.rect.centerx, object_id_=id(IMPACT1), screenrect_=self.gl.SCREENRECT) # Play impact sound on client computer. self.impact_sound_object.play() else: self.quit() def hit(self, damage_): if self.alive(): self.life -= damage_ else: self.quit() def get_centre(self) -> tuple: return self.rect.center def display_fire_particle_fx(self) -> None: # Display fire particles when the player has taken bad hits # Use the additive blend mode. if self.fxdt > self.timing: for p_ in self.vertex_array: # queue the particle in the vector direction p_.rect.move_ip(p_.vector) p_.image = p_.images[p_.index] if p_.index > len(p_.images) - 2: p_.kill() self.vertex_array.remove(p_) p_.index += 1 self.fxdt = 0 else: self.fxdt += self.gl.TIME_PASSED_SECONDS def fire_particles_fx(self, position_, # particle starting location (tuple or pygame.math.Vector2) vector_, # particle speed, pygame.math.Vector2 images_, # surface used for the particle, (list of pygame.Surface) layer_=0, # Layer used to display the particles (int) blend_=pygame.BLEND_RGB_ADD # Blend mode (int) ) -> None: # Create fire particles around the aircraft hull when player is taking serious damages # Cap the number of particles to avoid lag # if len(self.gl.FIRE_PARTICLES_FX) > 100: # return # Create fire particles when the aircraft is disintegrating sprite_ = pygame.sprite.Sprite() self.gl.All.add(sprite_) # self.gl.FIRE_PARTICLES_FX.add(sprite__) # assign the particle to a specific layer if isinstance(self.gl.All, pygame.sprite.LayeredUpdates): self.gl.All.change_layer(sprite_, layer_) sprite_.layer = layer_ sprite_.blend = blend_ # use the additive mode sprite_.images = images_ sprite_.image = images_[0] sprite_.rect = sprite_.image.get_rect(center=position_) sprite_.vector = vector_ # vector sprite_.index = 0 # assign update method to self.display_fire_particle_fx # (local method to display the particles) sprite_.update = self.display_fire_particle_fx self.vertex_array.append(sprite_) def quit(self) -> None: Broadcast.remove_object_id(self.id_) obj = Broadcast(self.delete_object()) obj.queue() self.kill() def update(self): self.rect.clamp_ip(self.safe_zone) self.image = self.image_copy.copy() # in the 16ms area (60 FPS) if self.dt > self.timing: if self.life < self.half_life: position = pygame.math.Vector2(randint(-50, 50), randint(-100, 100)) self.fire_particles_fx(position_=position + pygame.math.Vector2(self.rect.center), vector_=pygame.math.Vector2(uniform(-1, 1), uniform(+1, +3)), images_=FIRE_PARTICLES, layer_=0, blend_=pygame.BLEND_RGB_ADD) if self.life < 1: self.explode() return if self.previous_pos == self.rect.center: self.rect.centerx += randint(-1, 1) self.rect.centery += randint(-1, 1) if self.gl.FRAME < 100: self.rect.centery -= 3 self.previous_pos = self.rect.center self.engine.update() self.transport_object.update( {'frame': self.gl.FRAME, 'rect': self.rect, 'damage': self.damage, 'life': self.life, 'impact': self.impact}) # Broadcast the spaceship position self.transport_object.queue() self.dt = 0 else: self.dt += self.gl.TIME_PASSED_SECONDS # outside the 60 FPS area. # Below code processed every frames. if self.impact: self.image.blit(DISRUPTION_ORG[self.index % len(DISRUPTION_ORG) - 1], (0, 0), special_flags=pygame.BLEND_RGB_ADD) self.index += 1 if self.index > len(DISRUPTION_ORG) - 2: self.impact = False self.index = 0