Я создал класс, который определяет и создает змею с помощью 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)
Проблема здесь в том, что вы не настроили специальный игровой цикл для этой игры. «Игровой цикл» для вашей игры прямо сейчас находится внутри отдельного объекта-змеи. Ваша функция 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
Я бы сказал так, да, я обновлю часть псевдокода в своем ответе, чтобы отразить это. В общем, я бы рекомендовал, чтобы игровые объекты не управляли состоянием игры, это должен делать сам игровой цикл, который должен быть отделен от объектов. Также обратите внимание, что в псевдокоде я просто использую некоторые функции, которые должен иметь класс змеи в игровом цикле, скрывая любые детали реализации.
Хорошо, спасибо, что наставили меня на правильный путь! Теперь я лучше понимаю :)
Вам нужен один цикл приложения для всего приложения, а не отдельное приложение для каждой змейки. Удалите цикл приложения из класса 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()
Привет, Джек, спасибо за ответ! Я просто хотел бы спросить, поскольку я перемещаю игровой цикл в свой класс App(), должен ли игровой цикл обрабатывать pygame.time.Clock()? Прямо сейчас я поместил это в класс Snake.