class Weapon(pygame.sprite.Sprite): def __init__(self, owner, sprite_sheet_location): pygame.sprite.Sprite.__init__(self) self.owner = owner self.screen = owner.screen self.owned = True # Loading Sprite Sheet image_types = owner.character_data['actions'] image_directions = owner.character_data['directions'] char_size = [32, 32] scaled_size = owner.scaled_size coords = owner.character_data self.loadSpriteSheets(image_types, image_directions, scaled_size, char_size, coords, sprite_sheet_location) # Setting up initial image self.state = owner.state self.image = self.images[self.state[0]][self.state[1]] self.index = owner.image_index self.rect = self.image[self.index].get_rect() self.rect.centerx = self.owner.rect.centerx self.rect.centery = self.owner.rect.centery # Assinging animation images to self def loadSpriteSheets(self, image_types, image_directions, scaled_size, char_size, coords, sprite_sheet_location, background_colour=(0, 255, 0)): self.spritesheet = SpriteSheet(sprite_sheet_location) self.images = {} for image_type in image_types: self.images[image_type] = {} for image_direction in image_directions: self.images[image_type][image_direction] = [] for coord in coords[image_type][image_direction]: specific_image = pygame.transform.scale( self.spritesheet.image_at(coord, char_size), scaled_size) specific_image.set_colorkey(background_colour) self.images[image_type][image_direction] += \ [specific_image] def addCharacterGroup(self, char_group): self.characters = char_group def display(self): ''' display function state takes form [action, direction], images[action][direction] gives a list of images ''' # Get owner variables self.state = self.owner.state self.index = self.owner.image_index action, direction = self.state[0], self.state[1] # Select Image self.image = self.images[action][direction] self.rect.center = self.owner.rect.center # Rect position alive if self.owner.alive: self.screen.blit(self.image[self.index], self.rect) else: self.owned = False # Attack function for weapon def attack(self, direction, target): if target.health < self.strength: self.owner.score += target.health else: self.owner.score += self.strength self.sound() target.recoil(self.strength, direction)
class Character(pygame.sprite.Sprite): ''' Character Class - Used to display and animate sprites from sprite sheets on screen. Usually won't be initialised directly, rather its two child classes (Player and NPC) will be called. ''' def __init__(self, character_data, background, screen, x_position, y_position, arm_type='arms'): ''' Init Character Function takes and unpacks relevat information from the characters JSON dictionary ''' # Initi for sprite pygame.sprite.Sprite.__init__(self) # Assigning character data to self.charactar_data self.character_data = character_data self.addSpritesheetJSON() # Putting object to screen self.screen = screen ### Unpacking some important JSON dictionary into variables self.speed = character_data['speed'] self.gravity = character_data['gravity'] self.jump_speed = character_data['jump_speed'] self.state = character_data['initialstate'] self.refresh_rate = character_data['refresh'] ### Health and stats data self.alive = True self.__max_health = character_data['initial_health_points'] self.__health = self.__max_health self.strength = character_data['initial_strength'] # Character Position self.position = [x_position, y_position] # Load sprite sheet and extract frames to dictionary self.loadSpriteSheets(character_data) # Adding screen to object self.image = self.images[self.state[0]][self.state[1]] self.image_index = 0 self.plot_rect = self.image[self.image_index].get_rect() self.plot_rect.center = self.position self.rect = pygame.Rect((0, 0, self.width, self.height)) self.rect.center = self.plot_rect.center self.feet_rect = pygame.Rect((0, 0, self.width, self.height // 10)) self.feet_rect.bottomleft = self.rect.bottomleft # setup score self.score = 0 # Get Character Arms TODO MAY need updating to reflect some # enemies having own arms/other arms self.arms = WEAPON_TYPES[arm_type](self) self.healthbar = HealthBar(self) # Important move variables self.refresh_counter = 0 self.x_y_moving = False self.recoil_status = (False, 0) # Storing dimension variables to self self.screen_dims = (screen.get_width(), screen.get_height()) # Referencing important background variables self.changeMap(background) ##### TO GO TO JSON self.is_falling = False self.is_jumping = False self.attacking = False self.init_attacking = False self.jumps_in_action = 0 self.max_jumps_in_action = 2 self.attack_frame_counter = 0 self.thrown_projectiles = pygame.sprite.Group() def changeMap(self, background): ''' changeMap(background) - used to update to new map Function to update player with new background. Call this on player when new map produced, map refers to class containing sprite group of tiles, and map_matrix ''' self.background = background self.map_matrix = background.map_matrix self.tiles_group = background.map_group self.tile_rect = [] for tile in self.tiles_group: self.tile_rect.append(tile.rect) def addSpritesheetJSON(self): ''' addSpritesheetJSON Loads spritesheet interpretation data from SPRITESHEET_JSON ''' for key in SPRITESHEET_JSON.keys(): self.character_data[key] = SPRITESHEET_JSON[key] def loadSpriteSheets(self, character_data): ''' loadSpriteSheets(self, character_data) Procedure which loads spritesheet from path given, and extracts each frame of the sprite and stores to dictionary self.images These can then be updated depending on this instances state ''' self.spritesheet = SpriteSheet(character_data['path']) char_size = character_data['charsize'] scale_factor = character_data['scale_factor'] scaled_size = [ char_size[0] * scale_factor, char_size[1] * scale_factor ] self.scaled_size = scaled_size background_colour = character_data['background'] image_types = character_data['actions'] image_directions = character_data['directions'] graphic_dims = character_data['graphic_dims'] self.width = graphic_dims[0] * scale_factor self.height = graphic_dims[1] * scale_factor self.images = {} # Importing images into self.images dictionary # This interacts with spritesheet code from https://ehmatthes.gi # thub.io/pcc_2e/beyond_pcc/pygame_sprite_sheets/#a-simple-sprit # e-sheet # to load sprites into a dictinoary for image_type in image_types: self.images[image_type] = {} for image_direction in image_directions: self.images[image_type][image_direction] = [] for coords in character_data[image_type][image_direction]: specific_image = pygame.transform.scale( self.spritesheet.image_at(coords, char_size), scaled_size) specific_image.set_colorkey(background_colour) self.images[image_type][image_direction] += \ [specific_image] def changeArms(self, arm_type): self.arms = WEAPON_TYPES[arm_type](self) def addTarget(self, target_group): ''' Adds group of enemies to player ''' self.target_group = target_group def spriteCollision(self, other): if pygame.sprite.collide_rect(self, other): print('COLLISION') else: print('NO COLLISION') def attack(self, target, type=1): ''' Attack function - Attacks player assigned to it Causes player being attacked to recoil in opposite direction, and lose health. ''' if self.rect[0] < target.rect[0]: direction = 1 else: direction = -1 self.arms.attack(direction, target) def isFacingTarget(self, target): ''' Function to check whether self is facing a particular enemy ''' if self.state[1] == 'left' and \ (self.rect.centerx > target.rect.centerx): return True elif self.state[1] == 'right' and \ (self.rect.centerx < target.rect.centerx): return True return False def recoil(self, force, direction): ''' Recoil function - Loses health from attack and sets recoil counter Recoil counter processed in display function. Each frame pushes character back while recoiling. ''' self.health -= force self.score -= force // 5 self.recoil_status = (True, direction) self.recoil_counter = 5 def update(self): ''' Update function Updates position of characters subject to state. ''' # Check if attacking, if attacking change state to attacking for # one frame if (self.attacking) and (self.init_attacking == False): self.pre_attack_action, self.pre_action_direction = self.state self.updateState('attack', self.state[1]) self.init_attacking = True elif self.init_attacking: self.attack_frame_counter += 1 if self.attack_frame_counter == self.refresh_rate: self.attack_frame_counter = 0 self.attacking = False self.init_attacking = False self.updateState(self.pre_attack_action, self.state[1]) # Update verticle subject to jumping #if self.state[0] == 'jumping': if self.is_jumping: self.applyJump() else: if not self.collisionWithGround(): self.is_falling = True # Updating position subject to gravity if self.is_falling: self.applyGravity() # Updating subject to recoil. If character is recoiling, move # in recoil direction if self.recoil_status[0]: if self.recoil_counter == 0: self.recoil_status = (False, 0) self.moveX(15 * self.recoil_status[1]) self.recoil_counter = self.recoil_counter - 1 # Update x/y subject to status if self.x_y_moving: if self.state[1] == 'right': self.moveX(self.speed) if self.state[1] == 'left': move_speed = -1 * self.speed self.moveX(move_speed) def syncRects(self): self.feet_rect.bottomleft = self.rect.bottomleft self.plot_rect.center = self.rect.center def display(self): ''' Display function Updates image if required, and displays image(s) to screen ''' # Update state image TODO CHANGE image code self.image = self.images[self.state[0]][self.state[1]] # Updating counter, and if necessary incrementing image self.refresh_counter += 1 if self.refresh_counter == self.refresh_rate: self.incrementImage() # Catch frames changed mid refresh if self.image_index >= len(self.image): self.incrementImage() self.syncRects() # ################################################### # # TODO DELETE THE FOLLOWING CODE - FOR TESTING ONLY # surf = pygame.Surface((self.rect.width, self.rect.height)) # surf.fill((100, 100, 0)) # self.screen.blit(surf, self.rect) # surf = pygame.Surface((self.feet_rect.width, self.feet_rect.he # ight)) # surf.fill((0, 100, 100)) # self.screen.blit(surf, self.feet_rect) # ################################################### # Displaying current image at current position self.screen.blit(self.image[self.image_index], self.plot_rect) # Display arms and health bar #print(self.arms) self.arms.display() self.healthbar.display() def collisionWithGround(self): ''' Collision Detection Detects collision with the ground - if colliding with ground, returns True Based on code from Python Basics YouTube series https://www.youtube.com/watch?v=bQnEQvyS1Ns - Approx 4 minutes in, and the pygame documentation. Initially this was implemented with pygame.sprite.spritecollide with the tiles_group, however this caused issues with any point of a sprite colliding with a tile causing it to cease to fall. This means you could get characters awkwardly suspended by their heads. Instead we used pygame.Rect.collidelist ''' if self.feet_rect.collidelist(self.tile_rect) != -1: self.is_falling = False self.is_jumping = False self.jumps_in_action = 0 self.stopMoveY() return True else: return False def applyGravity(self): ''' applyGravity Updates position subject to gravity. If self is falling, then move down by gravity. Then checks for collisions with tiles to update falling status. ''' # Updating positions subject to gravity self.moveY(self.gravity) self.collisionWithGround() def applyJump(self): ''' Applys Jump to player after jump has been pressed ''' jump = self.jump_speed // self.jumpcount self.moveY(-1 * jump) self.jumpcount = self.jumpcount * 2 if (jump == 1) or (jump == 0): self.is_falling = True self.is_jumping = False #self.state[0] = 'falling' def incrementImage(self): ''' Increment Image function Increments image by 1, and resets counting variable ''' # Resetting refresh counter self.refresh_counter = 0 # Incrementing image index self.image_index += 1 # Returning to 0 when image index > length n_images = len(self.image) if self.image_index >= n_images: self.image_index = 0 def updateState(self, action, direction): ''' updateState(action, direction) function to update state of character ''' self.state = [action, direction] self.refresh_counter = 0 self.image_index = 0 def moveX(self, step): ''' moveX(step) Function to move character step pixels in the X direction ''' self.rect.centerx += step def moveY(self, step): ''' moveY(step) Function to move character step pixels in the Y direction. - Note: the y axis is flipped from what we might naturally assume, 0 is at the top and not the bottom ''' self.rect.centery += step def startMove(self, direction): ''' startMove(direction) Input 'l' or 'r' for horizontal movement, 'u' for jump ''' if self.state[0] == 'idle': self.state[0] = 'running' if direction == 'l': # Moving one speed step left self.x_y_moving = True self.state[1] = 'left' elif direction == 'r': # Moving right self.x_y_moving = True self.state[1] = 'right' elif direction == 'u': #self.state[0] = 'jumping' if (self.jumps_in_action < self.max_jumps_in_action): self.is_jumping = True self.jumpcount = 1 self.jumps_in_action += 1 def stopMoveX(self, direction='none'): ''' stopMove() Returns state to idle when no longer moving. Purpose of function is to stop running animation, and prevent piece from continueing to move after a jump if player ahs taken hand off ''' if self.state == ['running', direction]: self.updateState('idle', direction) self.x_y_moving = False elif (self.state[0] == 'attack') and direction == self.state[1]: self.pre_attack_action == 'idle' self.x_y_moving = False def stopMoveY(self): ''' Sets state to idle to ensure animation matches with lack of moving ''' if (not self.x_y_moving) and (self.state[0] != 'attack'): self.state[0] = 'idle' def setArms(self, new_arms): self.arms = new_arms @property def health(self): return self.__health @health.setter def health(self, new): self.__health = new if self.health <= 0: self.alive = False #self.kill() return self.healthbar.updateHealth() @property def max_health(self): return self.__max_health @max_health.setter def max_health(self, new): self.healthbar.max_health = new self.health += new - self.max_health self.__max_health = new
class BoomerangAmmo(pygame.sprite.Sprite): def __init__(self, owner, characters, strength, direction): pygame.sprite.Sprite.__init__(self) self.loadFrames() self.group = characters self.owner = owner self.strength = strength self.screen = self.owner.screen if direction == 'left': self.speed = -3 else: self.speed = 3 self.frame_rate = 10 self.frame_counter = 0 self.period = 1 self.period_counter = 0 self.rect.center = self.owner.rect.center def loadFrames(self): self.frames = [] self.spritesheet = SpriteSheet(BOOMERANG_ANIMATION_LOCATION) for i in range(4): frame = self.spritesheet.image_at((i, 0), (32, 32), (0, 255, 0)) self.frames.append(frame) self.current_frame = 0 self.num_frames = len(self.frames) self.image = self.frames[self.current_frame] self.rect = self.image.get_rect() def update(self): self.period_counter += 1 if self.period_counter == self.period: self.rect.centerx += self.speed self.period_counter = 0 self.frame_counter += 1 if self.frame_counter == self.frame_rate: self.current_frame += 1 self.frame_counter = 0 if self.current_frame >= len(self.frames): self.current_frame = 0 collisions = pygame.sprite.spritecollide(self, self.group, False) if collisions != None: for sprite in collisions: if self.rect.centerx < sprite.rect.centerx: direction = 1 else: direction = -1 sprite.recoil(self.strength, direction) self.owner.score += self.strength self.kill() def display(self): self.image = self.frames[self.current_frame] self.screen.blit(self.image, self.rect)