Pygame 只在按键上前进

Posted

技术标签:

【中文标题】Pygame 只在按键上前进【英文标题】:Pygame only advances on keypress 【发布时间】:2020-01-02 10:53:45 【问题描述】:

我想这是一个简单的解决方法,并且看到过类似的问题,但这真的让我很沮丧。基本上,当您运行游戏时,它只会在按键上推进模拟,而不是以设定的帧速率。

https://pastebin.com/aP6LsMMA

主要代码是:

pg.init()
clock = pg.time.Clock()
FPS = 10
# ...
Game = Control()
while not Game.done:
    Game.main_loop()
    pg.display.update()
    clock.tick(FPS)
pg.quit()

控制类中的事件处理方法是:

def event_handler(self):
        for event in pg.event.get():
            if event.type == pg.QUIT:
                self.done = True
            elif event.type == pg.KEYDOWN:
                self.scene.process_event(event)

对我来说奇怪的是,在 pastebin 的底部,是我的旧测试代码,它没有使用 Scenes/Control 的类完成。但是,在我看来,它应该完全一样。我尝试在 Control 类中输入和输出时钟均无济于事。

非常感谢任何帮助(和一般提示!)。

谢谢

【问题讨论】:

您应该为所有事件运行process_event(),并在process_event 内部检查KEYDOWN。或者即使你只应该在process_event() 内改变方向,而休息(更新、绘制、检查碰撞)应该在process_event() 之外执行——在事件循环之后——甚至在event_handler 之外——在分离的函数中。 【参考方案1】:

process_event() 应该只更改依赖于事件的变量,但不应该更新应该在每一帧中更新的其他值。您必须将一些元素移动到新方法 update() 并在每个循环中执行它。

或多或少

class Scene:

    def process_event(self, event):
        pass

    def update(self):
        pass

class GamePlayState(Scene):

    def process_event(self, event):
        self.snake.get_key(event)

    def update(self):
        self.snake.update()
        self.snake.food_check(self.apple)
        self.snake.collision_check()
        if self.snake.alive == False:
            print("GAME OVER")
            print(self.snake.points)
            self.done = True

class Control:

    def update(self):
        self.scene.update()

    def main_loop(self):
        self.event_handler()
        self.update()
        self.scene_checker()
        self.draw()

完整的工作代码

import pygame as pg
import sys
import random
import queue

# TODO: Walls, queue for keypress, scene stuff, 2 player, difficulty(?)

""" ######################
         PREAMBLE
    ###################### """

""" Dictionaries for direction/velocity mapping - stolen from https://github.com/Mekire """
DIRECT_DICT = "left" : (-1, 0), "right" : (1, 0),
               "up" : (0,-1), "down" : (0, 1)

KEY_MAPPING = pg.K_LEFT : "left", pg.K_RIGHT : "right",
               pg.K_UP : "up", pg.K_DOWN : "down"

OPPOSITES = "left" : "right", "right" : "left",
             "up" : "down", "down" : "up"

""" Colour Mapping """
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
DARK_GREY = (70, 70, 70)
GREY = (211, 211, 211)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
COLOUR_MAP = "snake": GREEN, "apple": RED, "wall": BLACK, "surface": GREY, "background": DARK_GREY 

""" ################
        CLASSES
    ################ """

""" ####################### Object Classes ########################## """

class Square:
    """ All other objects in the game will be built up from this """
    def __init__(self, pos, colour, length):
        self.xi, self.yi = pos # i for index, p for pixel
        self.colour = colour
        self.length = length

    def display(self):
        xp, yp = self.sq_to_pixs(self.xi, self.yi) # (x = left side, y = top edge)
        pg.draw.rect(screen, self.colour, (xp, yp, self.length, self.length), 0)

    def sq_to_pixs(self, x, y):
    # Converts index of square to pixel coords
        px = (x+1)*(2*MARGIN + SQUARE_SIZE) - MARGIN - SQUARE_SIZE
        py = (y+1)*(2*MARGIN + SQUARE_SIZE) - MARGIN
        return (px, py)

    def index_coords(self): # TODO - remove for direct ref?
        return (self.xi, self.yi)

class Arena:
    """ A grid within which the game takes place """
    def __init__(self, size, square_length, colour):
        self.size = size # i.e. number of squares = size**2 for square arena
        self.length = square_length # i.e. per square dimension
        self.colour = colour
        self.squares = [ [] for i in range(self.size) ]
        for y in range(self.size):
            for x in range(self.size):
                self.squares[y].append(Square((x,y), self.colour, self.length))

    def display(self):
        for y in self.squares:
            for square in y:
                square.display()

class Snake:
    """ Class for the agent(s) """
    def __init__(self, pos, colour, square_length):
        self.xi, self.yi = pos
        self.colour = colour
        self.size = 3
        self.length = square_length
        self.direction = "right"
        self.direction_queue = queue.Queue(4) # TODO
        self.points = 0
        self.growing = False
        self.alive = True
        self.squares = []
        for x in range(self.size): # horizontal initial orientation
            self.squares.append(Square((self.xi - x, self.yi), self.colour, self.length))

    def display(self):
        for square in self.squares:
            square.display()

    def food_check(self, apple):
        if self.squares[0].index_coords() == apple.square.index_coords():
            self.growing = True
            self.points += apple.points_value
            apple.respawn([self])

    def collision_check(self, walls = None):
        xh, yh = self.squares[0].index_coords()
        body = self.squares[-1:0:-1] # going backwards thru array as forwards [0:-1:1] didnt work...

        def _collide(obstacles):
            for sq in obstacles:
                _x, _y = sq.index_coords()
                if (_x == xh) and (_y == yh):
                    self.alive = False

        _collide(body)
        if walls is not None:
            _collide(walls)

    def update(self):
         # Add new head based on velocity and old head
        velocity = DIRECT_DICT[self.direction]
        head_coords = [ (self.squares[0].index_coords()[i] + velocity[i]) for i in (0,1) ]
        # Wrap around screen if reach the end
        for i in (0, 1):
            if head_coords[i] < 0:
                head_coords[i] = SQUARES_PER_ARENA_SIDE - 1
            elif head_coords[i] > SQUARES_PER_ARENA_SIDE - 1:
                head_coords[i] = 0

        self.squares.insert(0, Square(head_coords, self.colour, self.length))
        if self.growing:
            self.growing = False
        else:
            del self.squares[-1]

    """
    def queue_key_press(self, key):
        for keys in KEY_MAPPING:
            if key in keys:
                try:
                    self.direction_queue.put(KEY_MAPPING[keys], block=False)
                    break
                except queue.Full:
                    pass
    """

class Player(Snake):
    """ Human controlled snake via arrow keys """
    def __init__(self, pos, colour, size):
        Snake.__init__(self, pos, colour, size)

    def get_key(self, event):
        if event.type == pg.KEYDOWN and event.key in KEY_MAPPING:
                new_direction = KEY_MAPPING[event.key]
                if new_direction != OPPOSITES[self.direction]:
                    self.direction = new_direction

class Apple:
    """ Food our (veggie) snake is greedily after """
    def __init__(self, colour, length, points_value, snake):
        self.colour = colour
        self.length = length
        self.xi, self.yi = self._rand_coords()
        self.points_value = points_value
        self.square = Square((self.xi, self.yi), self.colour, self.length)

    def _rand_coords(self):
        rand_num = lambda x: random.randint(0, x)
        _x = rand_num(SQUARES_PER_ARENA_SIDE-1)
        _y = rand_num(SQUARES_PER_ARENA_SIDE-1)
        return _x, _y

    def respawn(self, obstacles):
        _x, _y = self._rand_coords()
        for ob in obstacles:
            for sq in ob.squares:
                while sq.index_coords() == (_x, _y):
                    _x, _y = self._rand_coords()
        self.square.xi, self.square.yi = _x, _y

    def display(self):
        self.square.display()


""" ################ SCENES ####################### """    

class Scene:
    """ Overload most of this - barebones structure
    A bit pointless in current state but easily expanded """
    def __init__(self):
        self.done = False

    def when_activated(self):
        pass

    def reset(self):
        self.done = False

    def render(self):
        pass

    def process_event(self, event):
        pass

    def update(self):
        pass

class StartUp(Scene):
    def __init__(self):
        Scene.__init__(self)

    def render(self):
        # test placeholder
        pass

    def when_activated(self):
        print("Press any key to continue")

    def process_event(self, event):
        if event.type == pg.KEYDOWN:
            self.done = True

class GamePlayState(Scene):
    def __init__(self):
        Scene.__init__(self)
        self.arena = Arena(SQUARES_PER_ARENA_SIDE, SQUARE_SIZE, COLOUR_MAP["surface"])
        self.snake = Player(SNAKE_START, COLOUR_MAP["snake"], SQUARE_SIZE)
        self.apple = Apple(COLOUR_MAP["apple"], SQUARE_SIZE, 1, self.snake)
        self.font = pg.font.SysFont("courier new", 50)

    def render(self):
        screen.fill(COLOUR_MAP["background"])
        self.arena.display()
        self.apple.display()
        self.snake.display()
        text = self.font.render(str(self.snake.points), True, [255,255,255])
        screen.blit(text, (500, 400))

    def process_event(self, event):
        self.snake.get_key(event)

    def update(self):
        self.snake.update()
        self.snake.food_check(self.apple)
        self.snake.collision_check()
        if self.snake.alive == False:
            print("GAME OVER")
            print(self.snake.points)
            self.done = True



""" ################## CONTROL CLASS  #########################"""

class Control:
    def __init__(self):
        #self.clock = pg.time.Clock()
        #self.fps = FPS
        self.done = False
        self.scene_array = [StartUp(), GamePlayState()]
        self.scene_index = 0 # dirty way whilst dict method needs tinkering
        #self.scene_dict = "START": StartUp(), "GAME": GamePlayState() #TODO
        self.scene = self.scene_array[self.scene_index]
        #self.scene = self.scene_dict["START"]
        self.scene.when_activated()

    def event_handler(self):
        for event in pg.event.get():
            if event.type == pg.QUIT:
                self.done = True
            elif event.type == pg.KEYDOWN:
                self.scene.process_event(event)

    def scene_checker(self):
        if self.scene.done:
            self.scene.reset() # for reuse  - TODO
            self.scene_index = (self.scene_index + 1) % len(self.scene_array)
            self.scene = self.scene_array[self.scene_index]
            self.scene.when_activated()
            #self.scene = self.scene_dict[self.scene.next]

    def update(self):
        self.scene.update()

    def draw(self):
        self.scene.render()

    def main_loop(self):
        self.event_handler()
        self.update()
        self.scene_checker()
        self.draw()


""" ################ RUN GAME ################ """

""" Game paramaters """
SQUARE_SIZE = 20 # pixels
SQUARES_PER_ARENA_SIDE = 20 # squares
MARGIN = 2 # pixels
SNAKE_START = (int(SQUARES_PER_ARENA_SIDE/2), int(SQUARES_PER_ARENA_SIDE/2)) # square coords

pg.init()
clock = pg.time.Clock()
#  Square.display() and a few others need a direct reference to "screen" TODO implement better
w, h = 620, 620
SCREEN_SIZE = [w, h]
FPS = 10
screen = pg.display.set_mode(SCREEN_SIZE)

Game = Control()
while not Game.done:
    Game.main_loop()
    pg.display.update()
    clock.tick(FPS)
pg.quit()

【讨论】:

以上是关于Pygame 只在按键上前进的主要内容,如果未能解决你的问题,请参考以下文章

将按键发送到嵌入式 Pygame

Pygame 跳过按键检查

按键被按下时如何增加速度变量?

Pygame - 永久调整主音量

如何在python中运行另一个程序的python中模拟按键事件

Pygame - 运动加速