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

一、前言

时间飞快,我有一段时间没有发表博客了,这段时间并不忙,一方面我自己也在不断学习,另一方面暂时不知写哪方面的内容了,感觉 Godot 中一些基础的部分我都或多或少谈到了,所以我打算使用我们学习过的知识来做一个小游戏吧。

这个游戏非常简单,但是对于完全“门外汉”的初学者来时还算有一定难度,不过别急,我会把我制作这个小游戏的一些思路以及常用的技巧娓娓道来,而且源代码我于上周就已经上传到 Github 啦: https://github.com/spkingr/Godot-Demos ,另外这个游戏来源于一本书:《 Godot Engine Game Development Projects 》,官网也有这个 Demo(Coin Dash) 以及其他示例的代码,我的思路和代码和官方有点不同,也实现了一些其他功能比如游戏暂停、金币数量显示等,强烈建议大家去围观。 ?

本文分上下两篇,第一篇,也就是在进入“金币”小游戏的开发制作讲解之前,我先把之前文章里没有遇到过的一些非常重要的节点介绍一下,还有一个提醒:最好的学习方法应该是先尝试一遍或者边思考边把代码浏览一下,然后再来看我的文章,这样效果会比较好。嗯,废话不多说,我们开始吧!

主要内容:认识一些新的节点和代码学习
阅读时间: 10 分钟
永久链接: http://liuqingwen.me/blog/2018/11/30/introduction-of-godot-3-part-10-introduce-some-node-types-and-make-a-new-game-part-1/
系列主页: http://liuqingwen.me/blog/tags/Godot/
二、正文

本篇目标

学习使用一些新的 Godot 节点
最基本的游戏开发规则
编写代码的规范
Godot 中常用节点

  1. Timer 节点

看名字就知道这是一个“计时器”。在 Godot 中一切皆节点,所以看到这种纯功能性的节点不要觉得奇怪,同时,我们完全可以不使用节点,直接使用代码 Timer.new() 动态创建一个计时器也是没任何问题的;甚至我们完全可以通过设置变量,利用 _process(delta) 方法来计算时间,不过显然没有 Timer 节点来得方便简洁!

Timer 时间计时器节点的属性非常简单,根据需求可以设置其等待时间、重复计时以及是否自动开始,这些属性我们也可以在 GDScript 脚本中使用代码修改:

wait_time :等待时间,即计时时长,结束触发 timeout 信号
one_shot :是否是一次性,如果是,只会触发一次 timeout 信号
autostart :自动开始,载入场景后计时,也可以使用 start 方法手动开启
游戏中计时功能使用非常频繁,不过,有部分计时场合我们还可以使用 yield 关键字代替,这样会省去节点的创建和信号的连接等繁琐、重复代码,这是分使用场合的,后面我会详述。 ?

  1. Tween 节点

在游戏开发过程中,我们一般使用 AnimationPlayer 节点来实现移动、缩放、颜色渐变等动画效果,但实际上,在有些场景中我们可能会直接使用 AnimatedSprite 节点,再结合一系列图片来实现动画特效,这个时候由于图片的限制(比如我们只做了金币的闪耀图片,并没有做金币的消失图片),我们并不能添加实现其他普通动画,那是不是没有其他办法呢?——办法当然有,这就需要 Tween 节点的隆重登场了!

Tween 即渐进/过渡的意思,从一种状态在一定时间内变化到另一种状态,从而产生一种视觉动画。渐变节点使用非常简单方便,可以对一个物体的任意属性进行动画控制,当然,也可以同时处理多个动画对象。其主要方法有以下几个:

repeat :是否重复
start() :开始渐变,结束后触发 tween_completed 信号
interpolate_property() :设置进行动画的节点属性以及时长等,需要传递属性名称、开始结束值、时长等参数
这里最重要的方法是 interpolate_property() ,可以在 Godot 编辑器中按 F4 搜索 Tween 类进行查看。当然,和 Timer 节点一样,我们完全可以在代码中动态创建 Tween 对象。

  1. Path2D 节点

Path2D 是一个路径节点,由很多位置点组成,这个路径可以是曲线,也可以是直线。实际上 Path2D 一般是与 PathFollow2D 配合使用,关于 Path2D 的使用,我推荐去看看官方的一个例子: Your first game 。

在我要讲解的这个小 Demo 中,我使用 Path2D 路径节点绘制了一些点来保存需要用到的位置,后续我会详述。 ?

GDScript 几个重要关键字

  1. export(PackedScene)/export(AudioStream)

在之前的文章中我们使用过 export(int) var speed = 10 来定义一个可以在编辑器中修改设置的整数值,以表示速度,同样地,我们可以使用 export 关键字来定义可以在编辑器中编辑的其他类型变量,比如:子场景、音频流等。

export(AudioStream) 用于定义一个音频流变量, export(PackedScene) 用于定义一个子场景变量,想象一下,游戏中我们制作了 3 种不同颜色的金币,每个关卡使用的金币可能不一样,这里我们就可以在关卡中定义一个 PackedScene 变量,然后直接在编辑器中选择对应的金币进行设置就可以了,非常方便。有点抽象,不过在后面的游戏代码中我们会应用到。

  1. preload(‘res://resource.tscn’)

preload 方法可以在代码中动态加载场景、文字、图片、音频等资源,比如我们可以预加载制作好的金币子场景,然后在代码中实例化,生成多个金币节点并添加到舞台中,实现动态添加金币的效果。 preload 是一个常用方法,不过在这个游戏中我并没有使用到,暂时提一下,以后讲 Singleton 单例再详述吧。

  1. ProjectSettings.get(‘display/window/size/width’)

在游戏创建的时候,我们都会对项目相关属性进行设置,比如游戏屏幕显示尺寸大小等,那么如何在代码中动态获取这些参数值呢?我们可以直接使用 ProjectSettings 这个单例,通过传入属性的路径,比如窗口大小的高度: display/window/size/height 即可获取相对应的配置值,这样能避免硬编码,即使修改了配置游戏依然能正常运行!

  1. rand_range/randomize/randi

很多游戏中都会大量使用随机值,比如金币数量随机、金币品类随机、出现时机随机等等,在 GDScript 脚本中使用随机同样非常简单直接,一个方法 randi() 即可生成一个随机整数,不过这个整数的范围很大,需要生成范围限制的随机数则可以用 rand_range() 方法,接收两个参数,一个最小值,一个最大值。

除了这两个方法,还有一个 randomize() 方法,这个方法有什么用呢?如果你在游戏中使用随机数,你会发现每次运行游戏,这个随机数都是相同的,这是因为生成随机数需要一个 seed 也就是名为种子的整数,因为种子并没有随机,所以根据这颗种子生成的随机数自然也就不会变化了,如何做到真正的随机呢?——在使用随机方法前,调用一下 randomize() 方法就可以啦!

  1. get_tree().paused

我在游戏中添加了暂停的功能,相信大部分游戏都有这个功能吧。在 Godot 中暂停功能非常容易实现!直接调用 get_tree().paused = true 这一行代码就可以了,是不是感觉非常轻松直接?哈哈,不过记住:一旦运行这行代码后,我们的游戏会完全处于暂停状态,也就是说不论游戏本身、还有输入、甚至弹出的 UI 界面等都一律等闲视之——后果就是你不能继续游戏了!

当然,解决这个问题是非常简单的,我们只需要把那些不被默认暂停的元素(暂停状态下依然可用)的 Pause Mode 暂停模式设置由 inherit 属性改成 process 就可以了:

  1. yield()

这可以算是 GDScript 脚本的一个高级功能,它和 Python 中的 yield 关键字如出一辙,如果你熟悉协程的概念,像 Unity C# 中的 StartCoroutine() 方法, Kotlin 中的 Coroutine 协程, Dart/JavaScript 语言中的 await/async 关键字,那么 yield 的工作原理是很好理解的。

对于新手来说,我觉得可以把协程简单地理解为:程序运行到该位置( yield ),暂停挂起在当前位置,继续执行其他代码,当时机到来,回到刚才挂起的位置继续执行。
嗯,听起来有点玄乎,不过在代码中使用起来非常简洁,参考运行下面的代码吧:

print('开始运行程序……')
yield(get_tree().create_time(1.0), 'timeout') # 挂起 1 秒钟
print('1秒钟后输出:结束运行。')
1
2
3
游戏开发的几个小 Tips

几个实用的小技巧或者说开发规则,也是我自己在开发实践中、他人的书籍里、一些博客文章中学到的,总结的不多,不过对于初学者来说还是比较重要的,可以先按部就班,之后再发展处自己的风格思路吧! ?

  1. 文件夹的管理

在我之前的文章里,对于小项目我都没有做特殊的文件管理,但是当游戏项目越来越大的时候,我们需要引起足够的重视,因为这会影响开发速度、以及团队合作的效率。其实,你完全可以按照自己的风格去管理资源文件,但是更推荐官方的一些做法和建议: Project organization

/project.godot
/docs/.gdignore
/docs/learning.html
/models/town/house/house.dae
/models/town/house/window.png
/models/town/house/door.png
/characters/player/cubio.dae
/characters/player/cubio.png
/characters/enemies/goblin/goblin.dae
/characters/enemies/goblin/goblin.png
/characters/npcs/suzanne/suzanne.dae
/characters/npcs/suzanne/suzanne.png
/levels/riverdale/riverdale.scn
1
2
3
4
5
6
7
8
9
10
11
12
13
这里我简单地比较了 Unity 和 Godot 中文件管理的风格样式,我个人更倾向于 Godot 的文件组织方式,因为等会我还会讨论一条重要的开发原则:尽量保持每个子场景的独立性!

  1. 保持场景独立

嗯,我认为这是 Godot 中开发游戏最重要的一条原则了!它能明显地提升开发效率,提高团队合作,更利于 Debug 调试。因为 Godot 中一切基于场景,场景中可以包含多个子场景,子场景依然可以由多个其他子场景组成,而且每个子场景是可以单独运行的!打开子场景,按 F6 来单独运行、测试,及早发现问题,提高程序的健壮性。

如何保持场景独立?这就需要我们去仔细思考了,具有独立功能的部分我们都可以抽离出来作为一个单独的子场景,通用、具有类似功能的节点也可以抽离出来以继承关系实现,需要说明的是:独立并不意味着不与其他场景发生任何关系了,独立只是让它能单独运行,能单独测试一部分功能,这是很重要的。

  1. 代码编写规范

代码构成了游戏的灵魂,代码编写不规范带来的直接后果就是:

自己看不懂,遇到 BUG 后越改越乱
团队里其他开发者看不懂,很难或者无法 DEBUG
不利于后续功能的开发、重构等
和文件组织管理方式一样,其实代码编写规范也会因人而异,在 Godot 中官方所推荐的方式如下:

枚举、常量等变量命名

enum State{INIT, IDLE, PLAYING, DEAD}
const CONST_GRAVITY = 98

普通变量、私有变量命名

var player_sprite = 1
var _walk_speed = 2

私有方法命名

func _private_method():
get_tree().paused = true
pass

公有方法命名

func public_method():
_private_method()
pass
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
注意,在 GDScript 中是没有 private/public/protected 等关键字来规范访问限制的,类似 Python ,这也正是我们需要保持一定的编码规范的原因之一。不过,你会发现我的命名方式会有所不同!我比较习惯 Java/C#/Dart 等语言的命名规则,采用驼峰式,同时利用 _ 下横线来标记私有变量或者方法,而且调用内部方法的时候我都会显式使用 self 关键字:

枚举、常量等变量命名

enum State{INIT, IDLE, PLAYING, DEAD}
const CONST_GRAVITY = 98

var playerSprite = 1 # 公有变量
var _walkSpeed = 2 # 私有变量

私有方法命名

func _privateMethod():
self.get_tree().paused = true
pass

公有方法命名

func publicMethod():
_private_method()
pass
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
至于选哪种,我觉得只要保持规范,符合个人或者团队共识就好啦! ?

三、总结

本篇文章算是一个经验小总结吧,也是为了更好地解释我们后面要出场的游戏项目,林林总总地列举了一些不成文的条条列列,不知道大家看后的感受是怎样的呢?

嗯,有两周没有写文章了,因为最近有其他的事情和同学在忙乎,不过我一定会坚持下去的,还是那句话,原创非常不易,希望大家喜欢! ?

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

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