现在我们有了基本的敌人和玩家,我们需要给自己一个杀死敌人的动机。我们可以通过升级系统来实现这一点,该系统在获得一定数量的 XP 后提高玩家的等级。如果玩家升级,他们会得到奖励,包括拾取物品和属性(健康、耐力)补充。我们还会在升级时增加他们的最大健康和最大耐力值。

您将在本部分中学习到的内容:

· 如何使用 Popup 节点。
· 如何暂停场景树。
· 如何允许/禁止输入处理。
· 如何改变节点的处理模式。
· 如何(可选)更改鼠标光标的图像和可见性。

1.jpg
升级概述

升级弹出窗口

准备就绪后,打开你的游戏项目,在你的播放器脚本中通过你的信号定义三个新信号,它们将更新我们的 xp、xp 要求和级别值。

### Player.gd
# Custom signals
signal health_updated
signal stamina_updated
signal ammo_pickups_updated
signal health_pickups_updated
signal stamina_pickups_updated
signal xp_updated
signal level_updated
signal xp_requirements_updated

接下来,我们需要创建这些信号在发出时将更新的变量。您可以将玩家的初始经验值、等级和所需经验值更改为您想要的任何值。

### Player.gd
# XP and levelling
var xp = 0 
var level = 1 
var xp_requirements = 100

如果您还记得我们在前面的部分如何更新健康和耐力 GUI 元素,您就会知道我们必须在玩家场景中的 XP 和 Level 元素中创建函数,然后将它们连接到玩家场景中的信号。

在您的 Player Scene 中,在您的 UI CanvasLayer 下方,将新脚本附加到您的 XP 和 Level ColorRect。确保将其保存在您的 GUI 文件夹下方。
2.jpg
3.jpg

我们的 XP ColorRect 也应该有两个值,一个用于我们的 XP,一个用于我们的 XP 要求,因此请继续复制您的 Value 节点。其变换值如下图所示。
4.jpg
5.jpg
6.jpg

我们想要从这些 ColorRects(而不是 Label)更新 Value 子节点,因此让我们继续为每个新脚本创建一个函数来更新我们的 XP 和 Level 值。

### XPAmount.gd

extends ColorRect

# Node refs
@onready var value = $Value
@onready var value2 = $Value2

#return xp
func update_xp_ui(xp):
    #return something like 0
    value.text = str(xp)

#return xp_requirements
func update_xp_requirements_ui(xp_requirements):
    #return something like / 100
### LevelAmount.gd

extends ColorRect

# Node refs
@onready var value = $Value

# Return level
func update_level_ui(level):
    #return something like 0
    value.text = str(level)

现在我们只需将这些 UI 元素函数连接到我们的 Player 脚本中每个新创建的信号。

### 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
@onready var xp_amount = $UI/XP
@onready var level_amount = $UI/Level
@onready var animation_player = $AnimationPlayer

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)
    xp_updated.connect(xp_amount.update_xp_ui)
    xp_requirements_updated.connect(xp_amount.update_xp_requirements_ui)
    level_updated.connect(level_amount.update_level_ui)

我们希望在杀死敌人后更新我们的 xp 数量,因此要做到这一点,我们需要在我们的 Player 脚本中创建一个新函数,它将更新我们的 xp 值并发出xp_updated信号。

### Player.gd
# older code
# ----------------- Level & XP ------------------------------
#updates player xp
func update_xp(value):
    xp += value    
    #emit signals
    xp_requirements_updated.emit(xp_requirements)   
    xp_updated.emit(xp)
    level_updated.emit(level)

我们可以在任意需要更新 xp 的地方调用此函数,比如在我们的add_pickups()函数中,或者在敌人的damage()函数中判断其死亡。

### 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))
    update_xp(5)
### Enemy.gd
# older code
#will damage the enemy when they get hit
func hit(damage):
    health -= damage
    if health > 0:
        #damage
        animation_player.play("damage")
    else:
        #death
        #stop movement
        timer_node.stop()
        direction = Vector2.ZERO
        #stop health regeneration
        set_process(false)
        #trigger animation finished signal
        is_attacking = true     
        #Finally, we play the death animation and emit the signal for the spawner.
        animation_sprite.play("death")
        #add xp values
        player.update_xp(70)
        death.emit()
        #drop loot randomly at a 90% chance
        if rng.randf() < 0.9:
            var pickup = Global.pickups_scene.instantiate()
            pickup.item = rng.randi() % 3 #we have three pickups in our enum
            get_tree().root.get_node("Main/PickupSpawner/SpawnedPickups").call_deferred("add_child", pickup)
            pickup.position = position

在本部分中,我们将更多地构建此功能,因为我们仍然需要更新xp_requirements并运行检查以查看我们的玩家是否已获得足够的 xp 来升级。如果他们获得了足够的 xp,我们需要将当前 xp 数量重置回零,并增加玩家的级别和所需的 xp 值。然后我们需要发出信号来通知我们的游戏这些值的变化。

### Player.gd

# older code

# ----------------- Level & XP ------------------------------
#updates player xp
func update_xp(value):
    xp += value
    #check if player leveled up after reaching xp requirements
    if xp >= xp_requirements:
        #reset xp to 0
        xp = 0
        #increase the level and xp requirements
        level += 1
        xp_requirements *= 2
    #emit signals
    xp_requirements_updated.emit(xp_requirements)   
    xp_updated.emit(xp)
    level_updated.emit(level)

如果您现在运行您的场景并杀死一些敌人(确保您将他们的伤害值改为零,以便他们无法在这次测试中杀死您),您将看到您的 xp 和等级值更新。
7.jpg

如果我们的玩家升级了,我们希望屏幕显示玩家升级的通知,以及他们升级后获得的奖励摘要。我们将通过向玩家的 UI 节点添加 CanvasLayer 节点来实现这一点。在您的玩家场景中,向您的 UI 层添加一个新的 CanvasLayer 节点。将其重命名为 LevelUpPopup。
8.jpg

在此 CanvasLayer 节点中,添加两个 ColorRects 和一个Button节点。第一个 ColorRect 将包含我们的升级标签,第二个 ColorRect 将包含我们的奖励摘要。该按钮将允许玩家确认通知并继续游戏。您可以按如下方式重命名它们:
9.jpg
10.jpg

将消息节点的颜色更改为 #d6c376,并将其大小更改为 (x:142,y:142);位置更改为 (x:4,y:4);anchor_preset 更改为 (中心)。
11.jpg

将 Rewards 节点拖到 Message 节点中,使其成为该节点的子节点。将其颜色更改为 #365655,并将其大小更改为 (x: 100, y: 75);位置更改为 (x: 20, y: 30);anchor_preset 更改为 (center)。
12.jpg

另外,将 Confirm 节点拖到 Message 节点中,使其成为该节点的子节点。将其颜色更改为 #365655,并将其大小更改为 (x: 100, y: 75);位置更改为 (x: 20, y: 30);anchor_preset 更改为 (center)。将其字体更改为“Schrodinger”,并将其文本更改为“CONTINUE”。
13.jpg
14.jpg

现在,在 Message 节点的顶部,让我们添加一个新的 Label 节点来显示我们的欢迎文本“Level Up!”。将其字体更改为“Arcade Classic”,字体大小更改为 15。然后更改其 anchor_preset (center-top)、水平对齐 (center)、垂直对齐 (center) 和位置 (y: 5)。
15.jpg

在您的奖励节点中,添加六个新的标签节点。
16.jpg

将其重命名如下:
17.jpg

更改其属性如下:

全部:
Text = “1”
Font = “Schrödinger”
Font-size = 10
Anchor Preset = center-top
Horizontal Alignment = center
Vertical Alignment = center
等级提升:
Position= y: 0
健康增加:
Position= y: 10
体力增加:
Position= y: 20
健康获得:
Position= y: 30
耐力提升获得:
Position= y:40
拾取弹药获得:
Position= y: 50
18.jpg
创建 Popup 后,我们现在可以继续隐藏 Popup。您可以在 Inspector 面板的 Canvas Item > Visibility 下执行此操作,或者只需单击节点旁边的眼睛图标即可将其隐藏。
19.jpg

我们需要返回Player 场景中的update_xp()函数来更新检查玩家是否升级的条件。如果他们正在升级,我们需要暂停游戏,显示包含所有奖励值的弹出窗口,并且仅在玩家单击确认按钮时隐藏弹出窗口。在这个函数中,我们必须提高玩家的最大生命值和耐力,并为他们提供一些弹药以及健康和耐力饮料。完成此操作后,我们需要在 UI 元素中反映这些变化。

如果我们想暂停游戏,只需使用SceneTree.paused方法。如果游戏暂停,则不会接受玩家或我们的任何输入,因为一切都暂停了。除非我们更改节点的处理模式。Godot 中的每个节点都有一个“处理模式”,用于定义其处理时间。可以在检查器中节点的Node属性下找到并更改它。
20.jpg

以下是每个模式告诉节点要做的事情:

  • 继承:节点将根据父节点的状态工作或被处理。如果父节点的处理模式是可暂停的,则该节点也将是可暂停的,等等。
  • 可暂停:仅当游戏未暂停时,节点才会工作或被处理。
  • 暂停时:仅当游戏暂停时,节点才会工作或被处理。
  • 始终:无论游戏是否暂停,节点都会工作或被处理。
  • 已禁用:该节点将不起作用,也不会被处理。

我们需要 LevelUpPopup 节点仅在游戏暂停时工作。这样我们就可以单击“确认”按钮来取消游戏暂停,从而允许其他节点继续处理,因为它们只有在游戏处于未暂停状态时才工作或处理输入。让我们将 LevelUpPopup 的“处理模式”更改为“ WhenPaused”。您可以在“节点”>“处理”>“模式”下找到此选项。
22.jpg

因为我们更改了 LevelUpPopup 节点的处理模式,所以它的所有子节点也将继承该处理模式,因此当游戏暂停时,它们都将正常工作。在代码中暂停游戏之前,我们还需要首先允许通过set_process_input方法处理输入。此方法启用或禁用输入处理。然后我们将增加健康、耐力、经验值、等级和拾取值,并将这些更改反映在我们的 UI 上!让我们在代码中进行这些更改。

### 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
@onready var xp_amount = $UI/XP
@onready var level_amount = $UI/Level
@onready var animation_player = $AnimationPlayer
@onready var level_popup = $UI/LevelPopup

# ----------------- Level & XP ------------------------------
#updates player xp
func update_xp(value):
    xp += value
    #check if player leveled up after reaching xp requirements
    if xp >= xp_requirements:
        #allows input
        set_process_input(true)
        #pause the game
        get_tree().paused = true
        #make popup visible
        level_popup.visible = true
        #reset xp to 0
        xp = 0
        #increase the level and xp requirements
        level += 1
        xp_requirements *= 2
    
        #update their max health and stamina
        max_health += 10 
        max_stamina += 10 
        
        #give the player some ammo and pickups
        ammo_pickup += 10 
        health_pickup += 5
        stamina_pickup += 3
        
        #update signals for Label values
        health_updated.emit(health, max_health)
        stamina_updated.emit(stamina, max_stamina)
        ammo_pickups_updated.emit(ammo_pickup)
        health_pickups_updated.emit(health_pickup)
        stamina_pickups_updated.emit(stamina_pickup)
        xp_updated.emit(xp)
        level_updated.emit(level)
        
        #reflect changes in Label
        $UI/LevelPopup/Message/Rewards/LevelGained.text = "LVL: " + str(level)
        $UI/LevelPopup/Message/Rewards/HealthIncreaseGained.text = "+ MAX HP: " + str(max_health)
        $UI/LevelPopup/Message/Rewards/StaminaIncreaseGained.text = "+ MAX SP: " + str(max_stamina)
        $UI/LevelPopup/Message/Rewards/HealthPickupsGained.text = "+ HEALTH: 5" 
        $UI/LevelPopup/Message/Rewards/StaminaPickupsGained.text = "+ STAMINA: 3" 
        $UI/LevelPopup/Message/Rewards/AmmoPickupsGained.text = "+ AMMO: 10" 
        
    #emit signals
    xp_requirements_updated.emit(xp_requirements)   
    xp_updated.emit(xp)
    level_updated.emit(level)

最后,我们需要让确认按钮能够关闭弹出窗口并取消游戏暂停。我们可以将其pressed()信号连接到 Player 脚本来实现这一点。
23.jpg

在这个新创建的_on_confirm_pressed():函数中,我们将简单地再次隐藏弹出窗口并取消游戏暂停。

### Player.gd
# older code
# close popup
func _on_confirm_pressed():
    level_popup.visible = false
    get_tree().paused = false

现在,如果我们运行场景并射击足够多的敌人,我们将看到弹出窗口显示我们的奖励值,如果我们单击确认按钮,弹出窗口将关闭,然后我们可以用新的值继续游戏!
24.jpg
25.jpg

显示和隐藏光标

我不喜欢光标总是显示的方式。无论游戏是否暂停,我们的光标总是停留在屏幕上。由于这不是点击游戏,因此当游戏未暂停时,光标没有理由显示,因为我们会花时间四处奔跑和射击敌人。因此,我们的光标应该只在我们处于菜单屏幕(例如暂停或主菜单)时显示,甚至在我们的对话屏幕中显示 - 即当游戏暂停并且我们需要光标进行输入时。

幸运的是,这是一个快速解决方案。我们可以通过 Input 单例MouseMode方法更改鼠标光标的可见性。在我们的 Player 脚本中,我们将在游戏暂停时显示光标,如果没有暂停,我们将隐藏光标。

### Player.gd

# ----------------- Level & XP ------------------------------
#updates player xp
func update_xp(value):
    xp += value
    #check if player leveled up after reaching xp requirements
    if xp >= xp_requirements:
        #allows input
        set_process_input(true)
        Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
        #pause the game
        
    #emit signals
    xp_requirements_updated.emit(xp_requirements)   
    xp_updated.emit(xp)
    level_updated.emit(level)

# close popup
func _on_confirm_pressed():
    level_popup.visible = false
    get_tree().paused = false
    Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)

我们还需要在加载时隐藏光标。

### Player.gd

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)
    xp_updated.connect(xp_amount.update_xp_ui)
    xp_requirements_updated.connect(xp_amount.update_xp_requirements_ui)
    level_updated.connect(level_amount.update_level_ui)
    
    # Reset color
    animation_sprite.modulate = Color(1,1,1,1)
    Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)

我们还可以更改鼠标光标的图像。如果您进入“项目设置”>“显示”>“鼠标光标”,则可以更改鼠标光标的图像。
26.jpg

您可以在此处找到免费的鼠标光标包。我使用了VOiD1 Gaming 的免费基本光标包。
27.jpg

现在,如果您运行游戏,当暂停状态改变时,光标应该被隐藏/显示。

加载显示经验值

如果我们更改变量的值并运行游戏,您可能会注意到 Level、XP 和 Pickup 值没有更新。我们需要通过进入 UI 脚本并在加载时从 Player 脚本调用我们的值来解决这个问题。

HealthAmount.gd

扩展了ColorRect

### 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)
### LevelAmount.gd

extends ColorRect

# Node refs
@onready var value = $Value
@onready var player = $"../.."

# On load
func _ready():
    value.text = str(player.level)
    
# Return level
func update_level_ui(level):
    #return something like 0
    value.text = str(level)
### XPAmount.gd

extends ColorRect

# Node refs
@onready var value = $Value
@onready var value2 = $Value2
@onready var player = $"../.."

# On load
func _ready():
    value.text = str(player.xp)
    value2.text = "/" + str(player.xp_requirements)
    
#return xp
func update_xp_ui(xp):
    #return something like 0
    value.text = str(xp)

#return xp_requirements
func update_xp_requirements_ui(xp_requirements):
    #return something like / 100
    value2.text = "/" + str(xp_requirements)
### 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)

现在,如果您运行场景,您的值应该正确显示,这在我们稍后加载游戏时很有用!
28.jpg

恭喜,您的玩家现在可以因杀死坏人而获得奖励。我们还没有 100% 完成,因为在下一部分中,我们将添加一个基本的 NPC 和任务,完成此任务后,玩家还会获得 XP 奖励。记得保存您的项目,下一部分见。

标签: Godot, 2D游戏, 游戏开发, 游戏自学

添加新评论