python-经典贪吃蛇(pygame)

自从开始学习编程后 ,就一直想着能够写一个自己的游戏,这大概是每一个男孩子的梦想吧!
这次我们使用python语言 ,调用pygame库 来写一个简单的小游戏
就拿最经典的游戏 贪吃蛇 作为我的第一个游戏!
看一下最终效果为


tanchishe.gif

我们先分一下步骤!

创建背景--写入蛇头食物--蛇的移动以及控制--蛇体算法--碰撞检测--食物随机生成--死亡判定--细节完善--源码

着急的小伙伴可以直接拉到最后看源码

先导入要用的库吧

import pygame
import random
from pygame.locals import *

第一个当然是必须要用到的pygame
随机生成食物点的时候要用到随机库
最后一个是导入pygame的上下左右键盘输入

一 、创建背景
这个是pygame的一个基本用法
我们先拿一个简单的例子来说

pygame.init()
window = pygame.display.set_mode((640,480),0,32)
pygame.display.set_caption("标题")

pygame.init()用语pygame的初始化
set_mode函数会返回一个Surface对象,代表了在桌面上出现的那个窗口,三个参数第一个为元祖,代表分 辨率(必须);第二个是一个标志位,具体意思见下表,如果不用什么特性,就指定0;第三个为色深。
set_caption顾名思义是用来设置标题的
这时候我们运行程序就会出现一个一闪而过的黑框
它就是我们的游戏窗体啦

那么如果要让黑框一直存在怎么办呢
肯定要用到一个while 循环

while True:
      pygame.draw.rect(window,bk_color,(0,0,w,h))
      pygame.display.update()

这样子我们的黑框就会一直存在
draw.rect是pygame带的一个画矩形的函数它一共有四个参数
第一个是对象 就是你要画的位置
第二个是颜色 矩形的色彩
第三个 是一个tuple 代表矩形的位置和大小
tuple里面一共四个参数 前面两个代表矩形的左上角坐标,后两个分别是宽和高

但是现在问题又来我们会发现 无法关闭掉这个窗口 或者说卡主

我们可以先定义一个

quit  = True

while quit:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            quit = False

这里我们引入了pygame的事件概念
pygame.event.gey()可以获取目前的所有事件

import pygame
from pygame.locals import *
from sys import exit

pygame.init()
SCREEN_SIZE = (640, 480)
screen = pygame.display.set_mode(SCREEN_SIZE, 0, 32)

font = pygame.font.SysFont("arial", 16);
font_height = font.get_linesize()
event_text = []

while True:

    event = pygame.event.wait()
    event_text.append(str(event))
    #获得时间的名称
    event_text = event_text[-SCREEN_SIZE[1]//font_height:]
    #这个切片操作保证了event_text里面只保留一个屏幕的文字

    if event.type == QUIT:
        exit()

    screen.fill((255, 255, 255))

    y = SCREEN_SIZE[1]-font_height
    #找一个合适的起笔位置,最下面开始但是要留一行的空
    for text in reversed(event_text):
        screen.blit( font.render(text, True, (0, 0, 0)), (0, y) )
        #以后会讲
        y-=font_height
        #把笔提一行

    pygame.display.update()

运行以上代码 就可以看到我们的所有事件了
通过上面的说明我们已经可以让一个黑框完整的出现了!

为了方便
我们可以把贪吃蛇的背景看成一个表格
我们先定义了一个600*800的一个分辨率
然后分别设置行数和列数
代码如下

w = 800
h = 600
ROW = 30
COL = 40
size = (w, h)
direct = "left" #我们下面会讲
window = pygame.display.set_mode(size)
pygame.display.set_caption("贪吃蛇")

二、写入蛇头、食物
为了方便坐标的表示
我们写一个简单的坐标类

class Point:
    row = 0
    col = 0
    def __init__(self,row,col):
        self.row = row
        self.col = col

我们要画一个蛇头 当然要用到draw.rect的函数
于是需要一个蛇头的颜色和坐标
食物也同样如此

head = Point(ROW / 2,COL / 2)
food = Point(10,10)
head_color = (0, 128, 128)
food_color = (255, 255, 0)

然后再在循环中用rect函数把我们的蛇头和食物画上
运行后效果如图所示


Snipaste_2018-12-01_19-17-04.png

三、蛇的移动以及控制
如何让蛇自动前进
这里我们先引入一个时间的概念

clock =pygame.time.Clock()
clock.tick(20)

当我们把这个clock.tick(20)加入到游戏的主循环后
就代表的画面每秒刷新20次 也就是20帧的意思
我们通过这个刷新次数来控制蛇的移动速度
当然 这是一个比较low的控制方法..
因为他并不稳定 和你的电脑配置是有关系的

那么如何让蛇来自动移动呢
方法很简单
那就是每次刷新的时候
如果方向向左那么让它的列数-1
同理其他方向

    if direct == "left":
        head.col -= 1
    if direct == "down":
        head.row += 1
    if direct == "up":
        head.row -= 1
    if direct == "right":
        head.col += 1

那改变蛇头方向怎么做呢
当我们按下键盘的时候 pygame会检测到这个事件
用if来判断不同的事件 同时控制direct的方向

        if event.type == pygame.KEYDOWN:
            print(event)
            if event.key == K_LEFT:
                direct = "left"
            elif event.key == K_RIGHT:
                direct = "right"
            elif event.key == K_UP:
                direct = "up"
            elif event.key == K_DOWN:
                direct = "down"

四、蛇体算法
我们先假设蛇头和蛇身体的坐标为:
head=(14,7)
l=[
(14,8)
(14,9)
(15,9)
(15,10)
(15,11)
(15,12)
(16,12)
(17,12)
]

移动:
1.把原来的头插到前面
2.把原来的尾删掉

head'=(14,6)
l'=[
(14,7) <-原来的头

(14,8)
(14,9)
(15,9)
(15,10)
(15,11)
(15,12)
(16,12)

del (17,12)     <-原来的尾

]
所以本质上说一共就两步
1 把蛇头插入到身体的第一个位置
2 删除尾巴

还有一个问题 因为我们需要保存原先的蛇头位置
因为需要再在类中定义一个 copy

class Point:
    row = 0
    col = 0
    def __init__(self,row,col):
        self.row = row
        self.col = col
    def copy(self):
        return Point(self.row, self.col)
snakes =[Point(head.row,head.col+1),
         Point(head.row, head.col+2),
         Point(head.row, head.col+3)]
#在循环上方定义一个身体 snake
 snakes.insert(0,head.copy())  #插入旧蛇头
 snakes.pop() #删除蛇尾

到这一步我们就可以简单的控制小蛇移动啦

五、碰撞检测
嘛。。听起来很高级的一个名词
但是我们这里并不需要很复杂的算法
只要检测蛇头的位置和食物的位置是否一样
如果一样
小蛇身体变长且食物新生成
所以还是很简单的

 eat =(head.row == food.row and head.col == food.col)
    snakes.insert(0,head.copy())

    if eat:
        food = food_exist() #食物生成
    else:
        snakes.pop()

六、食物随机生成
这一步肯定要用到random库拉

food = Point(random.randint(0,ROW-1),random.randint(0, COL-1))

这一句话就搞定了

七、死亡判定
死亡时无非是两种情况
1吃到墙了
2吃到自己
那么我们只要判断蛇头的位置死亡位置就好了

dead = False
    if (head.row < 0 or head.col< 0 or head.row>ROW or head.col >COL):
        print("你死了")
        dead = True
    for snake in snakes:
        if(head.row == snake.row and head.col == snake.col):
            print("你死了")
            dead = True
    if dead ==True:
        break

在主循环中加入这段代码即可
先给予dead ==False 假设它一直不死
一旦玩家触发死亡 dead变为True 并且输出了一个 你死了
退出循环

到这里我们的贪吃蛇已经初步完成了 但是还有一些小小的细节需要优化

八、细节优化
1.我们的贪吃蛇是可以直接由左边转向右边 由上面转向下面 ,这显然是非常不科学的。
2.食物随机生成位置可能出现在蛇的身体或者头部上
先对第一点解决
为了避免这个问题 我们可以在转向前进行判定 只有在上下方向感才可以进行左右转
左右转的时候同理

 if event.key == K_LEFT:
                if (direct == "up" or direct == "down"):
                    direct = "left"
            elif event.key == K_RIGHT:
                if (direct == "up" or direct == "down"):
                    direct = "right"
            elif event.key == K_UP:
                if (direct == "right" or direct == "left"):
                    direct = "up"
            elif event.key == K_DOWN:
                if (direct == "right" or direct == "left"):
                    direct = "down"

第二点
也就是需要在生成食物前
进行判定食物的位置和蛇头位置,蛇身体位置
如果重复,就重新生成

def food_exist():
    is_in =False
    while True:
        pos = Point(random.randint(0,ROW-1),random.randint(0, COL-1))
        if (pos.row ==head.row and pos.col == head.col):
            is_in =True
        for snake in snakes:
            if(pos.row == snake.row and pos.col == snake.col):
              is_in = True
        if is_in == False:
            break
    return pos

和死亡判定差不多
先定义一个is_in =False假设不会生成到蛇头和蛇身体位置
一旦 ...就重新生成
好了 到这里 我们简陋的贪吃蛇就全部完成了

九、源码

import pygame
import random
from pygame.locals import *
class Point:
    row = 0
    col = 0
    def __init__(self,row,col):
        self.row = row
        self.col = col
    def copy(self):
        return Point(self.row, self.col)

pygame.init()

w = 800
h = 600
ROW = 30
COL = 40
size = (w, h)
direct = "left"
window = pygame.display.set_mode(size)
pygame.display.set_caption("贪吃蛇")

def rect(point, color):
    w_cell = w/COL
    h_cell = h/ROW
    left = point.col*w_cell
    top = point.row*h_cell
    pygame.draw.rect(window,color,(left,top,w_cell,h_cell))

def food_exist():
    is_in =False
    while True:
        pos = Point(random.randint(0,ROW-1),random.randint(0, COL-1))
        if (pos.row ==head.row and pos.col == head.col):
            is_in =True
        for snake in snakes:
            if(pos.row == snake.row and pos.col == snake.col):
              is_in = True
        if is_in == False:
            break
    return pos


quit = True
clock =pygame.time.Clock()
head = Point(ROW / 2,COL / 2)
snakes =[Point(head.row,head.col+1),
         Point(head.row, head.col+2),
         Point(head.row, head.col+3)]
food = food_exist()
head_color = (0, 128, 128)
food_color = (255, 255, 0)
bk_color = (255, 255, 255)
snake_color = (200,200,200)

while quit:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            quit = False
        elif event.type == pygame.KEYDOWN:
            print(event)
            if event.key == K_LEFT:
                if (direct == "up" or direct == "down"):
                    direct = "left"
            elif event.key == K_RIGHT:
                if (direct == "up" or direct == "down"):
                    direct = "right"
            elif event.key == K_UP:
                if (direct == "right" or direct == "left"):
                    direct = "up"
            elif event.key == K_DOWN:
                if (direct == "right" or direct == "left"):
                    direct = "down"
    dead = False
    if (head.row < 0 or head.col< 0 or head.row>ROW or head.col >COL):
        print("你死了")
        dead = True
    for snake in snakes:
        if(head.row == snake.row and head.col == snake.col):
            print("你死了")
            dead = True
    if dead ==True:
        break

    eat =(head.row == food.row and head.col == food.col)
    snakes.insert(0,head.copy())

    if eat:
        food = food_exist()
    else:
        snakes.pop()

    if direct == "left":
        head.col -= 1
    if direct == "down":
        head.row += 1
    if direct == "up":
        head.row -= 1
    if direct == "right":
        head.col += 1
    pygame.draw.rect(window,bk_color,(0,0,w,h))

    rect(head,head_color)
    rect(food,food_color)
    #生成蛇身体

    for snake in snakes:
        rect(snake,snake_color)
    pygame.display.update()
    clock.tick(20)

感谢大家的阅读。
第一次写游戏
也参考了网上的一些教程
有问题欢迎大家讨论纠正

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355

推荐阅读更多精彩内容