For starters my game is supper laggy and I want my game to have that Geometry Dash kind of feel where as you move forward the map updates.
import os import random import math from typing import Any import pygame from os import listdir from os.path import isfile, join pygame.init() pygame.display.set_caption('Platformer') WIDTH, HEIGHT = (900,700) FPS = 60 PLAYER_VEL = 9 # this is how fast the character is moving velocity ANIMATION_DELAY = 3 # this is how fast the characters sprite is changing ( per frame ) last_block_x = 0 window = pygame.display.set_mode((WIDTH, HEIGHT)) def flip(sprites): return [pygame.transform.flip(sprite, True, False) for sprite in sprites ] # this function is to flip the sprites/characters. we are doing this because in the assets the animations are facing only one way so we have to flip the characters so that the can also face the opposite way def load_sprite_sheets(dir1, dir2, width, height, direction=False): # this entire function is so that we can load our sprite sheets/ characters path = join('assets', dir1, dir2) images = [f for f in listdir(path) if isfile(join(path,f))] # this is the function that finds the images and then we can split the images up. all_sprites = {} for image in images: sprite_sheet = pygame.image.load(join(path, image)). convert_alpha() # The function convery_alpha is essentially going to allow me to load a transparent background image. sprites = [] for i in range(sprite_sheet.get_width() // width): surface = pygame.Surface((width, height), pygame.SRCALPHA, 32) rect = pygame.Rect( i * width,0, width, height) surface.blit(sprite_sheet, (0,0), rect) sprites.append(pygame.transform.scale2x(surface)) if direction: # the function is basically saying that if you want a multidirectional animation, then as we did we need to add two keys to our dictionary for every single one of our animations so that it has a left facing side and a right all_sprites[image.replace('.png', "") + "_right"] = sprites all_sprites[image.replace('.png', "") + "_left"] = flip(sprites) else: all_sprites[image.replace('.png', '')] = sprites return all_sprites def get_block(size): path = join('assets', 'Terrain', 'Terrain.png') image = pygame.image.load(path).convert_alpha() surface = pygame.Surface((size, size), pygame.SRCALPHA, 32) # This size values is the dimension of the block that we want to use in the image rect = pygame.Rect(96, 0, size, size) # Here is where you can change the block image (note that to upload the block you want you have to explain to the program what part of the image you want to use because its one image so saying 96, 0 for example is a part of the image that we want to use) surface.blit(image, (0,0), rect) return pygame.transform.scale2x(surface) class Player(pygame.sprite.Sprite): COLOR = (255, 0, 0) GRAVITY = 1 # longer you fall the faster you fall = so this function represents the acceleration of gravity (if you want the players in the game to fall faster you increase this value) SPRITES = load_sprite_sheets('MainCharacters', 'NinjaFrog', 32, 32, True) # this is where you can change the player + change his attributes def __init__(self, x, y, width, height): super().__init__() self.rect = pygame.Rect(x, y, width, height) self.x_vel = 0 self.y_vel = 0 self.mask = None self.direction = "left" self.animation_count = 0 self.fall_count = 0 self.jump_count = 0 self.hit = False self.hit_count = 0 def jump(self): self.y_vel = -self.GRAVITY * 8 # this function allows me to change my y velocity upwards but at the same time it subtracts that by the gravity loop that's constantly bringing the charater down on the y axis self.animation_count = 0 self.jump_count += 1 if self.jump_count == 1: self.fall_count = 0 def move(self, dx, dy): self.rect.x += dx self.rect.y += dy def make_hit(self): self.hit = True self.hit_count = 0 def move_left(self, vel): self.x_vel = -vel if self.direction != 'left': self.direction = 'left' self.animation_count = 0 def move_right(self, vel): self.x_vel = vel if self.direction != 'right': self.direction = 'right' self.animation_count = 0 def loop(self, fps): self.y_vel += min(1, (self.fall_count / fps) * self.GRAVITY) self.move(self.x_vel, self.y_vel) # this is the loop function which is going to move the player in the x and y velocity direction. We are going to call this loop later in the code if self.hit: self.hit_count += 1 if self.hit_count > fps * 2: self.hit = False self.hit_count = 0 self.fall_count +=1 self.update_sprite() def landed(self): self.fall_count = 0 self.y_vel = 0 self.jump_count = 0 def hit_head(self): self.count = 0 self.y_vel *= -1 def update_sprite(self): sprite_sheet = "idle" if self.hit: sprite_sheet = 'hit' elif self.y_vel < 0: if self.jump_count == 1: sprite_sheet = 'jump' elif self.jump_count == 2: sprite_sheet = 'double_jump' elif self.y_vel > self.GRAVITY * 2: sprite_sheet = 'fall' elif self.x_vel != 0: sprite_sheet = "run" sprite_sheet_name = sprite_sheet + "_" + self.direction sprites = self.SPRITES[sprite_sheet_name] # so we take the animation_count, we divide it by five, then we mod whatever the line of our sprites is. (So if we have five sprites then when we're on, say, animation, count ten we're showing the second sprite right, it restarts once we get to the end of all of our sprites) sprite_index = (self.animation_count // ANIMATION_DELAY) % len(sprites) # the animation_delay is set to every five frames by the variable animation delay, so for every five frames we want to show a different sprite in whatever animation we're using. (ex. running left running right) = there is a five frame delay before you can change to a different animation self.sprite = sprites[sprite_index] self.animation_count += 1 self.update() def update(self): self.rect = self.sprite.get_rect(topleft=(self.rect.x, self.rect.y)) # this line is to make sure that the rectangle we're using to kind of bound our character is constantly adjusted based on the sprite that we're using self.mask = pygame.mask.from_surface(self.sprite) # This fixes the problem of collision, the problem is that technically our character is drawn on a kinda rectangle box and the collision happens with the rectangle box instead of the collision happening by pixels (so this function makes sure that the collision happens by pixels not by the rectangle that the charaters are drawn on) def draw(self, win, offset_x): win.blit(self.sprite, (self.rect.x - offset_x, self.rect.y)) # this is the position on the screen class Object(pygame.sprite.Sprite): def __init__(self, x, y, width, height, name=None): super().__init__() self.rect = pygame.Rect(x, y, width, height) self.image = pygame.Surface((width, height), pygame.SRCALPHA) self.width = width self.height = height self.name = name def draw(self, win, offset_x): win.blit(self.image, (self.rect.x - offset_x, self.rect.y)) class Block(Object): def __init__(self, x, y, size): super().__init__(x, y, size, size) block = get_block(size) self.image.blit(block, (0, 0)) self.mask = pygame.mask.from_surface(self.image) class Fire(Object): ANIMATION_DELAY = 3 def __init__(self, x, y, width, height): super().__init__(x, y, width, height, "fire") self.fire = load_sprite_sheets('Traps', 'Fire', width, height) self.image = self.fire['on'][0] self.mask = pygame.mask.from_surface(self.image) self.animation_count = 0 def loop(self): sprites = self.fire['on'] sprite_index = (self.animation_count // self.ANIMATION_DELAY) % len(sprites) self.image = sprites[sprite_index] self.animation_count += 1 self.rect.x -= 5 # Adjust the speed as needed if self.rect.right < 0: # Reset position when it goes off the left side self.rect.x = WIDTH self.rect.y = random.randint(100, HEIGHT - block_size - 100) self.rect = self.image.get_rect(topleft=(self.rect.x, self.rect.y)) self.mask = pygame.mask.from_surface(self.image) def spawn_fire(): # this is how the fire randomly spawns on the right side of the screen x = WIDTH + random.randint(100, 400) # Adjust the range as needed y = random.randint(100, HEIGHT - block_size - 100) # Adjust the range as needed return Fire(x, y, 16, 32) def get_background(name): image = pygame.image.load(join('assets','Background', name)) _, _, width, height = image.get_rect() tiles = [] for i in range(WIDTH // width + 1): for j in range(HEIGHT // height + 1): pos = (i * width, j * height) tiles.append(pos) return tiles, image def draw(window, background, bg_image, player, objects, offset_x): for tile in background: window.blit(bg_image, tuple(tile)) for obj in objects: obj.draw(window, offset_x) player.draw(window, offset_x) pygame.display.update() def handle_vertical_collisioin(player, objects, dy): collided_objects = [] for obj in objects: if pygame.sprite.collide_mask(player, obj): if dy > 0: player.rect.bottom = obj.rect.top player.landed() elif dy < 0: player.rect.top = obj.rect.bottom player.hit_head() collided_objects.append(obj) return collided_objects def collide(player, object, dx): player.move(dx, 0) player.update() collided_object = None for obj in object: if pygame.sprite.collide_mask(player, obj): # checking if there where to collided with something if they were to move in that direction collided_object = obj break player.move(-dx, 0) player.update() return collided_object def handle_move(player, objects): keys = pygame.key.get_pressed() player.x_vel = 0 # we essentially set the velocity to zero and then if we are moving left or right, then we change the velocity to be the negative player velocity or the positive collide_left = collide(player, objects, -PLAYER_VEL * 2 ) collide_right = collide(player, objects, PLAYER_VEL * 2 ) # this is so that when we hold down the assigned key it will go in that direction until we let go or press another key for another direction if keys[pygame.K_LEFT] and not collide_left: # this is the keys on the key board in which you can control the game (moving to the left) player.move_left(PLAYER_VEL) if keys[pygame.K_RIGHT] and not collide_right: # moving to the right player.move_right(PLAYER_VEL) vertical_collide = handle_vertical_collisioin(player, objects, player.y_vel) to_check = [collide_left, collide_right, *vertical_collide] for obj in to_check: if obj and obj.name == 'fire': player.make_hit() def main(window, block_size): global last_block_x clock = pygame.time.Clock() background, bg_image = get_background('Pink.png') player = Player(100, 100, 50, 50) fire = Fire(WIDTH, random.randint(100, HEIGHT - block_size - 100), 16, 32) floor = [Block(i * block_size, HEIGHT - block_size, block_size) for i in range(-WIDTH // block_size, (WIDTH * 2) // block_size)] objects = [*floor, Block(0, HEIGHT - block_size * 2, block_size), Block(block_size * 3, HEIGHT - block_size * 4, block_size), fire] offset_x = 0 scroll_area_width = 200 run = True while run: clock.tick(FPS) for event in pygame.event.get(): if event.type == pygame.QUIT: run = False break if event.type == pygame.KEYDOWN: if event.key == pygame.K_SPACE and player.jump_count < 5: player.jump() player.loop(FPS) handle_move(player, objects) if player.rect.right - offset_x >= WIDTH - scroll_area_width: last_block_x += block_size new_block = Block(last_block_x, HEIGHT - block_size, block_size) objects.append(new_block) # Move the existing fire sprite fire.rect.x -= 5 # Adjust the speed as needed if fire.rect.right < 0: # Respawn the fire sprite on the right side fire.rect.x = WIDTH fire.rect.y = random.randint(100, HEIGHT - block_size - 100) draw(window, background, bg_image, player, objects, offset_x) if ((player.rect.right - offset_x >= WIDTH - scroll_area_width) and player.x_vel > 0) or ( (player.rect.left - offset_x <= scroll_area_width) and player.x_vel < 0): offset_x += player.x_vel pygame.display.flip() pygame.quit() quit() if __name__ == "__main__": block_size = 96 main(window, block_size)
What I’m trying to accomplish is I want the fire sprite in my code to be randomly shot from the right side of the screen as the character is moving forward, the character has to dough the fire and if it hits the character 3 times in a row the game is over. I also want the game to force the character to move forward as soon as the character does so. Also not at the same quantity as the fire but I want blocks to appear from the right to help dough the fire. So a Mario / Geometry Dash type of game.
To achieve the desired behavior of having the fire sprite randomly appear from the right side of the screen, the character automatically moving forward, and blocks appearing from the right, you can make the following modifications to your code:
scroll_speed
offset_x
Fire
rect
WIDTH
y
Block
Here are the modifications to your code:
# Add these global variables scroll_speed = 5 block_spawn_interval = 200 # Adjust as needed last_block_spawn_x = 0 # Modify the Fire class class Fire(Object): # ... (unchanged code) def loop(self): sprites = self.fire['on'] sprite_index = (self.animation_count // self.ANIMATION_DELAY) % len(sprites) self.image = sprites[sprite_index] self.animation_count += 1 self.rect.x -= scroll_speed # Adjust the speed as needed if self.rect.right < 0: # Respawn the fire sprite on the right side self.rect.x = WIDTH self.rect.y = random.randint(100, HEIGHT - block_size - 100) self.rect = self.image.get_rect(topleft=(self.rect.x, self.rect.y)) self.mask = pygame.mask.from_surface(self.image) # Modify the main function def main(window, block_size): global last_block_x, last_block_spawn_x, scroll_speed # ... (unchanged code) run = True while run: clock.tick(FPS) for event in pygame.event.get(): # ... (unchanged code) player.loop(FPS) handle_move(player, objects) # Scroll the screen offset_x += scroll_speed # Spawn new blocks if last_block_x - offset_x <= WIDTH: last_block_x += block_size new_block = Block(last_block_x, HEIGHT - block_size, block_size) objects.append(new_block) # Spawn new fire sprites last_block_spawn_x += scroll_speed if last_block_spawn_x >= block_spawn_interval: fire = spawn_fire() objects.append(fire) last_block_spawn_x = 0 # Move the existing fire sprites for obj in objects: if isinstance(obj, Fire): obj.loop() # ... (unchanged code) pygame.display.flip() pygame.quit() quit() if __name__ == "__main__": # ... (unchanged code)
These modifications should create the desired Mario/Geometry Dash-type gameplay with automatic scrolling, random fire spawn, and block spawning. Adjust the parameters like scroll_speed and block_spawn_interval based on your preferences.
block_spawn_interval