初入Python(一) Pygame贪吃蛇游戏的编写与改进

   日期:2020-11-09     浏览:94    评论:0    
核心提示:@TOC初入Python(一) Pygame贪吃蛇游戏的编写与改进贪吃蛇游戏是一款简单耐玩的休闲益智类游戏,利用pygame可以实现轻松编写,不需要辅佐图片等等元素,可以直接利用涂色方块表示,吃果子变长的原理也很容易实现,将body分为一块一块,每块有自己的位置属性,从而可以轻松表示出来。通过对贪吃蛇游戏的编写和改进可以在pygame模块的应用以及面向对象编程的编程方面得到不错的练习效果。作为一个初学者,我也打算利用这种方式对学习进行一个有趣的总结,maybe效果不错哦!初步编写初步编写的代码来自于

@TOC初入Python(一) Pygame贪吃蛇游戏的编写与改进
贪吃蛇游戏是一款简单耐玩的休闲益智类游戏,利用pygame可以实现轻松编写,不需要辅佐图片等等元素,可以直接利用涂色方块表示,吃果子变长的原理也很容易实现,将body分为一块一块,每块有自己的位置属性,从而可以轻松表示出来。通过对贪吃蛇游戏的编写和改进可以在pygame模块的应用以及面向对象编程的编程方面得到不错的练习效果。
作为一个初学者,我也打算利用这种方式对学习进行一个有趣的总结,maybe效果不错哦!

初步编写

初步编写的代码来自于简书上的一个程序,看起来清晰好懂。

https://gitee.com/codetimer/Snake/blob/master/main.py

前期定义

这部分主要导入库以及定义游戏窗口大小

import pygame
import sys
import random

# 全局定义
SCREEN_X = 600
SCREEN_Y = 600

蛇类的定义

蛇类拥有方向和身体块两个属性,蛇块的增加、减少和移动都可以利用列表的增删轻松实现,方向的判断处要注意左右、上下方向不能被直接逆向改变。死亡判断处,直接用头部方块的位置做if判断即可。

# 蛇类 点以25为单位
class Snake(object):
    # 初始化各种需要的属性 [开始时默认向右/身体块x5]
    def __init__(self):
        self.dirction = pygame.K_RIGHT
        self.body = []
        for x in range(5):
            self.addnode()
        
    # 无论何时 都在前端增加蛇块
    def addnode(self):
        left,top = (0,0)
        if self.body:
            left,top = (self.body[0].left,self.body[0].top)
        node = pygame.Rect(left,top,25,25)
        if self.dirction == pygame.K_LEFT:
            node.left -= 25
        elif self.dirction == pygame.K_RIGHT:
            node.left += 25
        elif self.dirction == pygame.K_UP:
            node.top -= 25
        elif self.dirction == pygame.K_DOWN:
            node.top += 25
        self.body.insert(0,node)
        
    # 删除最后一个块
    def delnode(self):
        self.body.pop()
        
    # 死亡判断
    def isdead(self):
        # 撞墙
        if self.body[0].x  not in range(SCREEN_X):
            return True
        if self.body[0].y  not in range(SCREEN_Y):
            return True
        # 撞自己
        if self.body[0] in self.body[1:]:
            return True
        return False
        
    # 移动!
    def move(self):
        self.addnode()
        self.delnode()
        
    # 改变方向 但是左右、上下不能被逆向改变
    def changedirection(self,curkey):
        LR = [pygame.K_LEFT,pygame.K_RIGHT]
        UD = [pygame.K_UP,pygame.K_DOWN]
        if curkey in LR+UD:
            if (curkey in LR) and (self.dirction in LR):
                return
            if (curkey in UD) and (self.dirction in UD):
                return
            self.dirction = curkey

食物类的定义

食物类主要包含一个方块,remove和set方法都是对食物的坐标进行判断,这里需要注意pygame中rect方法的原理。

# 食物类 点以25为单位
class Food:
    def __init__(self):
        self.rect = pygame.Rect(-25,0,25,25)
        
    def remove(self):
        self.rect.x=-25
    
    def set(self):
        if self.rect.x == -25:
            allpos = []
            # 不靠墙太近 25 ~ SCREEN_X-25 之间
            for pos in range(25,SCREEN_X-25,25):
                allpos.append(pos)
            self.rect.left = random.choice(allpos)
            self.rect.top  = random.choice(allpos)
            print(self.rect)

pygame.Rect()
通过Rect可以创造一个矩形区域,可以由LEFT,TOP,WIDTH,HEIGHT这四个值创建。

也可以用来裁剪图片
eg:
加载图片img = pygame.image.load(r’D:\Python\images\xxx.png’)
剪切图片rect = pygame.Rect(LEFT,TOP,WIDTH,HEIGHT)

显示与主函数

show_text没有什么可说的,对应好参数就OK。main函数的逻辑基本可以分解为——初始化、进入循环、检测事件、运动并更新蛇身、判断是否吃到并完成食物投递、判断死亡、显示分数。蛇运动的速度由clock.tick()中的参数决定,它决定了游戏绘制的帧率,参数越大,运动速度越大。

def show_text(screen, pos, text, color, font_bold = False, font_size = 60, font_italic = False):   
    #获取系统字体,并设置文字大小 
    cur_font = pygame.font.SysFont("宋体", font_size)  
    #设置是否加粗属性 
    cur_font.set_bold(font_bold)  
    #设置是否斜体属性 
    cur_font.set_italic(font_italic)  
    #设置文字内容 
    text_fmt = cur_font.render(text, 1, color)  
    #绘制文字 
    screen.blit(text_fmt, pos)

     
def main():
    pygame.init()
    screen_size = (SCREEN_X,SCREEN_Y)
    screen = pygame.display.set_mode(screen_size)
    pygame.display.set_caption('Snake')
    clock = pygame.time.Clock()
    scores = 0
    isdead = False
    
    # 蛇/食物
    snake = Snake()
    food = Food()
    
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            if event.type == pygame.KEYDOWN:
                snake.changedirection(event.key)
                # 死后按space重新
                if event.key == pygame.K_SPACE and isdead:
                    return main()
                
            
        screen.fill((255,255,255))
        
        # 画蛇身 / 每一步+1分
        if not isdead:
            scores+=1
            snake.move()
        for rect in snake.body:
            pygame.draw.rect(screen,(20,220,39),rect,0)
            
        # 显示死亡文字
        isdead = snake.isdead()
        if isdead:
            show_text(screen,(100,200),'YOU DEAD!',(227,29,18),False,100)
            show_text(screen,(150,260),'press space to try again...',(0,0,22),False,30)
            
        # 食物处理 / 吃到+50分
        # 当食物rect与蛇头重合,吃掉 -> Snake增加一个Node
        if food.rect == snake.body[0]:
            scores+=50
            food.remove()
            snake.addnode()
        
        # 食物投递
        food.set()
        pygame.draw.rect(screen,(136,0,21),food.rect,0)
        
        # 显示分数文字
        show_text(screen,(50,500),'Scores: '+str(scores),(223,223,223))
        
        pygame.display.update()
        clock.tick(10)
    
    
if __name__ == '__main__':
    main()

初步效果

请大家自行测试初步效果,死亡后按空格重新开始。

优化过程

有了上面的成果,一个贪吃蛇游戏也就基本完成了,有意思么,还行吧。不过可能是个人对于游戏的痴迷让我觉得这个游戏总是少了点什么,或者说,太不完善了,只是单纯有它该有的功能罢了,什么游戏UI,闯关的感觉,都是没有的。所以,让我们来开动脑筋,试着去改造它吧,我将其与平常玩的游戏做了点小对比,提出了一点点优化目标,其中有些比较简单,有些也会有点困难,需要我们推翻现在的代码重新进行架构。

优化目标

1.cfg和函数的封装;
2.开始ui和结束ui;
3.难度设置(选择蛇的运行速度);
4.障碍物的设置\特殊方块的设置(高得分方块、暂时减速方块);
*5.对于帧数的优化(帧数固定,即每次运动之间进行多次检测和输出);
*6.分数排行榜(玩之前输入用户名,这需要涉及到文件读写的操作)。
目前大概就这么多

cfg文件和函数的封装

cfg文件的封装我觉得对于开发来说至关重要,就像csgo中的config文件,玩家也可以改来改去,添加一些想要的参数(指插入二刺螈图片做背景),增加了游戏的可玩性。虽然这个贪吃蛇游戏没有太多的参数需要设置,但是我觉得养成一个好习惯是不错的。

import pygame


# 方块大小
cube = 25
pygame.init()
# 屏幕大小
SCREENSIZE_X = 600
SCREENSIZE_Y = 600

# highest scores的text
pos1 = (50, 500)
text1 = 'Highest Scores: '
color1 = (223, 223, 223)
font_size1 = 30
# curscores 的text
pos2 = (50, 550)
text2 = 'Scores: '
color2 = (223, 223, 223)
font_size2 = 30

# UI字体
UIfont_size_big = 60
UIfont_size_small = 30
UIfont1 = pygame.font.SysFont("宋体", UIfont_size_big)
UIfont2 = pygame.font.SysFont("宋体", UIfont_size_small)
UIcolor1 = (255, 151, 23)
UIcolor2 = (255, 151, 23)

cfg文件代码如上,其实有些参数我并没有设置过来,因为config中也不是需要包含所有参数的,有的参数如果改起来可行性不高我觉得也没必要全部放在这里,反而会很乱。
部分后面要用的函数封装如下(当然所有优化指标还没完全实现,因此必然还会改变):

'''other_function.py'''
import pygame
import sys


def show_scores(screen, scores, pos, text, color, font_size=30):
	pass
def StartUI(screen, cfg):
	pass
def EndUI(screen, cfg):
	pass

当然,蛇类和食物类也要封装起来,就直接封装就好。

'''Food.py'''
import pygame
import random

class Food(object):

    def __init__(self):
        self.rect = pygame.Rect(-25, 0, 25, 25)

    def remove(self):
        self.rect.x = -25

    def set(self, SCREENSIZE_X=600):
        if self.rect.x == -25:
            allpos = []
            # 不靠近墙
            for pos in range(25, SCREENSIZE_X - 25, 25):
                allpos.append(pos)
            self.rect.left = random.choice(allpos)
            self.rect.top = random.choice(allpos)
            print(self.rect)
'''Snake.py'''
import pygame


class Snake(object):
    def __init__(self):
        # 设定初始方向为向右 初始身体为空
        self.direction = pygame.K_RIGHT
        self.body = []

        # 初始化5个身体块
        for x in range(5):
            self.addnote()

    def addnote(self):
        # left top为身体块的定位位置
        left, top = (0, 0)
        if self.body:
            left, top = (self.body[0].left, self.body[0].top)
        node = pygame.Rect(left, top, 25, 25)
        if self.direction == pygame.K_LEFT:
            node.left -= 25
        elif self.direction == pygame.K_RIGHT:
            node.left += 25
        elif self.direction == pygame.K_UP:
            node.top -= 25
        elif self.direction == pygame.K_DOWN:
            node.top += 25
        self.body.insert(0, node)

    # 删除身体块
    def delnote(self):
        self.body.pop()

    def isdead(self, SCREENSIZE_X=600, SCREENSIZE_Y=600):
        # 撞墙
        if self.body[0].x not in range(SCREENSIZE_X):
            return True
        if self.body[0].y not in range(SCREENSIZE_Y):
            return True
        # 撞自己
        if self.body[0] in self.body[1:]:
            return True
        return False

    def move(self):
        # 在前进方向上头部增加一个方块 尾部减少一个方块
        self.addnote()
        self.delnote()

    def changedirection(self, curkey):
        LR = [pygame.K_LEFT, pygame.K_RIGHT]
        UD = [pygame.K_UP, pygame.K_DOWN]
        if curkey in LR + UD:
            if (curkey in LR) and (self.direction in LR):
                return
            if (curkey in UD) and (self.direction in UD):
                return
            self.direction = curkey

需要注意的点有:
1.利用cfg文件分装后,文件存放目录要进行改变,像下图一样:

同时main函数中的库的导入也应该改变(面向对象的基础~),比如:

import snake_cfg
from functions.Snake import *
from functions.Food import *
from functions.other_function import *

2.调用cfg中的参数时,要加前缀“cfg.”当然这取决于你导入的方式。
3.每个函数的参数都需要重写。同时为了让主函数循环运行,采用了如下的结构:

if __name__ == '__main__':
    while True:
        if not main(snake_cfg):
            break

main函数的返回值是由结束UI决定的,这部分需要结合后面的结束部分自己去理解。

def main(cfg):
    pygame.init()
    #省略......
    ...
    while not isdead:
    ...
    ...
    ...
        # 设置时间间隔
        clock.tick(difficulty)
    # 如果死亡 返回输出UI
    return EndUI(screen, cfg)

开始UI和结束UI

开始UI和结束UI是什么,其实经常玩游戏的人都很清楚,一个背景,几个选项,不同的选项导向不同的结果,比如开始游戏、继续游戏,选择难度、退出游戏等等。这里我们就实行一个简单的UI,开始UI包括两个难度的选择level1、level2(当然你想写10个都行),这与下一部分难度设置相重合,结束UI包括restart和quit.
UI的设计也是主要依托pygame中的显示功能,前面初步编写时也并没有谈到pygame中显示方式的使用,比如pygame.display.set_mode、screen.fill、pygame.draw.rect、font.render、surface.blit,因为这部分的内容实在是太繁琐,需要自己去查找与学习。
简单来说,开始UI分为几个部分:
文字的定义与显示、方块的创建与按钮的绑定、事件的选择。

def StartUI(screen, cfg):
    # 欢迎为大号字体
    cur_font1 = cfg.UIfont1
    text_fmt1 = cur_font1.render('Welcome!', False, cfg.UIcolor1)
    # 选项为小号字体 选项1为level1 选项2为level2
    cur_font2 = cfg.UIfont2
    text_fmt2 = cur_font2.render('level1', False, cfg.UIcolor2)
    text_fmt3 = cur_font2.render('level2', False, cfg.UIcolor2)
    # alpha通道起到滤镜效果 填充一层模糊界面
    surface = screen.convert_alpha()
    surface.fill((127, 255, 212, 2))
    # 定义文字位置并创建按钮
    text_rect1 = text_fmt1.get_rect()
    text_rect1.centerx, text_rect1.centery = cfg.SCREENSIZE_X / 2, cfg.SCREENSIZE_Y / 2 - 50
    # 显示文字1 即欢迎选项
    surface.blit(text_fmt1, text_rect1)
    # 定义按钮的位置 以屏幕中心位置开始进行微调(先随便给个数再调也行)
    button_width, button_height = 100, 40
    button_start_x_left = cfg.SCREENSIZE_X / 2 - button_width - 20
    button_start_x_right = cfg.SCREENSIZE_X / 2 + 20
    button_start_y = cfg.SCREENSIZE_Y / 2 - button_height / 2 + 20
    pygame.draw.rect(surface, (0, 255, 255), (button_start_x_left, button_start_y, button_width, button_height))
    # 定义按钮1的矩形
    text_level1_rect = text_fmt2.get_rect()
    text_level1_rect.centerx, text_level1_rect.centery = button_start_x_left + button_width / 2, button_start_y + button_height / 2
    surface.blit(text_fmt2, text_level1_rect)
    # 定义按钮2的矩形
    pygame.draw.rect(surface, (0, 255, 255), (button_start_x_right, button_start_y, button_width, button_height))
    text_level2_rect = text_fmt3.get_rect()
    text_level2_rect.centerx, text_level2_rect.centery = button_start_x_right + button_width / 2, button_start_y + button_height / 2
    surface.blit(text_fmt3, text_level2_rect)
    while True:
        screen.blit(surface, (0, 0))
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == pygame.MOUSEBUTTONDOWN and event.button:
                # 鼠标选择level1 返回死亡为False 速度为10
                if text_level1_rect.collidepoint(pygame.mouse.get_pos()):
                    return False, 10
                # 鼠标选择level2 返回死亡为False 速度为15
                if text_level2_rect.collidepoint(pygame.mouse.get_pos()):
                    return False, 15
        # 更新显示状态
        pygame.display.update()

你问我字体的参数和操作?查百度。你问我矩形和按钮怎么创建绑定?查百度。你问我键盘输入输出key,鼠标点击判断是怎么回事?查百度。
一些资料:

pygame.key 键值说明:
https://blog.csdn.net/stoneyyhit/article/details/52259993
pygame 字体设置:
https://blog.csdn.net/zengxiantao1994/article/details/58590594
pygame 屏幕显示
https://zhuanlan.zhihu.com/p/99450316
RGB查询:
https://www.fontke.com/tool/rgb/ffffff/

结束UI和开始UI几乎完全一致,只需要改变下文本和按钮的绑定即可:

def EndUI(screen, cfg):
    # 欢迎为大号字体
    cur_font1 = cfg.UIfont1
    text_fmt1 = cur_font1.render('Game Over!', False, cfg.UIcolor1)
    # 选项为小号字体 选项1为level1 选项2为level2
    cur_font2 = cfg.UIfont2
    text_fmt2 = cur_font2.render('restart', False, cfg.UIcolor2)
    text_fmt3 = cur_font2.render('quit', False, cfg.UIcolor2)
    # alpha通道起到滤镜效果 填充一层模糊界面
    surface = screen.convert_alpha()
    surface.fill((127, 255, 212, 2))
    # 定义文字位置并创建按钮
    text_rect1 = text_fmt1.get_rect()
    text_rect1.centerx, text_rect1.centery = cfg.SCREENSIZE_X / 2, cfg.SCREENSIZE_Y / 2 - 50
    # 显示文字1 即欢迎选项
    surface.blit(text_fmt1, text_rect1)
    # 定义按钮的位置 屏幕中心位置
    button_width, button_height = 100, 40
    button_start_x_left = cfg.SCREENSIZE_X / 2 - button_width - 20
    button_start_x_right = cfg.SCREENSIZE_X / 2 + 20
    button_start_y = cfg.SCREENSIZE_Y / 2 - button_height / 2 + 20
    pygame.draw.rect(surface, (0, 255, 255), (button_start_x_left, button_start_y, button_width, button_height))
    # 定义按钮1的矩形
    text_restart_rect = text_fmt2.get_rect()
    text_restart_rect.centerx, text_restart_rect.centery = button_start_x_left + button_width / 2, button_start_y + button_height / 2
    surface.blit(text_fmt2, text_restart_rect)
    # 定义按钮2的矩形
    pygame.draw.rect(surface, (0, 255, 255), (button_start_x_right, button_start_y, button_width, button_height))
    text_quit_rect = text_fmt3.get_rect()
    text_quit_rect.centerx, text_quit_rect.centery = button_start_x_right + button_width / 2, button_start_y + button_height / 2
    surface.blit(text_fmt3, text_quit_rect)
    while True:
        screen.blit(surface, (0, 0))
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == pygame.MOUSEBUTTONDOWN and event.button:
                # 鼠标选择restart 返回死亡为False
                if text_restart_rect.collidepoint(pygame.mouse.get_pos()):
                    return True
                # 鼠标选择quit 返回死亡为True
                if text_quit_rect.collidepoint(pygame.mouse.get_pos()):
                    return False
        pygame.display.update()

难度设置

开始UI的编写和难度设置是同时进行的,观察前面的开始UI可以发现开始UI中的选项level1和level2分别对应了两组不同的return值,在main函数中,在循环正式开始前,会有这么一行代码:

isdead, difficulty = StartUI(screen, cfg)

其中,isdead是while循环继续的判断条件,也是游戏继续进行的“钥匙”,而difficulty则是开始UI的第二个返回值,它代表了难度,我在这里简化(偷懒)了一点,返回值为10或者15,还记得前面说过的clock.tick么,它代表了游戏的帧数,也决定了蛇运动的速度,将diffculty直接作为参数,那么就会直接控制蛇的速度,从而实现难度的区分。
这样的区分好像确实有点太简单了,所以我才提出了后面的优化——添加障碍物,不同难度会对应不同大小、数量的障碍物,说实话,带障碍物的贪吃蛇我还真没怎么玩过,不过应该会很有意思。
当然,为了增加游戏性,你也可以发动脑筋,想出不同的策略为游戏添砖加瓦,比如食物有几率变成幸运方块,得分会增加或者可以让你的蛇暂时慢下来等等,听起来就很有意思的样子。

未完待续

其他改进

这个蛇头的颜色还是要变一下的,不然分不清蛇头真的蛋疼。

# 画出贪吃蛇的每个身体块
        pygame.draw.rect(screen, (4, 150, 254), snake.body[0], 0)
        for rect in snake.body[1:]:
            pygame.draw.rect(screen, (20, 220, 39), rect, 0)

还有对于输入的判断,多加些条件可以使得程序更稳定:

# 如果按键为方向键 则改变蛇的运动方向
                if event.key in [pygame.K_UP, pygame.K_DOWN, pygame.K_LEFT, pygame.K_RIGHT]:
                    snake.changedirection(event.key)

分数的判断也要改变一下,每次吃方块得1分,每次运动不得分(当然这个随便你怎么改了)。

curscores += 1

食物类的remove方法也可以改进一下

    def remove(self):
        self.rect.x = -25

貌似改成等于号会更稳定一点,之前-=好像有可能会出bug

另外,在游玩时也发现,同时按两个方向键,会直接死亡,啊这,优化方法我还没找到(其实有个快捷死亡的bug也不错0.0)。

当前效果

懒得搞gif图了,凑合看吧。


未完待续

鉴于目前还有不少作业以及一些别的学习,这个优化任务打算先放一放,过几天再回来做吧。总结什么的,哎,懒得写了。

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服