Вы должны использовать класс Sprite
по назначению: определить хотя бы свойство image
и rect
и поместить логику каждого игрового объекта в метод update
.Не нужно обрабатывать рисование самостоятельно.
Тогда ваш основной цикл станет очень четким и классическим циклом из трех частей, в котором вы по порядку сделаете следующее:
- обработка событий
- обновить состояние игры
- нарисовать все
Вот как это может выглядеть (обратите внимание на комментарии, где я объясняю некоторые вещи):
import pygame
import itertools
pygame.init()
SCREENWIDTH = 1000
SCREENHEIGHT = 650
screen = pygame.display.set_mode([SCREENWIDTH, SCREENHEIGHT])
screen.fill((255, 123, 67))
pygame.draw.rect(screen, (0, 255, 188), (50, 50, 900, 575), 0)
# let's create a background surface that we can reuse instead of
# drawing manually to the screen
background = screen.copy()
clock = pygame.time.Clock()
class Player(pygame.sprite.Sprite):
# the player sprite is constant, so let's use a class variable
sprite = pygame.image.load("Sprites/player.png")
def __init__(self, *groups):
# we want to use sprites, so we have to call __init__ of the Sprite class
# groups is a list of groups we want this sprite to add to
super().__init__(*groups)
# the image of the sprite needs to be in an attribute called 'image'
self.image = Player.sprite
# for pygame to know where to draw the sprite, we need a 'rect' attribute
self.rect = self.image.get_rect(topleft=(445, 550))
def update(self):
# moving by keyboard is unique to the player class, so let's to this here
key = pygame.key.get_pressed()
dist = 3
# we just update our rect's position to move the sprite
# we should use vectors here, too, but for now this is good enough
if key[pygame.K_DOWN]:
self.rect.y += dist
elif key[pygame.K_UP]:
self.rect.y -= dist
if key[pygame.K_RIGHT]:
self.rect.x += dist
elif key[pygame.K_LEFT]:
self.rect.x -= dist
class Opponent(pygame.sprite.Sprite):
def __init__(self, sprite, *groups):
super().__init__(*groups)
self.image = sprite
self.rect = self.image.get_rect(topleft=(445, 30))
# we keep an additional attribute 'pos' to store the postion
# it's a vector so we can use some vector math
self.pos = pygame.Vector2(self.rect.topleft)
# we want to move the Opponent in a specific pattern
# so let's keep a list of points we want to move to
# 'cycle' will generate and "endless loop" of this points
self.path = itertools.cycle(((445, 30), (345, 235), (90, 115), (490, 80), (850, 250), (745, 110)))
# we can use 'next' to get the next position
self.next_point = pygame.Vector2(next(self.path))
# maybe we want to change the speed of the opponent
self.speed = 1
# we use ticks to store the milliseconds since the game started later
# this allows us to do thing over time
self.ticks = 1000
# a list of bullets we want to shoot later
self.queue = []
def update(self):
# so we want to move to a specific point
# we use some vector math to get the direction we have to move to
move = self.next_point - self.pos
move_length = move.length()
if move_length != 0:
# since 'move' is the vector between 'pos' and 'next_point'
# we have to normalize it so it just points into the right
# direction at a certain length instead of all the way from
# 'pos' and 'next_point'
move.normalize_ip()
move = move * self.speed
self.pos += move
# if we are already at the target position (or overshoot it)
# we take the next position from 'path'
if move.length() == 0 or move_length < self.speed:
self.next_point = pygame.Vector2(next(self.path))
# we update the 'rect' position so pygame draws the sprite at the right position on the screen
self.rect.topleft = self.pos
# so let's count some time. Every 3000ms passed, we want to shoot some bullets
if pygame.time.get_ticks() - self.ticks > 3000:
self.ticks = pygame.time.get_ticks()
self.shoot()
# see how much time passed since the last shooting
time_gone = pygame.time.get_ticks() - self.ticks
for bullet in self.queue:
# the first value of the tuples in the 'queue' describes when to shoot the bullet
if bullet[0] <= time_gone:
# create the bullet and add them to the 'sprites' and 'bullets' groups
# 'bullets' isn't used yet, but you can use it later for collision detection
Bullet(self.rect.center, bullet[1], sprites, bullets)
# now remove all bullets that have been fired
self.queue = [bullet for bullet in self.queue if bullet[0] > time_gone]
def shoot(self):
bullet_speed = 4
# this list describes the pattern of the attack
# the first value is the time when to shoot the bullet (afer X ms)
# the second value is the movement vector of the bullet
pattern = ((0, pygame.Vector2(-0.5, 1) * bullet_speed),
(0, pygame.Vector2( 0, 1) * bullet_speed),
(0, pygame.Vector2(0.5, 1) * bullet_speed),
(150, pygame.Vector2(-0.5, 1) * bullet_speed),
(150, pygame.Vector2( 0, 1) * bullet_speed),
(150, pygame.Vector2(0.5, 1) * bullet_speed),
(300, pygame.Vector2(-0.5, 1) * bullet_speed),
(300, pygame.Vector2( 0, 1) * bullet_speed),
(300, pygame.Vector2(0.5, 1) * bullet_speed))
self.queue = pattern
class Bullet(pygame.sprite.Sprite):
sprite = pygame.image.load("Sprites/purple-glowey.png")
def __init__(self, pos, direction, *groups):
super().__init__(*groups)
self.image = Bullet.sprite
self.rect = self.image.get_rect(topleft=pos)
self.direction = direction
self.pos = pygame.Vector2(self.rect.topleft)
def update(self):
# just move along the direction
self.pos += self.direction
self.rect.topleft = (self.pos.x, self.pos.y)
# if no longer on screen, remove from all groups
if not screen.get_rect().colliderect(self.rect):
self.kill()
sprites = pygame.sprite.Group()
bullets = pygame.sprite.Group()
player = Player(sprites)
Minty = Opponent(pygame.image.load("Sprites/minty.png"), sprites)
def main():
running = True
while running:
for events in pygame.event.get():
if events.type == pygame.QUIT:
return
# update all sprites, a.k.a. game logic
sprites.update()
# draw everything
screen.blit(background, (0, 0))
sprites.draw(screen)
pygame.display.update()
clock.tick(60)
if __name__ == '__main__':
main()