Python turtle 模組可以編寫遊戲,是真的嗎?

一枚大果殼發表於2022-03-30

1. 前言

turtle (小海龜) 是 Python 內建的一個繪圖模組,其實它不僅可以用來繪圖,還可以製作簡單的小遊戲,甚至可以當成簡易的 GUI 模組,編寫簡單的 GUI 程式。

本文使用 turtle 模組編寫一個簡單的小遊戲,通過此程式的編寫過程聊一聊對 turtle 模組的感悟。

編寫遊戲,如果要做專業的、趣味性高的,還是請找 pygame,本文用 turtle 編寫遊戲的目的是為了深度理解 turtle 的功能。

turtle 模組的使用相對而言較簡單,對於基礎方法不做講解。只聊 turtle 模組中稍難或大家忽視的地方。

2. 需求描述

程式執行時,畫布上會出現一個紅色的小球很多綠色、藍色的小球

剛開始紅色的小球會朝某一個方向移動,使用者可以通過按下上、下、左、右方向鍵控制紅色小球的運動方向。

綠色、藍色小球以初始的預設方向在畫布上移動。

當紅色的小球碰到綠色小球時,紅色小球球體會變大,當紅色小球碰到藍色小球時,紅色球體會變小。

當紅色小球球體縮小到某一個閾值時,遊戲結束。

3. 製作流程

3.1 初始化變數

本程式需要使用到 turtlerandommath 模組,使用之前,先匯入。

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_ballsblue_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 模組提供有很多事件,可以以互動式的方式使用turtleturtle 模組中主要有 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 模組的過程說明了一個道理,沒有所謂簡單的知識,如果你認為簡單,那是因為你對它的認知太淺。只是學到了大家都學到的內容。

如果要真正悟透知識點的核心,需要多查閱官方文件,把所有內容吃透,並試著把這些知識向更高層面拔高。

相關文章