让我们通过制作 RPG 来学习 Godot 4 — 第 8 部分:添加弹药拾取和消耗品
如果您已成功完成本系列教程的这一部分,那么您应该已经设置好了基本地图、可移动角色和游戏 GUI。在本节中,我们将重点介绍如何将拾取物添加到我们的游戏中。这些拾取物将包括健康和耐力消耗品以及弹药。当玩家拾取这些物品时,我们还将更新我们的 GUI 值。
您将在本部分中学习到的内容:
· 如何使用枚举。
· 如何使用@tool对象。
· 如何在编辑器中执行代码。
· 如何通过内存队列从场景中删除节点。
· 如何使用 Area2D 检测体。
· 如何引用 TileMap 属性。
拾取物品设置
我们不会为每种消耗品创建单独的拾取物品(即弹药拾取、体力拾取和健康拾取的场景),而是为我们的拾取物品创建一个单一的基础场景,以便在整个游戏中重复使用。我们将通过枚举和导出资源来实现这一点。
在您的项目中,创建一个以Area2D节点为根的新场景。Area2D 节点用作检测碰撞的区域,换句话说,我们将使用此区域来检测我们的 Player 场景是否通过碰撞进入其主体。如果玩家已经撞到了 Pickup 场景的碰撞主体,则应移除 Pickup 并更新 UI 值。
将此 Area2D 节点重命名为“Pickup”,并将场景保存在 Scenes 文件夹下。
您会注意到该节点旁边有一个警告,因为就像我们的 CharacterBody2D 节点一样,它需要形状或碰撞。让我们继续向其添加 CollisionShape2D 节点来解决此警告。
在选择碰撞形状之前,我们先给它一个 Sprite,这样我们就可以在给它一个碰撞边界之前看到它是什么图像。添加一个 Sprite2D 节点并从 Assets/Icons 文件夹中为其分配任何图标。
现在,让我们为 CollisionShape2D 节点分配一个形状。将其设为新的 RectangleShape2D,然后拖动碰撞形状的边界以框住您的图标。
此 Pickup 场景将作为我们所有拾取物的基础场景。从这里我们希望能够动态选择拾取物类型,并在玩家跑过拾取物时将其添加到玩家的库存中。为此,我们需要将脚本附加到 Pickup 场景的根节点。将其保存在您的 Scripts 文件夹下。
在脚本顶部,让我们创建一个枚举,包含所有拾取物品(弹药、生命值和耐力饮料)。我们将导出此枚举,以便我们可以从检查器面板/编辑器中动态选择要放置的拾取物品类型。枚举是常量的简写,如果您想将连续的整数分配给某个常量,枚举非常有用。要创建枚举,我们使用关键字 enum,后跟括号中的常量。
### Pickup.gd
extends Area2D
# Pickups to choose from
enum Pickups { AMMO, STAMINA, HEALTH }
@export var item : Pickups
现在,如果我们查看检查器面板,我们可以从下拉菜单中选择 Pickup 项。
接下来,我们还要选择它的图标或纹理,因为并非所有拾取物品看起来都像钻石!为此,我们将使用一些新东西,以便我们可以在编辑器中看到这些纹理的实时变化。我们将使用@tool,这是一行功能强大的代码,当将其添加到脚本顶部时,它会在编辑器中执行。您还可以决定脚本的哪些部分在编辑器中执行,哪些部分在游戏中执行,哪些部分在两者中执行。
您需要在节点类扩展之前将@tool添加到脚本的顶部。
### Pickup.gd
@tool
extends Area2D
# Pickups to choose from
enum Pickups { AMMO, STAMINA, HEALTH }
@export var item : Pickups
然后我们需要分配纹理,因此对于每个 Pickups,我们将预加载纹理。预加载是指在场景播放/运行之前加载资源。我们预加载资源以加快加载速度并减少冻结,因为资源已经加载。让我们将用于 GUI 图标的纹理分配给每个 Pickups 项目。
### Pickup.gd
@tool
extends Area2D
# Pickups to choose from
enum Pickups { AMMO, STAMINA, HEALTH }
@export var item : Pickups
# Texture assets/resources
var ammo_texture = preload("res://Assets/Icons/shard_01i.png")
var stamina_texture = preload("res://Assets/Icons/potion_02b.png")
var health_texture = preload("res://Assets/Icons/potion_02c.png")
由于我们希望在分配新的 Pickups 时不断检查这些精灵帧是否发生变化,因此我们希望在process()函数中添加条件检查以更改这些纹理。我们在实现自定义信号时曾使用过此函数。
我们希望在编辑器中执行条件,以便在无需运行游戏的情况下在编辑器中查看纹理变化。为此,我们将使用 @tool 功能,该功能需要Engine.is_editor_hint()函数在编辑器中运行代码。我们还将创建指向 Node2D 节点的节点引用。
### Pickup.gd
# Node refs
@onready var sprite = $Sprite2D
#older code
# ----------------------- Icon --------------------------------------
#allow us to change the icon in the editor
func _process(_delta):
#executes the code in the editor without running the game
if Engine.is_editor_hint():
#if we choose x item from Inspector dropdown, change the texture
if item == Pickups.AMMO:
sprite.set_texture(ammo_texture)
elif item == Pickups.HEALTH:
sprite.set_texture(health_texture)
elif item == Pickups.STAMINA:
sprite.set_texture(stamina_texture)
现在如果你在主场景中实例化你的 Pickup 场景,并且在 Inspector 面板中更改你的 Pickup 项目,你的纹理就会改变!
我们还必须在游戏中反映这些设置,因此,我们将在ready()函数中设置纹理。
### Pickup.gd
# Node refs
@onready var sprite = $Sprite2D
#older code
# ----------------------- Icon --------------------------------------
func _ready():
#executes the code in the game
if not Engine.is_editor_hint():
#if we choose x item from Inspector dropdown, change the texture
if item == Pickups.AMMO:
sprite.set_texture(ammo_texture)
elif item == Pickups.HEALTH:
sprite.set_texture(health_texture)
elif item == Pickups.STAMINA:
sprite.set_texture(stamina_texture)
如果您在主场景中实例化多个 Pickup 场景并更改其项目值,然后运行场景,您现在将看到游戏中的纹理也发生了变化。
使用物品拾取
如果我们的玩家碰到这些拾取物,我们希望将它们从场景中移除并添加到玩家的库存中。我们将不得不再次使用一些自定义信号和函数来实现这一点,但首先,让我们专注于在玩家与场景发生碰撞时从场景中移除物品。
Area2D 节点带有一个名为body_entered()的内置信号,当定义的物体进入此区域时会发出该信号。此定义的物体是任何添加了碰撞形状的物体,例如来自 Player 场景的 CharacterBody2D。
将此信号连接到您的 Pickup 脚本。您将看到它在脚本末尾创建了一个新的“func _on_body_entered(body):”函数。
在这个新的信号函数中,我们要检查进入我们区域的主体是否名为“Player”,如果是,我们的 Pickup 场景应该从主场景树中删除自身。当节点是场景树的一部分时,可以通过调用.get_tree()方法获取树。要从场景树中删除节点,我们可以使用queue_free() 或queue_delete()。
### Pickup.gd
#older code
# -------------------- Using Pickups -------------------
func _on_body_entered(body):
if body.name == "Player":
#todo: adding function will come here
#delete from scene tree
get_tree().queue_delete(self)
如果您现在运行场景并且让玩家浏览您的拾取物品,则它应该从场景中完全移除。
您将在我的代码示例中看到我添加了一行“#todo:添加函数将在此处出现”。我们首先需要创建从 Player 脚本将这些物品添加到我们玩家库存中的函数,然后在 Pickup 脚本中引用它。我们的玩家库存将不再是我们可以添加许多物品和对象的传统库存,而更像是一个只能通过我们预先存在的 GUI 显示的隐形库存。换句话说,我们的库存只能包含 Pickup 物品。
在您的 Player 脚本中,让我们定义更多信号来更新拾取物品的值。每当我们移除或添加拾取物品时,这些信号就会发出。
### Player.gd
# older code
# Custom signals
signal health_updated
signal stamina_updated
signal ammo_pickups_updated
signal health_pickups_updated
signal stamina_pickups_updated
我们还需要再次定义我们的 Pickups 枚举,以便我们可以使用相同的常量。
### Player.gd
# older code
# Custom signals
signal health_updated
signal stamina_updated
signal ammo_pickups_updated
signal health_pickups_updated
signal stamina_pickups_updated
# Pickups
enum Pickups { AMMO, STAMINA, HEALTH }
接下来,我们需要为每个拾取物品定义一个新变量,以便我们可以存储它们的数量。
### Player.gd
# older code
# Pickups
enum Pickups { AMMO, STAMINA, HEALTH }
var ammo_pickup = 0
var health_pickup = 0
var stamina_pickup = 0
最后,我们可以继续创建自定义函数,将拾取物添加到玩家的库存中(屏幕左上角的 GUI)。我们将在 Pickups 脚本中调用此函数,这样我们就可以将item变量传递到参数中,因为这是我们在 Inspector 面板中选择的。
我们将执行一个条件来检查已拾取哪个物品,然后将 x 个该物品添加到我们的玩家。我们还需要发出信号来通知游戏我们的变量值已更改。
### Player.gd
# older code
# ---------------------- Consumables --------------------
# Add the pickup to our GUI-based inventory
func add_pickup(item):
if item == Pickups.AMMO:
ammo_pickup = ammo_pickup + 3 # + 3 bullets
ammo_pickups_updated.emit(ammo_pickup)
print("ammo val:" + str(ammo_pickup))
if item == Pickups.HEALTH:
health_pickup = health_pickup + 1 # + 1 health drink
health_pickups_updated.emit(health_pickup)
print("health val:" + str(health_pickup))
if item == Pickups.STAMINA:
stamina_pickup = stamina_pickup + 1 # + 1 stamina drink
stamina_pickups_updated.emit(stamina_pickup)
print("stamina val:" + str(stamina_pickup))
现在我们可以回到我们的 Pickup 脚本并通过 body 方法引用此函数来添加该项目。
### Pickup.gd
# older code
# -------------------- Using Pickups -------------------
func _on_body_entered(body):
if body.name == "Player":
body.add_pickup(item)
#delete from scene tree
get_tree().queue_delete(self)
现在,我们可以运行我们的场景,看看当我们的玩家运行我们的 Pickup 物品时这是否有效。
我们的拾取物品现在已添加到玩家的库存中,但我们尚未看到 GUI 中的变化。我们现在将添加它,以便我们的更改可以反映在游戏中而不是控制台中。在您的 Player 场景中,将一个名为 AmmoAmount、HealthAmount 和 StaminaAmount 的脚本附加到每个 UI 节点。将它们保存在您的 GUI 文件夹下。
### AmmoAmount.gd
extends ColorRect
# Node ref
@onready var value = $Value
# Update ui
func update_ammo_pickup_ui(ammo_pickup):
value.text = str(ammo_pickup)
### HealthAmount.gd
extends ColorRect
# Node ref
@onready var value = $Value
# Update ui
func update_health_pickup_ui(health_pickup):
value.text = str(health_pickup)
### StaminaAmount.gd
extends ColorRect
# Node ref
@onready var value = $Value
# Update ui
func update_stamina_pickup_ui(stamina_pickup):
value.text = str(stamina_pickup)
### Player.gd
extends CharacterBody2D
# Node references
@onready var animation_sprite = $AnimatedSprite2D
@onready var health_bar = $UI/HealthBar
@onready var stamina_bar = $UI/StaminaBar
@onready var ammo_amount = $UI/AmmoAmount
@onready var stamina_amount = $UI/StaminaAmount
@onready var health_amount = $UI/HealthAmount
# older code
func _ready():
# Connect the signals to the UI components' functions
health_updated.connect(health_bar.update_health_ui)
stamina_updated.connect(stamina_bar.update_stamina_ui)
ammo_pickups_updated.connect(ammo_amount.update_ammo_pickup_ui)
health_pickups_updated.connect(health_amount.update_health_pickup_ui)
stamina_pickups_updated.connect(stamina_amount.update_stamina_pickup_ui)
现在,如果您运行场景,并让玩家角色穿过拾取物,您的 GUI 应该会更新。在我们添加向玩家添加拾取物的功能的同时,我们还要继续添加一些输入,以便我们的玩家可以使用我们的健康和耐力拾取物。我们稍后会更新代码,以便使用弹药拾取物或子弹进行攻击。
创建两个新的输入操作,分别称为“ui_consume_health”和“ui_consume_stamina”,并为它们分配输入键。我使用了键盘上的“1”和“2”键,但您可以分配任何您觉得合适的键。
在我们的 Player 脚本中,在我们的输入(事件)函数中,让我们为这些输入操作创建新的条件。如果我们有拾取物,并且进度条上的健康或耐力值较低,我们希望在玩家按“1”或“2”饮用健康或耐力饮料时从拾取物数量中扣除 1。
饮用这些消耗品将恢复分配的 100 分中的 50 分(请记住我们的健康和耐力变量等于 100)。在我们的健康或耐力增加并移除拾取物后,我们需要相应地发出信号。
### Player.gd
extends CharacterBody2D
# older code
func _input(event):
#input event for our attacking, i.e. our shooting
if event.is_action_pressed("ui_attack"):
#attacking/shooting anim
is_attacking = true
var animation = "attack_" + returned_direction(new_direction)
animation_sprite.play(animation)
#using health consumables
elif event.is_action_pressed("ui_consume_health"):
if health > 0 && health_pickup > 0:
health_pickup = health_pickup - 1
health = min(health + 50, max_health)
health_updated.emit(health, max_health)
health_pickups_updated.emit(health_pickup)
#using stamina consumables
elif event.is_action_pressed("ui_consume_stamina"):
if stamina > 0 && stamina_pickup > 0:
stamina_pickup = stamina_pickup - 1
stamina = min(stamina + 50, max_stamina)
stamina_updated.emit(stamina, max_stamina)
stamina_pickups_updated.emit(stamina_pickup)
为了让我们的 UI 节点在生成时显示正确的值(例如,假设我们想在玩家生成时为玩家提供 5 发弹药而不是 0 发),我们必须在 UI 脚本中加载时更新我们的值节点。
### AmmoAmount.gd
extends ColorRect
# Node ref
@onready var value = $Value
@onready var player = $"../.."
# Show correct value on load
func _ready():
value.text = str(player.ammo_pickup)
# Update ui
func update_ammo_pickup_ui(ammo_pickup):
value.text = str(ammo_pickup)
### HealthAmount.gd
extends ColorRect
# Node ref
@onready var value = $Value
@onready var player = $"../.."
# Show correct value on load
func _ready():
value.text = str(player.health_pickup)
# Update ui
func update_health_pickup_ui(health_pickup):
value.text = str(health_pickup)
### StaminaAmount.gd
extends ColorRect
# Node ref
@onready var value = $Value
@onready var player = $"../.."
# Show correct value on load
func _ready():
value.text = str(player.stamina_pickup)
# Update ui
func update_stamina_pickup_ui(stamina_pickup):
value.text = str(stamina_pickup)
### Pickup.gd
# Pickups to choose from
@export var item : Global.Pickups
# ----------------------- Icon --------------------------------------
func _ready():
#executes the code in the game
if not Engine.is_editor_hint():
#if we choose x item from Inspector dropdown, change the texture
if item == Global.Pickups.AMMO:
sprite.set_texture(ammo_texture)
elif item == Global.Pickups.HEALTH:
sprite.set_texture(health_texture)
elif item == Global.Pickups.STAMINA:
sprite.set_texture(stamina_texture)
#allow us to change the icon in the editor
func _process(_delta):
#executes the code in the editor without running the game
if Engine.is_editor_hint():
#if we choose x item from Inspector dropdown, change the texture
if item == Global.Pickups.AMMO:
sprite.set_texture(ammo_texture)
elif item == Global.Pickups.HEALTH:
sprite.set_texture(health_texture)
elif item == Global.Pickups.STAMINA:
sprite.set_texture(stamina_texture)
现在,如果您将变量更改为具有不同的值(例如 ammo_pickup = 2)并运行场景,则应该会显示正确的值。如果您拾起耐力拾取物,冲刺,然后按“2”消耗耐力拾取物,您的耐力 GUI 值应该会更新,耐力进度条也会更新!
复制物品拾取
我们不必手动放置拾取物,而是创建一个随机器,在游戏运行时,它会在地图上随机生成 x 个拾取物。为此,我们将创建一个新的 Global Autoload Singleton脚本。此脚本将包含游戏中多个场景中使用的所有变量、场景资源和方法。
什么是单例脚本?
单例脚本是一种在游戏启动时自动加载并在游戏的整个生命周期内保持可访问的脚本。这使其成为管理需要从多个场景或脚本访问的游戏范围数据、功能和系统的理想选择。
在脚本文件夹下,创建一个名为“Global.gd”的新脚本。
然后,在“项目设置”>“自动加载”菜单中,将“全局”脚本指定为新的单例。这将使存储在此脚本中的变量和函数在每个脚本中都可以访问。
现在,在您的主场景中,删除您实例化的任何 Pickups 场景。然后添加一个重命名为“SpawnedPickups”的 Node2D 节点。此节点将充当我们的 pickups 的组织者,即它将保存我们生成的所有 pickup 实例。
现在,在您新创建的 Global 脚本中,让我们预加载Pickups 场景,以便稍后实例化。通过在 Global 脚本中预加载场景,我们可以在其他场景中实例化这些场景,而无需在每个脚本中加载它们。预加载场景可减少资源加载时间和潜在的滞后。
何时使用加载,何时使用预加载?
load 和 preload 都可用于在 GDScript 中加载资源。如果您知道某个资源将在 load 时使用,并且希望在脚本编译期间加载该资源,则使用 preload。如果您希望仅在满足特定条件(例如调用函数时)时加载资源,则使用 load。
### Global.gd
extends Node
# Scene resources
@onready var pickups_scene = preload("res://Scenes/Pickup.tscn")
接下来,将新脚本附加到主场景。将此脚本保存在您的脚本文件夹下。
现在,在我们的主脚本中,让我们创建一个指向 TileMap 节点 (Map) 以及 Map 节点上的图层的节点引用。请记住,图层的 ID 从零开始,因此water = 0, grass = 1, sand = 2, foliage = n。仅添加对您拥有的图层的引用。例如,我有 exterior_1 和 exterior_2,但您可能没有。
### Main.gd
extends Node2D
# Node refs
@onready var map = $Map
# TileMap layers
const WATER_LAYER = 0
const GRASS_LAYER = 1
const SAND_LAYER = 2
const FOLIAGE_LAYER = 3
const EXTERIOR_1_LAYER = 4
const EXTERIOR_2_LAYER = 5
现在,让我们创建一个函数来检查 TileMap 上的给定位置是否是有效的生成位置。此函数将检查指定层(第一层或第二层)中的单元格类型,以确定它是否是生成拾取物的有效位置。我们将通过get_cell_source_id()方法检查指定层上的单元格类型,该方法检索有关 TileMap 特定层中特定单元格位置的图块的信息。
get_cell_source_id 和 get_cell_tile_data 有什么区别?
get_cell_source_id 和 get_cell_tile_data 都是用于查询图块地图中单元格信息的方法,但它们返回的信息类型不同。get_cell_source_id方法仅返回特定单元格的 ID,因此我们使用此方法进行简单检查,例如单元格是否为空或是否包含特定类型的图块。get_cell_tile_data方法返回有关图块的更详细信息,而不仅仅是其 ID,因此我们使用此方法获取单元格或图块的自定义数据、转换属性或状态值。
如果单元格位于沙层或草地上,我们将返回其作为有效生成位置。如果单元格位于任何其他层上,则它将不是有效生成位置。这将阻止拾取物在建筑物和水上生成。
### Main.gd
# older code
# Valid pickup spawn location
func is_valid_spawn_location(layer, position):
var cell_coords = Vector2(position.x, position.y)
# Check if there's a tile on the water, foliage, or exterior layers
if map.get_cell_source_id(WATER_LAYER, cell_coords) != -1 || map.get_cell_source_id(FOLIAGE_LAYER, cell_coords) != -1 || map.get_cell_source_id(EXTERIOR_1_LAYER, cell_coords) != -1 || map.get_cell_source_id(EXTERIOR_2_LAYER, cell_coords) != -1:
return false
# Check if there's a tile on the grass or sand layers
if map.get_cell_source_id(GRASS_LAYER, cell_coords) != -1 || map.get_cell_source_id(SAND_LAYER, cell_coords) != -1:
return true
return false
现在,为了生成拾取物,我们需要创建一个新函数。此函数将随机选择 TileMap 上的一个位置并检查它是否是有效的生成位置。如果是,它将在该位置实例化一个拾取物。我们将实例化我们在 Global 脚本中加载的场景。在 Godot 4 中,我们通过实例化 方法实例化场景引用。
### Main.gd
# older code
# Spawn pickup
func spawn_pickups(amount):
var spawned = 0
while spawned < amount:
# Randomly choose a location on the first or second layer
var random_position = Vector2(randi() % map.get_used_rect().size.x, randi() % map.get_used_rect().size.y)
var layer = randi() % 2
# Spawn it underneath SpawnedPickups node
if is_valid_spawn_location(layer, random_position):
var pickup_instance = Global.pickups_scene.instantiate()
pickup_instance.position = map.map_to_local(random_position)
spawned_pickups.add_child(pickup_instance)
spawned += 1
什么是 map_to_local?
TileMap 节点的map_to_local(Vector2i map_position)方法将给定的地图位置(例如我们的玩家坐标)转换为 TileMap 的本地坐标空间。当您想在本地坐标系中找到 TileMap 中特定单元格的像素位置时,这很有用。
最后,在我们的 ready 函数中,我们可以调用该函数在加载时在我们的地图上生成随机数量的拾取物。我们需要使用 RandomNumberGenerator来随机化拾取物的数量,以便每次调用该函数时它都是不同的。
### Main.gd
extends Node2D
# Node refs
@onready var map = $Map
@onready var spawned_pickups = $SpawnedPickups
# TileMap layers
const WATER_LAYER = 0
const GRASS_LAYER = 1
const SAND_LAYER = 2
const FOLIAGE_LAYER = 3
const EXTERIOR_1_LAYER = 4
const EXTERIOR_2_LAYER = 5
var rng = RandomNumberGenerator.new()
func _ready():
# Spawn between 5 and 10 pickups
var spawn_pickup_amount = rng.randf_range(5, 10)
spawn_pickups(spawn_pickup_amount)
如果您现在运行场景,它将仅生成一种拾取物 — 弹药、生命值或耐力。我们希望它是随机的。让我们从 Player 和 Pickup 脚本中删除 Pickups 枚举,并在 Global 脚本中重新定义它。然后我们需要更新对枚举的引用。
### Global.gd
extends Node
# Scene resources
@onready var pickups_scene = preload("res://Scenes/Pickup.tscn")
# Pickups
enum Pickups { AMMO, STAMINA, HEALTH }
### Player.gd
# older code
# ---------------------- Consumables ------------------------------------------
# Add the pickup to our GUI-based inventory
func add_pickup(item):
if item == Global.Pickups.AMMO:
ammo_pickup = ammo_pickup + 3 # + 3 bullets
ammo_pickups_updated.emit(ammo_pickup)
print("ammo val:" + str(ammo_pickup))
if item == Global.Pickups.HEALTH:
health_pickup = health_pickup + 1 # + 1 health drink
health_pickups_updated.emit(health_pickup)
print("health val:" + str(health_pickup))
if item == Global.Pickups.STAMINA:
stamina_pickup = stamina_pickup + 1 # + 1 stamina drink
stamina_pickups_updated.emit(stamina_pickup)
print("stamina val:" + str(stamina_pickup))
### Pickup.gd
# Pickups to choose from
@export var item : Global.Pickups
# ----------------------- Icon --------------------------------------
func _ready():
#executes the code in the game
if not Engine.is_editor_hint():
#if we choose x item from Inspector dropdown, change the texture
if item == Global.Pickups.AMMO:
sprite.set_texture(ammo_texture)
elif item == Global.Pickups.HEALTH:
sprite.set_texture(health_texture)
elif item == Global.Pickups.STAMINA:
sprite.set_texture(stamina_texture)
#allow us to change the icon in the editor
func _process(_delta):
#executes the code in the editor without running the game
if Engine.is_editor_hint():
#if we choose x item from Inspector dropdown, change the texture
if item == Global.Pickups.AMMO:
sprite.set_texture(ammo_texture)
elif item == Global.Pickups.HEALTH:
sprite.set_texture(health_texture)
elif item == Global.Pickups.STAMINA:
sprite.set_texture(stamina_texture)
现在,在我们的主场景中,让我们在将拾取器类型添加到场景之前将其随机化。
### Main.gd
# older code
# Spawn pickup
func spawn_pickups(amount):
var spawned = 0
while spawned < amount:
# Randomly choose a location on the first or second layer
var random_position = Vector2(randi() % map.get_used_rect().size.x, randi() % map.get_used_rect().size.y)
var layer = randi() % 2
# Spawn it underneath SpawnedPickups node
if is_valid_spawn_location(layer, random_position):
var pickup_instance = Global.pickups_scene.instantiate()
# Randomly select a pickup type
pickup_instance.item = Global.Pickups.values()[randi() % 3]
# Add pickup to scene
pickup_instance.position = map.map_to_local(random_position)
spawned_pickups.add_child(pickup_instance)
spawned += 1
此外,如果没有足够的有效生成位置,spawn_pickups 中的循环可能会无限。为了防止这种情况,您可以添加最大尝试次数:
### Main.gd
# older code
# Spawn pickup
func spawn_pickups(amount):
var spawned = 0
var attempts = 0
var max_attempts = 1000 # Arbitrary number, adjust as needed
while spawned < amount and attempts < max_attempts:
attempts += 1
var random_position = Vector2(randi() % map.get_used_rect().size.x, randi() % map.get_used_rect().size.y)
var layer = randi() % 2
if is_valid_spawn_location(layer, random_position):
var pickup_instance = Global.pickups_scene.instantiate()
pickup_instance.item = Global.Pickups.values()[randi() % 3]
pickup_instance.position = map.map_to_local(random_position)
spawned_pickups.add_child(pickup_instance)
spawned += 1
这将确保当有效生成位置不足时,循环不会无限期运行。如果您现在运行场景,您的拾取物应该会随机生成在地图上!
在下一部分中,我们将进入游戏的有趣部分。我们将设置游戏中第一个挑战玩家的敌人。这将是一段漫长的旅程,所以记得保存你的游戏项目,下一部分见。
本节代码下载。