Skip to content

Latest commit

 

History

History
657 lines (381 loc) · 24 KB

2020081902.HowToMakeYourFirstGameWithGodot.md

File metadata and controls

657 lines (381 loc) · 24 KB

如何使用Godot制作你的第一款游戏

Godot游戏开发新手教程 #1

翻译自:Godot Game Development for Beginner #1

作者:Daniel Buckley

目录

  • 介绍
  • 项目文件
  • 安装Godot
  • 创建新项目
  • 探索编辑器
  • Godot如何工作
  • 创建第一个场景
  • 创建Player
  • 编写Player脚本
  • 创建Tile
  • 创建敌人
  • 碰撞敌人
  • 收集硬币
  • 追踪摄像机
  • UI
  • 编写UI脚本
  • 结论

介绍

着急开始制作你自己的游戏吗?

游戏开发从来没有像现在这样流行过 - 销售额达到数十亿美元,成千上万的独立开发者磨炼他们的技能,并将他们的创造力带入其中。

即使是一个从未编写过代码的超级新手,在大量引擎的帮助下,任何人都可以制作他们自己梦想的游戏。

在这个综合教程里,我们将学习如何使用Godot创建你的第一个游戏。Godot是一个免费开源的2D和3D游戏引擎。因其开源,用户可以随意的添加或删除引擎中的内容,因此迅速获得了广泛欢迎。它还有一个活跃的社区随时可以提供帮助,所以它是制作第一个游戏最理想的选择。

在安装好Godot并学习一些编辑器的基础后,我们将制作一款2D平台类游戏,所以请打起精神,准备开始开发游戏吧!

项目文件

本项目,还需要一些角色,平台,硬币,敌人等等素材。你可以自己制作也可以使用教程提供的。素材可以在这里下载

你也可以通过上面相同的连接下载完整的项目文件。

安装Godot

要下载安装Godot,打开https://godotengine.org/。在这里,可以查看Godot的特性,去论坛交流等等。点击Download按钮。

选择你需要的平台,然后下载64或32bit标准版本(取决于你的操作系统)。

下载包为.ZIP文件。里面是一个应用程序,你可以将它解压到你电脑的任何位置。然后就没有然后了,Godot安装完成。

创建新项目

运行下载的Godot应用程序,可以看到Project Manager。在这里,我们可以创建项目,查看其它内容和下载模板。

点击New Project按钮来创建新项目。这将弹出一个新窗口,如下图:

  1. 输入项目名称
  2. 点击Create Folder按钮来创建一个新的文件夹
  3. 点击Create & Edit按钮启动项目

探索编辑器

当编辑器弹出时,是不是看不明白?没关系,让我们分解一下。

Godot有4个主要的面板,我们将用它们来创建游戏,每个面板都有特定的用途。

  1. Scene面板。它显示在当前场景下所有的节点及其层级结构。我们将在后面简单的讲一下场景和节点。
  2. FileSystem面板。它显示所有的文件和素材。精灵,模块,脚本,场景,文件夹,音频等等。
  3. 在这里可以看到并创造我们的游戏。四处移动,选择,编写脚本等。在此面板的上方有4个按钮,它们可以切换面板显示的内容。如2D,3D,脚本编辑器和外部素材库。
  4. Inspector面板。当选择一个节点时,它显示此节点所有的设置项。如:位置,旋转以及其它一些可以修改的属性。

Godot如何工作

上面讲到的场景和节点。它们都是什么?Godot游戏是有层级结构的节点组成的。一个节点可以是任何对象:一个角色,摄像机,3D模型,光,UI等等。节点组成了游戏中所有的实体,还可以成为一个节点子节点。

下面是游戏中一个角色示例。首先,我们有一个KinematicBody(运动体)节点,可以控制移动和一些物理交互。其次,它有两个子节点,一个CollisionShape2D(碰撞体)和一个Sprite(精灵)。当KinematicBody节点移动,旋转等其它行为时,它所有了子节点都会随之进行。甚至删除父节点时,所有子节点都同样会被删除。

Godot游戏由许多的父子节点为组成,它们创建各种各样不同的元素和系统。

由于游戏是由节点组件,所以在一个场景面板里,最终可能有上百甚至上千个节点。这会导致很难找到正确的节点,并且会让整个游戏开发变得混乱。要解决这个问题,我们可以将节点放入不同的场景里。

场景是可以做为节点放入其它场景的节点包。让我们将上面角色示例中的节点层级结构放入一个场景中。在文件系统中,它是一个保存的文件,我们可以将它拖入另一个场景中。

将游戏划分为节点的一个好处还在于,我们可以很轻松的将重复节点移除,与其在游戏里放入100个具有精灵,碰撞体的节点,不如将它们创建为一个场景,放在多个实体里。

在整个项目中,我们都将探索节点和场景使用。

创建第一个场景

那么,开始我们的2D平台游戏吧。首先,我们需要创建一个主场景,它是我们游戏的基础,它包含其它场景,如:角色,平台,硬币和敌人。

在scene面板里,点击2D Scene按钮来创建一个新的2D场景。

双击这个新节点,重命名为MainScene

即时保存场景是个好习惯。可以使用Ctrl + S或Scene > Save Scene来保存它。点击enter后你将在FileSystem里看到它。

创建角色

好了,我们有了主场景。接下来,我们继续创建角色场景,它将拥有所有需要的节点和对应的脚本。

要创建一个新场景,我们可以通过Scene > New Scene来进行。在场景面板里,选择Custom Node。这次会打开一个窗口,搜索并选择KinematicBody2D节点。

将节点重命名为Player,然后保存它。

在我们的平台游戏中,将需要一些sprites和其它的素材。本教程带有一个ZIP文件,包含了所有需要的素材。下载后将三个文件夹拖入Godot的项目目录里。

回到Godot,右键点击Player节点,选择Add Child Node。添加一个CollisionShape2D节点作为子节点。然后到FileSystem里找到Player_Idle图片并将它拖到场景里。重命名这个节点为Sprite

我们想让我们的sprite在居中,所以选择它,然后在Inspector中重新设置它。

  • 打开Transform下拉框
  • 设置Position为0, 0

你可能已经注意到,CollisionShape2D节点的边上有一个错误的符号。它的意思是需要给它一个形状。选择CollisionShape2D节点,在Inspector中设置Shape为Capsule。要使其可见,需要调整节点的层级,将CollisionShape2D节点移动到Srpite节点的下方。

设置CollisionShape2D的属性,点击Inspector中shape的选项,它会展开更多的选项。

  • 设置Radius到27
  • 设置Height到12
  • 设置Position到-0.5, 14.7

编写Player的脚本

现在了解了节点结构设置,我们可以开始编写Player脚本了。这将涉及移动,跳跃和收集硬币。

要创建脚本,选择Player父节点,在它的Inspector中,选择创建新的脚本。这会打开一个新窗口,点击回车。

可以看到,主窗口已经从2D切换到脚本模式。

Godot使用它自己的脚本语言,叫做GDScript。它的语法类似于Python。我们不会讨论语言的各个方面或编程的基本概念,但想要进行下去,需要基础的了解一下。

现在在脚本中,我们有两件事情。

extends KinematicBody2D

Extends类似于使用或导入,表示继承于kinematic body 2D对象,因此我们可以直接使用它的属性。

func _ready ():
    pass

这是一个可重用的基本的函数代码块。_ready函数是Godot内置函数,它会在节点初始化后被调用一次。

pass只有定义函数时的一个填充,没有任何意义。如果函数体里是空的,那将会产生一个错误。

接下来添加一些变量。

# stats
var score : int = 0
 
# physics
var speed : int = 200
var jumpForce : int = 600
var gravity : int = 800
 
var vel : Vector2 = Vector2()
var grounded : bool = false

Vector2定义了x和y两个值,它可使用于位置,比例,旋转,速度。我们将使用它来存储Player的当前速度。

我们还需要一个变量。它是一个子节点Sprite的引用。onready的意思是当节点初始化后,获取到Sprite节点。

# components
onready var sprite = $Sprite

现在,我们定义了变量,那么来让我们的Player移动起来吧。首先,我们需要定义一些按钮的输入。打开Project Settings窗口(Project -> Project Settings),打开Input Map标签。

Action字段中,输入一个名称,点击Add。

我们需要创建3个action。

  • move_left
  • move_right
  • jump

在每个action的后边有一个'+'小图标。点击它,选择Key。它可以让我们为action设置一个按键。

为3个action都设置一个按键。

都设置好后,关掉窗口继续编写脚本。

我们继续创建一个Godot的内置函数。_physics_process函数每秒会被调用60次,它是用来做物理计算的函数。一旦我们开始在函数里写代码,就可以移除pass了。

delta参数是每帧之前的时间。我们可以将移动乘上它,以便基于每秒像素移动,而不是每帧像素移动。

func _physics_process (delta):
    pass

首先,我们需要重置水平速度。然后监听左和右移动按键,它们将改变水平速度。

# reset horizontal velocity
vel.x = 0

# movement inputs
if Input.is_action_pressed("move_left"):
    vel.x -= speed
if Input.is_action_pressed("move_right"):
    vel.x += speed

接着,我们使用move_and_slide函数来移动Player,该函数将以特定的速度移动,并检测碰撞或其它事情。第二个参数是地面线(地面朝哪个方向?)

# applying the velocity
vel = move_and_slide(vel, Vector2.UP)

之后,我们可以施加重力,并检测是否按下跳跃键并且在地面上。如果是,Player将跳起来。

# gravity
vel.y += gravity * delta
 
# jump input
if Input.is_action_pressed("jump") and is_on_floor():
    vel.y -= jumpForce

最后,我们可以根据移动的方向翻转精灵(转身)。

# sprite direction
if vel.x < 0:
    sprite.flip_h = true
elif vel.x > 0:
    sprite.flip_h = false

现在我们完成了脚本,让我们切回到2D模式进入MainScene。将Player.tscn场景从FileSystem中拖进来。

现在要做的就是测试游戏。在屏幕的右上角,点击Play按钮。

一个窗口将弹出,提示未选择主场景。点击Select按钮,然后选择MainScene.tscn做为主场景。

游戏界面将会显示,但是在重力作用下,我们的Player很快就掉出了屏幕。我们需要做的是创建一个平台场景,然后在主场景里复制它。

创建平台

创建一个新场景(Scene -> New Scene),选择Custom Node,搜索StaticBody2D节点。它是一个静态的物理节点,也就是说它无法移动或做任何事情,只是检测碰撞。

  1. 重命名节点为Tile
  2. 添加一个CollisionShape2D节点为子节点
  3. 设置Shape为Rectangle
  4. 设置Extents为32, 32
  5. 从FileSystem中将Tile图片拖入场景创建一个sprite节点
  6. 重命名它为Tile
  7. 设置Position为0, 0
  8. 保存场景为Tile.tscn

回到MainScene,我们可以将tile场景拖入。然后使用Ctrl + D复制它并在周围移动它。

为了放置tiles更容易,可以打开对齐。在场景面板的左上角,点击snap按钮选中它。然后点击它边上的三个点展开snap设置框。确保只选中Use Pixel Snap。现在移动tile,它将基于像素进行移动。

现在,按下play,测试看看。

创建敌人

Area2D作为根节点创建一个新场景。使用CollisionShape2D节点创建一个子节点,将其shape设置为circle,然后将Enemy sprite拖入到场景里(确保它居中)。最后保存场景为Enemy.tscn

选择根节点的Area2D节点,创建一个新的脚本Enemy

首先,创建变量。

export var speed : int = 100
export var moveDist : int = 100
 
onready var startX : float = position.x
onready var targetX : float = position.x + moveDist

export意思是此变量将会显示在Inspector中,允许我们改变场景实例的属性值。

在**_physics_process**函数中,我们将在startX和targetX位置进行移动。move_to函数是自定义函数,我们将在后面创建。

func _physics_process (delta):
    
    # move to the "targetX" position
    position.x = move_to(position.x, targetX, speed * delta)
 
    # if we're at our target, move in the other direction
    if position.x == targetX:
        if targetX == startX:
            targetX = position.x + moveDist
        else:
            targetX = startX

move_to函数按步骤将给定值转换为目标值(移动过去)。

# moves "current" towards "to" in an increment of "step"
func move_to (current, to, step):
 
    var new = current
 
    # are we moving positive?
    if new < to:
        new += step
 
        if new > to:
            new = to
    # are we moving negative?
    else:
        new -= step
 
        if new < to:
            new = to
 
    return new

回到MainScene,将Enemy.tscn拖入场景里。注意敌人与你要移动到的位置的距离,并在Move Dist属性中进行设置。

现在可以按下play,看看enemy的动作。你可能注意到了,它移动得实在太慢了,所以,我们将Speed设置为500.

碰撞敌人

现在,player可以穿过敌人而不会发生任何事。而我们想要的是,如果我们碰到敌人,游戏将会重置。为此,我们需要处理信号。这些是当节点发生一些事情后可以被调用的事件。

Enemy场景,选择根节点Area2D节点,在Inspector里,切换到Node标签。在这里,可以看到此节点可以发出的所有信号。在body_entered信号上双击。按下回车键将在Enemy脚本里创建一个新的函数。

在此函数中,我们将检测进入body的对象是否Player,如果是,调用那节点的die函数。

# called when we collide with a physics body
func _on_Enemy_body_entered (body):
 
    if body.name == "Player":
        body.die()

现在打开Player脚本,创建die函数。

# called when we hit an enemy
func die ():
 
    get_tree().reload_current_scene()

这样,当player碰撞到enemy时,场景将会被重置。

收集硬币

我们希望player达到某个目标,所以,让我们来添加一些硬币。在创建场景前,让我们先在Player脚本中添加一些内容。

collect_coin函数将在我们碰撞到硬币时被调用,它会根据给定的参数值来增加我们的分数。

# called when we run into a coin
func collect_coin (value):
    
    score += value

接下来,创建一个新场景,根节点使用Area2d。为它添加一个CollisionShape2D节点做为子节点,shape选择circle。然后将Coin sprite拖入场景并让它居中。

在Area2D的根节点上,创建一个新的脚本Coin。它将检测是否和player产生碰撞,如果是则调用collect_coin函数。

我们从硬币唯一的变量value开始。

export var value = 1

_process函数是Godot的内置函数,它会在每一帧被调用。我们将使用它来制作硬币旋转。

func _process (delta):
 
    # rotate 90 degrees a second
    rotation_degrees += 90 * delta

接下来,选择Area2D节点,在Inspector中选择Node标签,双击body_entered信号在脚本中添加函数。

回到脚本,在函数中填上检测进入body的对象是否是player,如果是,调用collect_coin函数,销毁硬币。

# called when something collides with us
func _on_Coin_body_entered (body):
 
    # was it the player?
    if body.name == "Player":
        body.collect_coin(value)
        queue_free()

在主场景中,现在可以拖入硬币来填充关卡了。

追踪摄像机

目前,我们的摄像机是静态的,因此让我们实现水平移动摄像机的功能。

摄像机是我们现在要创建的新节点,游戏将根据节点和像素的位置来渲染,而不是摄像机的位置。因此,在MainScene中,右键点击父节点创建一个新的子节点Camera2D

  • 勾选Current,摄像机将被激活
  • 移动摄像机覆盖整个关卡

接着在摄像机节点添加一个新脚本CameraController

首先,需要创建一个变量来存储player的引用,get_node会根据指定的路径来搜索节点。

onready var player = get_node("/root/MainScene/Player")

然后在**_process**函数里(每帧都会被调用),设置x position和player一样。

# tracks the player along the X axis
func _process (delta):
 
    position.x = player.position.x

现在,当我们按下play,可以看到摄像机跟随着player的x position。但它有点怪异。选中摄像机,在Inspector里,设置Drag Margins为0, 0, 0, 0。这意味着我们无法自动移动,摄像机将会始终跟踪我们的x position。

按下play,可以看到它们已经正常工作了。

UI

我们的游戏已经完成了许多内容,现在需要添加一些用户界面来展示我们当前的分数。

首先,使用control节点(User Interface)创建一个root节点。

  1. 重命名节点为UI
  2. 保存场景

这时可以看到一个有颜色的矩形。这是control节点的边界,我们的UI元素将会构建在里面。

此矩形覆盖在整个屏幕上,让我们添加两个元素。

  • 一个硬币图标
  • 一个文本框用于显示当前分数

让我们从图标开始。创建一个新的子节点,类型为TextureRect。这是一个control节点,用于显示图像。在我们的FileSystem里,找到硬币图片,将它拖入Inspector的Texture域里。

Position设置为20, 20。

接下来处理文本,创建一个新的子节点,类型为Label。它是一个control节点,用于显示文本。

  1. 重命名为ScoreText
  2. 设置Position为90, 20
  3. 设置Size为100, 64

这时,我们还看不到我们的文本,因为还需要一个字体。这里有一个默认字体,但局限性太大,所以,我们将使用自己的字体。

在FileSystem里,打开字体文件夹,右键点击字体文件,选择New Resource...,找到DynamicFont,创建它。

选择我们新的资源,进入Inspector,将.ttf字体文件拖到Font Data属性里。

在Settings标签下,设置Size为40。

选择ScoreText,在Custom Fonts标签下,将我们新的dynamic font资源拖进去。

现在可以在Text属性里输入一个占位符数字,设置Valign属性为Center,让文字垂直居中。

这样,我们完成UI设置。打开MainScene,将UI场景拖入场景中,将它显示出来。

如果按下play,你可以看到,它没有跟随摄像机移动。要修正这个问题,我们需要创建一个新的节点叫做CanvasLayer,并将UI节点变成它的子节点。该节点定义哪些内容渲染为UI。

编写UI脚本

剩下要做的就是将UI连接到脚本。在UI场景里,创建一个新的脚本叫UI,并将它附到父节点上。

首先,需要创建一个变量,在游戏开始的时候获取score文本节点。

onready var scoreText = get_node("ScoreText")

然后在唯一的函数里,设置score text的值为参数的值。

func set_score_text (score):
 
    scoreText.text = str(score)

Player脚本里,创建一个UI节点的引用变量。

onready var ui = get_node("/root/MainScene/CanvasLayer/UI")

接着来到collect_coin函数,添加一行代码设置UI text。

ui.set_score_text(score)

按下play,现在当我们收集一个硬币时,score text将更新。我们还要进行一些设置,以便游戏开始时显示设置的文本。在UI脚本里...

func _ready ():
 
    scoreText.text = "0"

现在,当游戏开始时,score text将显示为0。

结论

如果你在Godot项目里点击Play,一切都是正常的。恭喜你完成了第一个游戏!

现在,我们有了一个具有功能的2D平台游戏,它具有可玩的角色,硬币,UI和一个危险的敌人来阻碍我们前进。你可以改进此项目添加更多的挑战,但毫无疑问,这里讲解的基础将是你进一步提高游戏开发技能的有用起点。天空确实有极限,你可以根据需要扩展以创建具有这些基本原理的新游戏项目。

无论怎样,你都迈出了关键的第一步,创建了第一个游戏!我们祝愿你好运,我们已经迫不急待的想看到你制作的各种游戏了!