Github存储库,带有图像
#!/usr/bin/env python3
"""Flappy Bird, implemented using Pygame."""
import math
import os
from random import randint
import pygame
from pygame.locals import *
FPS = 60
EVENT_NEWPIPE = USEREVENT + 1 # custom event
PIPE_ADD_INTERVAL = 3000 # milliseconds
FRAME_ANIMATION_WIDTH = 3 # pixels per frame
FRAME_BIRD_DROP_HEIGHT = 3 # pixels per frame
FRAME_BIRD_JUMP_HEIGHT = 5 # pixels per frame
BIRD_JUMP_STEPS = 20 # see get_frame_jump_height docstring
WIN_WIDTH = 284 * 2 # BG image size: 284x512 px; tiled twice
WIN_HEIGHT = 512
PIPE_WIDTH = 80
PIPE_PIECE_HEIGHT = BIRD_WIDTH = BIRD_HEIGHT = 32
class PipePair:
"""Represents an obstacle.
A PipePair has a top and a bottom pipe, and only between them can
the bird pass -- if it collides with either part, the game is over.
Attributes:
x: The PipePair's X position. Note that there is no y attribute,
as it will only ever be 0.
surface: A pygame.Surface which can be blitted to the main surface
to display the PipePair.
top_pieces: The number of pieces, including the end piece, in the
top pipe.
bottom_pieces: The number of pieces, including the end piece, in
the bottom pipe.
"""
def __init__(self, surface, top_pieces, bottom_pieces):
"""Initialises a new PipePair with the given arguments.
The new PipePair will automatically be assigned an x attribute of
WIN_WIDTH.
Arguments:
surface: A pygame.Surface which can be blitted to the main
surface to display the PipePair. You are responsible for
converting it, if desired.
top_pieces: The number of pieces, including the end piece, which
make up the top pipe.
bottom_pieces: The number of pieces, including the end piece,
which make up the bottom pipe.
"""
self.x = WIN_WIDTH
self.surface = surface
self.top_pieces = top_pieces
self.bottom_pieces = bottom_pieces
self.score_counted = False
@property
def top_height_px(self):
"""Get the top pipe's height, in pixels."""
return self.top_pieces * PIPE_PIECE_HEIGHT
@property
def bottom_height_px(self):
"""Get the bottom pipe's height, in pixels."""
return self.bottom_pieces * PIPE_PIECE_HEIGHT
def is_bird_collision(self, bird_position):
"""Get whether the bird crashed into a pipe in this PipePair.
Arguments:
bird_position: The bird's position on screen, as a tuple in
the form (X, Y).
"""
bx, by = bird_position
in_x_range = bx + BIRD_WIDTH > self.x and bx < self.x + PIPE_WIDTH
in_y_range = (by < self.top_height_px or
by + BIRD_HEIGHT > WIN_HEIGHT - self.bottom_height_px)
return in_x_range and in_y_range
def load_images():
"""Load all images required by the game and return a dict of them.
The returned dict has the following keys:
background: The game's background image.
bird-wingup: An image of the bird with its wing pointing upward.
Use this and bird-wingdown to create a flapping bird.
bird-wingdown: An image of the bird with its wing pointing downward.
Use this and bird-wingup to create a flapping bird.
pipe-end: An image of a pipe's end piece (the slightly wider bit).
Use this and pipe-body to make pipes.
pipe-body: An image of a slice of a pipe's body. Use this and
pipe-body to make pipes.
"""
def load_image(img_file_name):
"""Return the loaded pygame image with the specified file name.
This function looks for images in the game's images folder
(./images/). All images are converted before being returned to
speed up blitting.
Arguments:
img_file_name: The file name (including its extension, e.g.
'.png') of the required image, without a file path.
"""
file_name = os.path.join('.', 'images', img_file_name)
img = pygame.image.load(file_name)
# converting all images before use speeds up blitting
img.convert()
return img
return {'background': load_image('background.png'),
'pipe-end': load_image('pipe_end.png'),
'pipe-body': load_image('pipe_body.png'),
# images for animating the flapping bird -- animated GIFs are
# not supported in pygame
'bird-wingup': load_image('bird_wing_up.png'),
'bird-wingdown': load_image('bird_wing_down.png')}
def get_frame_jump_height(jump_step):
"""Calculate how high the bird should jump in a particular frame.
This function uses the cosine function to achieve a smooth jump:
In the first and last few frames, the bird jumps very little, in the
middle of the jump, it jumps a lot.
After a completed jump, the bird will have jumped
FRAME_BIRD_JUMP_HEIGHT * BIRD_JUMP_STEPS pixels high, thus jumping,
on average, FRAME_BIRD_JUMP_HEIGHT pixels every step.
Arguments:
jump_step: Which frame of the jump this is, where one complete jump
consists of BIRD_JUMP_STEPS frames.
"""
frac_jump_done = jump_step / float(BIRD_JUMP_STEPS)
return (1 - math.cos(frac_jump_done * math.pi)) * FRAME_BIRD_JUMP_HEIGHT
def random_pipe_pair(pipe_end_img, pipe_body_img):
"""Return a PipePair with pipes of random height.
The returned PipePair's surface will contain one bottom-up pipe
and one top-down pipe. The pipes will have a distance of
BIRD_HEIGHT*3.
Both passed images are assumed to have a size of (PIPE_WIDTH,
PIPE_PIECE_HEIGHT).
Arguments:
pipe_end_img: The image to use to represent a pipe's endpiece.
pipe_body_img: The image to use to represent one horizontal slice
of a pipe's body.
"""
surface = pygame.Surface((PIPE_WIDTH, WIN_HEIGHT), SRCALPHA)
surface.convert() # speeds up blitting
surface.fill((0, 0, 0, 0))
max_pipe_body_pieces = int(
(WIN_HEIGHT - # fill window from top to bottom
3 * BIRD_HEIGHT - # make room for bird to fit through
3 * PIPE_PIECE_HEIGHT) / # 2 end pieces and 1 body piece for top pipe
PIPE_PIECE_HEIGHT # to get number of pipe pieces
)
bottom_pipe_pieces = randint(1, max_pipe_body_pieces)
top_pipe_pieces = max_pipe_body_pieces - bottom_pipe_pieces
# bottom pipe
for i in range(1, bottom_pipe_pieces + 1):
surface.blit(pipe_body_img, (0, WIN_HEIGHT - i*PIPE_PIECE_HEIGHT))
bottom_pipe_end_y = WIN_HEIGHT - bottom_pipe_pieces*PIPE_PIECE_HEIGHT
surface.blit(pipe_end_img, (0, bottom_pipe_end_y - PIPE_PIECE_HEIGHT))
# top pipe
for i in range(top_pipe_pieces):
surface.blit(pipe_body_img, (0, i * PIPE_PIECE_HEIGHT))
top_pipe_end_y = top_pipe_pieces * PIPE_PIECE_HEIGHT
surface.blit(pipe_end_img, (0, top_pipe_end_y))
# compensate for added end pieces
top_pipe_pieces += 1
bottom_pipe_pieces += 1
return PipePair(surface, top_pipe_pieces, bottom_pipe_pieces)
def main():
"""The application's entry point.
If someone executes this module (instead of importing it, for
example), this function is called.
"""
pygame.init()
display_surface = pygame.display.set_mode((WIN_WIDTH, WIN_HEIGHT))
pygame.display.set_caption('Pygame Flappy Bird')
clock = pygame.time.Clock()
score_font = pygame.font.SysFont(None, 32, bold=True) # default font
# the bird stays in the same x position, so BIRD_X is a constant
BIRD_X = 50
bird_y = int(WIN_HEIGHT/2 - BIRD_HEIGHT/2) # center bird on screen
images = load_images()
# timer for adding new pipes
pygame.time.set_timer(EVENT_NEWPIPE, PIPE_ADD_INTERVAL)
pipes = []
steps_to_jump = 2
score = 0
done = paused = False
while not done:
for e in pygame.event.get():
if e.type == QUIT or (e.type == KEYUP and e.key == K_ESCAPE):
done = True
break
elif e.type == KEYUP and e.key in (K_PAUSE, K_p):
paused = not paused
elif e.type == MOUSEBUTTONUP or (e.type == KEYUP and
e.key in (K_UP, K_RETURN, K_SPACE)):
steps_to_jump = BIRD_JUMP_STEPS
elif e.type == EVENT_NEWPIPE:
pp = random_pipe_pair(images['pipe-end'], images['pipe-body'])
pipes.append(pp)
clock.tick(FPS)
if paused:
continue # don't draw anything
for x in (0, WIN_WIDTH / 2):
display_surface.blit(images['background'], (x, 0))
for p in pipes:
p.x -= FRAME_ANIMATION_WIDTH
if p.x <= -PIPE_WIDTH: # PipePair is off screen
pipes.remove(p)
else:
display_surface.blit(p.surface, (p.x, 0))
# calculate position of jumping bird
if steps_to_jump > 0:
bird_y -= get_frame_jump_height(BIRD_JUMP_STEPS - steps_to_jump)
steps_to_jump -= 1
else:
bird_y += FRAME_BIRD_DROP_HEIGHT
# because pygame doesn't support animated GIFs, we have to
# animate the flapping bird ourselves
if pygame.time.get_ticks() % 500 >= 250:
display_surface.blit(images['bird-wingup'], (BIRD_X, bird_y))
else:
display_surface.blit(images['bird-wingdown'], (BIRD_X, bird_y))
# update and display score
for p in pipes:
if p.x + PIPE_WIDTH < BIRD_X and not p.score_counted:
score += 1
p.score_counted = True
score_surface = score_font.render(str(score), True, (255, 255, 255))
score_x = WIN_WIDTH/2 - score_surface.get_width()/2
display_surface.blit(score_surface, (score_x, PIPE_PIECE_HEIGHT))
pygame.display.update()
# check for collisions
pipe_collisions = [p.is_bird_collision((BIRD_X, bird_y)) for p in pipes]
if (0 >= bird_y or bird_y >= WIN_HEIGHT - BIRD_HEIGHT or
True in pipe_collisions):
print('You crashed! Score: %i' % score)
break
pygame.quit()
if __name__ == '__main__':
# If this module had been imported, __name__ would be 'flappybird'.
# It was executed (e.g. by double-clicking the file), so call main.
main()
#1 楼
函数和类具有文档字符串,这使您的代码比提交给Code Review的代码的95%更好。
管道的行为分为几部分:(i)
PipePair
类; (ii)main
中的运动,绘制和销毁逻辑; (iii)main
中的计分逻辑; (iv)工厂功能random_pipe_pair
。如果将所有管道逻辑收集到PipePair
类的方法中,将使代码更易于理解和维护。同样,鸟的行为分布在多个位置:(i)局部变量
bird_y
和steps_to_jump
在main
中; (ii)“计算跳鸟的位置”逻辑; (iii)拍打动画逻辑; (iv)get_frame_jump_height
功能。如果将所有鸟逻辑收集到Bird
类的方法中,将使代码更易于理解。“跳跃”一词似乎不太能说明鸟的行为。
名称
is_bird_collision
英文没有意义。在碰撞逻辑中,您正在有效地测试矩形点击框的交集。 Pygame提供了一个
Rect
类,其中包含各种collide
方法,这些方法可以使您的代码更清晰,并使绘制诸如命中框之类的操作更容易进行调试。拆卸管道时:list
花费的时间与列表的长度成正比。您应该使用list.remove
,或者,因为您知道管道是在右侧创建并在左侧销毁的,因此应该使用set
。然后测试以查看collections.deque
是否为列表的元素。相反,您应该使用内置函数True
:if any(p.collides_with(bird) for p in pipes):
(这还有短路的另一个优点:也就是说,一旦检测到碰撞就立即停止,而不是继续测试其余的管道。)
测量时间以帧为单位(例如,管道以每帧特定数量的像素向左移动)。结果是,您必须更改许多其他参数才能更改帧速率。通常以秒为单位来测量时间:这可以改变帧速率。我需要能够改变帧速率,因此值得实践必要的技巧。)
在提交583c3e49中,您通过(i)在不更改调用方的情况下删除了
any
函数来破坏了游戏; (ii)在某些地方而不是其他地方将局部变量random_pipe_pair
更改为属性surface
。您尚未在提交代码之前对其进行测试。这是一个坏习惯!评论
\ $ \ begingroup \ $
我同意,鸟并没有真正跳起来,但是你会建议什么动词?起飞?爬升(用于航空)?
\ $ \ endgroup \ $
– Timo
2014年8月30日在18:17
\ $ \ begingroup \ $
我建议您进行爬升/下降,上升/下降或上升/下降。
\ $ \ endgroup \ $
– 200_success
2014年8月30日18:36
\ $ \ begingroup \ $
“挡水板”将是我的选择。
\ $ \ endgroup \ $
–加雷斯·里斯(Gareth Rees)
2014年8月30日在18:46
\ $ \ begingroup \ $
这里的所有答案都很好,但是我接受这个答案,因为它的建议最多。
\ $ \ endgroup \ $
– Timo
2014年8月31日下午16:40
\ $ \ begingroup \ $
好吧,我想我已经涵盖了所有内容。最新的代码在Github仓库中。
\ $ \ endgroup \ $
– Timo
14年8月31日19:00
#2 楼
这是非常不错的代码!我仍然可以稍加嘲笑:)您可以使用很酷的
.. < .. < ..
运算符:>
像这样:
in_x_range = bx + BIRD_WIDTH > self.x and bx < self.x + PIPE_WIDTH
如果添加一些换行符,也许
random_pipe_pair
的可读性会更高。要点,但无论如何要保持不变,请在底部查看我的结论。
这里不需要括号:
in_x_range = bx - PIPE_WIDTH < self.x < bx + BIRD_WIDTH
这里:
in_y_range = (by < self.top_height_px or
by + BIRD_HEIGHT > WIN_HEIGHT - self.bottom_height_px)
这里是外部括号:
if e.type == QUIT or (e.type == KEYUP and e.key == K_ESCAPE):
这里:
elif e.type == MOUSEBUTTONUP or (e.type == KEYUP and
e.key in (K_UP, K_RETURN, K_SPACE)):
但是...正如您所评论的那样,您主要是使用括号来中断长行(可能遵循PEP8),而不会出现难看的
\
。我完全同意这一点,所以,请保留它们! (实际上,我什至不知道这是可能的,所以感谢您的教训,教'!)评论
\ $ \ begingroup \ $
好点!关于括号:或者我正在使用它们进行换行,所以我不必使用丑陋的反斜线:),或者为了清楚起见,我都在使用它们-我知道and和or的优先级,但是'explicit is比隐式更好”,正如Python的Zen所说的:)
\ $ \ endgroup \ $
– Timo
2014年8月29日在17:16
\ $ \ begingroup \ $
我实际上并不了解Python,但是发现了这个问题很有趣。关于从a或(b和c)中删除括号:我将其保留在里面,仅仅是因为它使它变得明确,并且您不需要知道/记住运算符的优先级。我现在已经使用了大约20种语言,并且运算符优先级规则让我感到厌烦:只需加上括号,每个人都知道它应该如何工作。
\ $ \ endgroup \ $
– DarkDust
2014年8月29日在17:26
\ $ \ begingroup \ $
我同意显式比隐式更好,但这对我来说有点偏执。但是,我同意您关于换行的其他观点,并更新了我的帖子
\ $ \ endgroup \ $
– janos
2014年8月29日在17:29
\ $ \ begingroup \ $
更改了代码,使其包含“ cool x
– Timo
2014年8月29日在18:48
#3 楼
我对暂停处理不满意。首先,繁忙等待循环。其次,暂停的游戏只是不呈现任何内容,但仍可以提供事件,例如添加了管道。
random_pipe_pair
确实希望成为PipePair
的构造函数。同样,images['pipe_body']
和images['pipe_end']
应该是PipePair
的静态成员。评论
\ $ \ begingroup \ $
好点,谢谢。让我们看看如何解决。.完成后我将致力于Github。
\ $ \ endgroup \ $
– Timo
2014年8月29日在17:10
\ $ \ begingroup \ $
最后...在git遇到问题后,我提交了更改。我也会更新问题。
\ $ \ endgroup \ $
– Timo
2014年8月29日在18:41
\ $ \ begingroup \ $
编辑代码违反CR政策,因为它会使评论无效。请回滚编辑。非常欢迎您发布更新的代码作为后续问题。
\ $ \ endgroup \ $
–vnp
2014年8月29日在18:55
#4 楼
这很整洁:)我认为除非您跟踪已更改的
rects
,否则display.update()
并不比display.flip()
更好,尽管我认为对于学生而言,它更容易阅读。 > 我很好奇为什么视图更新后要检查冲突?我知道,在事件循环的情况下,进程的实际顺序可能有点松懈,尤其是当您以较高的FPS工作时,我发现它以循环的结尾包含“退出或不执行”代码,但我想我认为所有状态检查工作都应在更新视图之前进行。那可能是小土豆,我不知道您正在与之合作的学生的年龄或经验。
我必须同意暂停的处理有点奇怪,只是因为它有点开始有关使用状态控制(非常简短)脚本流的讨论。宁愿看到类似“如果不暂停:do_stuff()”的内容,而不是“如果暂停:继续”的内容。 ;就我而言,在不使用Sprite的情况下进行Intro To Games类感到非常奇怪。 Pygame sprite.Sprite对象是非常有用的东西!但是当我查看代码时,它似乎引入了一系列不同的概念,因此也许没有必要在组合中添加另一个概念。
评论
\ $ \ begingroup \ $
谢谢!因为如果检测到碰撞,我是直接从主循环中断开的,所以当我第一次编码游戏的那一部分时,我将其放在末尾,这样就不会告诉用户它们在鸟儿仍显示一些时就撞了死。像素远离管道。我将所有状态检查移到循环的开头,然后将done设置为True-例如,对于QUIT事件,它已完成。
\ $ \ endgroup \ $
– Timo
2014年8月29日在18:17
\ $ \ begingroup \ $
关于display.flip()与display.update()的要点-阅读文档时,我一定忽略了flip。
\ $ \ endgroup \ $
– Timo
14年8月29日在18:19
\ $ \ begingroup \ $
如果不确定是否暂停,我不太确定您的意思:do_stuff()-您是否建议我将整个循环体提取到另一个函数中,如果游戏没有暂停,就直接调用它?
\ $ \ endgroup \ $
– Timo
2014年8月29日在18:21
\ $ \ begingroup \ $
关于sprite:我可以创建一个新的Bird类sprite.Sprite子类,并让PipePair子类为sprite.Group,恕我直言,这会使游戏变得比它需要的复杂。或者我可以用some_sprite.image或类似的东西替换image [...]的所有用法,然后用some_pipe_pair.draw(display_surface)替换每个display_surface.blit(some_pipe_pair.surface,...),这实际上不是简化代码,恕我直言。但是我肯定会以某种方式介绍精灵,也许是在另一个示例中。
\ $ \ endgroup \ $
– Timo
2014年8月29日在18:30
\ $ \ begingroup \ $
好吧,您不必一定要从视图中拉出所有视图内容,但是我明白您在说什么。至于子类sprite.Group的子类化,我可能误解了您的意思,但是Group是使用方便方法的sprite的容器。例如,MyGroup.draw(DispSurf)会在不使用for循环的情况下将该组中的所有sprite变为blit。并且像pygame.sprite.spritecollide(Bird,PipesGroup)之类的调用将返回PipesGroup中所有被击中的子画面的列表,因此简单的“ if spritecollide(Bird,PipesGroup):”将用作击中检测(因为为空清单虚假)
\ $ \ endgroup \ $
–棍子
2014年8月29日在19:14
评论
哦,我希望更多的老师像你一样!欢迎使用代码审查!教你的学生复习代码!@SimonAndréForsberg:谢谢!而且,这是一个好主意-我会找到一些他们可以学习的好代码,或者一些错误发现的错误代码。
问这个问题时冻结的GitHub树
@Timo没问题:)是的,继续并在那里进行更改。另外,如果您想在更新后的代码上发表更多评论,您可能需要等待一段时间才能获得更多答案,调整代码并发布后续评论。
很好,一旦学生完成了项目,并且代码运行正常,请告诉他们创建帐户并将其发布到此处。祝你好运。