游戏主场景

现在是时候将我们所做的一切整合到一个可玩的游戏场景中了。

创建新场景并添加一个 Node 节点,命名为 Main。(我们之所以使用 Node 而不是 Node2D,是因为这个节点会作为处理游戏逻辑的容器使用。本身是不需要 2D 功能的。)

点击实例化按钮(由链条图标表示)并选择保存的 player.tscn

../../_images/instance_scene.webp

现在, 将以下节点添加为 Main 的子节点, 并按如下所示对其进行命名(值以秒为单位):

  • Timer(名为 MobTimer)——控制怪物产生的频率

  • Timer(名为 ScoreTimer)——每秒增加分数

  • Timer(名为 StartTimer)——在开始之前给出延迟

  • Marker2D(名为 StartPosition)——表示玩家的起始位置

如下设置每个 Timer 节点的 Wait Time 属性:

  • MobTimer0.5

  • ScoreTimer1

  • StartTimer2

此外,将 StartTimerOne Shot 属性设置为“启用”,并将 StartPosition 节点的 Position 设置为 (240, 450)

生成怪物

Main 节点将产生新的生物, 我们希望它们出现在屏幕边缘的随机位置. 添加一个名为 MobPathPath2D 节点作为 Main 的子级. 当你选择 Path2D 时, 你将在编辑器顶部看到一些新按钮:

../../_images/path2d_buttons.webp

选择添加点按钮,并单击以添加拐角点来绘制路径。可使用网格捕捉和用智能捕捉,使点对齐到网格。

../../_images/grid_snap_button.webp

重要

顺时针的顺序绘制路径,否则小怪会向外而非向内生成!

../../_images/draw_path2d.gif

在图像上放置点 4 后, 点击 闭合曲线 按钮, 你的曲线将完成.

现在已经定义了路径, 添加一个 PathFollow2D 节点作为 MobPath 的子节点, 并将其命名为 MobSpawnLocation. 该节点在移动时, 将自动旋转并沿着该路径, 因此我们可以使用它沿路径来选择随机位置和方向.

你的场景应如下所示:

../../_images/main_scene_nodes.webp

Main 脚本

将脚本添加到 Main。在脚本的顶部,我们使用 @export var mob_scene: PackedScene 来允许我们选择要实例化的 Mob 场景。

GDScriptC#

  1. extends Node
  2. @export var mob_scene: PackedScene
  3. var score
  1. using Godot;
  2. public partial class Main : Node
  3. {
  4. // Don't forget to rebuild the project so the editor knows about the new export variable.
  5. [Export]
  6. public PackedScene MobScene { get; set; }
  7. private int _score;
  8. }

单击 Main 节点,就可以在“检查器”的“Script Variables”(脚本变量)下看到 Mob Scene 属性。

有两种方法来给这个属性赋值:

  • mob.tscn 从“文件系统”面板拖放到 Mob Scene 属性里。

  • 单击“[空]”旁边的下拉箭头按钮,选择“加载”。选择 mob.tscn

然后选中“场景”面板中 Main 节点下的 Player 场景实例,切换到侧边栏的“节点”面板。请确保“节点”面板中的“信号”选项卡处于选中状态。

你可以看到 Player 的信号列表。找到 hit 信号并双击(或右键选择 “Connect…”)将会打开信号连接窗口。接下来创建用于在游戏结束时进行一些处理的 game_over 函数。在信号连接窗口底部的 “Receiver Method” 框中输入 “game_over”,并点击 “Connect”。 你的目标是从 Player 发出 hit 信号,并在 Main 脚本中进行处理。将以下代码添加到新函数中,以及一个 new_game 函数,该函数将为新游戏设置一切:

GDScriptC#

  1. func game_over():
  2. $ScoreTimer.stop()
  3. $MobTimer.stop()
  4. func new_game():
  5. score = 0
  6. $Player.start($StartPosition.position)
  7. $StartTimer.start()
  1. public void GameOver()
  2. {
  3. GetNode<Timer>("MobTimer").Stop();
  4. GetNode<Timer>("ScoreTimer").Stop();
  5. }
  6. public void NewGame()
  7. {
  8. _score = 0;
  9. var player = GetNode<Player>("Player");
  10. var startPosition = GetNode<Marker2D>("StartPosition");
  11. player.Start(startPosition.Position);
  12. GetNode<Timer>("StartTimer").Start();
  13. }

现在将每个 Timer 节点(StartTimerScoreTimerMobTimer)的 timeout() 信号连接到 main 脚本。 StartTimer 将启动其他两个计时器。 ScoreTimer 将使得分加1。

GDScriptC#

  1. func _on_score_timer_timeout():
  2. score += 1
  3. func _on_start_timer_timeout():
  4. $MobTimer.start()
  5. $ScoreTimer.start()
  1. private void OnScoreTimerTimeout()
  2. {
  3. _score++;
  4. }
  5. private void OnStartTimerTimeout()
  6. {
  7. GetNode<Timer>("MobTimer").Start();
  8. GetNode<Timer>("ScoreTimer").Start();
  9. }

_on_mob_timer_timeout() 中, 我们先创建小怪实例,然后沿着 Path2D 路径随机选取起始位置,最后让小怪移动。PathFollow2D 节点将沿路径移动,并会自动旋转,所以我们将使用它来选择怪物的方位和朝向。生成小怪后,我们会在 150.0250.0 之间选取随机值,表示每只小怪的移动速度(如果它们都以相同的速度移动,那么就太无聊了)。

注意,必须使用 add_child() 将新实例添加到场景中。

GDScriptC#

  1. func _on_mob_timer_timeout():
  2. # Create a new instance of the Mob scene.
  3. var mob = mob_scene.instantiate()
  4. # Choose a random location on Path2D.
  5. var mob_spawn_location = get_node("MobPath/MobSpawnLocation")
  6. mob_spawn_location.progress_ratio = randf()
  7. # Set the mob's direction perpendicular to the path direction.
  8. var direction = mob_spawn_location.rotation + PI / 2
  9. # Set the mob's position to a random location.
  10. mob.position = mob_spawn_location.position
  11. # Add some randomness to the direction.
  12. direction += randf_range(-PI / 4, PI / 4)
  13. mob.rotation = direction
  14. # Choose the velocity for the mob.
  15. var velocity = Vector2(randf_range(150.0, 250.0), 0.0)
  16. mob.linear_velocity = velocity.rotated(direction)
  17. # Spawn the mob by adding it to the Main scene.
  18. add_child(mob)
  1. private void OnMobTimerTimeout()
  2. {
  3. // Note: Normally it is best to use explicit types rather than the `var`
  4. // keyword. However, var is acceptable to use here because the types are
  5. // obviously Mob and PathFollow2D, since they appear later on the line.
  6. // Create a new instance of the Mob scene.
  7. Mob mob = MobScene.Instantiate<Mob>();
  8. // Choose a random location on Path2D.
  9. var mobSpawnLocation = GetNode<PathFollow2D>("MobPath/MobSpawnLocation");
  10. mobSpawnLocation.ProgressRatio = GD.Randf();
  11. // Set the mob's direction perpendicular to the path direction.
  12. float direction = mobSpawnLocation.Rotation + Mathf.Pi / 2;
  13. // Set the mob's position to a random location.
  14. mob.Position = mobSpawnLocation.Position;
  15. // Add some randomness to the direction.
  16. direction += (float)GD.RandRange(-Mathf.Pi / 4, Mathf.Pi / 4);
  17. mob.Rotation = direction;
  18. // Choose the velocity.
  19. var velocity = new Vector2((float)GD.RandRange(150.0, 250.0), 0);
  20. mob.LinearVelocity = velocity.Rotated(direction);
  21. // Spawn the mob by adding it to the Main scene.
  22. AddChild(mob);
  23. }

重要

为什么要用 PI?在需要传入角度的函数中,Godot 使用的是弧度而不是度数。圆周率(Pi)表示转半圈的弧度,约为 3.1415(还提供了等于 2 * PITAU)。如果你更喜欢使用度数,则需使用 deg_to_rad()rad_to_deg() 函数在这两种单位之间进行转换。

测试场景

让我们测试这个场景,确保一切正常。请将对 new_game 的调用添加至 _ready()

GDScriptC#

  1. func _ready():
  2. new_game()
  1. public override void _Ready()
  2. {
  3. NewGame();
  4. }

让我们同时指定 Main 作为我们的“主场景”——游戏启动时自动运行的场景。按下“运行”按钮,当弹出提示时选择 main.tscn

小技巧

如果你已经将别的场景设置为“主场景”了,你可以在“文件系统”面板上右键点击 main.tscn 并选择“设为主场景”。

你应该可以四处移动游戏角色,观察敌人的生成,以及玩家被敌人击中时会消失。

当你确定一切正常时,在 _ready() 中移除对 new_game() 的调用。

我们的游戏还缺点啥?缺用户界面。在下一课中,我们将会添加标题界面并且显示玩家的分数。

Previous Next


© 版权所有 2014-present Juan Linietsky, Ariel Manzur and the Godot community (CC BY 3.0). Revision b1c660f7.

Built with Sphinx using a theme provided by Read the Docs.