小能豆

I having trouble with a game that im trying to model after the Mario game, im building it with python

python

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.


阅读 81

收藏
2023-12-22

共1个答案

小能豆

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:

  1. Auto-Scrolling: Create a variable to represent the scrolling speed (e.g., scroll_speed). In the game loop, update the offset_x by adding the scrolling speed in each iteration. This will give the effect of the character moving forward automatically.
  2. Random Fire Spawn: Modify the Fire class to initialize the rect at the rightmost edge of the screen (WIDTH) and at a random y position. In the game loop, update the Fire sprite’s position by subtracting a certain amount in each iteration, simulating the movement from right to left.
  3. Block Spawn: Similar to the fire, create a Block spawn mechanism. Spawn a new block when the right edge of the screen is reached.

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.

2023-12-22