老程序员的数字游戏开发笔记(二) —— 直接开始一个Godot项目
目录
本篇简述
一个最简单的Godot项目
创建一个新项目
创建一个 Sprite2D 节点
创建Gd脚本-GDScript
添加打印
运行项目
加个旋转
让它前进
监听输入
查看帮助
再添加按“上”键时移动
总结
政安晨的个人主页:政安晨
欢迎 👍点赞✍评论⭐收藏
希望政安晨的博客能够对您有所裨益,如有不足之处,欢迎在评论区提出指正!
本篇简述
实施数字游戏开发,是对接并应用AI能力的一类综合体现,也是开展广泛而灵活的AI应用的形态,是自由度比较高的方式,所以,这方面感兴趣的小伙伴们建议还是掌握一款游戏引擎,当然,作者政安晨是极力推荐Godot作为您要掌握的游戏引擎,它开源、小巧、开箱即用,只不过它确实需要您具备一定的程序员功底。
我这个栏目的前些篇文章在选择引擎的时候走了些弯路,也是因为项目的需要,选择了别的游戏引擎,但我确实不太喜欢其它的引擎,不为别的,仅仅是因为其它引擎不是MIT协议开源的,作者真的很喜欢开源,喜欢那种读开源软件代码的踏实感......
我的这个系列,力求在全实践中取得各类相关的必要知识,不做理论讲解,所有理论都在实践中获得并感受。小伙伴随着我的这个系列发展下去也就明白了。
一个最简单的Godot项目
下载并打开godot引擎
https://godotengine.org/https://godotengine.org/
创建一个新项目
可以根据电脑主机的情况,看是否要选择兼容模式。
项目应该包含一张图片:Godot 图标,我们经常在社区中使用它来制作原型。
创建一个 Sprite2D 节点
我们需要创建一个 Sprite2D 节点来在游戏中显示它。在“场景”面板中,点击“其他节点”按钮。
在节点界面,输入Sprite ,自动出现Sprites2D节点:
创建好之后,左侧场景栏出现:
我们接下来即将用到的图标文件在这里:
Sprite2D 节点需要用于显示的纹理。在右边的“检查器”中,你可以看到 Texture(纹理)属性写着“[空]”。要显示 Godot 图标,请点击并拖拽“文件系统”面板中的 icon.svg
文件到 Texture 插槽上。
然后,点击并拖动视口中的图标,使其在游戏视图中居中。
此时可以保存一下场景。
创建Gd脚本-GDScript
在场景面板的 Sprite2D 上点击右键并选择“添加脚本”,来创建或附加一个新的脚本到我们的节点上。
弹出“附加节点脚本”窗口。你可以选择脚本的语言和文件路径,以及其他选项。
把模板字段从“Node: Default”改为“Object: Empty”从而得到一个干净的脚本文件。其他选项保持默认,然后点击“创建”按钮来创建脚本。
此时 Script 工作区将自动打开并显示你新建的 sprite_2d.gd
文件,显示以下代码行:
每个 GDScript 文件都是一个隐含的类。extends
关键字定义了这个脚本所继承或扩展的类。本例中为``Sprite2D``,意味着我们的脚本将获得 Sprite2D 节点的所有属性和方法,包括它继承的 Node2D
、CanvasItem
、Node
等类。
继承的属性包括你可以在“检查器”面板中看到的属性,例如节点的 texture
。
添加打印
func _init():
print("Hello, world!")
让我们把它分解一下。 func
关键字定义了一个名为 _init
的新函数。这是类构造函数的一个特殊名称。如果你定义了这个函数,引擎会在内存中创建每个对象或节点时调用 _init()
。
运行项目
看到打印如下:
加个旋转
完整代码如下:
extends Sprite2D
var speed = 400
var angular_speed = PI
func _init():
print("Hello, world!")
func _process(delta):
rotation += angular_speed * delta
说明:
我们将向脚本中添加两个成员变量:以像素每秒为单位的移动速度,和以弧度每秒为单位的角速度。将下述内容添加到 extends Sprite2D
行的后面。
成员变量位于脚本的顶部,在“extends”之后、函数之前。附加了此脚本的每个节点实例都将具有自己的 speed
和 angular_speed
属性副本。
为了移动我们的图标,我们需要在游戏循环中每一帧更新其位置和旋转。我们可以使用 Node
类中的虚函数 _process()
。如果你在任何扩展自 Node 类的类中定义它,如 Sprite2D,Godot将在每一帧调用该函数,并传递给它一个名为 delta
的参数,即从上一帧开始经过的时间。
func
关键字定义了一个新函数。在它之后,我们必须在括号里写上函数的名称和它所接受的参数。冒号结束定义,后面的缩进块是函数的内容或指令。
游戏的工作方式是每秒钟渲染许多图像,每幅图像称为一帧,而且是循环进行的。我们用每秒帧数(FPS)来衡量一个游戏产生图像的速度。大多数游戏的目标是60FPS,尽管你可能会发现在较慢的移动设备上的数字是30FPS,或者是虚拟现实游戏的90至240。
引擎和游戏开发者尽最大努力以恒定的时间间隔更新游戏世界和渲染图像,但在帧的渲染时间上总是存在着微小的变化。这就是为什么引擎为我们提供了这个delta时间值,使我们的运动与我们的帧速率无关。
可以执行下项目,看看旋转效果,快捷键F5.
可以观察一下在旋转过程中是不是丝滑不卡顿。
让它前进
现在我们来让节点移动。在 _process()
函数中添加下面两行代码,确保每一行都和之前的 rotation += angular_speed * delta
行的缩进保持一致。
完整代码:
extends Sprite2D
var speed = 400
var angular_speed = PI
func _init():
print("Hello, world!")
func _process(delta):
rotation += angular_speed * delta
var velocity = Vector2.UP.rotated(rotation) * speed
position += velocity * delta
说明:
正如我们所看到的,var
关键字可以定义新变量。如果你把它放在脚本顶部,定义的就是类的属性。在函数内部,定义的则是局部变量:只在函数的作用域中存在。
我们定义一个名为 velocity
的局部变量,该变量是用于表示方向和速度的 2D 向量。要让节点向前移动,我们可以从 Vector2 类的常量 Vector2.UP
入手,这个向量指向上方,调用 Vector2
的 rotated()
方法可以将其进行旋转。表达式 Vector2.UP.rotated(rotation)
表示的是指向图标前方的向量。用这个方向与我们的 speed
属性相乘后,得到的就是用来移动节点的速度。
我们在节点的 position
里加上 velocity * delta
来实现移动。位置本身是 Vector2 类型的,是 Godot 用于表示 2D 向量的内置类型。
运行场景就可以看到 Godot 头像在绕圈圈。
监听输入
游戏的重要特征:玩家输入。为了增加这一点,我们需要修改 sprite_2d.gd
的代码。
在 Godot 中,你有两个主要工具来处理玩家的输入:
-
内置的输入回调,主要是
_unhandled_input()
。和_process()
一样 ,它是一个内置的虚函数,Godot 每次在玩家按下一个键时都会调用。它是你想用来对那些不是每一帧都发生的事件做出反应的工具,比如按 Space 来跳跃。 -
Input
单例。单例是一个全局可访问的对象。Godot 在脚本中提供对几个对象的访问。它是每一帧检查输入的有效工具。
我们这里将使用 Input
单例,因为我们需要知道在每一帧中玩家是否想转身或者移动。
对于转弯,我们应该使用一个新的变量:direction
。在我们的 _process()
函数中,将 rotation += angular_speed * delta
替换成以下代码。
修改这部分代码:
完整代码如下:
extends Sprite2D
var speed = 400
var angular_speed = PI
func _init():
print("Hello, world!")
func _process(delta):
var direction = 0
if Input.is_action_pressed("ui_left"):
direction = -1
if Input.is_action_pressed("ui_right"):
direction = 1
rotation += angular_speed * direction * delta
这样,玩家的输入就被监听到了,然后控制了节点的某个属性。
查看帮助
在脚本中,CTRL+鼠标左键可以查看节点的属性。
这些节点的属性在哪里呢?
在这里:
大家明白这个对应关系了吧。
刚开始接触游戏的小伙伴可能会懵,别急以后就熟悉了。
再看一下上面代码:
我们的 direction
局部变量是一个乘数,代表玩家想要转向的方向。0
的值表示玩家没有按左或右方向键。1
表示玩家想向右转,而 -1
表示他们想向左转。
为了产生这些值,我们引入了条件和 Input
的使用。条件以 GDScript 中的 if
关键字开始,以冒号结束。条件是关键字和行末之间的表达式。
为了检查当前帧玩家是否按下了某个键,我们需要调用 Input.is_action_pressed()
。这个方法使用一个字符串来表示一个输入动作。当该按键被按下时,函数返回 true
,否则这个函数将返回 false
。
上面我们使用的两个动作,“ui_left”和“ui_right”,是每个 Godot 项目中预定义的。它们分别在玩家按键盘上的左右箭头或游戏手柄上的左右键时触发。
最后,当我们更新节点的 rotation
时,我们使用 direction
作为乘数:rotation += angular_speed * direction * delta
。
注释代码的话就在改行前面加#号:
再添加按“上”键时移动
为了只在按键时移动,我们需要修改计算速度的代码。取消注释代码,并将以 var velocity
开头的行替换为下面的代码。
var velocity = Vector2.ZERO
if Input.is_action_pressed("ui_up"):
velocity = Vector2.UP.rotated(rotation) * speed
我们将 velocity
的值初始化为 Vector2.ZERO
,这是内置 Vector
类型的一个常量,代表长度为 0 的二维向量。
如果玩家按下“ui_up”动作,我们就会更新速度的值,使精灵向前移动。
完整脚本如下:
extends Sprite2D
var speed = 400
var angular_speed = PI
func _process(delta):
var direction = 0
if Input.is_action_pressed("ui_left"):
direction = -1
if Input.is_action_pressed("ui_right"):
direction = 1
rotation += angular_speed * direction * delta
var velocity = Vector2.ZERO
if Input.is_action_pressed("ui_up"):
velocity = Vector2.UP.rotated(rotation) * speed
position += velocity * delta
这里Vector2是使用浮点数坐标的2D向量。
按F5调试体会一下吧。
总结
总之,Godot中的每个脚本都代表一个类,并扩展了引擎的一个内置类。在我们sprite的例子中,你的类所继承的节点类型可以让你访问一些属性,例如在“精灵” 例子中的 rotation
和 position
。你还继承了许多函数,但我们在这个例子中没有使用这些函数。
在 GDScript 中,放在文件顶部的变量是类的属性,也称为成员变量。除了变量之外,你还可以定义函数,在大多数情况下,这些函数将是类的方法。
Godot 提供了几个虚函数,你可以定义这些函数来将类与引擎连接起来。其中包括 _process()
,用于每帧将更改应用于节点,以及 _unhandled_input()
,用于接收用户的输入事件,如按键和按钮。还有很多。
Input
单例允许你在代码中的任何位置对玩家的输入做出反应。 尤其是,你在 _process()
循环中使用它。
初步感受了一下Godot引擎,是不是还不错?后续您要探索的时间长着呐,会越来越有趣的,嘻嘻。