繁体   English   中英

我在 pygame 中遇到敌人问题

[英]I am having trouble with enemies in pygame

我一直在尝试为我的蛇游戏创建一个敌人 class,我可以在我的 pygame循环中执行,但是我的fill_screen() function 一直在屏幕上,所以屏幕上没有一个敌人是敌人. 我希望有人可以帮助我解决这个问题,因为我已经研究了 3 天多,但我还没有找到任何实际的解决方案。 一个例子真的很有帮助。 我的代码附在下面。

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

pygame.init()
clock=pygame.time.Clock()

movement="nil"

enemy_list = []
enemy_img = pygame.image.load(r'C:\Users\hudso\Documents\PyGame\__pycache__\Images\mele_enemy.png')
SPAWNENEMY = pygame.USEREVENT
pygame.time.set_timer(SPAWNENEMY,1000)

cooled=False

food_created=False
food_counter=0

cooldown_tracker=0

window_size=[1000,1000]

screen=pygame.display.set_mode((window_size[0],window_size[1]),RESIZABLE)
display=pygame.Surface((400,400))

snake_health=100
snake_pos=[188,188]
enemy_pos=[100,100]

snake_img=pygame.image.load(r"C:\Users\hudso\Documents\PyGame\__pycache__\Images\snake.png")

def new_food_pos():
    food_x=random.randint(25,350)
    food_y=random.randint(25,350)
    food_coords=[food_x,food_y]
    return food_coords

class snake():
    def __init__(self,snake_position,snake_hitpoints):
        self.snake_position=snake_position
        self.snake_hp=snake_hitpoints

      
    def create_snake(self):
        global snake_img
        display.blit(snake_img,(self.snake_position[0],self.snake_position[1]))
        pygame.draw.rect(display,(0,0,0),[self.snake_position[0]-3,self.snake_position[1]-7,31,5])
        pygame.draw.rect(display,(212,23,48),[self.snake_position[0]-1,self.snake_position[1]-6,27,3])
        sub_hp=self.snake_hp-100
        take_health=abs(sub_hp)
        gray_health=(take_health*0.27)
        pygame.draw.rect(display,(40,40,40),[self.snake_position[0]-0+self.snake_hp*.27,self.snake_position[1]-6,gray_health,3])
    
    def move_snake(self,moving):
        global snake_img
        if moving=="up":
            snake_pos[1]-=1
        if moving=="down":
            snake_pos[1]+=1
        if moving=="right":
            snake_pos[0]+=1
        if moving=="left":
            snake_pos[0]-=1
        if moving=="nil":
            display.blit(snake_img,(self.snake_position[0],self.snake_position[1]))

food_pos=new_food_pos()

class food():
        
    def __init__(self):
        global food_pos
        self.food_location=food_pos
        self.food=pygame.image.load(r"C:\Users\hudso\Documents\PyGame\__pycache__\Images\apple.png")
        
    def create_food(self):
        display.blit(self.food,(self.food_location[0],self.food_location[1]))

class Enemys:
    def __init__(self):
            #self.xval = random.randint(0,700)
            self.size = random.randint(50,50)
    def create_enemy(self):
        Enemy = pygame.Rect(random.randint(20,480), random.randint(20,480), self.size,self.size)
        #enemy_updated = pygame.transform.scale(enemy_img,(self.size,self.size))
        enemy_list.append(Enemy)
        display.blit(enemy_img,Enemy)
        
    def draw(self):  # draw all enemies
        for e in enemy_list:
            display.blit(enemy_img,e)
    
    def move_enemy(self):
        if self.enemy_position[0]!=snake_pos[0]:
            if self.enemy_position[0]+15>snake_pos[0]:
                self.enemy_position[0]-=.5
            if self.enemy_position[0]-15<snake_pos[0]:
                self.enemy_position[0]+=.5
        if snake_pos[0]-16<self.enemy_position[0] and snake_pos[0]+16>self.enemy_position[0]:
            if self.enemy_position[1]!=snake_pos[1]:
                if self.enemy_position[1]-15>snake_pos[1]:
                    self.enemy_position[1]-=.5
                if self.enemy_position[1]+15<snake_pos[1]:
                    self.enemy_position[1]+=.5
        
def fill_screen():
    display.fill((0,0,0))
    pygame.draw.rect(display,(29,10,200),[2,2,396,396])
    pygame.draw.rect(display,(43,7,230),[3,3,394,394])

while True:    
    fill_screen()
    
    food().create_food()
    food_rect=pygame.Rect((food_pos[0],food_pos[1]),(25,25))
    
    snake(snake_pos,snake_health).create_snake()
    for event in pygame.event.get():
        if event.type==pygame.QUIT:
            pygame.quit()
            sys.exit()
        if event.type==pygame.KEYDOWN:
            if event.key==K_RIGHT:
                movement="right"
            if event.key==K_LEFT:
                movement="left"
            if event.key==K_UP:
                movement="up"
            if event.key==K_DOWN:
                movement="down"
            if event.type == SPAWNENEMY:
                Enemys().create_enemy()
    
    snake(snake_pos,snake_health).move_snake(movement)
    
    snake_rect=pygame.Rect((snake_pos[0],snake_pos[1]),(25,25))
    
    food_eaten=snake_rect.colliderect(food_rect)
    
    if snake_health<=0:
        pygame.quit()
        sys.exit()
        print("You Died From Lack Of Living")
    
    if food_eaten:
        food_pos=new_food_pos()
        food_counter+=1
        snake_health+=5
    
    if snake_health>=100:
        snake_health=100
    
    surf=pygame.transform.scale(display,window_size)
    screen.blit(surf,(0,0))
    
    Enemys().draw()
    
    pygame.display.update()
    clock.tick(60)


谢谢

这个答案会很长,关于这段代码有很多话要说。 不要将此视为严厉的批评,而是希望它能指导您以更好的方式组织代码并理解 OOP(面向对象编程)。

这或多或少是对您的代码的竞争性重构,并解释了我为什么要进行每次更改。 我重构了你写的东西,但我没有完成游戏,这留给你做。

我注意到的第一件事是你使用类,但你似乎并不真正理解它们是如何被使用的。 例如,如果您将诸如“snake_health”或“snake_position”之类的变量放在 class 之外,然后使用一些时髦的方法将值返回到您的类中,为什么还要使用蛇 class。

这是我重新编写蛇 class 的方法:

class Snake():
    def __init__(self, pos):
        self.pos = pos
        self.lenght = 0
        self.health = 100
        self.direction = None
        self.img = pg.image.load("snake.png")
    
    def move(self):
        if self.direction=="up":
            self.pos[1]-=1
        elif self.direction=="down":
            self.pos[1]+=1
        elif self.direction=="right":
            self.pos[0]+=1
        elif self.direction=="left":
            self.pos[0]-=1
    
    def take_damage(self, damage):
        self.health -= damage

有几点需要您考虑:

  1. 将 class 名称的第一个字母大写
  2. 您不需要将 class 的名称放在实例变量名称中,因为它是冗余的。 因此,例如: snake_position简单地变为position (或者我个人更喜欢称之为pos ),因为它位于蛇 class 内部,我们已经知道它是与蛇的链接。 class 方法也是如此,因此move_snake(...)变为move(...) ,因为无论如何您都将使用snake.move()调用它
  3. 请注意我是如何删除与 draw 和 blit 相关的所有内容的? 由于您是初学者,因此在您真正知道自己在做什么之前,我建议您不要在课堂上进行任何平局或 blit。 对于您的第一个项目,我鼓励您使用一个名为 draw() 的方法,该方法在主循环中调用并绘制所有内容。

这是我刚才提到的抽奖 function:

def draw():
    # Clear screen
    screen.fill((0,0,0))

    # Draw snake
    screen.blit(snake.img, snake.pos)

    # Draw hp bar
    snake_x, snake_y = snake.pos
    bar_width, bar_height = 30,5
    bar_x, bar_y = snake_x - bar_width // 2 + snake.img.get_width() // 2, snake_y - bar_height - snake.img.get_height() // 2

    # hp bar background
    pg.draw.rect(screen, (255,0,0), pg.Rect(bar_x, bar_y, bar_width, bar_height))
    # hp bar filled part
    pg.draw.rect(screen, (0,255,0), pg.Rect(bar_x, bar_y, bar_width * (snake.health / 100), bar_height))
    # hp bar contour
    pg.draw.rect(screen, (80,80,80), pg.Rect(bar_x, bar_y, bar_width, bar_height), 1)

    # Draw food
    screen.blit(food.img, food.pos)

    # Draw enemies
    for enemy in Enemy.LIST:
        screen.blit(enemy.img, enemy.pos)

要更新您的值并执行一些游戏逻辑:将其分组为 function(大多数人将其称为update() ,所以让我们坚持下去)

def update():
    # Move the snake every frame
    snake.move()

    # Check if snake ate the food
    snake_rect = pg.Rect(snake.pos, snake.img.get_size())
    food_rect = pg.Rect(food.pos, food.img.get_size())
    if snake_rect.colliderect(food_rect): # (if food_eaten)
        food.pos = new_food_pos()
        snake.lenght += 1
    
    # Move the enemies
    for enemy in Enemy.LIST:
        enemy.move(snake.pos)

        # Check if enemy reached snake
        enemy_rect = pg.Rect(enemy.pos, enemy.img.get_size())
        if snake_rect.colliderect(enemy_rect):
            snake.take_damage(10)
            enemy.kill()
            if snake.health <= 0:
                game_over()

一个好的经验法则是编写您的类,以便通过调用更新 function 中的方法,无需查看代码中的其他地方就可以很容易地理解逻辑。 例如,如果您查看:

if snake_rect.colliderect(enemy_rect):
        snake.take_damage(10)
        enemy.kill()
        if snake.health <= 0:
            game_over()

很明显,如果蛇和敌人发生碰撞,蛇会受到伤害,敌人会被杀死,然后如果蛇的生命值达到0,那么游戏就结束了。

这是我重新编写食物 class 的方法:

class Food():
    def __init__(self, pos):
        self.pos = pos
        self.img = pg.image.load("apple.png")

如此简单,您可能根本不需要 class,但我想坚持您为它编写 class 的选择。 我想您看到了,但我也将“位置”一词替换为“位置”。 这是您应该避免的另一件事:使用不同的术语说同样的事情:对于蛇的 position,您使用术语“位置”,对于您使用术语“位置”的食物的 position,两者都是正确的名称,但请选择一个并在所有课程中使用相同的内容,否则只会增加混乱。

现在你的敌人 class:

class Enemy:
    LIST = []

    def __init__(self, pos):
            self.pos = pos
            self.img = pg.image.load("enemy.png")
            w = h = rd.randint(10, 20)
            self.size = (w, h)
            self.img = pg.transform.scale(self.img, self.size)

            # Add the enemy to the list
            Enemy.LIST.append(self)
    
    def move(self, snake_pos):
        enemy_x, enemy_y = self.pos
        snake_x, snake_y = snake_pos

        # Move enemy towards the player
        # (no need to check if position are equals since it will never happend -> as soon as rects collides)
        if enemy_x - snake_x > 0:
            enemy_x -= .5
        else:
            enemy_x += .5
        if enemy_y - snake_y > 0:
            enemy_y -= .5
        else:
            enemy_y += .5
        
        self.pos = (enemy_x, enemy_y)
    
    def kill(self):
        Enemy.LIST.remove(self)
  1. 使用复数作为 class 名称几乎总是一个坏主意,如果你真的想要一个代表多个敌人的 class,你必须找到一个像“GroupOfEnemy”这样的名称。 但是对于当前 state 中的游戏,您不需要它,您只需要 class 来描述单个敌人的样子和可以做什么。
  2. 对于敌人列表,我们可以像您一样将其留在 class 之外,对于这个项目,这完全没问题,但我只是想向您展示一个替代方案,将其作为 ZA2F2ED4F8EBC2CBB4C21A29DC40AB61 变量放入 class 中(变量或您的实例不是 D会遇到很多麻烦)
  3. 请注意,在您的每个 class 中,我如何删除了 create_... function? 那是因为它不是必需的,如果您想在创建实例时执行代码,那么只需将其放入init function 中,除非它妨碍了良好的可读性。
  4. 请注意,每个敌人都有一个 position 所以你真的需要把 position 放在 class 里面,而不是像你那样作为一个全局变量。

对于事件处理部分,您基本上是对的,@Rabbid76 提到了一个识别问题,我写的有点不同,但关闭了原版:

def handle_events():
    for evt in pg.event.get():
        if evt.type == pg.QUIT:
            exit()
        if evt.type == pg.KEYDOWN:
            if evt.key == pg.K_ESCAPE:
                exit()

        if evt.type == pg.KEYDOWN:
            if evt.key == pg.K_RIGHT:
                snake.direction = "right"
            elif evt.key == pg.K_LEFT:
                snake.direction = "left"
            elif evt.key == pg.K_UP:
                snake.direction = "up"
            elif evt.key == pg.K_DOWN:
                snake.direction = "down"

        if evt.type == SPAWN_ENEMY:
            Enemy((rd.randint(20, 480), rd.randint(20, 480)))

现在唯一剩下的就是这 3 个小函数:

def new_food_pos():
    return [rd.randint(25,350),rd.randint(25,350)]

def game_over():
    print("You Died From Lack Of Living")
    exit()

def exit():
    pg.quit()
    sys.exit()

我认为他们不需要解释。

最后一个关键点是要了解,当您像这样调用 class 名称时:

ClassName(...)

您创建了该 class 的实例,因此每次在您的代码中调用snake(snake_position, snake_health)时,您都在创建一个新的snake 实例,该实例与之前已经创建的所有snake 没有任何共享,这就是为什么您仍然需要使用全局变量和class 变得毫无意义。

总结:您对 OOP 概念感到困惑,我建议您观看/阅读有关该主题的教程。 如果您发现自己很难理解这些教程,这可能意味着 OOP 对您来说太先进了,先巩固基础,过几周再回到 OOP,它会变得更加顺畅 Z34D1F91FB2E514B8576FAB1A5

注意可读性:与您的命名习惯保持一致,使用更多空格。 您可以通过打开终端来查阅 python 的禅宗(python 代码的指南),输入“python”然后“import this”

由于您是初学者,请首先在 pygame 项目中使用此结构:

    def update():
        pass

    def draw():
        pass

    def handle_events():
        pass

    while True:
        handle_events()
        update()
        draw()
        pg.display.update()
        clock.tick(FPS)

以后您将有时间体验代码结构。 现在:更新所有值并在 update() 中执行游戏逻辑,在 draw() 中渲染所有组件并在 handle_events() 中管理每个事件。 这将使您更快、更轻松地学习 pygame。

除了那些有点特殊的功能外,尽量遵循单一职责原则,这意味着每个 function 必须有一份工作,只有一份工作,它的可读性大大提高。

最重要的是:不要气馁,我们都需要从某个地方开始,即使你写的很多东西都不会被认为是“好代码”,你仍然可以编写出你想要的代码,这已经是一个很大的成就你可以自豪。 随着您继续编程,您的代码会越来越好。 我什至从你的代码中学到了一些东西(我在 5 年前开始使用 pygame):我不知道有一个“pygame.USEREVENT”,这对我未来的项目来说非常方便。

快乐的编程,这里是一个完整的重构:

# General import
import pygame as pg
import random as rd
import sys

# Classes
class Snake():
    def __init__(self, pos):
        self.pos = pos
        self.lenght = 0
        self.health = 100
        self.direction = None
        self.img = pg.image.load("snake.png")
    
    def move(self):
        if self.direction=="up":
            self.pos[1]-=1
        elif self.direction=="down":
            self.pos[1]+=1
        elif self.direction=="right":
            self.pos[0]+=1
        elif self.direction=="left":
            self.pos[0]-=1
    
    def take_damage(self, damage):
        self.health -= damage

class Food():
    def __init__(self, pos):
        self.pos = pos
        self.img = pg.image.load("apple.png")

class Enemy:
    LIST = []

    def __init__(self, pos):
            self.pos = pos
            self.img = pg.image.load("enemy.png")
            w = h = rd.randint(10, 20)
            self.size = (w, h)
            self.img = pg.transform.scale(self.img, self.size)

            # Add the enemy to the list
            Enemy.LIST.append(self)
    
    def move(self, snake_pos):
        enemy_x, enemy_y = self.pos
        snake_x, snake_y = snake_pos

        # Move enemy towards the player
        # (no need to check if position are equals since it will never happend -> as soon as rects collides)
        if enemy_x - snake_x > 0:
            enemy_x -= .5
        else:
            enemy_x += .5
        if enemy_y - snake_y > 0:
            enemy_y -= .5
        else:
            enemy_y += .5
        
        self.pos = (enemy_x, enemy_y)
    
    def kill(self):
        Enemy.LIST.remove(self)

# Init
pg.init()

# Display
screen = pg.display.set_mode((500, 500))
pg.display.set_caption('Snake - fix')
FPS = 60
clock = pg.time.Clock()

# Set up custom event
SPAWN_ENEMY = pg.USEREVENT
pg.time.set_timer(SPAWN_ENEMY, 1000)

# Main functions
def update():
    # Move the snake every frame
    snake.move()

    # Check if snake ate the food
    snake_rect = pg.Rect(snake.pos, snake.img.get_size())
    food_rect = pg.Rect(food.pos, food.img.get_size())
    if snake_rect.colliderect(food_rect): # (if food_eaten)
        food.pos = new_food_pos()
        snake.lenght += 1
    
    # Move the enemies
    for enemy in Enemy.LIST:
        enemy.move(snake.pos)

        # Check if enemy reached snake
        enemy_rect = pg.Rect(enemy.pos, enemy.img.get_size())
        if snake_rect.colliderect(enemy_rect):
            snake.take_damage(10)
            enemy.kill()
            if snake.health <= 0:
                game_over()

def draw():
    # Clear screen
    screen.fill((0,0,0))

    # Draw snake
    screen.blit(snake.img, snake.pos)

    # Draw hp bar
    snake_x, snake_y = snake.pos
    bar_width, bar_height = 30,5
    bar_x, bar_y = snake_x - bar_width // 2 + snake.img.get_width() // 2, snake_y - bar_height - snake.img.get_height() // 2

    # hp bar background
    pg.draw.rect(screen, (255,0,0), pg.Rect(bar_x, bar_y, bar_width, bar_height))
    # hp bar filled part
    pg.draw.rect(screen, (0,255,0), pg.Rect(bar_x, bar_y, bar_width * (snake.health / 100), bar_height))
    # hp bar contour
    pg.draw.rect(screen, (80,80,80), pg.Rect(bar_x, bar_y, bar_width, bar_height), 1)

    # Draw food
    screen.blit(food.img, food.pos)

    # Draw enemies
    for enemy in Enemy.LIST:
        screen.blit(enemy.img, enemy.pos)

def handle_input():
    for evt in pg.event.get():
        if evt.type == pg.QUIT:
            exit()
        if evt.type == pg.KEYDOWN:
            if evt.key == pg.K_ESCAPE:
                exit()

        if evt.type == pg.KEYDOWN:
            if evt.key == pg.K_RIGHT:
                snake.direction = "right"
            elif evt.key == pg.K_LEFT:
                snake.direction = "left"
            elif evt.key == pg.K_UP:
                snake.direction = "up"
            elif evt.key == pg.K_DOWN:
                snake.direction = "down"

        if evt.type == SPAWN_ENEMY:
            Enemy((rd.randint(20, 480), rd.randint(20, 480)))

def new_food_pos():
    return [rd.randint(25,350),rd.randint(25,350)]

def game_over():
    print("You Died From Lack Of Living")
    exit()

def exit():
    pg.quit()
    sys.exit()

# Main loop
if __name__ == "__main__":
    snake = Snake([100, 100])
    food = Food(new_food_pos())

    while True:
        handle_input()
        update()
        draw()
        pg.display.update()
        clock.tick(FPS)

这是我使用的 3 张图片(对于enemy.png、apple.png 和snake.png): 敌人.png 苹果.png 蛇.png

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM