Godot4 你的第一个 3D 游戏 上集

系列文章目录

Godot4 Godot 4引擎调试以及游戏开发-前言
Godot4 你的第一个2d游戏



本文主要还是对官方文档的补充,方便的话还是去看官方文档,但是我这里面有一定自己的注释和英文翻译,可能一起看会好一些
再提一下,如果有问题最好开着chatgpt进行问答,方便你排故,老手就不用了

你的第一个 3D 游戏

Godot 创建你的第一个完整 3D 游戏。在本系列最后,你会完成属于自己的简单项目,类似下面的 GIF 动画。动画门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。
在这里插入图片描述


我们要编写的游戏和 你的第一个 2D 游戏 差不多,但是:你可以跳起来了,并且目标是把敌人踩扁。这样,你既可以复习之前教程中学到的内容,也可以以此为基础编写新的代码和特性。
你将学到:

使用 3D 坐标和跳跃机制。
使用运动学实体移动 3D 角色,检测何时何地发生了碰撞。
使用物理层和节点组,检测特定实体之间的交互。
编写基础的程序玩法,按照固定的时间间隔实例化怪物。
设计移动动画,在运行时改变播放速度。
在 3D 游戏中绘制用户界面。

我们将首先为玩家的移动制作一个基本的原型。然后,添加我们将在屏幕周围随机生成的怪物。之后,我们将实现跳跃和压扁机制,然后用一些漂亮的动画来完善游戏。我们将以分数和重玩屏幕结束。

一、设置游戏区域

在第一部分中,我们将设置游戏区域。让我们从导入初始资产、设置游戏场景入手。

我们为这个教程准备了一个带有 3D 模型和音效的 Godot 项目,链接在索引页。如果你还没有下载,可以下载这个压缩包:Squash the Creeps 资产

下载完成之后,请将 .zip 压缩包解压到你的电脑上。打开 Godot 项目管理器,然后点击导入按钮。
在这里插入图片描述
请在导入弹出框中输入刚才新建的目录 squash_the_creeps_start/ 的完整路径。你也可以点击右侧的浏览按钮,打开文件浏览器并找到该文件夹所包含的 project.godot 文件。
在这里插入图片描述
点击导入并编辑在编辑器中打开该项目。
在这里插入图片描述
起始项目中包含一个图标和两个文件夹:art/ 和 fonts/。你可以在里面找到游戏中我们会用到的艺术资产和音乐。
在这里插入图片描述
里面有两个 3D 模型,player.glb 和 mob.glb,一些模型使用的材质,以及一首音乐。

设置游玩区域
我们将以普通的 Node 作为其根创建主场景。在场景面板中,单击左上角由“+”图标表示的添加子节点按钮,然后双击 Node。将节点命名为 Main。另一种重命名节点的方法是在节点上单击右键,然后选择重命名(或者按 F2)。另一种将节点添加到场景中的方法是按 Ctrl + a )这个和2d的添加节点一样
在这里插入图片描述
按 Ctrl + s 将场景保存为 main.tscn

我们先添加一个地板,以防止角色掉落。要创建地板、墙壁或天花板等静态碰撞器,可以使用 StaticBody3D 节点。它们需要 CollisionShape3D 子节点来定义碰撞区域。选择 Main 节点后,添加 StaticBody3D 节点,然后添加 CollisionShape3D。将 StaticBody3D 重命名为 Ground 。

在这里插入图片描述
你的场景树应该看上去像这样
在这里插入图片描述
在 CollisionShape3D 旁边会出现一个警告标志,因为我们还没有定义它的形状。如果你点击这个图标,就会弹出一个窗口,为你提供更多信息。(这个也和2d类似,基本上所有节点都需要碰撞体积)
在这里插入图片描述
要创建形状,请选中 CollisionShape3D,转到检查器,然后单击 Shape(形状)属性旁边的 <空> 字段。创建一个新的 BoxShape3D。

在这里插入图片描述
盒子形状非常适合平坦的地面和墙壁。它的厚度使它能够可靠地阻挡甚至快速移动的物体。

盒子的线框会在视口中出现三个橙色的小点。你可以点击并拖动这些点来交互地编辑形状的范围。我们也可以在检查器中精确设置尺寸。点击 BoxShape3D 来展开资源。将它的 Size 设置为 X 轴上的 60,Y 轴的 2 和 Z 轴上的 60。

在这里插入图片描述
碰撞形状是不可见的。我们需要添加一个与之配套的视觉层。选择 Ground 节点并添加一个 MeshInstance3D 作为其子节点。

在这里插入图片描述
在检查器中,点击 Mesh 旁边的字段,创建一个 BoxMesh 资源,创建一个可见的立方体。
在这里插入图片描述
再次设置大小,对于默认值来说它有点太小了。点击立方体图标展开资源,并将其 Size 设置为 60、2、60。由于立方体资源使用的是大小(size)而不是范围(extents),我们需要使用这些值,以便它与我们的碰撞形状相匹配。
在这里插入图片描述
你应该会在视口中看到一个覆盖网格以及蓝色和红色轴的宽灰色平板。

我们要把地面往下移一点,这样才能看到地板网格。选中 Ground 节点,按住 Ctrl键启用网格吸附,然后单击并在 Y 轴上向下拖动。也就是移动小工具里的绿色箭头

为了有一个可见的编辑器栅格,可以将地面往下移动 1 米。视口左下角的标签会显示你将该节点平移了多远。

最终,Ground 的 transform.position.y 应当是 -1
在这里插入图片描述
现在来添加一个平行光,从而让我们的整个场景不全都是灰色的。选择 Main 节点,然后添加一个子节点 DirectionalLight3D。
在这里插入图片描述
我们需要移动并旋转 DirectionalLight3D 节点。通过单击并拖动操纵器的绿色箭头将其向上移动,然后单击并拖动红色弧线以围绕 X 轴旋转它,直到地面被照亮。

在检查器中,勾选复选框打开Shadow -> Enabled。
在这里插入图片描述
项目此时看起来是这个样子。
在这里插入图片描述
这就是我们的起点了。在下一部分中,我们将处理玩家场景与基础移动。

总结:基本上就是一个光照和一个地面

二、Player 场景与输入事件

在接下来,我们将会设计玩家场景、注册自定义输入动作、编写玩家移动代码。在最后,你将会得到一个可以八方向移动的可游玩角色。

在左上角的场景菜单中单击新建场景来创建一个新场景。
在这里插入图片描述
创建一个 CharacterBody3D 节点来当根节点
在这里插入图片描述

将 CharacterBody3D 命名为Player。角色身体(Character body)对应的是 2D 游戏教程中的区域(Area)和刚体(Rigid Body)。与刚体类似,它可以移动并与环境发生碰撞,但它的运动并不是由物理引擎控制的,而是由支配。当我们编写跳跃和踩踏机制时,你就会看到我们是如何使用这一该节点独有的特性的。

要学习更多关于不同物理节点类型的内容,请参阅 物理介绍

现在,我们将为角色的 3D 模型创建一个基本的装备。稍后我们将在播放动画时通过代码旋转模型。

新建一个 Node3D 节点作为 Player 的子节点,并将其命名为 Pivot
在这里插入图片描述
然后在文件系统面板中,双击展开 art/ 文件夹,将 player.glb 拖放到 Pivot 节点上。
在这里插入图片描述
这样应该就会把这个模型实例化为 Pivot 的子项。你可以将其重命名为 Character 。

在这里插入图片描述
与所有类型的物理节点一样,我们的角色需要一个碰撞形状才能与环境相碰撞。再次选中 Player 节点并添加 CollisionShape3D 子节点。在检查器中,为 Shape 属性新建一个 SphereShape3D。
在这里插入图片描述
球体的线框出现在角色的下面。
在这里插入图片描述
它将是物理引擎用来与环境碰撞的形状,因此我们希望它更适合 3D 模型。通过拖动视口中的橙色点将其缩小一点。球体半径约为 0.8 米(其实可以做一些更改,更好的理解物理引擎碰撞的含义)。

然后,向上移动形状,使其底部与网格平面大致对齐。
在这里插入图片描述
你可以通过单击 Character 或 Pivot 节点旁边的眼睛图标来切换模型的可见性。(这与2d相同)
在这里插入图片描述
将场景保存为 player.tscn

节点准备就绪后,我们开始编写程序。但首先,我们需要定义一些输入动作。

创建输入动作
要移动角色,我们就要监听玩家的输入,比如按下方向键。在 Godot 中,我们能够使用代码来绑定按键,但还有一个非常强大的系统,可以让你为一系列按键和按钮设置标签。这样可以简化我们的脚本,让它们更易读。 这一部分和2d类型的第一个游戏基本相同,如果已看过文档可以跳过教程,直接从最后面那个按键映射图来添加就好

这个系统是“按键映射”。可以在项目菜单中选择项目设置来打开编辑器。

在这里插入图片描述
顶部有许多标签。点击按键映射。你可以在这个窗口顶部添加新的动作;即标签。下半部分可以为这些动作绑定按键。
在这里插入图片描述
Godot 项目针对用户界面设计提供了一些预定义的动作,我们这里可以直接使用。不过为了支持手柄,我们还是自己来定义。

我们要把这些动作命名为 move_left、move_right、move_forward、move_back、jump(向左移动、向右移动、向前移动、向后移动、跳跃)。

要添加动作,可以在顶部的框中输入名称然后按回车键。

在这里插入图片描述
创建以下五个动作:
在这里插入图片描述
要为动作绑定按键或按钮,请点击右侧的“+”按钮。对 move_left 执行此操作,按下左方向键,然后单击确定。
在这里插入图片描述
将 A 键也绑定在动作 move_left 上面。
在这里插入图片描述
现在让我们为手柄左摇杆添加支持。再次点击“+”按钮,但是这一次选择手动选择 -> 摇杆轴。
在这里插入图片描述
选择左边遥感的负 X 轴。
在这里插入图片描述
将其它值保持为默认,然后点击 确定
为其他输入动作也执行同样的操作。比如将右方向键、D、左摇杆的正轴绑定给 move_right。全部绑定完后,你的界面应该类似这样。
在这里插入图片描述
最后一个动作是设置 jump 动作。绑定空格键和手柄的 A 键。
在这里插入图片描述
你的跳跃输入动作应该看上去类似这样。
在这里插入图片描述
这些就是这个游戏所需的所有动作了。你可以使用这个菜单来对项目中的任意按键和按钮组进行标记。

在下一部分,我们将为玩家的移动进行编程和测试。

三、使用代码移动玩家

从这部分开始编写脚本代码。我们将使用先前创建的输入动作来移动角色。

右键单击 Player 节点,选择附加脚本为其添加一个新脚本。在弹出窗口中,先将模板设置为 空,后按下创建按钮 。

在这里插入图片描述
先定义类的属性。我们将定义移动速率(标量)、重力加速度,以及一个我们将用来移动角色的速度(向量)。

extends CharacterBody3D

# How fast the player moves in meters per second.
@export var speed = 14
# The downward acceleration when in the air, in meters per second squared.
@export var fall_acceleration = 75

var target_velocity = Vector3.ZERO

这是一个移动物体的常见属性。 target_velocity 是一个组合了速度和方向的 3D 向量。在这里,我们将其定义为属性,因为我们希望在帧之间更新并重用其值。

那么来编写移动的代码。首先在 _physics_process() 中使用全局 Input 对象来计算输入方向向量。

必须要注意,这些值和二维数据完全不同

因为距离以米为单位。在 2D 中,一千个单位(像素)可能只对应于屏幕宽度的一半,而在 3D 中,它是一千米。

那么来编写移动的代码。首先在 _physics_process() 中使用全局 Input 对象来计算输入方向向量。

func _physics_process(delta):
	# We create a local variable to store the input direction.
	var direction = Vector3.ZERO

	# We check for each move input and update the direction accordingly.
	if Input.is_action_pressed("move_right"):
		direction.x += 1
	if Input.is_action_pressed("move_left"):
		direction.x -= 1
	if Input.is_action_pressed("move_back"):
		# Notice how we are working with the vector's x and z axes.
		# In 3D, the XZ plane is the ground plane.
		direction.z += 1
	if Input.is_action_pressed("move_forward"):
		direction.z -= 1

在这里,我们将使用 _physics_process() 虚函数进行所有计算。与 _process() 一样,它允许你每帧更新节点,但它是专门为物理相关代码设计的,例如运动学物体或刚体。

要了解更多关于 _process() 和 _physics_process() 之间的区别,见 空闲处理与物理处理

我们首先将一个 direction 变量初始化为 Vector3.ZERO。然后,我们检查玩家是否正在按下一个或多个 move_* 输入,并相应地更新矢量的 x 和 z 分量。它们对应于地平面的轴。

这四个条件给了我们八个可能性和八个可能的方向。

如果玩家同时按下 W 键 和 D 键,这个向量长度大约为 1.4。但如果他们只按一个键,则它的长度将为 1。我们希望该向量的长度保持一致,而不是在对角线上移动得更快。为此,我们需调用其 normalize() 方法。

#func _physics_process(delta):
	#...

	if direction != Vector3.ZERO:
		direction = direction.normalized()
		# Setting the basis property will affect the rotation of the node.
		$Pivot.basis = Basis.looking_at(direction)

在这里,我们只在方向的长度大于零的情况下对向量进行归一化,因为玩家正在按某个方向键。

静态体和刚体可以配置为使用PhysicsMaterial。这允许调整对象的摩擦力和弹跳,并设置其是否吸收和/或粗糙。

然后,更新速度。需要分别计算地面速度和下降速度。请确保 tab 缩进,使行在 _physics_process() 函数内部,而不在刚编写的条件外部。

func _physics_process(delta):
	#...
	if direction != Vector3.ZERO:
		#...

	# Ground Velocity
	target_velocity.x = direction.x * speed
	target_velocity.z = direction.z * speed

	# Vertical Velocity
	if not is_on_floor(): # If in the air, fall towards the floor. Literally gravity
		target_velocity.y = target_velocity.y - (fall_acceleration * delta)

	# Moving the Character
	velocity = target_velocity
	move_and_slide()

如果物体在这一帧中与地板发生了碰撞,那么 CharacterBody3D.is_on_floor() 函数就会返回 true。这就是为什么我们只在空中对 Player 施加重力。

对于垂直速度,在每一帧中减去下降加速度乘以增量时间(delta time,每个帧之间的时间,也称帧时间)。这条代码将使角色在没有在地板上或是碰撞地板的情况下,每帧都会下降。

物理引擎只有在运动和碰撞发生的情况下才能检测到在某一帧中与墙壁、地板或其他物体的相互作用。我们将在后面使用这个属性来编写跳跃的代码。

在最后一行,我们调用了 CharacterBody3D.move_and_slide(),这是 CharacterBody3D 类的一个强大方法,可以让你顺利地移动一个角色。如果它在运动过程中撞到了墙,引擎会试着为你把它进行平滑处理。它使用的是 CharacterBody3D 自带的速度值

这就是你在地面上移动角色所需的所有代码。

下面是供参考的完整 Player.gd 代码。

extends CharacterBody3D

# How fast the player moves in meters per second.
@export var speed = 14
# The downward acceleration when in the air, in meters per second squared.
@export var fall_acceleration = 75

var target_velocity = Vector3.ZERO


func _physics_process(delta):
	var direction = Vector3.ZERO

	if Input.is_action_pressed("move_right"):
		direction.x += 1
	if Input.is_action_pressed("move_left"):
		direction.x -= 1
	if Input.is_action_pressed("move_back"):
		direction.z += 1
	if Input.is_action_pressed("move_forward"):
		direction.z -= 1

	if direction != Vector3.ZERO:
		direction = direction.normalized()
		$Pivot.basis = Basis.looking_at(direction)

	# Ground Velocity
	target_velocity.x = direction.x * speed
	target_velocity.z = direction.z * speed

	# Vertical Velocity
	if not is_on_floor(): # If in the air, fall towards the floor. Literally gravity
		target_velocity.y = target_velocity.y - (fall_acceleration * delta)

	# Moving the Character
	velocity = target_velocity
	move_and_slide()

测试玩家的移动
将玩家放在 Main 场景中进行测试,这时,需要先实例化玩家,然后添加相机。 3D 与 2D 不同,如果没有添加摄像机,你将无法看到任何物体。

保存 Player 场景,然后打开 Main 场景。可以点击编辑器顶部的 Main 选项卡切换。
在这里插入图片描述
在这里插入图片描述
如果场景之前已关闭,请转到 文件系统 面板,双击 main.tscn 文件重新打开。

要实例化 Player ,可右键单击 Main 节点,然后选择 实例化子场景 。
在这里插入图片描述
在弹出窗口中,双击 player.tscn ,角色将显示在视窗的中心。

添加摄像机
接下来我们来添加摄像机。和 Player 的 Pivot 类似,我们要创建一个基本的架构。再次右键单击 Main 节点,这次选择添加子节点。新建一个 Marker3D,命名为 CameraPivot,然后添加一个 Camera3D 节点作为其子项。你的场景树应该看起来像这样。
在这里插入图片描述
请注意在选中 Camera 时,左上角会出现一个预览复选框。你可以单击预览游戏中的摄像机投影视角。
在这里插入图片描述
我们要使用 Pivot 来旋转摄像机,让他像被吊车吊起来一样。让我们先拆分 3D 视图,以便在进行自由移动的同时观察摄像机拍摄到的内容。

在视口上方的工具栏中,单击视图,然后单击2 个视口。你也可以按 Ctrl + 2
在这里插入图片描述
在这里插入图片描述
在下面那个视图中,选中 Camera3D,然后勾选预览复选框打开摄像机预览。
在这里插入图片描述
在上面那个视图中,将摄像机沿 Z 轴(蓝色)移动 19 个单位。
在这里插入图片描述
接下来就是关键。选中 CameraPivot 并将其围绕 X 周旋转 -45 度(使用红色的圆圈)。你会看到摄像机就像是被连上了吊车一样移动。

在这里插入图片描述
你可以按 F6 运行场景,然后按方向键来移动角色。

因为透视投影的缘故,我们会在角色的周围看到一些空白区域。在这个游戏中,我们要使用的是正交投影,从而更好地展示游戏区域,让玩家更易于识别距离。

再次选中 Camera,然后在检查器 中将 Projection(投影)设为 Orthogonal(正交)、将 Size(大小)设为 19。角色现在看起来应该更加扁平,背景应该被地面充满。

测试你的场景,你应该能够在所有 8 个方向上移动,并且不会在地板上出现故障!

这样,我们就完成了玩家的移动以及视图。接下来,我们要来处理怪物。

四、设计小怪场景

在这一部分中,我们要为怪物编写代码,我们后续会称之为“mob”(小怪)。在下一节课中,我们会在游戏区域周围随机生成它们。

让我们在一个新场景中设计这些怪物。节点结构和 player.tscn 场景类似。

还是用 CharacterBody3D 节点作为根节点来创建场景。命名为 Mob。添加一个 Node3D 节点作为其子项,将其命名为 Pivot。将 mob.glb 文件从文件系统面板拖放到 Pivot 上,这样就把怪物的 3D 模型添加到了场景之中。
在这里插入图片描述
你可以将新创建的 mob 节点重命名成 Character。
在这里插入图片描述
我们的实体要添加碰撞形状后才能正常工作。右键单击场景的根节点 Mob,然后单击添加子节点。
在这里插入图片描述
添加一个 CollisionShape3D。
在这里插入图片描述
在检查器中为 Shape(形状)属性分配一个 BoxShape3D。

在这里插入图片描述
我们要调整一下它的大小,来更好地框住 3D 模型。可以单击并拖动橙色的小点来进行。

碰撞盒应该接触地面,并且比模型稍微瘦一点点。即便玩家的球体只接触了这个碰撞盒的角落,物理引擎也会判定发生了碰撞。如果盒子比 3D 模型要大一点,你可能距离怪物还有一定的距离就死了,玩家就会觉得不公平。

在这里插入图片描述
请注意,我的盒子要比怪物稍高。在这个游戏里是没问题的,因为我们是从游戏场景的上方用固定角度观察的。碰撞形状不必精确匹配模型。决定碰撞形状形式和大小的关键是你在试玩游戏时的手感。

移除离屏的怪物

我们要在游戏关卡中按照一定的时间间隔刷怪。如果你不小心,它们的数量可能就会无限地增长下去,我们可不想那样。每个小怪实例都需要付出一定的内存和处理代价,我们不希望让屏幕之外的小怪浪费资源。

怪物离开屏幕之后,我们就不再需要它了,所以我们可以把它删除。Godot 有一个可以检测对象离开屏幕的节点, VisibleOnScreenNotifier3D ,我们就要用它来销毁我们的小怪。

备注
选中 Mob 节点,并为其添加一个 VisibleOnScreenNotifier3D 作为子项。这回出现的就是一个粉色的框。这个框完全离开屏幕后,该节点就会发出信号。
在这里插入图片描述使用橙色的点来调整大小,让它覆盖住整个 3D 模型。
在这里插入图片描述

为小怪的移动编写代码
让我们来实现怪物的运动。我们要分两步来实现。首先,我们要为 Mob 编写脚本,定义初始化怪物的函数。然后我们会在 main.tscn 场景中编写随机刷怪的机制并进行调用。

为 Mob 附加脚本。
在这里插入图片描述
这是最初的移动代码。我们定义了两个属性 min_speed 和 max_speed(最小速度和最大速度)来定义随机速度的范围,后面我们会用这两个属性来定义 CharacterBody3D.velocity。
extends CharacterBody3D

# Minimum speed of the mob in meters per second.
@export var min_speed = 10
# Maximum speed of the mob in meters per second.
@export var max_speed = 18


func _physics_process(_delta):
	move_and_slide()

与玩家类似,在每一帧我们都会通过调用 CharacterBody3D.move_and_slide() 方法来移动小怪。这一回,我们不会再每帧更新 velocity 了:我们希望怪物匀速移动,然后离开屏幕,即便碰到障碍物也一样。

我们需要再定义一个函数来计算初始的速度。这个函数会让怪物面朝玩家,并将其运动角度和速度随机化。

这个函数接受小怪的生成位置 start_position 以及玩家的位置 player_position 作为参数。

我们首先将小怪定位在 start_position 并用 look_at_from_position() 方法将它转向玩家,并通过围绕 Y 轴旋转随机量来随机化角度。下面,rand_range() 输出一个介于 -PI / 4 弧度和 PI / 4 弧度的随机值。

 This function will be called from the Main scene.
func initialize(start_position, player_position):
	# We position the mob by placing it at start_position
	# and rotate it towards player_position, so it looks at the player.
	look_at_from_position(start_position, player_position, Vector3.UP)
	# Rotate this mob randomly within range of -45 and +45 degrees,
	# so that it doesn't move directly towards the player.
	rotate_y(randf_range(-PI / 4, PI / 4))

我们已经获取到了一个随机的位置,现在我们需要一个 random_speed。randi_range() 可以给我们需要的随机整数,并且我们要使用 min_speed 和 max_speed。random_speed 是一个整数,我们只是使用它与我们的 CharacterBody3D.velocity 相乘。在乘完 random_speed 之后,我们将 random_speed 旋转至朝向玩家的方向。

func initialize(start_position, player_position):
	# ...

	# We calculate a random speed (integer)
	var random_speed = randi_range(min_speed, max_speed)
	# We calculate a forward velocity that represents the speed.
	velocity = Vector3.FORWARD * random_speed
	# We then rotate the velocity vector based on the mob's Y rotation
	# in order to move in the direction the mob is looking.
	velocity = velocity.rotated(Vector3.UP, rotation.y)

离开屏幕
我们还需要在小怪离开屏幕后将其销毁。实现方法是将 VisibleOnScreenNotifier3D 节点的 screen_exited 信号连接到 Mob 上。

单击编辑器顶部的 3D 标签回到 3D 视口。你也可以按 Ctrl + F2(macOS 上则是 Alt + 2)。
在这里插入图片描述

选中 VisibleOnScreenNotifier3D 节点,然后在界面右侧打开节点面板。双击 screen_exited() 信号。
在这里插入图片描述
将信号连接到 Mob
在这里插入图片描述

这样你就会被带回到脚本编辑器,并且帮你添加了一个新的函数 _on_visible_on_screen_notifier_3d_screen_exited()。请在里面调用 queue_free() 方法。这个函数会将调用它的实例销毁。

func _on_visible_on_screen_notifier_3d_screen_exited():
	queue_free()

我们的怪物已经准备好进入游戏了!在下一部分,你将在游戏关卡中生成怪物。

这是仅供参考的完整 Mob.gd 脚本。

extends CharacterBody3D

Minimum speed of the mob in meters per second.
@export var min_speed = 10
Maximum speed of the mob in meters per second.
@export var max_speed = 18

func _physics_process(_delta):
	move_and_slide()

 This function will be called from the Main scene.


func initialize(start_position, player_position):
	# We position the mob by placing it at start_position
	# and rotate it towards player_position, so it looks at the player.
	look_at_from_position(start_position, player_position, Vector3.UP)
	# Rotate this mob randomly within range of -90 and +90 degrees,
	# so that it doesn't move directly towards the player.
	rotate_y(randf_range(-PI / 4, PI / 4))

	# We calculate a random speed (integer)
	var random_speed = randi_range(min_speed, max_speed)
	# We calculate a forward velocity that represents the speed.
	velocity = Vector3.FORWARD * random_speed
	# We then rotate the velocity vector based on the mob's Y rotation
	# in order to move in the direction the mob is looking.
	velocity = velocity.rotated(Vector3.UP, rotation.y)

func _on_visible_on_screen_notifier_3d_screen_exited():
	queue_free()

总结

物理介绍:

在游戏开发中, 你经常需要知道游戏中的两个对象在何时相交或接触. 这被称为 碰撞检测 . 检测到碰撞时, 你通常希望某些事情发生. 这被称为 碰撞响应 .

Godot在2D和3D中提供了许多碰撞对象, 以提供碰撞检测和响应. 你可能很难决定哪个适合你的项目. 一旦了解了每种方法的工作原理以及它们的优缺点, 你就可以避免出现问题并简化开发过程.

Godot的四种碰撞对象类型

每个碰撞对象的工作原理

碰撞物体
Godot提供了四种碰撞对象,它们都扩展自CollisionObject2D。以下是列出的最后三种是物理体,并额外扩展自PhysicsBody2D。

Area2D
Area2D 节点提供 检测 和 影响 . 它们可以检测物体何时重叠, 并在物体进入或离开时发出信号. Area2D 也可用于覆盖物理属性, 例如一定区域内的重力或阻尼.

StaticBody2D
静态主体是物理引擎不移动的主体. 它参与碰撞检测, 但不会响应碰撞而移动. 它们通常用于属于环境的对象或不需要任何动态行为的对象.

RigidBody2D
这是实现模拟2D物理的节点. 你不直接控制 RigidBody2D , 而是你对它施加力(重力, 冲动等), 物理引擎计算得到的运动. 阅读更多关于使用刚体的信息.

CharacterBody2D
提供碰撞检测的物体, 但没有物理特性. 所有移动和碰撞响应必须在代码中实现.

物理材质
静态体和刚体可以配置为使用PhysicsMaterial。这允许调整对象的摩擦力和弹跳,并设置其是否吸收和/或粗糙。

碰撞形状
物理体可以包含任意数量的 Shape2D 对象作为子对象. 这些形状用于定义对象的碰撞边界并检测与其他对象的接触.

为了检测碰撞, 必须至少为对象分配一个 Shape2D .

分配形状的最常用方法是添加 CollisionShape2D 或 CollisionPolygon2D 作为对象的子项. 这些节点允许你直接在编辑器工作区中绘制形状.

空闲处理与物理处理

游戏是通过循环来运行的,每一帧都需要先更新游戏世界的状态,然后再把它绘制到屏幕上。Godot 为 Node 类提供了两个虚方法来完成帧循环处理:Node._process() 和 Node._physics_process()。如果你在脚本中定义了这两个函数的其中之一,或者两者都定义了,引擎就会自动进行调用这个(这些)虚函数。

可以使用两种帧循环处理方式:

空闲处理(Idle processing)可以用来执行每帧更新节点的代码,执行频率会尽可能地快。

物理处理(Physics processing)的执行频率是固定的,默认为每秒 60 次。物理处理和游戏的实际帧率无关,可以让物理平滑执行,故一切与物理引擎相关的行为都应该用物理处理帧循环函数来进行处理,如移动可能会与环境相碰撞的实体。

在脚本中定义 _process() 方法就会激活空闲处理。可以通过调用 Node.set_process() 来对空闲处理的启用状态进行控制。

引擎每需要绘制一帧画面,就会调用一次该方法:

  • 12
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

超级馒头神

看看谁给我第一个打赏。太感谢您

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值