前言
在上一篇博文里,从讯飞星火中获取到游戏“贪吃蛇”的代码,运行效果如上图。代码并不完整,当蛇死了就退出游戏,不能多次玩这个游戏。
前文链接:
讯飞星火、文心一言和通义千问同时编“贪吃蛇”游戏,谁会胜出?
代码如下:
import pygame import sys import random # 定义颜色 WHITE = (255, 255, 255) GREEN = (0, 255, 0) RED = (255, 0, 0) GREY = (211, 211, 211) # 淡灰色 def init(): global screen, screen_size global snake_pos, food_pos, snake_speed # 初始化pygame pygame.init() # 设置屏幕大小 screen_size = (640, 480) screen = pygame.display.set_mode(screen_size) # 设置游戏标题 pygame.display.set_caption("贪吃蛇") # 蛇的初始位置 snake_pos = [[100, 100], [80, 100], [60, 100]] # 食物的初始位置 food_pos = [300, 300] # 蛇的初始速度 snake_speed = [20, 0] def repaint(): # 绘制游戏界面 screen.fill(WHITE) # 定义线段的端点坐标 x,y = (-1,640,640,-1)*16, [] for i in range(36): for _ in range(2): y.append(19+i*20) # 使用pygame.draw.lines()函数绘制线段 points = list(zip(x,y)) pygame.draw.lines(screen, GREY, False, points, 1) # 线宽为1 points = list(zip(y,x)) pygame.draw.lines(screen, GREY, False, points, 1) # 重画蛇和食物 for pos in snake_pos: pygame.draw.rect(screen, GREEN, pygame.Rect(pos[0], pos[1], 20, 20)) pygame.draw.rect(screen, RED, pygame.Rect(food_pos[0], food_pos[1], 20, 20)) pygame.display.flip() def game_quit(): pygame.quit() sys.exit() def main(): global screen, screen_size global snake_pos, food_pos, snake_speed # 主循环 while True: # 处理游戏事件 for event in pygame.event.get(): if event.type == pygame.QUIT: game_quit() elif event.type == pygame.KEYDOWN: if event.key == pygame.K_UP: snake_speed = [0, -20] elif event.key == pygame.K_DOWN: snake_speed = [0, 20] elif event.key == pygame.K_LEFT: snake_speed = [-20, 0] elif event.key == pygame.K_RIGHT: snake_speed = [20, 0] # 更新蛇的位置 snake_pos.insert(0, [snake_pos[0][0] + snake_speed[0], snake_pos[0][1] + snake_speed[1]]) # 检查蛇头是否碰到食物 if snake_pos[0] == food_pos: food_pos = [random.randrange(1, screen_size[0] // 20) * 20, random.randrange(1, screen_size[1] // 20) * 20] else: snake_pos.pop() # 检查蛇头是否碰到墙壁或者蛇身 if snake_pos[0][0] < 0 or snake_pos[0][0] >= screen_size[0] or snake_pos[0][1] < 0 or snake_pos[0][1] >= screen_size[1] or snake_pos[0] in snake_pos[1:]: game_quit() ''' 此处可增加与用户的交互,如: if askyesno('title','again?'): init() # Yes to Play again else: game_quit() # No to Exit ''' # 重画界面及蛇和食物 repaint() # 控制游戏速度 pygame.time.Clock().tick(10) if __name__ == "__main__": init() main()
改进过程一
增加提示信息
之前没有仔细学过pygame的编程,刚好拿这例程练练手,在不断改进中学习pygame编程。
原代码一执行,蛇就开始游动了,就从这里入手。开始前,增加显示“按任意键开始...”的提示。
蛇死后,提醒是否重来?Yes to play again, No to quit game.
同时,在游戏中允许按ESC键暂停游戏,再按一次继续。
由于没查到 pygame 有弹出信息窗口的方法(函数),于是用了DOS时代显示信息窗口的办法,画多个矩形窗口来模拟窗口,最后在矩形框上写上提示文字。代码如下:
def show_msg(msg, color = BLUE): x = screen.get_rect().centerx y = screen.get_rect().centery - 50 font = pygame.font.Font(None, 36) text = font.render(msg, True, color) text_rect = text.get_rect() text_rect.centerx = x text_rect.centery = y rectangle1 = pygame.Rect(x-155, y-25, 320, 60) rectangle2 = pygame.Rect(x-160, y-30, 320, 60) pygame.draw.rect(screen, DARK, rectangle1) pygame.draw.rect(screen, GREY, rectangle2) pygame.draw.rect(screen, BLACK, rectangle2, 2) # 边框宽为2 screen.blit(text, text_rect) pygame.display.flip()
原版帮助摘要
pygame.draw
NAME pygame.draw - pygame module for drawing shapes FUNCTIONS aaline(...) aaline(surface, color, start_pos, end_pos) -> Rect aaline(surface, color, start_pos, end_pos, blend=1) -> Rect draw a straight antialiased line aalines(...) aalines(surface, color, closed, points) -> Rect aalines(surface, color, closed, points, blend=1) -> Rect draw multiple contiguous straight antialiased line segments arc(...) arc(surface, color, rect, start_angle, stop_angle) -> Rect arc(surface, color, rect, start_angle, stop_angle, width=1) -> Rect draw an elliptical arc circle(...) circle(surface, color, center, radius) -> Rect circle(surface, color, center, radius, width=0, draw_top_right=None, draw_top_left=None, draw_bottom_left=None, draw_bottom_right=None) -> Rect draw a circle ellipse(...) ellipse(surface, color, rect) -> Rect ellipse(surface, color, rect, width=0) -> Rect draw an ellipse line(...) line(surface, color, start_pos, end_pos) -> Rect line(surface, color, start_pos, end_pos, width=1) -> Rect draw a straight line lines(...) lines(surface, color, closed, points) -> Rect lines(surface, color, closed, points, width=1) -> Rect draw multiple contiguous straight line segments polygon(...) polygon(surface, color, points) -> Rect polygon(surface, color, points, width=0) -> Rect draw a polygon rect(...) rect(surface, color, rect) -> Rect rect(surface, color, rect, width=0, border_radius=0, border_top_left_radius=-1, border_top_right_radius=-1, border_bottom_left_radius=-1, border_bottom_right_radius=-1) -> Rect draw a rectangle
pygame.font
NAME pygame.font - pygame module for loading and rendering fonts CLASSES builtins.object Font class Font(builtins.object) | Font(file_path=None, size=12) -> Font | Font(file_path, size) -> Font | Font(pathlib.Path, size) -> Font | Font(object, size) -> Font | create a new Font object from a file | | Methods defined here: | | __init__(self, /, *args, **kwargs) | Initialize self. See help(type(self)) for accurate signature. | | get_ascent(...) | get_ascent() -> int | get the ascent of the font | | get_bold(...) | get_bold() -> bool | check if text will be rendered bold | | get_descent(...) | get_descent() -> int | get the descent of the font | | get_height(...) | get_height() -> int | get the height of the font | | get_italic(...) | get_italic() -> bool | check if the text will be rendered italic | | get_linesize(...) | get_linesize() -> int | get the line space of the font text | | get_strikethrough(...) | get_strikethrough() -> bool | check if text will be rendered with a strikethrough | | get_underline(...) | get_underline() -> bool | check if text will be rendered with an underline | | metrics(...) | metrics(text) -> list | gets the metrics for each character in the passed string | | render(...) | render(text, antialias, color, background=None) -> Surface | draw text on a new Surface | | set_bold(...) | set_bold(bool) -> None | enable fake rendering of bold text | | set_italic(...) | set_italic(bool) -> None | enable fake rendering of italic text | | set_script(...) | set_script(str) -> None | set the script code for text shaping | | set_strikethrough(...) | set_strikethrough(bool) -> None | control if text is rendered with a strikethrough | | set_underline(...) | set_underline(bool) -> None | control if text is rendered with an underline | | size(...) | size(text) -> (width, height) | determine the amount of space needed to render text | | ---------------------------------------------------------------------- | Data descriptors defined here: | | bold | bold -> bool | Gets or sets whether the font should be rendered in (faked) bold. | | italic | italic -> bool | Gets or sets whether the font should be rendered in (faked) italics. | | strikethrough | strikethrough -> bool | Gets or sets whether the font should be rendered with a strikethrough. | | underline | underline -> bool | Gets or sets whether the font should be rendered with an underline. class Rect Help on class Rect in module pygame.rect: class Rect(builtins.object) | Rect(left, top, width, height) -> Rect | Rect((left, top), (width, height)) -> Rect | Rect(object) -> Rect | pygame object for storing rectangular coordinates | | Methods defined here: | | clamp(...) | clamp(Rect) -> Rect | moves the rectangle inside another | | clamp_ip(...) | clamp_ip(Rect) -> None | moves the rectangle inside another, in place | | clip(...) | clip(Rect) -> Rect | crops a rectangle inside another | | clipline(...) | clipline(x1, y1, x2, y2) -> ((cx1, cy1), (cx2, cy2)) | clipline(x1, y1, x2, y2) -> () | clipline((x1, y1), (x2, y2)) -> ((cx1, cy1), (cx2, cy2)) | clipline((x1, y1), (x2, y2)) -> () | clipline((x1, y1, x2, y2)) -> ((cx1, cy1), (cx2, cy2)) | clipline((x1, y1, x2, y2)) -> () | clipline(((x1, y1), (x2, y2))) -> ((cx1, cy1), (cx2, cy2)) | clipline(((x1, y1), (x2, y2))) -> () | crops a line inside a rectangle | | collidedict(...) | collidedict(dict) -> (key, value) | collidedict(dict) -> None | collidedict(dict, use_values=0) -> (key, value) | collidedict(dict, use_values=0) -> None | test if one rectangle in a dictionary intersects | | collidedictall(...) | collidedictall(dict) -> [(key, value), ...] | collidedictall(dict, use_values=0) -> [(key, value), ...] | test if all rectangles in a dictionary intersect | | collidelist(...) | collidelist(list) -> index | test if one rectangle in a list intersects | | collidelistall(...) | collidelistall(list) -> indices | test if all rectangles in a list intersect | | collideobjects(...) | collideobjects(rect_list) -> object | collideobjects(obj_list, key=func) -> object | test if any object in a list intersects | | collideobjectsall(...) | collideobjectsall(rect_list) -> objects | collideobjectsall(obj_list, key=func) -> objects | test if all objects in a list intersect | | collidepoint(...) | collidepoint(x, y) -> bool | collidepoint((x,y)) -> bool | test if a point is inside a rectangle | | colliderect(...) | colliderect(Rect) -> bool | test if two rectangles overlap | | contains(...) | contains(Rect) -> bool | test if one rectangle is inside another | | copy(...) | copy() -> Rect | copy the rectangle | | fit(...) | fit(Rect) -> Rect | resize and move a rectangle with aspect ratio | | inflate(...) | inflate(x, y) -> Rect | grow or shrink the rectangle size | | inflate_ip(...) | inflate_ip(x, y) -> None | grow or shrink the rectangle size, in place | | move(...) | move(x, y) -> Rect | moves the rectangle | | move_ip(...) | move_ip(x, y) -> None | moves the rectangle, in place | | normalize(...) | normalize() -> None | correct negative sizes | | scale_by(...) | scale_by(scalar) -> Rect | scale_by(scalex, scaley) -> Rect | scale the rectangle by given a multiplier | | scale_by_ip(...) | scale_by_ip(scalar) -> None | scale_by_ip(scalex, scaley) -> None | grow or shrink the rectangle size, in place | | union(...) | union(Rect) -> Rect | joins two rectangles into one | | union_ip(...) | union_ip(Rect) -> None | joins two rectangles into one, in place | | unionall(...) | unionall(Rect_sequence) -> Rect | the union of many rectangles | | unionall_ip(...) | unionall_ip(Rect_sequence) -> None | the union of many rectangles, in place | | update(...) | update(left, top, width, height) -> None | update((left, top), (width, height)) -> None | update(object) -> None | sets the position and size of the rectangle class Surface class Surface(builtins.object) | Surface((width, height), flags=0, depth=0, masks=None) -> Surface | Surface((width, height), flags=0, Surface) -> Surface | pygame object for representing images | | Methods defined here: | | blit(...) | blit(source, dest, area=None, special_flags=0) -> Rect | draw one image onto another | | blits(...) | blits(blit_sequence=((source, dest), ...), doreturn=1) -> [Rect, ...] or None | blits(((source, dest, area), ...)) -> [Rect, ...] | blits(((source, dest, area, special_flags), ...)) -> [Rect, ...] | draw many images onto another | | convert(...) | convert(Surface=None) -> Surface | convert(depth, flags=0) -> Surface | convert(masks, flags=0) -> Surface | change the pixel format of an image | | convert_alpha(...) | convert_alpha(Surface) -> Surface | convert_alpha() -> Surface | change the pixel format of an image including per pixel alphas | | copy(...) | copy() -> Surface | create a new copy of a Surface | | fill(...) | fill(color, rect=None, special_flags=0) -> Rect | fill Surface with a solid color | | get_abs_offset(...) | get_abs_offset() -> (x, y) | find the absolute position of a child subsurface inside its top level parent | | get_abs_parent(...) | get_abs_parent() -> Surface | find the top level parent of a subsurface | | get_alpha(...) | get_alpha() -> int_value | get the current Surface transparency value | | get_at(...) | get_at((x, y)) -> Color | get the color value at a single pixel | | get_at_mapped(...) | get_at_mapped((x, y)) -> Color | get the mapped color value at a single pixel | | get_bitsize(...) | get_bitsize() -> int | get the bit depth of the Surface pixel format | | get_blendmode(...) | Return the surface's SDL 2 blend mode | | get_bounding_rect(...) | get_bounding_rect(min_alpha = 1) -> Rect | find the smallest rect containing data | | get_buffer(...) | get_buffer() -> BufferProxy | acquires a buffer object for the pixels of the Surface. | | get_bytesize(...) | get_bytesize() -> int | get the bytes used per Surface pixel | | get_clip(...) | get_clip() -> Rect | get the current clipping area of the Surface | | get_colorkey(...) | get_colorkey() -> RGB or None | Get the current transparent colorkey | | get_flags(...) | get_flags() -> int | get the additional flags used for the Surface | | get_height(...) | get_height() -> height | get the height of the Surface | | get_locked(...) | get_locked() -> bool | test if the Surface is current locked | | get_locks(...) | get_locks() -> tuple | Gets the locks for the Surface | | get_losses(...) | get_losses() -> (R, G, B, A) | the significant bits used to convert between a color and a mapped integer | | get_masks(...) | get_masks() -> (R, G, B, A) | the bitmasks needed to convert between a color and a mapped integer | | get_offset(...) | get_offset() -> (x, y) | find the position of a child subsurface inside a parent | | get_palette(...) | get_palette() -> [RGB, RGB, RGB, ...] | get the color index palette for an 8-bit Surface | | get_palette_at(...) | get_palette_at(index) -> RGB | get the color for a single entry in a palette | | get_parent(...) | get_parent() -> Surface | find the parent of a subsurface | | get_pitch(...) | get_pitch() -> int | get the number of bytes used per Surface row | | get_rect(...) | get_rect(**kwargs) -> Rect | get the rectangular area of the Surface | | get_shifts(...) | get_shifts() -> (R, G, B, A) | the bit shifts needed to convert between a color and a mapped integer | | get_size(...) | get_size() -> (width, height) | get the dimensions of the Surface | | get_view(...) | get_view(='2') -> BufferProxy | return a buffer view of the Surface's pixels. | | get_width(...) | get_width() -> width | get the width of the Surface | | lock(...) | lock() -> None | lock the Surface memory for pixel access | | map_rgb(...) | map_rgb(Color) -> mapped_int | convert a color into a mapped color value | | mustlock(...) | mustlock() -> bool | test if the Surface requires locking | | premul_alpha(...) | premul_alpha() -> Surface | returns a copy of the surface with the RGB channels pre-multiplied by the alpha channel. | | scroll(...) | scroll(dx=0, dy=0) -> None | Shift the surface image in place | | set_alpha(...) | set_alpha(value, flags=0) -> None | set_alpha(None) -> None | set the alpha value for the full Surface image | | set_at(...) | set_at((x, y), Color) -> None | set the color value for a single pixel | | set_clip(...) | set_clip(rect) -> None | set_clip(None) -> None | set the current clipping area of the Surface | | set_colorkey(...) | set_colorkey(Color, flags=0) -> None | set_colorkey(None) -> None | Set the transparent colorkey | | set_masks(...) | set_masks((r,g,b,a)) -> None | set the bitmasks needed to convert between a color and a mapped integer | | set_palette(...) | set_palette([RGB, RGB, RGB, ...]) -> None | set the color palette for an 8-bit Surface | | set_palette_at(...) | set_palette_at(index, RGB) -> None | set the color for a single index in an 8-bit Surface palette | | set_shifts(...) | set_shifts((r,g,b,a)) -> None | sets the bit shifts needed to convert between a color and a mapped integer | | subsurface(...) | subsurface(Rect) -> Surface | create a new surface that references its parent | | unlock(...) | unlock() -> None | unlock the Surface memory from pixel access | | unmap_rgb(...) | unmap_rgb(mapped_int) -> Color | convert a mapped integer color value into a Color
另外增加了3个状态变量,初始状态为:
is_running = False
is_paused = False
is_dead = False
增加了4个按键判别:
is_dead时,判断重新开始还是退出游戏
pygame.K_y: 字母Y/y
pygame.K_n: 字母N/n
暂停和恢复
pygame.K_ESCAPE: Esc键
pygame.K_SPACE: 空格键
完整代码如下:
import pygame import sys import random # 定义颜色 BLACK = (0, 0, 0) WHITE = (255, 255, 255) RED = (255, 0, 0) GREEN = (0, 255, 0) BLUE = (0, 0, 255) GREY = (220, 220, 220) # 淡灰色 DARK = (120, 120, 120) # 深灰色 def init(): global screen, screen_size global snake_pos, food_pos, snake_speed # 初始化pygame pygame.init() # 设置屏幕大小 screen_size = (640, 480) screen = pygame.display.set_mode(screen_size) # 设置游戏标题 pygame.display.set_caption("贪吃蛇") # 蛇的初始位置 snake_pos = [[100, 100], [80, 100], [60, 100]] # 食物的初始位置 food_pos = [300, 300] # 蛇的初始速度 snake_speed = [20, 0] def show_msg(msg, color = BLUE): x = screen.get_rect().centerx y = screen.get_rect().centery - 50 font = pygame.font.Font(None, 36) text = font.render(msg, True, color) text_rect = text.get_rect() text_rect.centerx = x text_rect.centery = y rectangle1 = pygame.Rect(x-155, y-25, 320, 60) rectangle2 = pygame.Rect(x-160, y-30, 320, 60) pygame.draw.rect(screen, DARK, rectangle1) pygame.draw.rect(screen, GREY, rectangle2) pygame.draw.rect(screen, BLACK, rectangle2, 2) # 边框宽为2 screen.blit(text, text_rect) pygame.display.flip() def repaint(): # 绘制游戏界面 screen.fill(WHITE) # 定义线段的端点坐标 x,y = (-1,640,640,-1)*16, [] for i in range(36): for _ in range(2): y.append(19+i*20) # 使用pygame.draw.lines()函数绘制线段 points = list(zip(x,y)) pygame.draw.lines(screen, GREY, False, points, 1) # 线宽为1 points = list(zip(y,x)) pygame.draw.lines(screen, GREY, False, points, 1) # 重画蛇和食物 for pos in snake_pos: pygame.draw.rect(screen, GREEN, pygame.Rect(pos[0], pos[1], 20, 20)) pygame.draw.rect(screen, RED, pygame.Rect(food_pos[0], food_pos[1], 20, 20)) pygame.display.flip() def game_quit(): pygame.quit() sys.exit() def main(): global screen, screen_size global snake_pos, food_pos, snake_speed is_running = False is_paused = False is_dead = False repaint() show_msg("Press any key to start ...") # 主循环 while True: # 处理游戏事件 for event in pygame.event.get(): if event.type == pygame.QUIT: game_quit() elif event.type == pygame.KEYDOWN: if event.key == pygame.K_UP: snake_speed = [0, -20] elif event.key == pygame.K_DOWN: snake_speed = [0, 20] elif event.key == pygame.K_LEFT: snake_speed = [-20, 0] elif event.key == pygame.K_RIGHT: snake_speed = [20, 0] elif event.key == pygame.K_y: if is_dead: init() is_dead = False is_running = True elif event.key == pygame.K_n: if is_dead: game_quit() else: is_running = True elif event.key == pygame.K_ESCAPE: if is_running: show_msg(">>> Paused <<<") is_paused = not is_paused else: # 任意键进入开始状态 is_running = True if not is_running: continue if is_paused and is_running: continue # 更新蛇的位置 snake_pos.insert(0, [snake_pos[0][0] + snake_speed[0], snake_pos[0][1] + snake_speed[1]]) # 检查蛇头是否碰到食物 if snake_pos[0] == food_pos: food_pos = [random.randrange(1, screen_size[0] // 20) * 20, random.randrange(1, screen_size[1] // 20) * 20] else: snake_pos.pop() # 检查蛇头是否碰到墙壁或者蛇身 if snake_pos[0][0] < 0 or snake_pos[0][0] >= screen_size[0] or snake_pos[0][1] < 0 or snake_pos[0][1] >= screen_size[1] or snake_pos[0] in snake_pos[1:]: show_msg("Dead! Again? (Y or N)") is_running = False is_dead = True continue # 重画界面及蛇和食物 repaint() # 控制游戏速度 pygame.time.Clock().tick(10) if __name__ == "__main__": init() main()
改进过程二
增加显示得分
每吃到一个食物+10分,蛇长和食物靠近边界会有额外加分;顺带显示出蛇的坐标位置。
函数show_msg_at(),比show_msg增加x,y坐标,把信息显示到指定的位置:
def show_msg_at(x, y, msg): font = pygame.font.SysFont('Consolas', 14) # 使用系统字库 text = font.render(msg, True, BLACK) text_rect = text.get_rect() text_rect.x, text_rect.y = x, y screen.blit(text, text_rect) pygame.display.flip()
另外新增一个全局变量 scores,当碰到食物时加10分,蛇长超过5以及食物靠近边界的距离小3会有额外加分,规则可以自己定,例如:
if snake_pos[0] == food_pos:
scores += 10 + len(snake_pos) // 5
if not 1 < snake_pos[0][0]//20 < 30 or not 1 < snake_pos[0][1]//20 < 22:
scores += 5
完整代码如下:
import pygame import sys import random # 定义颜色 BLACK = (0, 0, 0) WHITE = (255, 255, 255) RED = (255, 0, 0) GREEN = (0, 255, 0) BLUE = (0, 0, 255) GREY = (220, 220, 220) # 淡灰色 DARK = (120, 120, 120) # 深灰色 def init(): global screen, screen_size, scores global snake_pos, food_pos, snake_speed # 初始化pygame scores = 0 pygame.init() # 设置屏幕大小 screen_size = (640, 480) screen = pygame.display.set_mode(screen_size) # 设置游戏标题 pygame.display.set_caption("贪吃蛇") # 蛇的初始位置 snake_pos = [[100, 100], [80, 100], [60, 100]] # 食物的初始位置 food_pos = [300, 300] # 蛇的初始速度 snake_speed = [20, 0] def show_msg(msg, color = BLUE): x = screen.get_rect().centerx y = screen.get_rect().centery - 50 font = pygame.font.Font(None, 36) text = font.render(msg, True, color) text_rect = text.get_rect() text_rect.centerx = x text_rect.centery = y rectangle1 = pygame.Rect(x-155, y-25, 320, 60) rectangle2 = pygame.Rect(x-160, y-30, 320, 60) pygame.draw.rect(screen, DARK, rectangle1) pygame.draw.rect(screen, GREY, rectangle2) pygame.draw.rect(screen, BLACK, rectangle2, 2) # 边框宽为2 screen.blit(text, text_rect) pygame.display.flip() def repaint(): # 绘制游戏界面 screen.fill(WHITE) # 定义线段的端点坐标 x,y = (-1,640,640,-1)*16, [] for i in range(36): for _ in range(2): y.append(19+i*20) # 使用pygame.draw.lines()函数绘制线段 points = list(zip(x,y)) pygame.draw.lines(screen, GREY, False, points, 1) # 线宽为1 points = list(zip(y,x)) pygame.draw.lines(screen, GREY, False, points, 1) # 重画蛇和食物 for pos in snake_pos: pygame.draw.rect(screen, GREEN, pygame.Rect(pos[0], pos[1], 20, 20)) pygame.draw.rect(screen, RED, pygame.Rect(food_pos[0], food_pos[1], 20, 20)) pygame.display.flip() show_msg_at(22, 6, f'Scores: {scores}') show_msg_at(410, 6, f'Snake coordinate: ({1+snake_pos[0][0]//20:2}, {1+snake_pos[0][1]//20:2})') def show_msg_at(x, y, msg): font = pygame.font.SysFont('Consolas', 14) text = font.render(msg, True, BLACK) text_rect = text.get_rect() text_rect.x, text_rect.y = x, y screen.blit(text, text_rect) pygame.display.flip() def game_quit(): pygame.quit() sys.exit() def main(): global screen, screen_size, scores global snake_pos, food_pos, snake_speed is_running = False is_paused = False is_dead = False repaint() show_msg("Press any key to start ...") # 主循环 while True: # 处理游戏事件 for event in pygame.event.get(): if event.type == pygame.QUIT: game_quit() elif event.type == pygame.KEYDOWN: if event.key == pygame.K_UP: snake_speed = [0, -20] elif event.key == pygame.K_DOWN: snake_speed = [0, 20] elif event.key == pygame.K_LEFT: snake_speed = [-20, 0] elif event.key == pygame.K_RIGHT: snake_speed = [20, 0] elif event.key == pygame.K_y: if is_dead: init() is_dead = False is_running = True elif event.key == pygame.K_n: if is_dead: game_quit() else: is_running = True elif event.key == pygame.K_SPACE: if is_dead: continue if is_paused: is_paused = False is_running = True elif event.key == pygame.K_ESCAPE: if is_running: show_msg(">>> Paused <<<") is_paused = not is_paused else: # 任意键进入开始状态 if is_dead: continue is_running = True if not is_running: continue if is_paused and is_running: continue # 更新蛇的位置 snake_pos.insert(0, [snake_pos[0][0] + snake_speed[0], snake_pos[0][1] + snake_speed[1]]) # 检查蛇头是否碰到食物 if snake_pos[0] == food_pos: scores += 10 + len(snake_pos) // 5 if not 1 < snake_pos[0][0]//20 < 30 or not 1 < snake_pos[0][1]//20 < 22: scores += 5 food_pos = [random.randrange(1, screen_size[0] // 20) * 20, random.randrange(1, screen_size[1] // 20) * 20] else: snake_pos.pop() # 检查蛇头是否碰到墙壁或者蛇身 if snake_pos[0][0] < 0 or snake_pos[0][0] >= screen_size[0] or snake_pos[0][1] < 0 or snake_pos[0][1] >= screen_size[1] or snake_pos[0] in snake_pos[1:]: show_msg("Dead! Again? (Y or N)") is_running = False is_dead = True continue # 重画界面及蛇和食物 repaint() # 控制游戏速度 pygame.time.Clock().tick(10) if __name__ == "__main__": init() main()
改进过程三
增加背景景乐
def play_music(mp3, volume = 1, loops = -1): # 初始化pygame的mixer模块 pygame.mixer.init() # 加载音乐文件 pygame.mixer.music.load(mp3) # 控制音量 volume = 0~1,1为最高音量 pygame.mixer.music.set_volume(volume) # 播放音乐 loops = -1 为循环播放 pygame.mixer.music.play(loops)
增加提示音效
def play_sound(wav_no): sound_fn = f'sound{wav_no}.wav' if os.path.exists(sound_fn): alert_sound = pygame.mixer.Sound(sound_fn) alert_sound.play()
音乐切换
快捷键 Ctrl+M
elif event.key == pygame.K_m and event.mod & pygame.KMOD_CTRL: # Ctrl+M 切换背景音乐 is_mute = False music_no = 1 if music_no == 3 else music_no + 1 music_fn = f"voice{music_no}.mp3" if os.path.exists(music_fn): t = threading.Thread(target=play_music, args=(music_fn,0.8,)) t.start()
静音切换
快捷键 Ctrl+S
elif event.key == pygame.K_s and event.mod & pygame.KMOD_CTRL: # Ctrl+S 切换静音状态 is_mute = not is_mute if is_mute: pygame.mixer.music.pause() else: pygame.mixer.music.unpause()
mixer.music.play 注意事项
1. pygame.mixer.music.play() 只能播放pygame支持的音频格式,包括WAV, MP3等。
2. 如果音频文件未找到或无法读取,pygame.mixer.music.play( ) 会抛出一个异常。使用需要确保音频文件的路径正确,且文件存在。导入os库,用os.path.exists(music_file) 判断文件是否存在。
3. pygame.mixer.music.play() 是一个阻塞函数,在音频播放期间程序将不会执行其他操作。如果需要在播放同时执行其他操作,需要在一个单独的线程中调用pygame.mixer.music.play()。
4. 多线程需要导入threading库,例如:
t = threading.Thread(target=play_music, args=(music_fn,0.8,))
t.start()
原版帮助摘要
pygame.mixer
NAME pygame.mixer_music - pygame module for controlling streamed audio FUNCTIONS fadeout(...) fadeout(time) -> None stop music playback after fading out get_busy(...) get_busy() -> bool check if the music stream is playing get_endevent(...) get_endevent() -> type get the event a channel sends when playback stops get_pos(...) get_pos() -> time get the music play time get_volume(...) get_volume() -> value get the music volume load(...) load(filename) -> None load(fileobj, namehint=) -> None Load a music file for playback pause(...) pause() -> None temporarily stop music playback play(...) play(loops=0, start=0.0, fade_ms=0) -> None Start the playback of the music stream queue(...) queue(filename) -> None queue(fileobj, namehint=, loops=0) -> None queue a sound file to follow the current rewind(...) rewind() -> None restart music set_endevent(...) set_endevent() -> None set_endevent(type) -> None have the music send an event when playback stops set_pos(...) set_pos(pos) -> None set position to play from set_volume(...) set_volume(volume) -> None set the music volume stop(...) stop() -> None stop the music playback unload(...) unload() -> None Unload the currently loaded music to free up resources unpause(...) unpause() -> None resume paused music pygame.mixer.Sound class Sound(builtins.object) | Sound(filename) -> Sound | Sound(file=filename) -> Sound | Sound(file=pathlib_path) -> Sound | Sound(buffer) -> Sound | Sound(buffer=buffer) -> Sound | Sound(object) -> Sound | Sound(file=object) -> Sound | Sound(array=object) -> Sound | Create a new Sound object from a file or buffer object | | Methods defined here: | | __init__(self, /, *args, **kwargs) | Initialize self. See help(type(self)) for accurate signature. | | fadeout(...) | fadeout(time) -> None | stop sound playback after fading out | | get_length(...) | get_length() -> seconds | get the length of the Sound | | get_num_channels(...) | get_num_channels() -> count | count how many times this Sound is playing | | get_raw(...) | get_raw() -> bytes | return a bytestring copy of the Sound samples. | | get_volume(...) | get_volume() -> value | get the playback volume | | play(...) | play(loops=0, maxtime=0, fade_ms=0) -> Channel | begin sound playback | | set_volume(...) | set_volume(value) -> None | set the playback volume for this Sound | | stop(...) | stop() -> None | stop sound playback
完整代码:
import pygame import sys, os import random import threading # 定义颜色 BLACK = (0, 0, 0) WHITE = (255, 255, 255) RED = (255, 0, 0) GREEN = (0, 255, 0) BLUE = (0, 0, 255) GREY = (220, 220, 220) # 淡灰色 DARK = (120, 120, 120) # 深灰色 def init(): global screen, screen_size, scores global snake_pos, food_pos, snake_speed # 初始化pygame scores = 0 pygame.init() # 设置屏幕大小 screen_size = (640, 480) screen = pygame.display.set_mode(screen_size) # 设置游戏标题 pygame.display.set_caption("贪吃蛇") # 蛇的初始位置 snake_pos = [[100, 100], [80, 100], [60, 100]] # 食物的初始位置 food_pos = [300, 300] # 蛇的初始速度 snake_speed = [20, 0] def play_music(mp3, volume = 1, loops = -1): # 初始化pygame的mixer模块 pygame.mixer.init() # 加载音乐文件 pygame.mixer.music.load(mp3) # 控制音量 pygame.mixer.music.set_volume(volume) # 播放音乐 pygame.mixer.music.play(loops) def play_sound(wav_no): sound_fn = f'sound{wav_no}.wav' if os.path.exists(sound_fn): alert_sound = pygame.mixer.Sound(sound_fn) alert_sound.play() def show_msg(msg, color = BLUE): x = screen.get_rect().centerx y = screen.get_rect().centery - 50 font = pygame.font.Font(None, 36) text = font.render(msg, True, color) text_rect = text.get_rect() text_rect.centerx = x text_rect.centery = y rectangle1 = pygame.Rect(x-155, y-25, 320, 60) rectangle2 = pygame.Rect(x-160, y-30, 320, 60) pygame.draw.rect(screen, DARK, rectangle1) pygame.draw.rect(screen, GREY, rectangle2) pygame.draw.rect(screen, BLACK, rectangle2, 2) # 边框宽为2 screen.blit(text, text_rect) pygame.display.flip() def repaint(): # 绘制游戏界面 screen.fill(WHITE) # 定义线段的端点坐标 x,y = (-1,640,640,-1)*16, [] for i in range(36): for _ in range(2): y.append(19+i*20) # 使用pygame.draw.lines()函数绘制线段 points = list(zip(x,y)) pygame.draw.lines(screen, GREY, False, points, 1) # 线宽为1 points = list(zip(y,x)) pygame.draw.lines(screen, GREY, False, points, 1) # 重画蛇和食物 for pos in snake_pos: pygame.draw.rect(screen, GREEN, pygame.Rect(pos[0], pos[1], 20, 20)) pygame.draw.rect(screen, RED, pygame.Rect(food_pos[0], food_pos[1], 20, 20)) pygame.display.flip() show_msg_at(22, 6, f'Scores: {scores}') show_msg_at(410, 6, f'Snake coordinate: ({1+snake_pos[0][0]//20:2}, {1+snake_pos[0][1]//20:2})') def show_msg_at(x, y, msg): font = pygame.font.SysFont('Consolas', 14) text = font.render(msg, True, BLACK) text_rect = text.get_rect() text_rect.x, text_rect.y = x, y screen.blit(text, text_rect) pygame.display.flip() def game_quit(): pygame.quit() sys.exit() def main(): global screen, screen_size, scores global snake_pos, food_pos, snake_speed is_running = False is_paused = False is_dead = False is_mute = False repaint() show_msg("Press any key to start ...") # 创建一个线程来播放音乐 music_no = 1 music_fn = "voice1.mp3" if os.path.exists(music_fn): t = threading.Thread(target=play_music, args=(music_fn,0.8,)) t.start() # 主循环 while True: # 处理游戏事件 for event in pygame.event.get(): if event.type == pygame.QUIT: game_quit() elif event.type == pygame.KEYDOWN: if event.key == pygame.K_UP: snake_speed = [0, -20] elif event.key == pygame.K_DOWN: snake_speed = [0, 20] elif event.key == pygame.K_LEFT: snake_speed = [-20, 0] elif event.key == pygame.K_RIGHT: snake_speed = [20, 0] elif event.key == pygame.K_y: if is_dead: init() is_dead = False is_running = True elif event.key == pygame.K_n: if is_dead: game_quit() else: is_running = True elif event.key == pygame.K_SPACE: if is_dead: continue if is_paused: is_paused = False is_running = True elif event.key == pygame.K_ESCAPE: if is_running: show_msg(">>> Paused <<<") is_paused = not is_paused if not is_mute and is_paused: play_sound(1) elif event.key == pygame.K_m and event.mod & pygame.KMOD_CTRL: # Ctrl+M 切换背景音乐 is_mute = False music_no = 1 if music_no == 3 else music_no + 1 music_fn = f"voice{music_no}.mp3" if os.path.exists(music_fn): t = threading.Thread(target=play_music, args=(music_fn,0.8,)) t.start() elif event.key == pygame.K_s and event.mod & pygame.KMOD_CTRL: # Ctrl+S 切换静音状态 is_mute = not is_mute if is_mute: pygame.mixer.music.pause() else: pygame.mixer.music.unpause() is_running = True else: # 任意键进入开始状态 if is_dead: continue is_running = True if not is_running: continue if is_paused and is_running: continue # 更新蛇的位置 snake_pos.insert(0, [snake_pos[0][0] + snake_speed[0], snake_pos[0][1] + snake_speed[1]]) # 检查蛇头是否碰到食物 if snake_pos[0] == food_pos: scores += 10 + len(snake_pos) // 5 if not 1 < snake_pos[0][0]//20 < 30 or not 1 < snake_pos[0][1]//20 < 22: scores += 5 if not is_mute: play_sound(2) food_pos = [random.randrange(1, screen_size[0] // 20) * 20, random.randrange(1, screen_size[1] // 20) * 20] else: snake_pos.pop() # 检查蛇头是否碰到墙壁或者蛇身 if snake_pos[0][0] < 0 or snake_pos[0][0] >= screen_size[0] or snake_pos[0][1] < 0 or snake_pos[0][1] >= screen_size[1] or snake_pos[0] in snake_pos[1:]: show_msg("Dead! Again? (Y or N)") is_running = False is_dead = True if not is_mute: play_sound(3) continue # 重画界面及蛇和食物 repaint() # 控制游戏速度 pygame.time.Clock().tick(10) if __name__ == "__main__": init() main()
改进过程四
增加WASD方向键
DOS时代的游戏经常用W、A、S、D四个字母,对应上左下右四个方向键,上面的代码中,快捷键 ctrl+S 换成 strl+Q 避免冲突。
另外,游戏中按了与前进方向相反的键,相当于蛇的自杀行为。为了避免这个bug,引入一个表示蛇移动方向的变量direction:
if not is_paused: if (event.key == pygame.K_w or event.key == pygame.K_UP) and direction != DOWN: direction = UP elif (event.key == pygame.K_s or event.key == pygame.K_DOWN) and direction != UP: direction = DOWN elif (event.key == pygame.K_a or event.key == pygame.K_LEFT) and direction != RIGHT: direction = LEFT elif (event.key == pygame.K_f or event.key == pygame.K_RIGHT) and direction != LEFT: direction = RIGHT 把移动和按键分离,控制移动的代码放到后面去: if direction == UP: snake_speed = [0, -20] elif direction == DOWN: snake_speed = [0, 20] elif direction == LEFT: snake_speed = [-20, 0] elif direction == RIGHT: snake_speed = [20, 0] 增加退出事件的确认 pygame 没有弹窗一类的方法,导入tkinter库,由messagebox来实现: from tkinter import messagebox ...... for event in pygame.event.get(): if event.type == pygame.QUIT: if messagebox.askyesno("确认", "是否要退出?"): game_quit() ......
效果如下:
最后,增加了按暂停键时背景音乐也暂停的功能;另外修改了一些小错误,估计还会有bug出现,状态变量设置多了感觉逻辑有点乱。
小结
本文以贪吃蛇游戏为例,对pygame编程的一个简单框架进行了深入的学习,包括对画图、字体、音乐等各个方面操作的各种方法和函数,学习后在pygame这方面的编程能力有所长进提高。
pygame编程框架
import pygame import sys # 初始化Pygame pygame.init() # 设置窗口大小 screen_size = (800, 600) # 创建窗口 screen = pygame.display.set_mode(screen_size) # 设置窗口标题 pygame.display.set_caption("Pygame Example") # 主循环 while True: # 处理事件 for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit() elif event.type == ... ... // 处理按键事件 # 填充背景色 screen.fill((255, 255, 255)) # 绘制矩形 pygame.draw.rect(screen, (0, 0, 255), (400, 300, 100, 50)) # 更新显示 pygame.display.flip()
完整代码
import pygame from sys import exit as system from random import randrange from os.path import exists from tkinter import messagebox from threading import Thread # 定义颜色 BLACK = (0, 0, 0) WHITE = (255, 255, 255) RED = (255, 0, 0) GREEN = (0, 255, 0) BLUE = (0, 0, 255) GREY = (220, 220, 220) # 淡灰色 DARK = (120, 120, 120) # 深灰色 def init(): global screen, screen_size, scores global snake_pos, food_pos, snake_speed # 初始化pygame scores = 0 pygame.init() # 设置屏幕大小 screen_size = (640, 480) screen = pygame.display.set_mode(screen_size) # 设置游戏标题 pygame.display.set_caption("贪吃蛇") # 蛇的初始位置 snake_pos = [[100, 100], [80, 100], [60, 100]] # 食物的初始位置 food_pos = [300, 300] # 蛇的初始速度 snake_speed = [20, 0] def play_music(mp3, volume = 1, loops = -1): # 初始化pygame的mixer模块 pygame.mixer.init() # 加载音乐文件 pygame.mixer.music.load(mp3) # 控制音量 pygame.mixer.music.set_volume(volume) # 播放音乐 pygame.mixer.music.play(loops) def play_sound(wav_no): sound_fn = f'sound{wav_no}.wav' if exists(sound_fn): alert_sound = pygame.mixer.Sound(sound_fn) alert_sound.play() def show_msg(msg, color = BLUE): x = screen.get_rect().centerx y = screen.get_rect().centery - 50 font = pygame.font.Font(None, 36) text = font.render(msg, True, color) text_rect = text.get_rect() text_rect.centerx = x text_rect.centery = y rectangle1 = pygame.Rect(x-155, y-25, 320, 60) rectangle2 = pygame.Rect(x-160, y-30, 320, 60) pygame.draw.rect(screen, DARK, rectangle1) pygame.draw.rect(screen, GREY, rectangle2) pygame.draw.rect(screen, BLACK, rectangle2, 2) # 边框宽为2 screen.blit(text, text_rect) pygame.display.flip() def repaint(): # 绘制游戏界面 screen.fill(WHITE) # 定义线段的端点坐标 x,y = (-1,640,640,-1)*16, [] for i in range(36): for _ in range(2): y.append(19+i*20) # 使用pygame.draw.lines()函数绘制线段 points = list(zip(x,y)) pygame.draw.lines(screen, GREY, False, points, 1) # 线宽为1 points = list(zip(y,x)) pygame.draw.lines(screen, GREY, False, points, 1) # 重画蛇和食物 for pos in snake_pos: pygame.draw.rect(screen, GREEN, pygame.Rect(pos[0], pos[1], 20, 20)) pygame.draw.rect(screen, RED, pygame.Rect(food_pos[0], food_pos[1], 20, 20)) pygame.display.flip() show_msg_at(22, 6, f'Scores: {scores}') show_msg_at(410, 6, f'Snake coordinate: ({1+snake_pos[0][0]//20:2}, {1+snake_pos[0][1]//20:2})') def show_msg_at(x, y, msg): font = pygame.font.SysFont('Consolas', 14) text = font.render(msg, True, BLACK) text_rect = text.get_rect() text_rect.x, text_rect.y = x, y screen.blit(text, text_rect) pygame.display.flip() def game_quit(): pygame.quit() system() def main(): global screen, screen_size, scores global snake_pos, food_pos, snake_speed is_running = False is_paused = False is_dead = False is_mute = False repaint() show_msg("Press any key to start ...") # 创建一个线程来播放音乐 music_no = 1 music_fn = "voice1.mp3" if exists(music_fn): t = Thread(target=play_music, args=(music_fn,0.8,)) t.start() # 主循环 UP, DOWN, LEFT, RIGHT = 1, 2, 3, 4 direction = RIGHT while True: # 处理游戏事件 for event in pygame.event.get(): if event.type == pygame.QUIT: if messagebox.askyesno("确认", "是否要退出?"): game_quit() elif event.type == pygame.KEYDOWN: # 增加 WASD 四键对应 上左下右 方向键 if not is_paused: if (event.key == pygame.K_w or event.key == pygame.K_UP) and direction != DOWN: direction = UP elif (event.key == pygame.K_s or event.key == pygame.K_DOWN) and direction != UP: direction = DOWN elif (event.key == pygame.K_a or event.key == pygame.K_LEFT) and direction != RIGHT: direction = LEFT elif (event.key == pygame.K_f or event.key == pygame.K_RIGHT) and direction != LEFT: direction = RIGHT if event.key == pygame.K_y: if is_dead: init() is_dead = False is_running = True elif event.key == pygame.K_n: if is_dead: game_quit() else: is_running = True elif event.key == pygame.K_SPACE: if is_dead: continue if is_paused: is_paused = False pygame.mixer.music.unpause() is_running = True elif event.key == pygame.K_ESCAPE: if is_running: show_msg(">>> Paused <<<") is_paused = not is_paused if is_paused: pygame.mixer.music.pause() if not is_mute: play_sound(1) else: pygame.mixer.music.unpause() elif event.key == pygame.K_m and event.mod & pygame.KMOD_CTRL: # Ctrl+M 切换背景音乐 is_mute = False music_no = 1 if music_no == 3 else music_no + 1 music_fn = f"voice{music_no}.mp3" if exists(music_fn): t = Thread(target=play_music, args=(music_fn,0.8,)) t.start() elif event.key == pygame.K_q and event.mod & pygame.KMOD_CTRL: # Ctrl+Q 切换静音状态 is_mute = not is_mute if is_mute: pygame.mixer.music.pause() else: pygame.mixer.music.unpause() is_running = True else: # 任意键进入开始状态 if is_dead: continue is_running = True if not is_running: continue if is_paused: continue if direction == UP: snake_speed = [0, -20] elif direction == DOWN: snake_speed = [0, 20] elif direction == LEFT: snake_speed = [-20, 0] elif direction == RIGHT: snake_speed = [20, 0] # 更新蛇的位置 snake_pos.insert(0, [snake_pos[0][0] + snake_speed[0], snake_pos[0][1] + snake_speed[1]]) # 检查蛇头是否碰到食物 if snake_pos[0] == food_pos: scores += 10 + len(snake_pos) // 5 if not 1 < snake_pos[0][0]//20 < 30 or not 1 < snake_pos[0][1]//20 < 22: scores += 5 if not is_mute: play_sound(2) food_pos = [randrange(1, screen_size[0] // 20) * 20, randrange(1, screen_size[1] // 20) * 20] else: snake_pos.pop() # 检查蛇头是否碰到墙壁或者蛇身 if snake_pos[0][0] < 0 or snake_pos[0][0] >= screen_size[0] or snake_pos[0][1] < 0 or snake_pos[0][1] >= screen_size[1] or snake_pos[0] in snake_pos[1:]: show_msg("Dead! Again? (Y or N)") is_running = False is_dead = True direction = RIGHT if not is_mute: play_sound(3) continue # 重画界面及蛇和食物 repaint() # 控制游戏速度 pygame.time.Clock().tick(10) if __name__ == "__main__": init() main()
最终版的源代码及音乐文件列表如下:
下载地址: