在上一部分中,我们为游戏的边界创建了基本碰撞和地形。我决定将 Tilemaps 部分分成几个部分,以防止信息过载。如果这是您第一次使用 Godot 中的 TileMap 节点,请不要担心,我保证您练习得越多就会变得越容易。这就是为什么我鼓励您在每部分结束时花时间复习您所做的事情。在这一部分中,我们将创建我们的梯子以及其余的平台。

您将在本部分中学习到的内容:
如何创建 Area2D 场景。
如何连接 Area2D 信号。
如何创建全局自动加载单例。

阶梯创造

在绘制玩家可以跳上的其他平台之前,我们首先需要创建梯子。我们将在新场景中执行此操作。让我们创建一个以Area2D节点为根节点的新场景。此节点创建一个 2D 区域,用于检测与其形状重叠、进入或退出的CollisionObject2D节点。我们将使用此 Area2D 节点检测玩家场景是否正在进入其碰撞形状,如果是,我们将信号指示is_climbing变量为真,这反过来又允许玩家攀爬。
1.jpg

此节点上出现警告,因为它没有形状,因此为其分配一个以 RectangleShape2D 为形状的 CollisionShape2D 节点。
2.jpg

我们将根节点重命名为“Ladder”。将此场景保存在您的“Scenes”文件夹下。
3.jpg

让我们通过向 Ladder 场景添加 Sprite2D 节点来可视化我们的 Ladder。将图像“res://Assets/wood_set/ladder/28x128/2.png”分配给其纹理。
4.jpg

现在,修复碰撞形状以勾勒出梯子的轮廓。
5.jpg

将新脚本附加到您的 Ladder 场景并将该脚本保存在您的 Scripts 文件夹中。
6.jpg

在这个脚本中,我们将获得对 Player 的 is_climbing 变量的引用。如果我们的 Player 进入梯子的碰撞,我们将触发他们的is_climbing变量为真。如果他们离开梯子的碰撞,我们将触发他们的is_climbing变量为假。我们可以使用 Area2D 节点的body_entered()和 body_exited() 信号触发这些变化。当接收的物体(我们的玩家)进入此区域时,会发出body_entered()信号。当接收的物体离开此区域时,会发出body_exited()信号。让我们将这两个信号连接到我们的 Ladder 脚本。
7.jpg

这将创建两个新函数,_on_body_exited()和_on_body_entered()。现在,我们可以通过我们的Loading资源获取对我们的玩家节点的引用来实现这一点,但让我们用更好的方法来实现。我们将创建一个 Global 脚本,该脚本将添加到我们的AutoLoad Singleton中。Singleton模式是解决需要在场景之间存储持久信息的常见用例的有用工具。这将使我们能够从游戏中的任何其他场景或脚本访问 Global 脚本中的变量 - 而无需先将它们作为资源加载!

让我们在脚本文件夹中创建一个新脚本,并将其命名为“Global”。
8.jpg

现在,让我们将其添加到我们的自动加载资源中,以便我们可以全局访问此脚本的值。要将其添加到您的自动加载资源中,请进入您的项目设置 > 自动加载,然后将 Global.gd 文件添加到列表中。
9.jpg

接下来,我们需要对 Player 脚本进行一些重构。我希望将is_climbing和is_attacking变量从 Player 脚本移至 Global 脚本。

### Global.gd

extends Node 

#移动状态
var is_attacking = false
 var is_climbing = false

现在,在我们的玩家脚本中,让我们用来自 Global 单例的引用替换我们的变量(例如,用 Global.is_climbing 替换 is_climbing,用Global.is_attacking替换 is_attacking)。

### Player.gd

extends CharacterBody2D

#player movement variables
@export var speed = 100
@export var gravity = 200
@export var jump_height = -100

#movement and physics
func _physics_process(delta):
    # vertical movement velocity (down)
    velocity.y += gravity * delta
    # horizontal movement processing (left, right)
    horizontal_movement()
    
    #applies movement
    move_and_slide() 
    
    #applies animations
    if !Global.is_attacking:
        player_animations()
        
#horizontal movement calculation
func horizontal_movement():
    # if keys are pressed it will return 1 for ui_right, -1 for ui_left, and 0 for neither
    var horizontal_input = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
    # horizontal velocity which moves player left or right based on input
    velocity.x = horizontal_input * speed

#animations
func player_animations():
    #on left (add is_action_just_released so you continue running after jumping)
    if Input.is_action_pressed("ui_left") || Input.is_action_just_released("ui_jump"):
        $AnimatedSprite2D.flip_h = true
        $AnimatedSprite2D.play("run")
        
    #on right (add is_action_just_released so you continue running after jumping)
    if Input.is_action_pressed("ui_right") || Input.is_action_just_released("ui_jump"):
        $AnimatedSprite2D.flip_h = false
        $AnimatedSprite2D.play("run")
    
    #on idle if nothing is being pressed
    if !Input.is_anything_pressed():
        $AnimatedSprite2D.play("idle")
        
#singular input captures
func _input(event):
    #on attack
    if event.is_action_pressed("ui_attack"):
        Global.is_attacking = true
        $AnimatedSprite2D.play("attack")        

    #on jump
    if event.is_action_pressed("ui_jump") and is_on_floor():
        velocity.y = jump_height
        $AnimatedSprite2D.play("jump")
    
    #on climbing ladders
    if Global.is_climbing == true:
        if Input.is_action_pressed("ui_up"):
            $AnimatedSprite2D.play("climb") 
            gravity = 100
            velocity.y = -200
        
    #reset gravity
    else:
        gravity = 200
        Global.is_climbing = false  
        
#reset our animation variables
func _on_animated_sprite_2d_animation_finished():
    Global.is_attacking = false
    Global.is_climbing = false

创建全局变量后,我们可以返回 Ladder 脚本,并在Player 场景进入/退出 Area2D 主体时将is_climbing值设置为 true 或 false。我们可以通过检查进入的主体是否具有“Player”名称(这是我们 Player 场景的根节点的名称)来执行此操作,如果为 true,则我们相应地设置布尔值。

### Ladder.gd
extends Area2D

#sets is_climbing to true to simulate climbing
func _on_body_entered(body):
    if body.name == "Player":
        Global.is_climbing = true

#sets is_climbing to false to simulate climbing
func _on_body_exited(body):
    if body.name == "Player":
        Global.is_climbing = false

现在,我们可以在主场景中实例化梯子场景。将其拖到靠近玩家的位置,以便测试is_climbing值是否正在变化。
10.jpg

现在,如果您运行场景,并且您的玩家跑进梯子区域,则只要您按下键盘上的 W 或 UP,它就会允许您攀爬。您可以根据需要将此“攀爬”输入的 velocity.y 值更改为更高或更低。您会注意到,如果我们在攀爬时按下其他输入来奔跑,我们的玩家将过渡到这些动画,而不是停留在攀爬动画中。让我们通过在玩家攀爬时禁用这些动画来解决这个问题。

### Player.gd

#older code

#movement and physics
func _physics_process(delta):
    # vertical movement velocity (down)
    velocity.y += gravity * delta
    # horizontal movement processing (left, right)
    horizontal_movement()
    
    #applies movement
    move_and_slide() 
    
    #applies animations
    if !Global.is_attacking || !Global.is_climbing:
        player_animations()

您的代码现在看起来应该是这样的。

由于我们将在场景中添加多个梯子,因此让我们将梯子组织为 Node2D 节点的子节点。添加一个新的 Node2D 节点并将其重命名为“梯子”。我们可以将现有的梯子场景拖入其中作为梯子节点的子节点。
11.jpg

平台创建

创建梯子后,我们终于可以发挥创造力了,因为现在是时候创建关卡了。我们已经有了 Platform_Floor 地形,现在我们将使用它来构建通往出口的其余楼层。

这是我们游戏的布局计划:

  • 我们将桶生成器放在顶部。我们的生成器将生成沿特定路径滚动的炸弹,而不是桶。
  • 我们还会将较小的障碍物生成器放在两侧,它们会向玩家投掷箱子,增加游戏难度。这些箱子也会遵循特定的路径。
  • 我们还将在我们的关卡中随机分布实时、攻击和得分提升拾取物。
  • 最后,我们将有一个玩家生成的起点和一个完成关卡的终点。

以下是该级别的示例:
12.jpg

创建关卡时请记住这些对象。让我们继续绘制平台地板。确保有足够的空间放置梯子,并尝试将地板绘制在玩家头顶上方 3 块瓷砖处。玩家应该有足够的空间跳跃,以及跨平台,但不要太容易或间隙太大。

要添加更多梯子,只需实例化新场景。您可以通过更改梯子的 y 比例来更改梯子的高度,您可以在节点的 Inspector 面板的 Transform > Scale 下找到它。请记住取消链接,以便它只更改 y 值。还应该有足够的空间让玩家爬上梯子而不会被平台的碰撞阻挡。另外,请记住将您的 Ladder 节点移到 TileMap 节点后面。
13.jpg
14.jpg

这就是我最终为我的第一个关卡所创建的内容:

15.jpg
运行场景来测试玩家是否可以顺利地从起点到达终点,并在需要时进行修复。
16.jpg

故障排除:我的播放器在梯子上停下来时继续奔跑!
我们需要检查 ui_climbing 输入中是否再次按下了任何按钮。如果没有按下任何按钮,我们的动画应该重置为空闲动画,但如果我们正在攀爬,我们的攀爬动画应该播放!

### Player.gd

#older code

#singular input captures
func _input(event):
    #older code

    #on climbing ladders
    if Global.is_climbing == true:
        if !Input.is_anything_pressed():
            $AnimatedSprite2D.play("idle")
            
        if Input.is_action_pressed("ui_up"):
            $AnimatedSprite2D.play("climb") 
            gravity = 100
            velocity.y = -160

恭喜您创建了第一个关卡的平台!在下一部分中,我们将为关卡添加一些装饰,使其感觉更完整一些,例如带有几个窗口的背景。我们还将创建第二个关卡。稍后我们将用炸弹生成器、盒子生成器和拾取物品填充这些关卡 — 但现在,让我们专注于创建关卡的基础。

现在是保存项目并备份的好时机,这样如果出现任何破坏游戏的错误,您就可以恢复到此部分。在继续本系列之前,请回顾并复习您学到的知识,一旦您准备好,我们将在下一部分与您见面!

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

添加新评论