在Godot Engine上创建游戏“ Like coin”。 第一部分

Godot Engine的发展非常迅速,并赢得了世界各地游戏开发商的青睐。 也许这是用于创建游戏的最友好,最易学的工具,为确保这一点,请尝试制作小型2D游戏。 为了更好地理解游戏开发过程,您应该从2D游戏开始-这将降低进入更严格的游戏系统的门槛。 尽管向3D过渡本身并不像看起来那样困难,但是毕竟Godot Engine中的大多数功能都可以在2D和3D中成功使用。


引言


您能想到的最简单的事情是一款游戏,其中我们的主角会收集硬币。 要使其复杂一点,请添加障碍和时间作为限制因素。 该游戏将包含3个场景: PlayerCoinHUD (不考虑本文),它们将合并为一个Main场景。



项目设定


在投入编写脚本(脚本)(大约占创建游戏总时间的80-90%)之前,首先要做的是设置我们的未来项目。 在大型项目中,创建用于存储脚本,场景,图像和声音的单独文件夹很有用,我们绝对应该考虑到这一点,因为谁知道最终的结果。


我想立即保留一下,因为本文假设您对Godot Engine有点熟悉,并且您具有使用此工具的一些知识和技能,尽管我将重点介绍您是第一次遇到Godot Engine,但我仍然我建议您首先熟悉引擎的基本组件,研究GDScript的语法,并了解所使用的术语(节点,场景,信号等),然后再回到此处继续认识。

在程序菜单中,转到Project -> Project Settings


另一个小的题外话。 我将始终基于最终用户使用引擎的英语界面这一事实给出示例,尽管事实是“ Godot引擎”支持俄语。 这样做是为了消除与程序接口某些元素的错误/不正确翻译有关的可能的误解或尴尬。

找到“ Display/Window部分,并将宽度设置为800 ,将高度设置为600 。 同样在本节中,将“ Stretch/Mode设置为“ 2D ,将“ Aspect为“ Keep 。 这将防止更改其大小时窗口内容的拉伸和变形,但是要防止窗口被调整大小,只需取消选中“ Resizable大小”框即可。 我建议您尝试这些选项。


现在转到“ Rendering/Quality部分,并在右窗格中启用“ Use Pixel Snap ”。 这是为了什么 Godot引擎中向量的坐标是浮点数。 由于不能仅绘制半个像素就可以绘制对象,因此这种差异会导致使用pixelart游戏出现视觉缺陷。 值得注意的是,在3D中此选项没有用。 请记住这一点。


玩家场景


让我们开始创建第一个场景Player


任何场景的优点在于,它们最初与游戏的其他部分无关,这使得可以自由测试它们并获得最初放置在其中的结果。 通常,将游戏对象划分为场景是创建复杂游戏的有用工具-更容易发现错误,对场景本身进行更改,而游戏的其他部分不会受到影响,它们还可以用作其他游戏的模板,并且它们肯定也会发挥作用。因为他们在调动之前工作。

创建场景是一个简单的动作-在“ Scene选项卡上,单击+ (添加/创建)并选择Area2D节点,然后立即更改其名称,以免引起混淆。 这是我们的父节点,要扩展功能,您需要添加子节点。 在我们的例子中,它们是AnimatedSpriteCollisionShape2D ,但我们不要着急,而是按顺序开始。 接下来,立即“锁定”根节点:



如果将身体碰撞形式(CollisionShape2D)或精灵(AnimatedSprite)相对于父节点拉伸,拉伸,则肯定会导致无法预料的错误,以后将很难纠正它们。 启用此选项后,“父母”及其所有“孩子”将始终一起移动。 听起来很有趣,但是使用此功能非常有用。


动画精灵


如果您需要了解与其他对象的重叠事件或它们之间的碰撞, Area2D非常有用的节点,但是就其本身而言,它是肉眼不可见的,并且要使Player对象可见,可以添加AnimatedSprite 。 节点的名称告诉我们,我们将处理动画和精灵。 在“ Inspector窗口中,转到Frames参数并创建一个新的SpriteFrames 。 使用SpriteFrames面板可创建必要的动画并将相应的精灵加载到其中。 我们不会详细分析创建动画的所有阶段,而是将其留给独立研究,我只能说我们应该拥有三个动画: walk (行走动画), idle (静止状态)和die (死亡或失败动画)。 不要忘记SPEED (FPS)的值应为8 (尽管您可以选择其他值-合适)。



Collisionshape2d


为了使Area2D能够检测到碰撞,您必须为其提供对象的形状。 形状由Shape2D参数确定,包括矩形,圆形,多边形和其他更复杂的形状类型,并且尺寸已经在编辑器本身中进行了编辑,但是您始终可以使用Inspector进行微调。


情境


现在,为了“复活”我们的游戏对象,您需要为其设置脚本,根据该脚本将执行本场景中规定的我们设置的动作。 在“ Scene选项卡中,创建一个脚本,保留默认设置,删除所有注释(以“#”符号开头的行)并继续声明变量:


 export (int) var speed var velocity = Vector2() var window_size = Vector2(800, 600) 

使用export关键字允许您在“ Inspector面板窗口中设置speed变量的值。 如果我们要获取方便在“ Inspector窗口中编辑的自定义值,这是一种非常有用的方法。 将Speed设置为350velocity值将确定运动方向,而window_size是限制玩家运动的区域。


我们的操作的进一步顺序如下:我们使用get_input()函数检查是否正在进行键盘输入。 然后,我们将根据按下的键移动对象,然后播放动画。 玩家将向四个方向移动。 默认情况下,Godot引擎具有分配给箭头键的事件(“ Project -> Project Settings -> Input Map ),因此我们可以将其应用于我们的项目。 若要确定是否已按下某个键,应使用Input.is_action_pressed()并滑动要跟踪的事件的名称。


 func get_input(): velocity = Vector2() if Input.is_action_pressed("ui_left"): velocity.x -= 1 if Input.is_action_pressed("ui_right"): velocity.x += 1 if Input.is_action_pressed("ui_up"): velocity.y -= 1 if Input.is_action_pressed("ui_down"): velocity.y += 1 if velocity.length() > 0: velocity = velocity.normalized() * speed 

乍一看,一切看起来都不错,但有一点细微差别。 按下的几个键的组合(例如,向下和向左)将导致添加矢量,在这种情况下,玩家的移动速度比简单地向下移动的速度快。 为了避免这种情况,我们将使用normalized()方法-将向量的长度返回1


因此,跟踪了击键事件,现在您需要移动Player对象。 _process()函数将为我们提供帮助,每次发生框架更改时都会调用_process()函数,因此建议将其用于那些经常更改的对象。


 func _process(delta): get_input() position += velocity * delta position.x = clamp(position.x, 0, window_size.x) position.y = clamp(position.y, 0, window_size.y) 

希望您注意到delta参数,该参数又乘以速度。 有必要解释它是什么。 游戏引擎最初配置为以每秒60帧的速度运行。 但是,在某些情况下,计算机或Godot引擎本身会变慢。 如果帧速率不一致(更改帧所需的时间),这将影响游戏对象运动的“平滑度”(结果,运动是“生涩的”)。 “ Godot引擎”通过引入delta变量解决了这个问题(就像大多数类似的引擎一样)-它提供了要更改帧的值。 由于这些值,您可以“对齐”运动。 注意另一项非凡的功能- clamp (在两个指定的参数内返回值),由于它我们可以通过简单地设置区域的最小值和最大值来限制Player可以移动的区域。


不要忘记为我们的对象设置动画。 请注意,当对象向右移动时,您需要翻转AnimatedSprite (使用flip_h ),我们的英雄将在移动时直接朝他移动的方向看。 确保在AnimatedSprite中启用了“ Playing参数,以便动画开始播放。


 if velocity.length() > 0: $AnimatedSprite.animation = "walk" $AnimatedSprite.flip_h = velocity.x < 0 else: $AnimatedSprite.animation = "idle" 

生与死


启动游戏时,必须将主要场景告知其主要场景以准备开始新游戏,在我们的情况下,您应告知Player对象游戏的开始并为其设置初始参数:外观位置,默认动画,运行set_process


 func start(pos): set_process(true) #     Vector2(x, y) position = pos $AnimatedSprite.animation = "idle" 

当时间用完或玩家遇到障碍物时,我们还会提供玩家的死亡事件,并且设置set_process (false)将导致对该场景不再执行_process ()函数。


 func die(): $AnimatedSprite.animation = "die" set_process(false) 

添加碰撞


轮到此,使玩家能够检测到与硬币和障碍物的碰撞。 使用信号最容易实现。 信号是一种发送消息的好方法,以便其他节点可以检测到它们并做出响应。 大多数节点已经具有内置信号,但是可以为自己的目的定义“用户”信号。 如果在脚本开头声明了信号,则会添加信号:


 signal pickup signal die 

在“ Inspector窗口(“ Node选项卡)中浏览信号列表,并注意我们的信号以及已经存在的信号。 现在我们对area_entered ()感兴趣,它假定将发生碰撞的对象也属于Area2D类型。 我们使用“ Connect按钮连接area_entered ()信号,然后在“ Connect Signal窗口中选择突出显示的节点(Player),其余部分默认保留。


 func _on_Player_area_entered( area ): if area.is_in_group("coins"): #  emit_signal("pickup") area.pickup() 

为了便于检测和交互对象,需要在适当的组中对其进行标识。 现在省略了组本身的创建,但是我们一定会在以后再次讨论。 pickup()函数确定硬币的行为(例如,它可以播放动画或声音,删除对象等)。


硬币场景


用一个硬币创建一个场景时,除了在AnimatedSprite只有一个动画(突出显示)外,您需要完成与“ Player”场景相同的操作。 Speed (FPS)可以提高到14 。 我们还将更改AnimatedSprite的比例AnimatedSprite 。 并且CollisionShape2D的尺寸应与硬币的图像相对应,主要是不缩放硬币,即在2D编辑器中使用表格上的适当标记更改大小。
调整圆的半径。



组是节点的一种标记,可让您标识相似的节点。 为了使Player对象对接触硬币做出反应,这些硬币必须属于该组,我们称其为coins 。 选择Area2D节点(名称为“ Coin”),然后在“ Node -> Groups选项卡中为其分配标签,以创建相应的组。



对于“ Coin节点,创建一个脚本。 pickup()函数将由Player对象的脚本调用,并将告诉硬币工作时该怎么做。 queue_free()方法将安全地从树中删除一个具有其所有子节点的节点,并清除内存,但是删除不会立即生效,它将首先移动到要在当前帧末尾删除的队列。 这比立即删除节点要安全得多,因为游戏中的其他“参与者”(节点或场景)可能仍需要该节点存在。


 func pickup (): queue_free () 

结论


现在我们可以创建Main场景, Coin鼠标将两个场景( PlayerCoin拖到2D编辑器中,并通过启动场景(F5)来检查玩家的移动以及他与硬币的接触。 好吧,我急于说“ Like Coins”游戏创建的第一部分已经完成,请允许我离开,并感谢大家的关注。 如果您有话要说,请补充材料或在文章中看到错误,请务必通过在下面写评论来报告。 不要害怕变得坚强和批判。 您的“反馈”将告诉您我是否朝着正确的方向前进,以及可以进行哪些修复以使材料更加有趣和有用。

Source: https://habr.com/ru/post/zh-CN429234/


All Articles