1

我目前正在尝试根据游戏植物大战僵尸制作游戏动画。当僵尸在屏幕上移动并接触到植物时,植物精灵会保存在名为plants_eaten_list 的列表中。僵尸也停止向左移动。在 1 秒延迟后(当触发plants_eaten_event 时),植物失去 10 点生命值。当植物的生命值达到 0 时,植物精灵会通过 plant.kill() 命令死亡。

设置计时器的代码 (pygame.time.set_timer(plants_eaten_event, 1000) 需要在主 while 循环内,以便不断检查plants_eaten_list 中的植物。但是,我的问题是每次刷新while循环,plants_eaten_event 的计时器被重置为 1 秒,因此plants_eaten_event 永远不会发生。

有什么办法可以构建这个程序的逻辑来缓解这个问题?

我不拥有任何这些图像的版权,并且这些图像仅供私人使用。僵尸图像从http://plantsvszombies.wikia.com/wiki/Zombie/Gallery?file=Regular_Zombie.png下载 植物图像从http://plantsvszombies.wikia.com/wiki/File:Peashooter.png下载

这是我的代码:

import pygame
import random

BLACK = (0,0,0)
WHITE = (255,255,255)
RED = (255,0,0)
BLUE = (0,0,255)
GREEN = (0,255,0)

pygame.init()

borders_sound = pygame.mixer.Sound("bump.wav")

class Zombie(pygame.sprite.Sprite):
    def __init__(self, x, y):
        pygame.sprite.Sprite.__init__(self)

        self.image = pygame.image.load("Regular_Zombie.png").convert()
        self.image.set_colorkey(WHITE)


        # Make our top-left corner the passed-in location.
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y

        # -- Attributes
        # Set speed vector
        self.change_x = -1
        self.change_y = 0

    def changespeed(self, x, y):

        """ Change the speed of the player"""
        self.change_x += x
        self.change_y += y

    def move(self):
        """ Find a new position for the player"""
        #self.change_x = -1
        self.rect.x += self.change_x
        #print(self.rect.x)
        #times = 0
        if self.rect.x < 0:
            self.rect.x = 0

    def eatplant(self,plant):
        pygame.time.set_timer(plants_eaten_event,6000)
        self.change_x = 0
        plant.health = plant.health - 10

class Plant(pygame.sprite.Sprite):
    def __init__(self, x, y):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load("Peashooter.png").convert()
        self.image.set_colorkey(BLUE)

        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y

        self.alive = True

        self.health = 60

screen_width = 700
screen_height = 400
screen = pygame.display.set_mode([screen_width, screen_height])

all_sprites_list = pygame.sprite.Group()
plants_list = pygame.sprite.Group()
zombies_list = pygame.sprite.Group()

zombie1 = Zombie(690, 15)
plant1 = Plant(300,15)

all_sprites_list.add(zombie1)
all_sprites_list.add(plant1)
plants_list.add(plant1)
zombies_list.add(zombie1)

done = False

clock = pygame.time.Clock()

plants_eaten_event = pygame.USEREVENT + 1

while not done:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            done = True

        # If plant.health = 0 at the end of the timer, the plant is eaten.
        # If plant.health != 0 at the end of the timer, the entire event
        # must happen again in the while loop until the health = 0

        elif event.type == plants_eaten_event:
            print("Zombie 1 ate plant 1")

            zombie1.eatplant(plant)
            # Set timer equal to 0 until another plant gets eaten or zombie continues eating current plant
            pygame.time.set_timer(plants_eaten_event, 0)

            if plant.health == 0:
                plant.alive = False
                plant.kill()

            zombie1.change_x = -1
            zombie1.move()
        elif event.type == zombie_eaten_plant_event:
            zombie1.change_x = -1
            zombie1.move()
            print("Zombie 1 is eating plant")

            pygame.time.set_timer(zombie_eaten_plant_event, 0)

    screen.fill(WHITE)

    zombie1.move()
    if times == 0 and zombie1.rect.x <= 0:
        times = times + 1
        borders_sound.play()

    all_sprites_list.draw(screen)
    all_sprites_list.update()

    # I set the do kill command in spritecollide command to False because I
    # needed to delay the plants that were eaten before they were removed. They
    # will be removed after six seconds(symbolize zombies eating plants). Aftera
    # a period of time, then I will create a loop that will loop through all plants
    # in the plants_eaten_list and use .kill() . I will need to create a separate
    # collision function that will stall the zombies until the plants are killed.

    plants_eaten_list = pygame.sprite.spritecollide(zombie1,plants_list,False)

    # I changed the delay to 6 seconds, and plant 1 still died instantly. The zombie
    # stayed frozen in place for 6 seconds before teh 2nd plant was instantly destroyed

    for plant in plants_eaten_list:

        # If I remove this, plant1 is not killed, even though
        # there is the same code within the plants_eaten_event function.
        if plant1.health == 0:
            plant1.kill()

        # The while loop loops so fast that the timer doesnot have a chance to
        # finish its countdown before it restarts again. Will I need some kind of a threading
        # module to prevent the timers from restarting until they end?
        pygame.time.set_timer(plants_eaten_event, 1000)
    for plant_eaten in plants_eaten_list:
        borders_sound.play()

    clock.tick(60)

    pygame.display.flip()
pygame.quit()
4

1 回答 1

1

我的问题是,对于 while 循环的每次刷新,plants_eaten_event 的计时器都会重置为 1 秒,因此plants_eaten_event 永远不会发生。

发生这种情况是因为僵尸的矩形仍然与下一帧中的植物的矩形发生碰撞,spritecollide返回带有碰撞植物的列表,并且for plant in plants_eaten_list:您调用的循环在set_timer每一帧中再次执行。

您需要做的第一件事是将zombie.rect.left位置设置为plant.rect.right第一次碰撞后,以便精灵在下一帧不再碰撞:

for plant in plants_eaten_list:
    zombie1.change_x = 0  # Stop the zombie.
    # Move the rect so that it doesn't touch the plant's rect anymore.
    zombie1.rect.left = plant.rect.right
    if plant1.health <= 0:
        plant1.kill()
    pygame.time.set_timer(plants_eaten_event, 1000)
    borders_sound.play()

然而,一旦你开始添加更多的僵尸,代码就会中断,因为你在事件循环中使用了zombie1andplant变量(取决于for plant in plants_eaten_list:循环),所以它只对这个僵尸和最后一个碰撞的植物起作用。不幸的是,不能发送比事件类型更多的信息pygame.time.set_timer,否则您可以将特定的僵尸和植物附加到事件中,以便在事件循环中处理它们。

另一种解决方案是给每个僵尸一个自己的计时器属性(只是一个数字),您可以在他们的update方法中更新它。

因此,当僵尸碰到植物时,我通过将其设置为 1(秒)来启动计时器,然后每帧将其递减增量时间(最后一帧所需的时间)。我还停止了僵尸self.change_x = 0,将矩形移回self.rect.left = plant.rect.right并存储对植物的引用,因为我们需要它来在计时器结束时降低其生命值。

一旦计时器是<= 0僵尸再次开始移动,咬一口植物,精灵再次接触并重新启动计时器。

import pygame


WHITE = (255,255,255)
BLUE = (0,0,255)
GREEN = (0,255,0)


class Zombie(pygame.sprite.Sprite):

    def __init__(self, x, y):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((30, 50))
        self.image.fill(BLUE)
        self.rect = self.image.get_rect(topleft=(x, y))
        self.change_x = -1
        self.timer = 0  # Timer for the attacks.
        self.plant = None  # Currently attacked target.

    def update(self, dt):
        """Update the zombie."""
        if self.timer != 0:
            self.timer -= dt
        if self.timer <= 0:  # Move if the timer is inactive.
            self.change_x = -1
            # Check if a plant is touching and eat it.
            if self.plant is not None:
                self.plant.health -= 10
                print('Health', self.plant.health)
                if self.plant.health <= 0:
                    print('Eaten')
                    self.plant.kill()
                    self.plant = None
                    self.timer = 0

        self.rect.x += self.change_x
        if self.rect.x < 0:
            self.rect.x = 0

    def eatplant(self, plants):
        print('Play sound')
        self.change_x = 0  # Stop the zombie.
        self.timer = 1  # Start the timer.
        for plant in plants:
            self.plant = plant  # Store a reference to the plant.
            # Move the zombie back, so that the sprites
            # don't collide anymore.
            self.rect.left = plant.rect.right


class Plant(pygame.sprite.Sprite):

    def __init__(self, x, y):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((30, 50))
        self.image.fill(GREEN)
        self.rect = self.image.get_rect(topleft=(x, y))
        self.health = 30


pygame.init()
screen = pygame.display.set_mode([700, 400])
# No need to assign the sprites to variables.
plants = pygame.sprite.Group(Plant(500, 15), Plant(350, 115))
zombies = pygame.sprite.Group(Zombie(690, 15), Zombie(690, 115))
all_sprites = pygame.sprite.Group(plants, zombies)

clock = pygame.time.Clock()
dt = 0
done = False

while not done:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            done = True

    # Pass the delta time to the update methods of the sprites.
    all_sprites.update(dt)

    # groupcollide returns a dictionary of the collided zombies and plants.
    plants_eaten = pygame.sprite.groupcollide(zombies, plants, False, False)
    for zombie, collided_plants in plants_eaten.items():
        zombie.eatplant(collided_plants)

    screen.fill(WHITE)
    all_sprites.draw(screen)

    dt = clock.tick(60) / 1000  # Time in seconds since last tick.
    pygame.display.flip()

pygame.quit()
于 2018-07-21T07:17:48.603 回答