def __FaceRight(self): image_bounding_rectangle = self.Sprite.image.get_rect() # The handle should be in the center in order to support easy rotation. self.HandleLocalImagePosition = Vector2( image_bounding_rectangle.centerx, image_bounding_rectangle.centery) # The tip should be at the right of the image. self.TipLocalImagePosition = Vector2(image_bounding_rectangle.right, image_bounding_rectangle.centery)
def __init__(self): # CREATE THE SPRITE FOR THE SWORD. ## The sprite (image) for the sword. self.Sprite = pygame.sprite.Sprite() # In order to handle transparency and rotation easily, a separate, larger surface # is needed onto which the actual sword image file is blitted to. self.Sprite.image = pygame.Surface( (Sword.IMAGE_DIMENSION_IN_PIXELS, Sword.IMAGE_DIMENSION_IN_PIXELS)) # Color-keying is currently used for transparency due to its simplicity. self.Sprite.image.fill(Sword.TRANSPARENT_COLOR.value) self.Sprite.image.set_colorkey(Sword.TRANSPARENT_COLOR.value) # Since the sword image is smaller than the full surface, it's position when # blitted to the surface needs to be adjusted so that it will appear in # the center when swinging. This exact offset is dependent on the exact # size and position of the sword within its image. SWORD_IMAGE_X_OFFSET_IN_PIXELS = 32 SWORD_IMAGE_Y_OFFSET_IN_PIXELS = 16 sword_image = pygame.image.load('../Images/Sword.gif').convert() self.Sprite.image.blit( sword_image, (SWORD_IMAGE_X_OFFSET_IN_PIXELS, SWORD_IMAGE_Y_OFFSET_IN_PIXELS)) # INITIALIZE REMAINING MEMBER VARIABLES. ## The screen position of the sword's handle. ## Expected to be set to near the player's position anytime ## the sword starts being swung. self.__HandleScreenPosition = Vector2(0, 0) ## The local position of the handle within the image. ## Should be properly initialized whenever the sword starts being swung. self.HandleLocalImagePosition = Vector2(0, 0) ## The tip position of the handle within the image. ## Should be properly initialized whenever the sword starts being swung. self.TipLocalImagePosition = Vector2(0, 0) ## The current amount of rotation of the sword from its initial ## position when being swung. self.CurrentRotationAngleInDegrees = 0.0 ## The destination rotation angle for the sword when it has finished ## being completely swung. self.DestinationRotationAngleInDegrees = 0.0 ## True if the sword is currently being swung; false if not. self.IsSwinging = False ## The bounding rectangle for the sword. ## \todo This is currently being set to a fix value each ## time the sword starts being swung. It would be better to ## have this dynamically calculated to account for rotation ## (or perform more advanced line or rotated bounding box ## collision, but there's likely not time to get that working ## before the game's deadline). self.BoundingScreenRectangle = pygame.Rect(0, 0, 0, 0) ## The width of the sword in pixels. self.WidthInPixels = 1 ## The color of the sword. self.Color = Color.Gray
def Render(self, screen): # ONLY RENDER THE SWORD IF IT'S BEING SWUNG. if not self.IsSwinging: return # DRAW THE A DEBUG LINE FOR THE SWORD IF ENABLED. DEBUG_DRAWING_ENABLED = False if DEBUG_DRAWING_ENABLED: pygame.draw.line(self.Sprite.image, self.Color.value, self.HandleLocalImagePosition.AsXYTuple(), self.TipLocalImagePosition.AsXYTuple(), self.WidthInPixels) # ROTATE THE SWORD BASED ON HOW IT'S BEING SWUNG. rotated_sword_image = pygame.transform.rotate( self.Sprite.image, self.CurrentRotationAngleInDegrees) # ADJUST THE SWORD'S POSITION BASED ON ROTATION. # When an image is rotated, the actual size of the bounding rectangle increases to encompass # an axis-aligned box that encompasses all of the image. To avoid having the sword appear # to change positions, the handle's position must be adjusted to account for the changing # size of the bounding rectangle. rotated_image_rect = rotated_sword_image.get_rect() original_image_rect = self.Sprite.image.get_rect() image_width_increase_in_pixels = rotated_image_rect.width - original_image_rect.width image_height_increase_in_pixels = rotated_image_rect.height - original_image_rect.height # A copy of the screen position is made to avoid altering the original position. handle_screen_position = Vector2(self.HandleScreenPosition.X, self.HandleScreenPosition.Y) # Due to this position being the center of rotation, it only needs to be adjusted by # half of the change in dimensions of the image's bounding box. handle_screen_position.X -= image_width_increase_in_pixels / 2 handle_screen_position.Y -= image_height_increase_in_pixels / 2 # DRAW THE SWORD ON THE SCREEN. screen.blit(rotated_sword_image, handle_screen_position.AsXYTuple()) # DRAW DEBUG RECTANGLES IF DEBUGGING IS ENABLED. if DEBUG_DRAWING_ENABLED: # DRAW A DEBUG RECTANGLE FOR THE UNADJUSTED POSITION. DEBUG_RECTANGLE_OUTLINE_WIDTH_IN_PIXELS = 1 unadjusted_debug_image_rect = rotated_image_rect.move( self.HandleScreenPosition.X, self.HandleScreenPosition.Y) pygame.draw.rect(screen, Color.FullGreen.value, unadjusted_debug_image_rect, DEBUG_RECTANGLE_OUTLINE_WIDTH_IN_PIXELS) # DRAW A DEBUG RECTANGLE FOR THE ADJUSTED POSITION. adjusted_debug_image_rect = rotated_image_rect.move( handle_screen_position.AsXYTuple()) pygame.draw.rect(screen, Color.Magenta.value, adjusted_debug_image_rect, DEBUG_RECTANGLE_OUTLINE_WIDTH_IN_PIXELS) # DRAW A DEBUG RECTANGLE FOR THE BOUNDING BOX FOR COLLISIONS. pygame.draw.rect(screen, Color.Red.value, self.BoundingScreenRectangle, DEBUG_RECTANGLE_OUTLINE_WIDTH_IN_PIXELS)
def neighbors(self, grid_position): # Get the possible neighbors of the position. neighbors = [ Vector2(grid_position.X, grid_position.Y - 1), Vector2(grid_position.X, grid_position.Y + 1), Vector2(grid_position.X - 1, grid_position.Y), Vector2(grid_position.X + 1, grid_position.Y) ] accessible_neighbors = [] for neighbor in neighbors: # Check whether this position is within map bounds and unoccupied. in_x_bounds = (0 <= neighbor.X < self.Map.MapWidth) in_y_bounds = (0 <= neighbor.Y < self.Map.MapHeight) unoccupied = (neighbor.X, neighbor.Y) not in self.Map.Map is_destination = (neighbor == self.Destination) if in_x_bounds and in_y_bounds and (unoccupied or is_destination): accessible_neighbors.append(neighbor) return accessible_neighbors
def Shoot(self, player): # CALCULATE THE TRAJECTORY TO THE PLAYER. enemy_position = Vector2(self.Coordinates.centerx, self.Coordinates.centery) player_position = Vector2(player.Coordinates.centerx, player.Coordinates.centery) trajectory_to_player = Vector2.Subtract(player_position, enemy_position) # The trajectory should be normalized to have it just represent the direction. # The laser can independently control the speed at which it moves. trajectory_to_player = Vector2.Normalize(trajectory_to_player) # GENERATE A LASER AND FIRE IT TOWARDS THE PLAYER. laser = Laser(self.Coordinates.centerx, self.Coordinates.centery, Laser.Color.Red, trajectory_to_player) # Move the laser closer to the player so that it doesn't hit the wall when it spawns. laser.Update(0.2) # INDICATE THAT A SHOT WAS JUST FIRED. self.TimeElapsedSinceLastShotInSeconds = 0 return laser
def UpdateEnemies(self, time_since_last_update_in_seconds): # UPDATE EACH ENEMY. player = self.Map.GetPlayer() player_position = self.Map.GetGridPosition(player.Coordinates.center) player_position_vector = Vector2(player_position[0], player_position[1]) enemies = self.Map.GetEnemies() for enemy in enemies: # CHECK IF THE ENEMY WAS HIT BY A REFLECTED LASER. lasers_that_kill_enemy = [laser for laser in self.Map.Lasers if laser.HasBeenReflected and enemy.Coordinates.colliderect(laser.Coordinates)] enemy_was_hit_by_laser = (len(lasers_that_kill_enemy) > 0) if enemy_was_hit_by_laser: # Remove this enemy from the map. self.Map.RemoveObject(enemy) # No further updates are necessary for this enemy. continue # TARGET THE PLAYER. enemy.TargetPlayer(player) # TRY TO SHOOT AT THE PLAYER. laser = enemy.TryShooting(time_since_last_update_in_seconds, player, self.Map) if laser: self.Map.Lasers.append(laser) # MOVE IF NON-STATIONARY. enemy_is_stationary = isinstance(enemy, Turret) if enemy_is_stationary: continue # CHECK IF THE ENEMY IS ALREADY CLOSE ENOUGH TO THE PLAYER. enemy_position = self.Map.GetGridPosition(enemy.Coordinates.center) enemy_position_vector = Vector2(enemy_position[0], enemy_position[1]) distance_to_player = self.Pathing.GetDirectDistanceBetweenGridPositions(player_position_vector, enemy_position_vector) MINIMUM_DISTANCE = 3.5 too_close_to_player = (distance_to_player < MINIMUM_DISTANCE) DESIRED_DISTANCE = 4.5 within_desired_distance_of_player = (distance_to_player < DESIRED_DISTANCE) if too_close_to_player: # BACK AWAY FROM THE PLAYER. directions_to_move = LevelHandler.GetDirectionTowardPosition(player_position_vector, enemy_position_vector) pass elif within_desired_distance_of_player: # DON'T MOVE. continue else: # MOVE TOWARD THE PLAYER. # Get the shortest path to the player from the enemy. path_to_player = self.Pathing.GetPath(enemy_position_vector, player_position_vector) if path_to_player is None: # This enemy cannot currently reach the player. continue # Get the cardinal direction in which the enemy should move in order to reach # the player (or directions, if the true direction is diagonal). next_grid_position_in_path_to_player = next(islice(path_to_player, 1, None), None) next_grid_position_center_x = (next_grid_position_in_path_to_player.X + 0.5) * GameObject.WidthPixels next_grid_position_center_y = (next_grid_position_in_path_to_player.Y + 0.5) * GameObject.HeightPixels directions_to_move = LevelHandler.GetDirectionTowardPosition( Vector2(enemy.Coordinates.centerx, enemy.Coordinates.centery), Vector2(next_grid_position_center_x, next_grid_position_center_y)) # MOVE. # If the true direction is diagonal, one of the directions to move may be blocked # by an obstacle, but trying to move in the other direction should succeed. for direction in directions_to_move: if direction == MoveDirection.Up: enemy.MoveUp(self.Map) elif direction == MoveDirection.Down: enemy.MoveDown(self.Map) elif direction == MoveDirection.Left: enemy.MoveLeft(self.Map) elif direction == MoveDirection.Right: enemy.MoveRight(self.Map)
def UpdateEnemies(self, time_since_last_update_in_seconds): # UPDATE EACH ENEMY. player = self.Map.GetPlayer() player_position = self.Map.GetGridPosition(player.Coordinates.center) player_position_vector = Vector2(player_position[0], player_position[1]) enemies = self.Map.GetEnemies() for enemy in enemies: # CHECK IF THE ENEMY WAS HIT BY A REFLECTED LASER. for laser in self.Map.Lasers: if laser.HasBeenReflected: # Check collision. enemy_was_hit_by_laser = enemy.Coordinates.colliderect(laser.Coordinates) if enemy_was_hit_by_laser: # Remove this enemy from the map. self.Map.RemoveObject(enemy) # No further updates are necessary for this enemy. continue # TARGET THE PLAYER. enemy.TargetPlayer(player) # RANDOMLY SHOOT AT THE PLAYER. laser = enemy.TryShooting(time_since_last_update_in_seconds, player, self.Map) if laser: self.Map.Lasers.append(laser) # MOVE IF NON-STATIONARY. enemy_is_stationary = isinstance(enemy, Turret) if enemy_is_stationary: continue # CHECK IF THE ENEMY IS ALREADY CLOSE ENOUGH TO THE PLAYER. enemy_position = self.Map.GetGridPosition(enemy.Coordinates.center) enemy_position_vector = Vector2(enemy_position[0], enemy_position[1]) distance_to_player = self.Pathing.GetDirectDistanceBetweenGridPositions(player_position_vector, enemy_position_vector) MINIMUM_DISTANCE = 3.5 too_close_to_player = (distance_to_player < MINIMUM_DISTANCE) DESIRED_DISTANCE = 4.5 within_desired_distance_of_player = (distance_to_player < DESIRED_DISTANCE) if too_close_to_player: # BACK AWAY FROM THE PLAYER. direction_to_move = LevelHandler.GetDirectionTowardPosition(player_position_vector, enemy_position_vector) pass elif within_desired_distance_of_player: # DON'T MOVE. continue else: # MOVE TOWARD THE PLAYER. # Get the shortest path to the player from the enemy. path_to_player = self.Pathing.GetPath(enemy_position_vector, player_position_vector) if path_to_player is None: # This enemy cannot currently reach the player. continue # Get the next grid position that the enemy should move to in # order to reach the player. next_grid_position_in_path_to_player = next(islice(path_to_player, 1, None), None) direction_to_move = LevelHandler.GetDirectionTowardPosition( enemy_position_vector, next_grid_position_in_path_to_player) # MOVE. if direction_to_move is None: continue elif direction_to_move == MoveDirection.Up: enemy.MoveUp(self.Map) elif direction_to_move == MoveDirection.Down: enemy.MoveDown(self.Map) elif direction_to_move == MoveDirection.Left: enemy.MoveLeft(self.Map) elif direction_to_move == MoveDirection.Right: enemy.MoveRight(self.Map)
def Update(self, time_since_last_update_in_seconds): MOVE_SPEED_IN_PIXELS_PER_SECOND = 200 movement_distance_in_pixels = MOVE_SPEED_IN_PIXELS_PER_SECOND * time_since_last_update_in_seconds movement_amount_in_pixels = Vector2.Scale(movement_distance_in_pixels, self.Trajectory) self.Coordinates = self.Coordinates.move(movement_amount_in_pixels.X, movement_amount_in_pixels.Y)