1. 前言
turtle
(小海龜) 是 Python 內建的一個繪圖模組,其實它不僅可以用來繪圖,還可以製作簡單的小遊戲,甚至可以當成簡易的 GUI 模組,編寫簡單的 GUI 程式。
本文使用 turtle
模組編寫一個簡單的小遊戲,通過此程式的編寫過程聊一聊對 turtle
模組的感悟。
編寫遊戲,如果要做專業的、趣味性高的,還是請找 pygame
,本文用 turtle
編寫遊戲的目的是為了深度理解 turtle
的功能。
turtle
模組的使用相對而言較簡單,對於基礎方法不做講解。只聊 turtle
模組中稍難或大家忽視的地方。
2. 需求描述
程式執行時,畫布上會出現一個紅色的小球
和很多綠色、藍色的小球
。
剛開始紅色的小球會朝某一個方向移動,使用者可以通過按下上、下、左、右方向鍵控制紅色小球的運動方向。
綠色、藍色小球以初始的預設方向在畫布上移動。
當紅色的小球碰到綠色小球時,紅色小球球體會變大,當紅色小球碰到藍色小球時,紅色球體會變小。
當紅色小球球體縮小到某一個閾值時,遊戲結束。
3. 製作流程
3.1 初始化變數
本程式需要使用到 turtle
、random
、math
模組,使用之前,先匯入。
import turtle
import random
import math
'''
初始化遊戲介面大小
'''
# 遊戲區域的寬度
game_wid = 600
# 遊戲區域的高度
game_hei = 400
# 磚塊的大小,以及每一個小球初始大小
cell = 20
# 紅球初始大小
red_size = cell
# 紅色小球
red_ball = None
# 儲存綠色小球的列表
green_balls = []
# 儲存藍色小球的列表
blue_balls = []
# 紅色小球的方向 當前方向 0 向右,90 向上 180 向左 -90 向下
dir = 0
上述程式碼說明:
紅色小球只有一個,由變數 red_ball
儲存,紅色小球在運動過程中可以改大小,red_size
儲存其大小。
綠色和藍色小球會有很多,這裡使用 green_balls
和 blue_balls
2 個列表儲存。
3.2 通用函式
隨機位置計算函式: 為小球們隨機生成剛開始出現的位置。
'''
隨機位置計算函式
'''
def rand_pos():
# 水平有 30 個單元格,垂直有 20 個單元格
x = random.randint(-14, 14)
y = random.randint(-9, 9)
return x * cell, y * cell
繪製指定填充顏色的小正方形: 在遊戲裡有一個虛擬區域,四周使用很多小正方形圍起來。
'''
繪製一個指定填充顏色的正方形
填充顏色可以不指定
'''
def draw_square(color):
if color is not None:
# 的顏色就填充
turtle.fillcolor(color)
turtle.begin_fill()
for i in range(4):
turtle.fd(cell)
turtle.left(90)
if color is not None:
turtle.end_fill()
自定義畫筆形狀:
使用 turtle
製作遊戲的底層思想:
當我們匯入 turtle
模組時,意味著我們有了一支可以在畫布上畫畫的畫筆,畫筆的預設形狀是一個小海龜。
本文稱這支預設畫筆叫主畫筆,可以使用 turtle
模組中的 turtle.Turtle()
類建立更多畫筆 ,並且可以使用 ``turtle模組提供的
turtle.register_shape(name, shape)` 方法為每一支畫筆定製畫筆形狀。
如上所述,是使用 turtle
設計遊戲的關鍵。
強調一下:
通過主畫筆建立更多的畫筆,以及為每一支畫筆設定不同的形狀。是編寫遊戲的關鍵,遊戲中的每一個角色,其本質是一支支畫筆,我們只是在控制畫筆在畫布上按我們設計好的軌跡移動。
本遊戲中紅、綠、藍 3 種顏色的小球就是形狀為圓形的畫筆。
畫筆清單:
紅色小球畫筆一支。
綠色小球畫筆 n 支。
藍色小球畫筆 n 支。
'''
自定義畫筆形狀
name:畫筆名稱
color:可選項
'''
def custom_shape(name, size):
turtle.begin_poly()
turtle.penup()
turtle.circle(size)
turtle.end_poly()
cs = turtle.get_poly()
turtle.register_shape(name, cs)
turtle.register_shape(name, shape)
方法引數說明:
-
name: 自定義形狀的名稱。
-
shape: 由開發者繪製的形狀。
開發者繪製的哪一部分圖形用來充當畫筆形狀?
由
turtle.begin_poly()
記錄的第一點到由turtle.end_poly()
記錄的最後一點之間的圖形作為畫筆形狀。cs = turtle.get_poly()
可以理解為獲取到剛繪製的圖形,然後使用turtle.register_shape(name, cs)
註冊畫筆形狀,以後就可以隨時使用此形狀。如上程式碼記錄了一個圓的繪製過程,也就是建立了一個圓形的畫筆形狀。
移動到某個位置函式:
此函式用來讓某一支畫筆移到指定位置,不留下移動過程中的軌跡。
'''
移到某點
'''
def move_pos(pen, pos):
pen.penup()
pen.goto(pos)
pen.pendown()
引數說明:
pen
: 畫筆物件。pos
:要移到的目標地。
註冊鍵盤事件函式:
使用者可以通過鍵盤上的方向鍵更改紅色小球的方向。
turtle
模組提供有很多事件,可以以互動式的方式使用turtle
。turtle
模組中主要有 2 類事件:鍵盤事件、點選事件。因 turtle
的工作重點還是繪製靜態圖案上,其動畫繪製比較弱,所以它的事件少而簡單。
'''
改變紅色小球 4 方向的函式,
這些函式只有當使用者觸發按鍵後方可呼叫,故這些函式也稱為回撥函式。
'''
def dir_right():
global dir
dir = 0
def dir_left():
global dir
dir = 180
def dir_up():
global dir
dir = 90
def dir_down():
global dir
dir = -90
'''
註冊鍵盤響應事件,用來改變紅球的方向
'''
def register_event():
for key, f in {"Up": dir_up, "Down": dir_down, "Left": dir_left, "Right": dir_right}.items():
turtle.onkey(f, key)
turtle.listen()
'''
當紅色小球遇到牆體後,也要修改方向
'''
def is_meet_qt():
global dir
if red_ball.xcor() < -220:
dir = 0
if red_ball.xcor() > 240:
dir = 180
if red_ball.ycor() > 140:
dir = -90
if red_ball.ycor() < -120:
dir = 90
紅色的小球在 2 個時間點需要改變方向,一是使用者按下了方向鍵,一是碰到了牆體。
3.3 遊戲角色函式
繪製牆體函式:
牆體是遊戲中的虛擬區域,用來限制小球的活動範圍。
Tips: 牆體由主畫筆繪製。
'''
繪製四面的牆體
'''
def draw_blocks():
# 隱藏畫筆
turtle.hideturtle()
# 上下各30個單元格,左右各 20 個單元格
for j in [30, 20, 30, 20]:
for i in range(j):
# 呼叫前面繪製正方形的函式
draw_square('gray')
turtle.fd(cell)
turtle.right(90)
turtle.fd(-cell)
# 回到原點
move_pos(turtle, (0, 0))
建立小球畫筆: 此函式用來建立新畫筆。本程式中的紅色、藍色、綠色小球都是由此函式建立的畫筆,且外觀形狀是圓。
def init_ball(pos, color, shape):
# 由主畫筆建立新畫筆
ball = turtle.Turtle()
ball.color(color)
# 指定新畫筆的形狀,如果不指定,則為預設形狀
ball.shape(shape)
# 移到隨機位置
move_pos(ball, pos)
# 移動過程要不顯示任何軌跡
ball.penup()
return ball
引數說明:
pos
: 建立畫筆後畫筆移動的位置。color
:指定畫筆和填充顏色。shape
: 已經定義好的畫筆形狀名稱。
建立綠色、藍色小球:
def ran_gb_ball(balls, color):
# 隨機建立藍色、綠色小球的頻率,
# 也就是說,不是呼叫此函式就一定會建立小球,概率大概是呼叫 5 次其中會有一次建立
ran = random.randint(1, 5)
# 隨機一個角度
a = random.randint(0, 360)
# 1/5 的概率
if ran == 5:
turtle.tracer(False)
# 每一個小球就是一隻畫筆
ball = init_ball(rand_pos(), color, 'ball')
ball.seth(a)
# 新增到列表中
balls.append(ball)
turtle.tracer(True)
為什麼要設定一個概率值?
適當控制藍色、綠色小球的數量。
turtle.tracer(False)
方法的作用:是否顯示畫筆繪製過程中的動畫。False
關閉動畫效果,True
開啟動畫效果。
這裡設定為 False
的原因是不希望使用者看到新畫筆建立過程。
藍色、綠色小球的移動函式:
藍色、綠色小球被建立後會移到一個隨機位置,然後按預設方向移動。
def gb_ball_m(balls):
s = 20
a = random.randint(0, 360)
r = random.randint(0, 10)
for b in balls:
b.fd(s)
if b.xcor() < -220 or b.xcor() > 240 or b.ycor() > 140 or b.ycor() < -120:
b.goto(rand_pos())
當小球碰到牆體後讓其再隨機移到牆體內部(簡單粗粗暴!!)。
紅色球是否碰到了藍色或綠色小球:
此函式邏輯不復雜,計算小球相互之間的座標,判斷座標是否重疊。
'''
紅球是否碰到綠、藍球
'''
def r_g_b_meet():
global red_size
# 紅色小球的座標
s_x, s_y = red_ball.pos()
# 迭代綠色小球,藍色小球列表
for bs in [green_balls, blue_balls]:
for b in bs:
# 計算藍色或綠色小球座標
f_x, f_y = b.pos()
# 計算和紅色球之間的距離
x_ = math.fabs(s_x - f_x)
y_ = math.fabs(s_y - f_y)
# 碰撞距離:兩個球的半徑之和
h = cell + red_size
if 0 <= x_ <= h and y_ >= 0 and y_ <= h:
if b in green_balls:
# 遇到綠色球紅球變大
red_size += 2
if b in blue_balls:
# 遇到藍色球紅球變大
red_size -= 2
# 關鍵程式碼
custom_shape('red', red_size)
return True
return False
上述程式碼整體邏輯不復雜。而 custom_shape('red', red_size)
是關鍵程式碼,因紅色小球的半徑發生了變化,所以需要重新定製紅色小球的外觀形狀,這樣才能在畫布上看到半徑變化的紅色小球。
3.4 讓小球動起來
怎樣讓小球動起來?
每隔一定時間,讓小球重新移動。 turtle.ontimer(ball_move, 100)
是讓小球動起來的核心邏輯,每隔一定時間,重新移動紅、藍、綠外觀如圓形狀的小球。
def ball_move():
red_ball.seth(dir)
red_ball.fd(40)
# 檢查紅球是否碰到牆體
is_meet_qt()
# 隨機建立綠色小球
ran_gb_ball(green_balls, 'green')
# 隨機建立藍色小球
ran_gb_ball(blue_balls, 'blue')
# 讓綠色小球移動
gb_ball_m(green_balls)
# 讓藍色小球移動
gb_ball_m(blue_balls)
# 檢查紅球是否碰到藍色、綠色小球
r_g_b_meet()
# 定時器
turtle.ontimer(ball_move, 100)
主方法:
if __name__ == "__main__":
# 關閉動畫效果
turtle.tracer(False)
# 註冊事件
register_event()
# 定製 2 種畫筆形狀
for name in ['red', 'ball']:
custom_shape(name, cell)
# 主畫筆移動牆體的左上角
move_pos(turtle, (-300, 200))
# 繪製牆體
draw_blocks()
red_ball = init_ball(rand_pos(), 'red', 'red')
turtle.tracer(True)
# 讓紅球移動起來
ball_move()
#
turtle.done()
以上為此遊戲程式中的每一個函式講解。
執行後,可以控制紅色小球,當遇到綠色球和藍色球時,紅色球體會變大或變小。
4. 總結
使用 turtle
模組的過程說明了一個道理,沒有所謂簡單的知識,如果你認為簡單,那是因為你對它的認知太淺。只是學到了大家都學到的內容。
如果要真正悟透知識點的核心,需要多查閱官方文件,把所有內容吃透,並試著把這些知識向更高層面拔高。