一、前言

繼續前面的兩篇文章,《Godot3遊戲引擎入門之十:介紹一些常用的節點並開發一個小遊戲》一共分為三小篇,鏈接如下:

劉慶文:Godot3遊戲引擎入門之十:介紹一些常用的節點並開發一個小遊戲(上)?

zhuanlan.zhihu.com
圖標
劉慶文:Godot3遊戲引擎入門之十:介紹一些常用的節點並開發一個小遊戲(中)?

zhuanlan.zhihu.com
圖標
劉慶文:Godot3遊戲引擎入門之十:介紹一些常用的節點並開發一個小遊戲(下)?

zhuanlan.zhihu.com
圖標

主要內容:分析並製作一個完整的小遊戲(下篇)

閱讀時間: 6 分鐘

永久鏈接: liuqingwen.me/blog/2018

系列主頁: liuqingwen.me/blog/tags

二、正文

本篇目標

  1. 了解學習遊戲中的幾個主要場景的製作
  2. 編寫實現遊戲中相關邏輯的代碼
  3. 分析整個項目的一個開發流程

主要的場景

請參考上一篇:Godot3遊戲引擎入門之十:介紹一些常用的節點並開發一個小遊戲(中)。

代碼與邏輯

部分代碼見上篇文章:Godot3遊戲引擎入門之十:介紹一些常用的節點並開發一個小遊戲(中)。

相關的細節解釋參考:Godot3遊戲引擎入門之十:介紹一些常用的節點並開發一個小遊戲(上)。

接下來是 UI 控制項場景和 Main 遊戲主場景的腳本代碼,相對來說比較長,但是不難理解,相關重要的地方我已經做了注釋,相信您能一目十行。 :grin:

5. UI.gd

extends Control

# 開始遊戲的信號
signal start_game()

onready var _labelScore = $MarginContainer/HBoxContainer/LabelScore
onready var _labelTime = $MarginContainer/HBoxContainer/LabelTime
onready var _labelMessage = $VBoxContainer/LabelMessage
onready var _labelReady = $VBoxContainer/LabelReady
onready var _buttonStart = $MarginContainer2/ButtonStart

# 當前遊戲是否被暫停,初始為「是」
var _isPaused = true

# 監聽用戶的輸入
func _input(event):
if event.is_action_pressed(start):
# 這個if條件語句只會在遊戲開始時運行一次!
if self.get_tree().paused != _isPaused:
self.emit_signal(start_game)

_isPaused = ! _isPaused
self.get_tree().paused = _isPaused
if _isPaused:
_labelMessage.visible = true
_labelMessage.text = Paused
else:
_labelMessage.visible = false
_buttonStart.visible = false

# 開始遊戲按鈕被按下
func _on_ButtonStart_pressed():
_isPaused = false
_labelMessage.visible = false
_buttonStart.visible = false
self.emit_signal(start_game)

# 顯示Ready和目標金幣數文本
func displayReady(target = 0, display = false):
_labelReady.text = %d, Ready! % target
_labelReady.visible = display

# 遊戲結束顯示的信息
func showGameOver():
_isPaused = true
_labelMessage.text = Game Over
_labelMessage.visible = true
_buttonStart.text = Restart
_buttonStart.visible = true

# 顯示分數(金幣個數)
func showScore(score):
_labelScore.text = str(score)

# 顯示時間(剩餘時間)
func showTime(time):
_labelTime.text = str(time)

UI 子場景代碼稍複雜,不僅要顯示一些文字信息,比如當前時間、收集到的金幣數等,還負責接收響應玩家的鍵盤輸入,處理開始、暫停以及遊戲重試等。當然,邏輯並不複雜。

唯一要注意的地方是 if self.get_tree().paused != _isPaused: 這個判斷語句,我在代碼中已經作了相關說明,它的判斷結果只有在遊戲開始運行的第一次時為true ,其他任何時間都為 false (因為 _isPaused 的初始值的原因),也就是表示在開始遊戲的時候玩家按了 start 按鍵(我在 Input Map 中設置 start輸入為空格和回車),然後發射遊戲開始的信號。當然,你完全可以再定義一個變數來實現遊戲的開始和暫停等。

6. Game.gd

extends Node2D

export(PackedScene) var coinScene = null
export(PackedScene) var powerScene = null
export(float) var minPlayerDist = 80
export(float) var minObstacleDist = 120

onready var _player = $Player
onready var _startPosition = _player.position
onready var _ui = $HUD/UI
onready var _pointsCurve = $CactusPoints.curve
onready var _cactus = $CactusPoints/Cactus
onready var _coinContainer = $CoinContainer
onready var _countTimer = $CountTimer
onready var _powerTimer = $PowerTimer
onready var _gameOverAudioPlayer = $GameOverAudio
onready var _levelAudioPlayer = $LevelUpAuido

var _level = 0 # 當前關卡
var _timeLeft = 0 # 剩餘時間
var _totalCoins = 0 # 金幣總數
var _collectedCoins = 0 # 收集金幣數

func _ready():
randomize() # 保證每次遊戲都隨機
_player.isControllable = false

# 遊戲結束初始化某些變數
func _gameOver():
_level = 0
_countTimer.stop()
_ui.showGameOver()
for coin in _coinContainer.get_children():
coin.queue_free()

# 重新開始遊戲調用方法
func _restartGame():
_player.isControllable = false
_totalCoins = _calculateTotal(_level)
_timeLeft = _calculateDuration(_level)
_collectedCoins = 0
_ui.showScore(_collectedCoins)
_ui.showTime(_timeLeft)
_spawnObstacles()
_spawnCoins()
_player.restart(_startPosition)

_ui.displayReady(_totalCoins, true)
# 關鍵代碼,如果不明白可以參考後面的解釋
yield(self.get_tree().create_timer(1.5, false), "timeout")
_ui.displayReady()
_player.isControllable = true
_countTimer.start()
_spawnPowerup()

# 進入下一關卡
func _nextLevel():
_level += 1
_restartGame()

# 玩家收集金幣發出的信號處理
func _on_Player_coin_collected(count):
_ui.showScore(count)
if count >= _totalCoins:
_countTimer.stop()
_levelAudioPlayer.play()
_nextLevel()

# 玩家受到傷害,遊戲結束信號處理
func _on_Player_game_over():
_gameOver()

# 玩家收集到能量幣發出的信號處理
func _on_Player_power_collected(buffer):
_timeLeft += buffer
_ui.showTime(_timeLeft)

# 遊戲時間超時,遊戲結束
func _on_Timer_timeout():
_timeLeft -= 1
_ui.showTime(_timeLeft)
if _timeLeft <= 0:
_player.isControllable = false
_gameOverAudioPlayer.play()
_gameOver()

# 能量幣定時生產
func _on_PowerTimer_timeout():
var power = powerScene.instance()
var pos = _makeRandomPosition()
power.position = pos
self.add_child(power)

# UI界面點擊開始按鈕觸發開始信號
func _on_UI_start_game():
_nextLevel()

# 創建當前關卡的所有金幣
func _spawnCoins():
if coinScene == null:
return
var playerPos = _player.position
var obstaclePos = _cactus.position
for i in range(_totalCoins):
var coin = coinScene.instance()
var pos = _makeRandomPosition()
# 如果金幣產生位置在玩家或者障礙物內,則重新生成一個位置
while pos.distance_to(playerPos) < minPlayerDist || pos.distance_to(obstaclePos) < minObstacleDist:
pos = _makeRandomPosition()
coin.position = pos
_coinContainer.add_child(coin)

# 設置當前關卡的障礙物置
func _spawnObstacles():
var index = randi() % _pointsCurve.get_point_count()
var position = _pointsCurve.get_point_position(index)
_cactus.position = position

# 設置能量幣出現的時間並計時
func _spawnPowerup():
var powerTime = _makeRandomPowerAppearTime(_timeLeft)
_powerTimer.wait_time = powerTime
_powerTimer.start()

# 根據當前關卡設計金幣總數
func _calculateTotal(level):
return level + 5

# 根據當前關卡設計超時時長
func _calculateDuration(level):
return level + 5

# 當前時間下設計隨機能量出現時間
func _makeRandomPowerAppearTime(timeLeft):
return rand_range(0, timeLeft)

# 根據窗口尺寸設計隨機金幣位置
func _makeRandomPosition():
var x = rand_range(0, ProjectSettings.get(display/window/size/width))
var y = rand_range(0, ProjectSettings.get(display/window/size/height))
return Vector2(x, y)

嗯,這代碼有點!當然,這是這個小遊戲的核心代碼部分了。 Game.gd 腳本把主場景中所有的子節點都相互關聯在一起,讓每個子場景相互配合,工作得有條不紊,另外它還會動態地創建一些其他的子節點,比如金幣、能量幣等。

代碼中的主要邏輯在於處理遊戲的開始、暫停、進入下一關卡以及結束等邏輯。對於每個關卡的元素合理設計,比如當前關卡的金幣總數、超時時間、能量幣的出現時機設計等,我沒怎麼用心,演算法不是很合理,如果大家有興趣,完全可以發揮自己的創造力豐富一下遊戲的可玩性吧!嘿嘿。

其他需要注意的代碼我在這裡列出來:

  • randomize() 這個方法只需調用一次就可以在每次遊戲運行時產生真實的隨機效果
  • for coin in _coinContainer.get_children(): 獲取該節點的所有子節點(金幣)
  • self.get_tree().create_timer(1.5, false) 創建一個計時器,關鍵在 false 這個參數,表示場景暫停計時同步暫停
  • var position = _pointsCurve.get_point_position(index) 獲取Path2D 節點曲線上的某個點的位置值

關於 yield 關鍵字可以在上一篇文章中查看。最後運行遊戲,進行測試吧! :smile:

三、總結

嗯,這個*不好玩*的小遊戲總算完成了,總結一下我們的內容:

  1. 學習了一些新的 Godot 節點,以及一些新的關鍵詞
  2. 探討了一些基本的遊戲開發規則,包括編寫代碼的規範
  3. 編寫實現遊戲中相關邏輯代碼,完成我們第一個完整的小遊戲

本次小項目以及相關的代碼已經上傳到 Github ,地址:github.com/spkingr/Godo原創不易,希望大家喜歡吧! :smile:

我的博客地址: http://liuqingwen.me ,歡迎關注我的微信公眾號:

weixin.qq.com/r/PB3M1BD (二維碼自動識別)


推薦閱讀:
查看原文 >>
相关文章