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 只在按键上前进的主要内容,如果未能解决你的问题,请参考以下文章