1. 程式人生 > >Godot3遊戲引擎入門之九:建立UI介面並新增背景音樂

Godot3遊戲引擎入門之九:建立UI介面並新增背景音樂

Godot

一、前言

本文開篇必須提到兩個值得高興的訊息:

  1. 有讀者專門給我來信了,鼓勵我堅持下去,有點受寵若驚,心裡非常高興,希望有更多讀者,更多交流,有建議歡迎留言到我的微信公眾號或者部落格。
  2. 新預覽版: Godot 3.1 Alpha2 已經發布,也就是第二個預覽版了,修復了一些問題,距離 Godot 3.1 正式版的釋出又近了一步!著實激動人心。

之前的文章裡我已經申明過:**我使用的是 Godot 3.1 預覽版,如果要使用我所上傳的 Github Demo 程式碼,那麼務必到官網相應的版本哦!**下面附上最新預覽版下載地址:

工欲善其事必先利其器,好了,繼續我們的 Godot 入門系列。依然基於上一篇文章,本篇我會給大家熟悉的“金幣收集者騎士”小 Demo

劃上一個句號,幾個簡單必要的任務是:新增常見的 UI 介面;然後再加一點料——遊戲的音樂效果。再瀏覽之前,請務必參考上一篇文章: Godot3 遊戲引擎入門之八:新增可收集元素和子場景

二、正文

本篇目標

  1. 給遊戲新增 UI 控制元件
  2. 建立獨立的遊戲主介面,使用按鍵切換遊戲場景
  3. 新增一些背景音樂和其他效果

Godot中的分組

在新增 UI 控制元件顯示金幣收集數量之前,我們需要思考三個小問題,這三個問題解決好了介面就非常簡單了,接下來我們一個一個解決。第一個問題就是:如何判斷遊戲場景中的金幣已經被收集?

這個問題其實很好解決,在上一篇文章中我們已經在 AnimationPlayer 製作消失動畫並結合程式碼實現的過程中已經解決了:使用 Signal

訊號!金幣在被採集的時刻,也就是玩家 Player 和金幣 CoinArea2D )發生碰撞的那一刻,節點會發出 body_entered 的訊號,我們通過連線這個訊號做出處理並切換了金幣的消失動畫,同樣的道理,我們可以利用這個訊號在遊戲主場景中加以利用,在訊號訂閱函式中進行計數處理,但與之前不同的是:

  • 訊號處理場景不同:一個在金幣子場景,一個在 Game 遊戲主場景
  • 訊號處理數量不同:子場景中只有一個 Area2D 節點,主場景有很多個金幣例項
  • 訊號處理方式不同:子場景中手動連線訊號,主場景中我們要避免手動連線訊號

因為這幾點不同,我們引出了第二個問題:既然金幣數量不確定,我們要避免手動連線訊號,那麼如何在程式碼中連線訊號呢?這個問題非常簡單,一句程式碼解決: coin.connect('body_entered', target, 'your_method')

,程式碼種 connect 方法第一個引數為訊號名稱,第二個為目標即訂閱者,第三個為處理訊號的函式。這和我們之前使用編輯器連線訊號是一樣的效果,同樣的,我們可以使用 disconnect 方法取消訊號的連線。

兩個問題都解決了,那麼我們的模板程式碼大概是這樣的:

# 這裡的self指當前節點
$Coin1.connect('body_entered', self, '_on_Coin_collected')
$Coin2.connect('body_entered', self, '_on_Coin_collected')
$Coin3.connect('body_entered', self, '_on_Coin_collected')
# ……

# 碰撞處理函式
func _on_Coin_collected(body):
    # 處理金幣收集
    pass

明顯地這裡引出了第三個問題:那麼多金幣,如何簡便地、一次性地獲取場景中所有金幣呢?解決這個問題的核心在於使用 Godot 中的另一個重要概念: Group 分組!考慮一下分組的應用場景:遊戲場景中有很多金幣,他們同屬於某個金幣分組,我們通過 GDScrip 程式碼的某個方法,獲取了這個分組的所有金幣資訊,然後使用一個迴圈就可以輕鬆解決上面的重複程式碼問題了。這就是 Group 的一個最簡單的應用場景。理論結束,實踐起來非常簡單:在編輯器中建立分組,然後新增到金幣子場景的節點即可!

godot_9_create_group.png

如上圖,我們建立了一個 coin 分組,之後我們並不需要在遊戲主場景中對每一個金幣例項進行分組的新增工作,只需在金幣子場景中直接給根節點 Coin 新增 coin 分組就可以了。

控制元件和字型設定

接下來我們需要把金幣收集數量顯示到遊戲場景中!也是第一次接觸 Godot 中的 UI 控制元件吧,哈哈。在 Godot 中使用控制元件和節點沒有任何區別。 Godot 中所有的控制元件都是繼承於 Control 節點,我們只需要新增相應的 UI 節點就能在場景中顯示,需要注意的是:控制元件的渲染和普通節點一樣,後面的節點會覆蓋前面節點的顯示!在遊戲中 UI 介面一般都會顯示在主介面的最上層,那麼我們新增控制元件的時候就需要把節點置為根節點 Game 的最後一個子節點。但是,這樣做有個缺陷:一旦有新節點新增到遊戲場景中,預設位置為最後,這就難免還要去修改 UI 元素。對於遊戲開發者來說,時間就是金錢,那有沒有辦法讓 UI 層忽略其他節點,一直顯示在最頂層,達到一勞永逸的效果呢?那就有請“金錢節約者” CanvasLayer 隆重登場!

CanvasLayer 節點是一個特殊節點,它能確保渲染在最頂層,這正是我們所需要的。我們只要把所有控制元件節點設定為 CanvasLayer 層的子節點即可。說做就做,在主場景中新增一個 CanvasLayer 子節點,改名為 UI ,然後往它裡面新增其他子節點:首先新增一個 HBoxContainer 控制元件節點,如同其名,這是一個內容水平排列的盒子容器;在該節點內部新增一個顯示金幣圖片的控制元件 TextureRect 節點,以及一個計數文字標籤節點: Label 控制元件。控制元件節點的屬性設定如下:

  • TextureRect 節點設定 texture 材質屬性為金幣圖片
  • Label 節點更名為 Score ,修改 text 屬性即文字內容為: Score: 0
  • HBoxContainer 容器節點的位置調整,在子選單欄中點選 Layout 選擇 Top Wide 即可

godot_9_UI_layout

如上圖,這裡的 Layout 屬性是所有容器節點具有的屬性, Top Wide 即頂寬,佔據視窗頂部位置並拉伸寬度到最大。不過,現在有一個問題就是:文字標籤中 Score 中的文字太小了!作為程式設計師,第一反應肯定是去找字型大小屬性設定即可,不過在 Godot 中控制元件的文字大小並不能直接設定,我們必須先提供字型資源然後在此基礎上設定字型大小!

這個字型資源就是 Custom Font 自定義字型,一般為 ttf 格式,準備好字型檔案,點選 LabelScore ) 標籤,在 Custom FontsFont 屬性標籤下,選擇 New DynamicFont 建立一個新的動態字型,點選新建的動態字型進入字型資源相關設定面板,把 ttf 格式的字型檔案拖拽到面板的 Font Data 屬性下,最後在屬性面板裡設定字型的大小,字型的輪廓、顏色等就可以了,操作稍微複雜,適應一下就好了。 ?

godot_9_font_resource

注意:如上圖,這裡我把新建的字型資源儲存成了單獨的檔案,該資原始檔命名為 font.tres ,這些資源在後面可以重複利用,如果你不知道如何儲存相關資源,可以翻一下我之前的文章。 ?

新增程式碼

金幣分組已設定好, UI 介面也準備完畢,現在可以新增程式碼實現我們“夢寐以求”地計數功能了,哈哈。接下來,通過場景獲取所有屬於 coin 分組中的金幣,然後把分組中的每個金幣逐個連線到碰撞訊號處理函式,最後在連線好的方法中實現計數功能,理論在前面已詳述,在 Game 根節點程式碼基礎上新增程式碼如下,可以參考我給的註釋:

# 省略程式碼……

# 新增 UI 後的程式碼
onready var scoreLabel = $UI/HBoxContainer/Score
var score = 0 # 用於統計金幣收集數量

func _ready():
    # 從場景數中獲取所有屬於coin分組的節點
    var coins = self.get_tree().get_nodes_in_group('coin')
    for *Coin* in coins:
        # 手動連線訊號,用connect方法,第三個引數為訊號處理函式名
       coin.connect('body_entered', self, '_on_Coin_collected')

# 碰撞處理函式
func _on_Coin_collected(body):
    score += 1
    updateScore()

# 更新UI介面
func updateScore():
    scoreLabel.text = 'Score: ' + str(score)

# 省略程式碼……

程式碼很簡單,唯一值得注意的是 body_entered 訊號處理函式需要傳遞一個引數。編寫程式碼過程中如果遇到有任何問題,隨時可以在 Godot 編輯器中按 F4 搜尋檢視相關說明。

一點點音效

執行我們的遊戲,左上角,終於知道自己口袋裡有多少 Money 了吧?!不過好像還是缺少點什麼?嗯,缺少點聲音——金幣收集後的音效。和很多其他遊戲引擎一樣,在 Godot 中新增普通的音效非常簡單,準備好我們需要的音樂素材,一個節點即可搞定: AudioStreamPlayer ,注意,你會發現 Godot 中有其他兩個節點: AudioStreamPlayer2DAudioStreamPlayer3D ,它們分別應用於 2D 世界和 3D 世界中的音特,比如聲音傳播立體感、傳輸的距離感等,不過這裡我們不需要。

我們給遊戲新增兩個音效,一個是金幣收集後消失的音效,一個是遊戲的背景音樂。

金幣收集音效:在金幣子場景中再新增一個節點 AudioStreamPlayer 作為音樂流載體,音效是在 disappear 消失動畫開始播放後才同時進行,所以我們需要把音效新增到相應的動畫軌道上。首先開啟動畫面板,選擇我們已經建立好的消失動畫,然後新增一個音訊軌道: Audio Playback Track ,在彈出的介面中選擇剛才新增的 AudioStreamPlayer 節點,然後把準備好的音樂資原始檔直接拖拽到新建的音訊軌道上即可!簡單,方便,又不失強大。 ?

godot_9_add_audiostream

遊戲背景音樂:同樣地,在遊戲主場景中新增一個 AudioStreamPlayer 節點,然後設定節點的 stream 音訊流屬性,只需要把準備好的背景音樂直接拖拽過去即可!另外,可以適當調整一下音樂的音量,這裡我把 Volume Db 音量的分貝設定為了 -20 降低了背景音樂的音量,比較合適。

最後,新增一行程式碼,讓場景載入完後自動播放背景音樂:

# 省略程式碼……
onready var audioPlayer = $AudioStreamPlayer

func _ready():
    # 場景載入完畢後開啟背景音樂
    audioPlayer.play()
    # 省略程式碼……

好了,運行遊戲,收集幾個金幣,喝上幾口涼茶,放鬆一下心情吧!騷年! ?

建立主場景

嗯,還沒完!我們已經掌握了幾個最基本的 UI 控制元件,在此基礎上再把遊戲打造的稍微完美一點。是時候介紹一波自己強大的遊戲了!哈哈。和大部分遊戲一樣,我們給自己的 Demo 新增一個入口介面作為啟動後的主介面,在這個介面的功能是突出顯示遊戲的名字,告訴玩家如何開始新的旅途,以及說明遊戲體驗是如何高大上,寫明遊戲的創作者有多牛逼……嗯,有點飄了,你繼續,我來寫。 ?

這個介面並不複雜,兩行文字即可,也恰如其分地體現了我們遊戲的簡陋,嘿嘿。首先新建一個子場景,因為主要是 UI 元素,使用Control節點作為根節點,改名為 StartMenu ,新增一個 CenterContainer 作為直接子節點,並在其下新增一個 VBoxContainer 垂直容器,容器內新增兩個 Label 標籤子節點。這幾個節點的名字很好地解釋了其功能: CenterContainer 是一個能把內容居中顯示的容器, VBoxContainer 為一個內容垂直分佈容器。這裡我設定 CenterContainerLayout 佈局屬性為 Full Rect 全屏顯示,而兩個文字標籤都設定了 Align 對齊屬性為 Center 居中,並寫上幾個“高大上”的文字。

給文字標籤修改字型,這裡我使用了之前儲存的字型資源: font.tres 。不過,當我想在第二個標籤中把字型放得更大、顏色更鮮豔、更突出表現的時候,你會發現一處修改,所有應用了該字型資源的文字標籤都變了!為了標新立異,是不是又要重新建立一個獨立的資原始檔呢?別急,很顯然, Godot 早已考慮到了這點,我們只需要讓資源唯一化即可輕鬆達到目的!在標籤屬性面板中,選中我們的字型資源,然後開啟屬性面板上的選項,選擇 Make Unique 就可以輕鬆搞定啦!

godot_9_reuse_font_resource

最後,給主場景也新增一個背景音樂,和之前的節點設定稍微有差別的是,這裡我給 AudioStreamPlayer 節點上勾選了 AutoPlay 屬性,也就是自動播放而無需使用程式碼進行控制了。我們的遊戲介面做完了,儲存好,按下 F5 啟動遊戲執行,這時候遊戲還是會自動進入騎士收集金幣的介面,這不是我們想要的,我們需要從 StartMenu 場景開始,所以要對主場景進行修改,在 Project -> Project Settings -> Application -> Run -> Main Scene 中,選擇建立好的主介面 StartMenu.tscn 即可修改主介面為我們建立的選單介面, OK ,一切準備就緒!

godot_9_set_main_scene

別忘了新增切換場景的程式碼,否則按 Enter 鍵或者空格鍵都不會有任何效果:

extends Control

# 遊戲場景資源路徑
var gameScene = 'res://Game.tscn'

func _input(event):
    if event.is_action_released('ui_accept'):
        # 當按下空格或者回車時切換場景到Game
        self.get_tree().change_scene(gameScene)

大功告成:

result_1

三、總結

總算結束了——這個“高大上”且“及其無聊”的“騎士滿地找錢”遊戲,哈哈。不知道大家看完後感覺怎樣?不管如何,我們還是來總結一下本次學習到的一些 Godot 中的新鮮知識點吧:

  1. 給遊戲新增 UI 控制元件元素,使用 CanvasLayer 節點
  2. 建立獨立的遊戲主介面,使用按鍵切換遊戲場景
  3. 新增背景音樂和其他聲音效果及動畫、程式碼控制
  4. 其他小知識點:分組、程式碼中訊號連線、字型資源等

最後的最後,我所要提醒的是, Godot 所支援的音訊檔案包括 OGGWAV 格式,前者一般用於背景音樂,後者用於短音效,而不支援 MP3 格式的音訊,另外我們的遊戲也缺少很多很多普通遊戲應有的一些機制,比如結束、暫停機制,沒有怪物敵人、粒子特效,無關卡設計,不支援多人遊戲等等,當然,這完全有待我們將來的開發啦!盡情期待吧!

本次程式碼已經上傳到 Github ,還是那句話,原創非常不易,希望大家喜歡! ?