Ролевая игра в подземелье с видом сверху

Я делаю мини-игру в подземелье с видом сверху, где вы рыцарь и можете атаковать зомби, взмахивая мечом. Я пока не реализовал столкновение, игрок rect(), враг rect(), экран завершения игры или анимация меча игрока, но весь код, который у меня есть, работает хорошо, даже несмотря на то, что основной цикл переполнен. Мне было интересно, как я могу сжать код или упростить его?

import pygame, random, os
from pygame.locals import *

pygame.init()

scr_width = 1020
scr_height = 510
screen = pygame.display.set_mode((scr_width, scr_height))
pygame.display.set_caption('Dungeon Minigame')

clock = pygame.time.Clock()

images = {}
path="Desktop/Files/Dungeon Minigame/"
filenames = [f for f in os.listdir(path) if f.endswith('.png')]
for name in filenames:
    imagename = os.path.splitext(name)[0]
    images[imagename] = pygame.image.load(os.path.join(path, name))

font = pygame.font.SysFont('Times_New_Roman', 27)

white = [240, 240, 240]

def main_menu():

    onclick = False

    while True:

        mx, my = pygame.mouse.get_pos()

        screen.blit(images['background'], (0, 0))

        button = screen.blit(images['button'], (480, 300))

        if button.collidepoint((mx, my)):
            if onclick:
                game()

        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()

            if event.type == MOUSEBUTTONDOWN:
                onclick = True

        pygame.display.update()
        clock.tick(60)

def game():

    lives = 3
    score = 0

    def player(x, y):
        screen.blit(images['r_knight'], (playerX, playerY))

    class Enemy:
        def __init__(self):
            self.x = random.randint(8, 800)
            self.y = random.randint(8, 440)
            self.moveX = 0
            self.moveY = 0

        def move(self, speed = 1):
            if self.x > playerX:
                self.x -= speed
            elif self.x < playerX:
                self.x += speed

            if self.y < playerY:
                self.y += speed
            elif self.y > playerY:
                self.y -= speed

        def draw(self):
            screen.blit(images['r_zombie'], (self.x, self.y))

    def enemy(x, y):
        screen.blit(images['r_zombie'], (x, y))

    enemy_list = []
    
    for i in range(4):
        new_enemy = Enemy()
        enemy_list.append(new_enemy)

    playerX = 510
    playerY = 220

    while True:

        screen.blit(images['background'], (0, 0))

        score_text = font.render('Score: ' + str(score), True, white)
        lives_text = font.render('Lives: ', True, white)

        screen.blit(score_text, (20, 20))
        screen.blit(lives_text, (840, 20))

        screen.blit(images['r_knight'], (playerX, playerY))

        if lives == 3:
            screen.blit(images['triple_heart'], (920, 0))

        if lives == 2:
            screen.blit(images['double_heart'], (920, 0))

        if lives == 1:
            screen.blit(images['single_heart'], (920, 0))

        if lives <= 0:
            screen.blit(images['triple_empty_heart'], (920, 0))
            if lives < 0:
                lives = 0

        onpress = pygame.key.get_pressed()

        if onpress[pygame.K_a]:
            playerX -= 3
            screen.blit(images['l_knight'], (playerX, playerY))

        if onpress[pygame.K_w]:
            playerY -= 3
            screen.blit(images['l_knight'], (playerX, playerY))

        if onpress[pygame.K_d]:
            playerX += 3
            screen.blit(images['r_knight'], (playerX, playerY))

        if onpress[pygame.K_s]:
            playerY += 3
            screen.blit(images['r_knight'], (playerX, playerY))

        if onpress[pygame.K_w] and onpress[pygame.K_a]:
            screen.blit(images['l_knight'], (playerX, playerY))

        if onpress[pygame.K_s] and onpress[pygame.K_a]:
            screen.blit(images['l_knight'], (playerX, playerY))

        if onpress[pygame.K_w] and onpress[pygame.K_d]:
            screen.blit(images['r_knight'], (playerX, playerY))

        if onpress[pygame.K_s] and onpress[pygame.K_d]:
            screen.blit(images['r_knight'], (playerX, playerY))

        if playerX <= -8:
            playerX = -8
        elif playerX >= 965:
            playerX = 965

        if playerY <= 5:
            playerY = 5
        elif playerY >= 440:
            playerY = 440
        
        for enemy in enemy_list:
            enemy.move()
            enemy.draw()

        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()

        clock.tick(60)
        pygame.display.update()
main_menu()

Выход

5 ответов
5

Используйте GLOBALS для постоянных настроек, положений и т. Д.

В Python у нас нет синтаксиса для объявления переменной, которая больше не может изменяться, но среди сообщества очень хорошо понимается, что переменные CAPS LOCK должны рассматриваться как константы, т. Е. Переменные, которые никогда не изменятся.

Таким образом, ваши переменные, которые вы создаете и которые не должны изменяться, на самом деле могут быть определены в верхнем регистре, например SCR_WIDTH а также SCR_HEIGHT.

Более того, на протяжении всего кода у вас здесь и там есть «магические числа», которые вы определили, но при чтении кода неясно, как они возникли. Например, вы всегда следите за тем, чтобы позиция игрока по оси X оставалась между -8 а также 965, но почему эти ценности ..? Более того, эти «магические» числа имеют тенденцию появляться более чем в одном месте, поэтому, если вы измените одно, вам придется искать все вхождения этого числа, чтобы изменить его снова. Более того, что произойдет, если два или более магических числа окажутся одинаковыми ?! Код становится действительно запутанным.

Поэтому рекомендуется взять все такие магические числа и / или константы и дать им имена. Например, мне нравится, когда мои константы для игры находятся в начале скрипта. Глядя на ваш код, я бы остановился на некоторых примерах:

SCR_WIDTH, SCR_HEIGHT = 1024, 512
WHITE = [240, 240, 240]
PLAYER_LIVES_POSITION = (920, 0)
PLAYER_X_BETWEEN = (-8, 965)
PLAYER_Y_BETWEEN = (5, 440)
ENEMY_X_BETWEEN = (8, 800)
ENEMY_Y_BETWEEN = (8, 440)
FPS = 60

    Оптимизация блиттинга плеера

    Вы хорошо поработали, осознав, что можете упростить часть кода, потому что у вас есть шаблоны, которые повторяются снова и снова. Это всегда хороший признак того, что можно сделать что-то более короткое.

    Взглянув на серию ifЕсли вам нужно скопировать правильные изображения, вот возможный вариант, чтобы сделать его более сжатым, как вы говорите.

    Вычислить изменения позиции игрока

    # ...
    onpress = pygame.key.get_pressed()
    
    dx = 0
    if onpress[pygame.K_a]:
        dx -= 3
    if onpress[pygame.K_d]:
        dx += 3
    playerX += dx
    
    dy = 0
    if onpress[pygame.K_w]:
        dy -= 3
    if onpress[pygame.K_s]:
        dy += 3
    playerY += dy
    

    Blit Knight после этого

    Обратите внимание, что в вашем коде 8 if которые не являются исключительными, поэтому, если в определенный момент игрок нажимает на AWSD в то же время ваша игра будет делать восемь блитов друг над другом, что необязательно.

    Если вы вычисляете изменения положения, как я показал выше, или любым другим способом, который вам больше нравится, вы можете использовать dx а также dy чтобы решить, какой образ игрока копировать. Например, вы можете просто посмотреть на dx который сообщает вам, переместился ли игрок влево или вправо, и соответственно мигает:

    if dx > 0:
        screen.blit(images['r_knight'], (playerX, playerY))
    elif dx < 0:
        screen.blit(images['l_knight'], (playerX, playerY))
    

    Внимание: Код, которым я поделился, нет строго эквивалентен вашему, и я взял на себя смелость интерпретировать ваш намерения, пожалуйста, дайте мне знать, если моя интерпретация неверна.

    • Есть ли способ реализовать элементы управления Player в классе? возможно, используя self.x а также self.y вместо dx и dy? @RGS

      — Зельда


    • @Zelda, вы имеете в виду только элементы управления или весь объект игрока?

      — РГО

    • Вся сущность игрока, но включая элементы управления, поскольку позже мне нужно добавить анимацию в указанный класс игрока. Изображения уже загружены в for name in filenames(): функция. Класс будет определять текущую позицию игрока, а затем, после ввода wsad, изменять эти значения и иметь функцию обновления, которая выводит его на экран.

      — Зельда


    • @Zelda Вы определенно можете это сделать, если вам нужно вдохновение, у меня есть две мини-игры это репо где я использовал WASD для перемещения игрока внутри класса игрока, agarIO и dumbfire единицы. В голубь один также определяет класс для объекта игрока. Дайте ему лучший шанс, а затем разместите свой новый код здесь, чтобы мы могли его просмотреть 🙂

      — РГО

    Загрузка изображений с помощью pathlib

    В настоящее время вы загружаете свои изображения так:

    import os
    
    images = {}
    path="Desktop/Files/Dungeon Minigame/"
    filenames = [f for f in os.listdir(path) if f.endswith('.png')]
    for name in filenames:
        imagename = os.path.splitext(name)[0]
        images[imagename] = pygame.image.load(os.path.join(path, name))
    

    Если вы обратитесь к pathlib, который, как я понимаю, является более современной альтернативой os.path, вы видите, что можете переписать загрузку изображения как таковую:

    import pathlib
    
    images = {}
    path = pathlib.Path('Desktop/Files/Dungeon Minigame/')
    for img_path in path.glob("*.png"):
        images[img_path.name] = pygame.image.load(img_path)
    

    или, возможно, даже немного короче, если вам больше нигде не нужен исходный путь:

    import pathlib
    
    images = {}
    for img_path in pathlib.Path('Desktop/Files/Dungeon Minigame/').glob("*.png"):
        images[img_path.name] = pygame.image.load(img_path)
    

    Заметьте, что короче не всегда лучше, я просто стараюсь не создавать слишком много переменных, которые используются только один раз.

    Однако, если вам придется загружать много ресурсов из той же папки, вы, вероятно, захотите объявить ее как переменную во всех CAPS LOCK (что является универсальным способом сигнализировать о постоянной глобальной переменной), а затем использовать ее в цикле. для загрузки изображений и в другом месте.

    Отсечение позиции игрока

    Есть несколько мест, где вы выполняете довольно распространенную операцию, которая заключается в обеспечении значения v находится между нижней границей l и верхняя граница u, и принуждение v быть там, если это не так. Это обычно называется отсечением, и вы делаете это в нескольких местах (более заметно, когда имеете дело с позицией игрока), поэтому вы можете фактически создать короткую функцию, которая сделает это за вас, что делает остальную часть кода более сложной. к точке.

    Мы хотим сократить этот код:

    if playerX <= -8:
        playerX = -8
    elif playerX >= 965:
        playerX = 965
    
    if playerY <= 5:
        playerY = 5
    elif playerY >= 440:
        playerY = 440
    

    Например, если вы установить clip_values, ты можешь сделать

    from clip_values import clip
    playerX = clip(playerX).between_(-8).and_(965)
    playerY = clip(playerY).between_(5).and_(440)
    

    Если вы не хотите ничего устанавливать, вы также можете сделать что-то вроде

    def clip(value, lower, upper):
        return min(upper, max(value, lower))
    

    который затем используется следующим образом:

    playerX = clip(playerX, -8, 965)
    playerY = clip(playerY, 5, 440)
    

    Вырезать жизни

    Теперь, когда у вас есть это clip функции, вы можете взглянуть на это:

            if lives == 3:
                screen.blit(images['triple_heart'], (920, 0))
    
            if lives == 2:
                screen.blit(images['double_heart'], (920, 0))
    
            if lives == 1:
                screen.blit(images['single_heart'], (920, 0))
    
            if lives <= 0:
                screen.blit(images['triple_empty_heart'], (920, 0))
                if lives < 0:
                    lives = 0
    

    То, как написан этот код, заставляет его выглядеть так, будто у пользователя всегда от 0 до 3 жизней, а затем вы хотите воспроизвести соответствующее изображение жизней, которые есть у пользователя. Если вы начнете с вырезания количества жизней, [0, 1, 2, 3], тогда вы можете определить программно изображение для копирования:

    heart_images = ["triple_empty_heart", "single_heart", "double_heart", "triple_heart"]
    lives = clip(lives).between_(0).and_(3)
    screen.blit(images[heart_images[lives]], (920, 0))
    

    • Хорошо, извините, я исправил весь код, который у меня есть, и я работаю над анимацией меча 🙂

      — Зельда

    • @Zelda, хорошо, удачи. Я только что отредактировал, чтобы добавить еще одну альтернативу вырезанию, которую, возможно, будет легче читать.

      — РГО

    • 1

      Я понял предыдущий код

      — Зельда

    Создать класс игрока

    У игрока много общего с вашими зомби:

    1. Есть логика в move() игрок.
    2. И у игроков, и у монстров непреодолимый образ.
    3. И у игроков, и у монстров есть «лицевое» направление.
    4. У игроков и у монстров есть скорость передвижения.

    Там достаточно, чтобы сформировать базовый класс:

    class Mobile:
        def __init__(self, **kwargs):
            self.x = kwargs['x']
            self.y = kwargs['y']
            self.speed = kwargs['speed']
            self.facing = kwargs['facing']
            self.images = ...
    
        def move(self): ...
    
        def draw(self):
            screen.blit(self.images[self.facing], (self.x, self.y))
    

    В .move() для Monsters and Players очевидно другое — монстры будут просто преследовать игрока, как вы и запрограммировали. Игроки move() может содержать логику, необходимую для проверки нажатых клавиш и фиксации положения, хотя я думаю, что вы настраиваете положение, а затем рисуете изображение, тогда зажимая положение, что кажется ошибкой.

    Разбейте свой код на функции и методы

    Объясните, что делается. Передайте любые аргументы, которые вам нужно передать.

    Я бы переписал ваш основной цикл так:

    mobiles = [Player(), *[Monster() for _ in range(4)]]
    still_playing = True
    
    while still_playing:
    
        draw_background()
    
        for mob in mobiles:
            mob.move()
            mob.draw()
    
        still_playing = was_quit_pressed()
    
        pygame.display.update()
        clock.tick(60)
    

      Добавить комментарий

      Ваш адрес email не будет опубликован. Обязательные поля помечены *