Невозможно создать два экземпляра класса (используя pygame, пытаясь создать змеиную игру с двумя змеями)

Я создал класс, который определяет и создает змею с помощью pygame. Когда я создаю один экземпляр класса змеи, одна змея будет создана правильно, но когда я попытаюсь создать два экземпляра, мой код не создаст вторую змею. Я не уверен, что это из-за того, что я неправильно использовал pygame, или потому что я делаю что-то неправильно в классах.

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

import os
os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide"; import pygame
import random
import time
import copy
black = pygame.Color(0, 0, 0)

class Snake():
    def __init__(self, game_window, controls, color, position, speed, window_specs,can_touch=True,respawn=True):

        self.game_window = game_window
        self.color = color
        self.window_x = window_specs[0]
        self.window_y = window_specs[1]

        self.can_touch = can_touch
        self.respawn = respawn

        self.snake_position = position.copy()
        self.snake_body = [position.copy()]
        self.speed = speed
        self.controls = controls.copy()
        
        self.direction = 'RIGHT'
        self.change_to = self.direction
        
        self.moving = True
        self.fps = pygame.time.Clock()
        self.start_moving()

    def start_moving(self):
        #Handles the controls and movement. Control first snake with 'wasd'. Control second snake with arrow keys.
        while self.moving == True:
            for event in pygame.event.get():
                if event.type == pygame.KEYDOWN:
                    if event.key == self.controls[0]:
                        self.change_to = 'UP'
                    if event.key == self.controls[1]:
                        self.change_to = 'LEFT'
                    if event.key == self.controls[2]:
                        self.change_to = 'DOWN'
                    if event.key == self.controls[3]:
                        self.change_to = 'RIGHT'
            if self.change_to == 'UP' and self.direction != 'DOWN':
                self.direction = 'UP'
            if self.change_to == 'DOWN' and self.direction != 'UP':
                self.direction = 'DOWN'
            if self.change_to == 'LEFT' and self.direction != 'RIGHT':
                self.direction = 'LEFT'
            if self.change_to == 'RIGHT' and self.direction != 'LEFT':
                self.direction = 'RIGHT'

            if self.direction == 'UP':
                self.snake_position[1] -= 10
            if self.direction == 'LEFT':
                self.snake_position[0] -= 10
            if self.direction == 'DOWN':
                self.snake_position[1] += 10
            if self.direction == 'RIGHT':
                self.snake_position[0] += 10
            self.snake_body.insert(0, list(self.snake_position))
            self.snake_body.pop()
            
            self.game_window.fill(black)
            for pos in self.snake_body:
                pygame.draw.rect(self.game_window,self.color,pygame.Rect(pos[0],pos[1],10,10))

            #Toggles whether snake will die if touching itself
            if self.can_touch == False:
                for block in self.snake_body[1:]:
                    if self.snake_position[0] == block[0] and self.snake_position[1] == block[1]:
                        self.die()

            #Kills snake if snake touches the edge of the window
            if self.snake_position[0] < 0 or self.snake_position[0] > (self.window_x - 10):
                self.die()
            if self.snake_position[1] < 0 or self.snake_position[1] > (self.window_y - 10):
                self.die()
            pygame.display.update()
            self.fps.tick(self.speed)

    def die(self):
        
        #Reset the snake
        self.moving = False
        self.game_window.fill(black)
        self.snake_body = []
        self.position = []
        pygame.display.update()
        time.sleep(1)
        
        #Makes the snake respawn in a random location
        if self.respawn == True:
            self.direction = 'RIGHT'
            self.change_to = self.direction
            self.snake_position = [random.randrange(1, (self.window_x//100)) * 10,
                     random.randrange(1, (self.window_y//100)) * 10]
            self.snake_body = [self.snake_position.copy()]
            self.moving = True
            self.start_moving()

class App():
    def __init__(self,num_players,can_touch=True,respawn=True):
        self.controls = [[pygame.K_w, pygame.K_a, pygame.K_s, pygame.K_d],
                                [pygame.K_UP,pygame.K_LEFT,pygame.K_DOWN,pygame.K_RIGHT]]
        self.starting_positions = [[100,50], [200, 100]]
        self.speed = 15
        self.window_specs = [720,480]
        self.num_players = num_players
        self.can_touch = can_touch
        self.respawn = respawn
        self.players = {}

        #Start up pygame
        pygame.init()
        pygame.display.set_caption('Snake Game')
        self.game_window = pygame.display.set_mode((self.window_specs[0], self.window_specs[1]))

        self.get_players()

    def get_players(self):
        #Let the players choose what color they want
        player_color = []
        for i in range(self.num_players):
            player_color.append(input(f'Player {i+1}, what color do you want your snake to be? '))
            if player_color[i] == 'red':
                player_color[i] = pygame.Color(255,0,0)
            elif player_color[i] == 'green':
                player_color[i] = pygame.Color(0,255,0)
            elif player_color[i] == 'blue':
                player_color[i] = pygame.Color(0,0,255)
            else:
                player_color[i] = pygame.Color(255,255,255)

        #Create the Snakes, this is the bit that does not work.
        for i in range(self.num_players):
            self.players[f'player{i+1}'] = Snake(self.game_window,self.controls[i],player_color[i],self.starting_positions[i],self.speed,self.window_specs)

if __name__ == "__main__":
    num_players = int(input('1 or 2 players: '))
    App(num_players)
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
0
44
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Проблема здесь в том, что вы не настроили специальный игровой цикл для этой игры. «Игровой цикл» для вашей игры прямо сейчас находится внутри отдельного объекта-змеи. Ваша функция Snake.start_moving — это то, что продолжает работать, и где вы видите, как движется ваша змея и т. д. В случае, когда вы создаете одну змею, вы запускаете функцию start_moving этой змеи, поэтому на поверхности все выглядит нормально.

Если вы работаете с двумя змеями, вызывается функция start_moving первой змеи, и вы застреваете там. Вторая змея еще не создана вообще. Чтобы убедиться в этом, попробуйте добавить, если event.key равен q, return из функции. Затем вы увидите, что когда вы попросите двух змей, первая змея побежит, затем вы нажмете q, первая змея исчезнет и появится вторая змея.

Способ решить вашу проблему — создать специальный игровой цикл в вашем проекте и делегировать ему все обновления вашей игры. Есть несколько разных способов сделать это, поэтому я оставлю это вам для разработки. Но в общих чертах это то, что я рекомендую в псевдокоде:

snakes = [Snake() for _ in range(numplayers)]

while True:
    for event in pg.events():
        if event is keypress:
            for snake in snakes:
                snake.handle_keys(event.key)
            
    for snake in snakes:
        snake.update(dt)

    for snake in snakes:
        snake.draw(display)
    display.flip()
    tick() # or calculate dt

Привет, Джек, спасибо за ответ! Я просто хотел бы спросить, поскольку я перемещаю игровой цикл в свой класс App(), должен ли игровой цикл обрабатывать pygame.time.Clock()? Прямо сейчас я поместил это в класс Snake.

PythonHacks999 21.03.2022 13:41

Я бы сказал так, да, я обновлю часть псевдокода в своем ответе, чтобы отразить это. В общем, я бы рекомендовал, чтобы игровые объекты не управляли состоянием игры, это должен делать сам игровой цикл, который должен быть отделен от объектов. Также обратите внимание, что в псевдокоде я просто использую некоторые функции, которые должен иметь класс змеи в игровом цикле, скрывая любые детали реализации.

Jack 'jryzkns' Zhou 21.03.2022 18:33

Хорошо, спасибо, что наставили меня на правильный путь! Теперь я лучше понимаю :)

PythonHacks999 22.03.2022 01:03
Ответ принят как подходящий

Вам нужен один цикл приложения для всего приложения, а не отдельное приложение для каждой змейки. Удалите цикл приложения из класса Snake:

class Snake():
    def __init__(self, game_window, controls, color, position, window_specs,can_touch=True,respawn=True):

        self.game_window = game_window
        self.color = color
        self.window_x = window_specs[0]
        self.window_y = window_specs[1]

        self.can_touch = can_touch
        self.respawn = respawn

        self.snake_position = position.copy()
        self.snake_body = [position.copy()]
        self.controls = controls.copy()
        self.direction = (1, 0)
        
    def move(self, event_list):
        #Handles the controls and movement. Control first snake with 'wasd'. Control second snake with arrow keys.
        for event in event_list:
            if event.type == pygame.QUIT:
                pygame.quit()
            if event.type == pygame.KEYDOWN:
                if event.key == self.controls[0] and self.direction[1] <= 0:
                    self.direction = (0, -1)
                if event.key == self.controls[1] and self.direction[0] <= 0:
                    self.direction = (-1, 0)
                if event.key == self.controls[2] and self.direction[1] >= 0:
                    self.direction = (0, 1)
                if event.key == self.controls[3] and self.direction[0] >= 0:
                    self.direction = (1, 0)

        self.snake_position[0] += self.direction[0] * 10
        self.snake_position[1] += self.direction[1] * 10
        self.snake_body.insert(0, list(self.snake_position))
        self.snake_body.pop()
        
        for pos in self.snake_body:
            pygame.draw.rect(self.game_window,self.color,pygame.Rect(pos[0],pos[1],10,10))

        #Toggles whether snake will die if touching itself
        if self.can_touch == False:
            for block in self.snake_body[1:]:
                if self.snake_position[0] == block[0] and self.snake_position[1] == block[1]:
                    self.die()

        #Kills snake if snake touches the edge of the window
        if self.snake_position[0] < 0 or self.snake_position[0] > (self.window_x - 10):
            self.die()
        if self.snake_position[1] < 0 or self.snake_position[1] > (self.window_y - 10):
            self.die()

    def die(self):
        self.snake_body = []
        self.position = []
        
        #Makes the snake respawn in a random location
        if self.respawn == True:
            self.direction = (1, 0)
            self.snake_position = [random.randrange(1, (self.window_x//100)) * 10,
                     random.randrange(1, (self.window_y//100)) * 10]
            self.snake_body = [self.snake_position.copy()]

Но добавьте цикл приложения в App:

class App():
    # [...]

    def get_players(self):
        #Let the players choose what color they want
        player_color = []
        color_dict = {'red': (255, 0, 0), 'green': (0, 255, 0), 'blue': (0, 0, 255)}
        for i in range(self.num_players):
            color = input(f'Player {i+1}, what color do you want your snake to be? ')
            player_color.append(color_dict[color] if color in color_dict else (255, 255, 255))

        #Create the Snakes, this is the bit that does not work.
        for i in range(self.num_players):
            self.players[f'player{i+1}'] = Snake(self.game_window,self.controls[i],player_color[i],self.starting_positions[i],self.window_specs)

        clock = pygame.time.Clock()
        run = True
        while run: 
            clock.tick(self.speed)  
            event_list = pygame.event.get()
            for event in event_list:
                if event.type == pygame.QUIT:
                    run = False

            self.game_window.fill(black)  
            for key in self.players:
                self.players[key].move(event_list)
            pygame.display.update()     
                   
        pygame.quit()

Другие вопросы по теме