2018-12-06 by Liuqingwen | Tags: http://liuqingwen.me/blog/tags/Godot/ | 2 Hits
一, 前言
继续前面的两篇文章,《Godot3 游戏引擎入门之十: 介绍一些常用的节点并开发一个小游戏》一共分为三小篇, 链接如下:
Godot3 游戏引擎入门之十: 介绍一些常用的节点并开发一个小游戏 (上)
Godot3 游戏引擎入门之十: 介绍一些常用的节点并开发一个小游戏 (中)
主要内容: 分析并制作一个完整的小游戏 (下篇)
阅读时间: 6 分钟
永久链接:
系列主页: http://liuqingwen.me/blog/tags/Godot/
二, 正文
本篇目标
了解学习游戏中的几个主要场景的制作
编写实现游戏中相关逻辑的代码
分析整个项目的一个开发流程
主要的场景
请参考上一篇: Godot3 游戏引擎入门之十: 介绍一些常用的节点并开发一个小游戏 (中).
代码与逻辑
部分代码见上篇文章: Godot3 游戏引擎入门之十: 介绍一些常用的节点并开发一个小游戏 (中).
相关的细节解释参考: Godot3 游戏引擎入门之十: 介绍一些常用的节点并开发一个小游戏 (上).
接下来是 UI 控件场景和 Main 游戏主场景的脚本代码, 相对来说比较长, 但是不难理解, 相关重要的地方我已经做了注释, 相信您能一目十行.
- 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 关键字可以在上一篇文章中查看. 最后运行游戏, 进行测试吧!
三, 总结
嗯, 这个不好玩的小游戏总算完成了, 总结一下我们的内容:
学习了一些新的 Godot 节点, 以及一些新的关键词
探讨了一些基本的游戏开发规则, 包括编写代码的规范
编写实现游戏中相关逻辑代码, 完成我们第一个完整的小游戏
本次小项目以及相关的代码已经上传到 GitHub , 地址: https://github.com/spkingr/Godot-Demos , 原创不易, 希望大家喜欢吧!
- http://liuqingwen.me/blog/tags/Godot/
- Comments:
来源: https://juejin.im/entry/5c0a32fff265da614312e061