【转】Godot3游戏引擎入门之十:介绍一些常用的节点并开发一个小游戏(下)

一、前言

继续前面的两篇文章,《Godot3游戏引擎入门之十:介绍一些常用的节点并开发一个小游戏》一共分为三小篇,链接如下:

Godot3游戏引擎入门之十:介绍一些常用的节点并开发一个小游戏(上)
Godot3游戏引擎入门之十:介绍一些常用的节点并开发一个小游戏(中)
Godot3游戏引擎入门之十:介绍一些常用的节点并开发一个小游戏(下)
主要内容:分析并制作一个完整的小游戏(下篇)
阅读时间: 6 分钟
永久链接: http://liuqingwen.me/blog/2018/12/06/introduction-of-godot-3-part-10-introduce-some-node-types-and-make-a-new-game-part-3/
系列主页: http://liuqingwen.me/blog/tags/Godot/
二、正文

本篇目标

了解学习游戏中的几个主要场景的制作
编写实现游戏中相关逻辑的代码
分析整个项目的一个开发流程
主要的场景

请参考上一篇:Godot3游戏引擎入门之十:介绍一些常用的节点并开发一个小游戏(中)。

代码与逻辑

部分代码见上篇文章:Godot3游戏引擎入门之十:介绍一些常用的节点并开发一个小游戏(中)。

相关的细节解释参考:Godot3游戏引擎入门之十:介绍一些常用的节点并开发一个小游戏(上)。

接下来是 UI 控件场景和 Main 游戏主场景的脚本代码,相对来说比较长,但是不难理解,相关重要的地方我已经做了注释,相信您能一目十行。 ?

  1. 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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
UI 子场景代码稍复杂,不仅要显示一些文字信息,比如当前时间、收集到的金币数等,还负责接收响应玩家的键盘输入,处理开始、暂停以及游戏重试等。当然,逻辑并不复杂。

唯一要注意的地方是 if self.get_tree().paused != _isPaused: 这个判断语句,我在代码中已经作了相关说明,它的判断结果只有在游戏开始运行的第一次时为 true ,其他任何时间都为 false (因为 _isPaused 的初始值的原因),也就是表示在开始游戏的时候玩家按了 start 按键(我在 Input Map 中设置 start 输入为空格和回车),然后发射游戏开始的信号。当然,你完全可以再定义一个变量来实现游戏的开始和暂停等。

  1. 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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
嗯,这代码有点长!当然,这是这个小游戏的核心代码部分了。 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 关键字可以在上一篇文章中查看。最后运行游戏,进行测试吧! ?

三、总结

嗯,这个不好玩的小游戏总算完成了,总结一下我们的内容:

学习了一些新的 Godot 节点,以及一些新的关键词
探讨了一些基本的游戏开发规则,包括编写代码的规范
编写实现游戏中相关逻辑代码,完成我们第一个完整的小游戏
本次小项目以及相关的代码已经上传到 Github ,地址: https://github.com/spkingr/Godot-Demos ,原创不易,希望大家喜欢吧! ?

原文链接:https://blog.csdn.net/SpkingR/article/details/84881081

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,099评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,828评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,540评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,848评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,971评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,132评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,193评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,934评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,376评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,687评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,846评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,537评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,175评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,887评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,134评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,674评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,741评论 2 351