Godot 游戏引擎

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

2018-12-07  本文已影响52人  spkingr
godot_cover.jpg

一、前言

继续前面的两篇文章,《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/

二、正文

本篇目标

  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 脚本把主场景中所有的子节点都相互关联在一起,让每个子场景相互配合,工作得有条不紊,另外它还会动态地创建一些其他的子节点,比如金币、能量币等。

代码中的主要逻辑在于处理游戏的开始、暂停、进入下一关卡以及结束等逻辑。对于每个关卡的元素合理设计,比如当前关卡的金币总数、超时时间、能量币的出现时机设计等,我没怎么用心,算法不是很合理,如果大家有兴趣,完全可以发挥自己的创造力丰富一下游戏的可玩性吧!嘿嘿。

其他需要注意的代码我在这里列出来:

关于 yield 关键字可以在上一篇文章中查看。最后运行游戏,进行测试吧! :smile:

游戏运行最终效果一览

三、总结

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

  1. 学习了一些新的 Godot 节点,以及一些新的关键词
  2. 探讨了一些基本的游戏开发规则,包括编写代码的规范
  3. 编写实现游戏中相关逻辑代码,完成我们第一个完整的小游戏

本次小项目以及相关的代码已经上传到 Github ,地址: https://github.com/spkingr/Godot-Demos原创不易,希望大家喜欢吧! :smile:

我的博客地址: http://liuqingwen.me ,欢迎关注我的微信公众号:

IT自学不成才
上一篇下一篇

猜你喜欢

热点阅读