pygame寫貪吃蛇
python小白嘗試寫遊戲..
學了點pygame不知道那什麽練手好,先拿貪吃蛇開刀吧.
一個遊戲可以粗略的分為兩個部分:
- 數據(變量)
- 處理數據(函數,方法)
設計變量
首先預想下,畫面的那些部分需要存儲在變量裏
整個畫面上只會有矩形,而且這些矩形整整齊齊,大小相等,原本一個矩形需要四個變量表示位置,這裏,只需要兩個變量(行數,列數)就能表示方塊的位置
蛇頭,食物可以用二元元組表示,蛇身的數量不確定,只能用包含數個元組的列表表示
另外設定窗口大小800x600,每個方塊都是50x50
import pygame import sys pygame.init() screen = pygame.display.set_mode((800, 600)) pygame.display.set_caption("貪吃蛇") food = (4, 5) body = [(1, 1),(1,2)] head = (1, 3) BLOCK = 0, 0, 0 GREEN = 0, 255, 0 RED = 255, 0, 0 BLUE = 0, 0, 255 WHITE = 255, 255, 255 while 1: for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit()
變量設定好了,遊戲已經完成了一半( ̄▽ ̄)~*
下一步
變量到畫面
pygame.draw.rect()是根據矩形四元數組繪制圖像的,那就寫個函數"對接"下我的二元坐標
這裏就成數學的問題了...
def new_draw_rect(zb, color,screen): pygame.draw.rect(screen,color,((zb[1]-1)*50+1,(zb[0]-1)*50+1,48,48))
鑒於50x50時相鄰方塊們會"粘"在一起,方塊向裏收一下成48x48 ↑
繪制圖形,
...
while 1: for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit() screen.fill(WHITE) new_draw_rect(food, RED, screen)for i in body: new_draw_rect(i, BLUE, screen) new_draw_rect(head, GREEN, screen) pygame.display.update()
由靜到動
兩個問題:
1.什麽時候動
2.怎麽動
問題1,什麽時候動,這裏有兩個思路,
- 間隔固定時間(1秒),動一次
- 按一次鍵動一次,無操作一定時間(1秒)後,重復最後一次操作
看起來第一種方案 簡單 不錯
首先,先用pygame的clock類限制幀率(100幀),以方便計時
再者,加入新變量times,times每次加1,超過一百就"動一動"
加入新變量direction,表示蛇頭朝向,銜接鍵盤操作和"怎麽動"
PS:程序結束之前,很難知道要用多少變量
import pygame import sys pygame.init() screen = pygame.display.set_mode((800, 600)) pygame.display.set_caption("貪吃蛇") fclock = pygame.time.Clock() food = (4, 5) body = [(1, 1)] head = (1, 2) times = 0 direction = ‘right‘ BLOCK = 0, 0, 0 GREEN = 0, 255, 0 RED = 255, 0, 0 BLUE = 0, 0, 255 WHITE = 255, 255, 255 def new_draw_rect(zb, color,screen): pygame.draw.rect(screen,color,((zb[1]-1)*50+1,(zb[0]-1)*50+1,48,48)) pass while 1: for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit() elif event.type == pygame.KEYDOWN: if event.key == pygame.K_UP: direction = "up" elif event.key == pygame.K_LEFT: direction = "left" elif event.key == pygame.K_DOWN: direction = "down" elif event.key == pygame.K_RIGHT: direction = "right" if times >= 100: pass#動一動 times = 0 else: times += 1 screen.fill(WHITE) new_draw_rect(food, RED, screen) for i in body: new_draw_rect(i, BLUE, screen) new_draw_rect(head, GREEN, screen) fclock.tick(100) pygame.display.update()
蛇頭的運動規律 : 向臨近的格子移動,上下左右具體那個格子由鍵盤確定
那就寫個新函數去生成蛇頭的新位置
def get_front(head,direction): x, y = head if direction == "up": return x-1, y elif direction == "left": return x, y-1 elif direction == "down": return x+1, y elif direction == "right": return x, y+1
然後
head = get_front(head,direction)
但是蛇蛇一頭紮墻裏怎麽辦.......
你的好友【front : 臨時記下蛇頭前方的位置】已上線
你的好友【alive : 記錄存活信息】已上線
PS:front可以不是全局變量
def ask_alive(front,body): x, y = front if x < 0 or x > 12 or y < 0 or y >16 : return False if front in body: return False return True
然後這樣用
front = get_front(head,direction)
alive = ask_alive(front,body)
if alive:
head = front
人話 :先看看蛇頭前面是否有危險,有危險就死了 , 不動啦
另外alive得加到前面的if裏面當限制條件,死了就不能亂動啦~~~~~
蛇身動的規律 : 近頭端跟頭走,尾端也跟著走--向程序靠攏-->>用過的head加入body,同時刪去body最老的成員
這裏可以看出body必須有序,可變.python裏面就用列表了
如果使用list的append方法,head加在body的末尾,那麽body[0]就會是"最老的成員"就得使用pop(0)刪去
PS. body.append(head)得寫在head=front前面,在head更新前加進body
if times >= 100 and alive: front = get_front(head,direction) alive = ask_alive(front,body) if alive: body.append(head) head = front body.pop(0) times = 0 else: times += 1
食物的運動規律:被吃掉後,隨機位置再出現 --向程序靠攏-->> 當head== food為真時 food隨機選擇一個蛇之外的地方出現
def new_food(head,body): while 1: x = random.randint(1, 12) y = random.randint(1, 16) if (x, y) != head and (x, y) not in body: return x, y
這裏存在一個隱式BUG,要是蛇充滿了每一個角落,那這就是死循環 ...........然後整個程序卡在這裏....
版本1 : 我選擇沒看見,不會有什麽人能吃到"全屏"的(′⊙ω⊙`) --來自開發者的懶惰
版本2 【我的選擇】: 修了這個BUG
def new_food(head,body): i = 0 while i < 100: x = random.randint(1, 12) y = random.randint(1, 16) if (x, y) != head and (x, y) not in body: return (x, y), True i += 1 else: return (0, 0), False food, alive = new_food()
100次機會 否則就死( ̄へ ̄) --來自開發者的惡意
另外,蛇吃了食物就要長一格====>>蛇頭前進一格,蛇尾不動,蛇就因此長了一格====>>當head = food 為真時body.pop(0)不用執行
if alive: body.append(head) head = front if food == head: food = new_food(head, body) else: body.pop(0)
完結撒花
細節優化
1,
什麽?遊戲結束了?
黑個屏,提醒下
你的好友【back_color】已上線
back_color一開始等於WHITE , alive為假時變為BLOCK
標題欄變成"遊戲結束"
if times >= 100 and alive: front = get_front(head, direction) alive = ask_alive(front, body) if alive: body.append(head) head = front if food == head: food, alive = new_food(head, body) else: body.pop(0) else: back_color = BLOCK pygame.display.set_caption("遊戲結束") times = 0 else: times += 1
2,
要是一開始按了一下left......
恭喜你獲得技能【撞脖子自殺】
要麽那就
你的好友【old_direction】已上線
old_direction = "right" def direction_yes_no(direction,old_direction): d = {"up": "down", "down": "up", "left": "right", "right": "left"} if d[direction] == old_direction: return old_direction return direction
PS : 字典代替if-elif結構省心省時
然後
if times >= 100 and alive: direction = direction_yes_no(direction, old_direction) old_direction = direction front = get_front(head, direction) alive = ask_alive(front, body)
人話 : 添加一個變量記錄上一次有效的輸入,兩個方向關系不正確時,以老變量為準--向人靠攏-->>向右跑時,不準向左!!!
Q : direction也可以在按鍵事件處理時限制呀,例如
elif event.type == pygame.KEYDOWN: if event.key == pygame.K_UP: direction = "up" """改為""" elif event.type == pygame.KEYDOWN: if event.key == pygame.K_UP: if direction != "down" direction = "up"
A:在發現這個方法的BUG之前,我也是這樣想的
還是上面的例子
直接按left行不通,right--XX-->left
先按down,迅速再按left ,,right --pass-->> down --pass-->> left
恭喜你獲得技能【繞過開發者防護,高水平撞脖子自殺】
3,
Q : 我要暫停!!!
A : 好好玩遊戲,不要動不動就暫停
你的好友【pause】已上線
邏輯值,P鍵控制
pause = False elif event.key == pygame.K_p: pause = not pause if times >= 100 and alive and (not pause): ....
下面我發一下全部的代碼,代碼包含上面的優化
↓↓↓↓
完結撒花
import pygame import sys import random pygame.init() screen = pygame.display.set_mode((800, 600)) pygame.display.set_caption("貪吃蛇") fclock = pygame.time.Clock() food = (4, 5) body = [(1, 1)] head = (1, 2)
times = 0 direction = "right" old_direction = "right" alive = True pause = False BLOCK = 0, 0, 0 GREEN = 0, 255, 0 RED = 255, 0, 0 BLUE = 0, 0, 255 WHITE = 255, 255, 255 back_color = WHITE def new_draw_rect(zb, color,screen): pygame.draw.rect(screen,color,((zb[1]-1)*50+1,(zb[0]-1)*50+1,48,48)) def get_front(head, direction): x, y = head if direction == "up": return x-1, y elif direction == "left": return x, y-1 elif direction == "down": return x+1, y elif direction == "right": return x, y+1 def ask_alive(front, body): x, y = front if x < 0 or x > 12 or y < 0 or y >16 : return False if front in body: return False return True def new_food(head, body): i = 0 while i < 100: x = random.randint(1, 12) y = random.randint(1, 16) if (x, y) != head and (x, y) not in body: return (x, y), True i += 1 else: return (0, 0), False def direction_yes_no(direction, old_direction): d = {"up": "down", "down": "up", "left": "right", "right": "left"} if d[direction] == old_direction: return old_direction return direction
#food = new_food(head,body) while 1: for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit() elif event.type == pygame.KEYDOWN: if event.key == pygame.K_UP: direction = "up" elif event.key == pygame.K_LEFT: direction = "left" elif event.key == pygame.K_DOWN: direction = "down" elif event.key == pygame.K_RIGHT: direction = "right" elif event.key == pygame.K_p: pause = not pause if times >= 100 and alive and (not pause): direction = direction_yes_no(direction, old_direction) old_direction = direction front = get_front(head, direction) alive = ask_alive(front, body) if alive: body.append(head) head = front if food == head: food, alive = new_food(head, body) else: body.pop(0) else: back_color = BLOCK pygame.display.set_caption("遊戲結束") times = 0 else: times += 1 screen.fill(back_color) new_draw_rect(food, RED, screen) for i in body: new_draw_rect(i, BLUE, screen) new_draw_rect(head, GREEN, screen) fclock.tick(100) pygame.display.update()
↑↑↑↑
↑↑↑↑
↑↑↑↑
4,
Q : 遊戲結束或者暫停後因為times >= 100 and alive and (not pause)始終為假
times += 1 一直運行,,,,會不會不太妥當
A : 又不會溢出,,,,,,,,取消暫停時蛇能迅速跑起來要是time==100那問題就大了,,幸虧當初留了些余地寫成 >=
(~ ̄▽ ̄)~
5,
Q : 蛇跑的太慢我想加速
A : 上面的times>=100的100隨便改一下就行
0->100動一動 變成 0->80 動一動
或者動前面的數
0->100動一動 變成 20->100動一動
或者將它設置成變量讓它隨蛇的長度變化而變化
6,
打字太累,,
direction那裏可以使用0,1,2,3
代替 up left down right
鍵盤事件和get_front()得用同一套詞
7,
Q : 第一個food的位置是固定的,不能"動"嗎?
A : 因為我是先定義變量,再定義函數
food = new_food(head,body)
就得寫在定義函數後面,我不忍心讓它一個變量孤單,,,,怕你看不到(寫了但註釋掉了)
動手術
前面說過
問題1,什麽時候動,這裏有兩個思路,
- 間隔固定時間(1秒),動一次
- 按一次鍵動一次,無操作一定時間(1秒)後,重復最後一次操作
看起來第一種方案 簡單 不錯
當初我以為第二種方案很難,,,
寫了這個博客後實力大增?( ? ? ? )?
現在我不怕了
第一個方法,
處理按鍵事件後times = 100 ,例
if event.key == pygame.K_UP: direction = "up" times = 100 elif event.key == pygame.K_LEFT: direction = "left" times = 100
while循環當輪就能動一動
隱藏操作 : 在0.01s內按下兩個鍵,第一個按的不會"生效"
pygame : 我得跑完事件列表的每個元素
這是假的第二方案!!!
第二個方法,
把"動一動"的全部代碼,打包成函數或者直接寫在按鍵處理的後面
就是上面times = 100的位置 例
if event.key == pygame.K_UP: direction = "up" ‘動一動‘ elif event.key == pygame.K_LEFT: direction = "left" ‘動一動‘
記住關鍵的一點times = 0也算"動一動"的內容,第二方案裏,按鍵後計時需要清零!!!
if times >= 100 and alive and (not pause): direction = direction_yes_no(direction, old_direction) old_direction = direction front = get_front(head, direction) alive = ask_alive(front, body) if alive: body.append(head) head = front if food == head: food, alive = new_food(head, body) else: body.pop(0) else: back_color = BLOCK pygame.display.set_caption("遊戲結束") times = 0 else: times += 1
為了防止"詐屍"
"動一動"前還得加上if alive and (not pause):
times沒必要加
我就懶得管了(╥╯^╰╥)這篇文章寫了太長時間,現在已經看不懂當初的代碼了
Q : 不知道你是不是記得我,優化2就是我問的.....是這樣的,我對我的方法還不死心...."動手術"改第二方案後能用嗎?
elif event.type == pygame.KEYDOWN: if event.key == pygame.K_UP: direction = "up" """改為""" elif event.type == pygame.KEYDOWN: if event.key == pygame.K_UP: if direction != "down" direction = "up"
A : 對於第一方法實現的第二方案還是會有小毛病
while循環當輪就能動一動
隱藏操作 : 在0.01s內按下兩個鍵,第一個按的不會"生效"
pygame : 我得跑完事件列表的每個元素
在0.01s內,按下兩個鍵,就可以照舊觸發【秘技: 撞脖子自殺】
第二方法實現的應該沒問題.......
完結撒花??ヽ(°▽°)ノ?
Q : 我想在加寬屏幕在右側顯示一下時間分數之類的信息,你還有什麽"交待"嗎?
A : 在pygame顯示字體比較有難度....我只能祝你程序不出BUG...另外點(800,0)到點(800,600)別忘了畫到線,提醒玩家"邊界"還是存在的,讓玩家摔鍵盤動怒就不好了
Q : 我就是對你的顏色搭配有意見,顏色有點紮眼....
A : 我又不是美工,,,,,,,,,,,,顏色搭配的問題,應該,,,,,,,,,應該可以原諒,,,,,,,,,,
Q : 遊戲太沒挑戰性,加點障礙物唄~~
A : walls 會在ask_alive()和new_food()用到,加個if的問題,,,,另外繪制屏幕那裏多加個for,,,,,
我就懶得管了(╥╯^╰╥)這篇文章寫了太長時間,現在已經看不懂當初的代碼了
完結撒花??ヽ(°▽°)ノ?
這次真沒了..??ヽ(°▽°)ノ?
如有疏漏,歡迎補充
pygame寫貪吃蛇