杀死玩家

我们可以通过跳到敌人身上来杀死他们,但玩家仍然不能死亡。让我们来解决这个问题。

我们希望检测到被敌人击中与压扁敌人时的不同。我们希望玩家在地板上移动时死亡,但如果他们在空中,则不会死亡。我们可以使用向量数学来区分这两种碰撞。但是,我们将使用 Area 节点,该节点适用于命中框。

使用 Area 节点制作攻击框

回到 Player 场景,添加一个新的 Area 节点。把它命名为 MobDetector(小怪检测器)。添加一个 CollisionShape 节点作为它的一个子节点。

image0

检查器中,给它指定一个圆柱体形状。

image1

这里有一个技巧,你可以用它来使碰撞只发生在玩家在地面上或靠近地面时。您可以降低圆柱体的高度并将其向上移动到角色的顶部。这样,当玩家跳跃时,形状会太高,敌人无法与之碰撞。

image2

你还希望圆柱体比球体更宽。这样一来,玩家在碰撞之前就会被击中,并被推到怪物的碰撞盒之上。

圆柱体越宽,玩家就越容易被杀死。

Next, select the MobDetector node again, and in the Inspector, turn off its Monitorable property. This makes it so other physics nodes cannot detect the area. The complementary Monitoring property allows it to detect collisions. Then, remove the Collision -> Layer and set the mask to the “enemies” layer.

image3

当区域检测到碰撞时,它们会发出信号。我们要将一个信号连接到 Player 节点。在节点选项卡中,双击 body_entered 信号并将其连接到 Player

image4

当一个 KinematicBodyRigidBody 节点进入它时,MobDetector 将发出 body_entered 信号。由于它只掩盖了“enemies”物理层,它将只检测 Mob 节点。

从代码上看,我们要做两件事:发出一个信号,我们以后会用来结束游戏,并销毁玩家。我们可以用 die() 函数来包装这些操作,帮助我们给代码贴上描述性标签。

GDScriptC#

  1. # Emitted when the player was hit by a mob.
  2. # Put this at the top of the script.
  3. signal hit
  4. # And this function at the bottom.
  5. func die():
  6. emit_signal("hit")
  7. queue_free()
  8. func _on_MobDetector_body_entered(_body):
  9. die()
  1. // Don't forget to rebuild the project so the editor knows about the new signal.
  2. // Emitted when the player was hit by a mob.
  3. [Signal]
  4. public delegate void Hit();
  5. // ...
  6. private void Die()
  7. {
  8. EmitSignal(nameof(Hit));
  9. QueueFree();
  10. }
  11. // We also specified this function name in PascalCase in the editor's connection window
  12. public void OnMobDetectorBodyEntered(Node body)
  13. {
  14. Die();
  15. }

按 F5 再试一下游戏。如果一切设置正确,角色在被敌人碰到时应该会死亡。

然而,请注意,这完全取决于 PlayerMob 的碰撞形状的大小和位置。你可能需要移动它们,调整它们的大小,以达到紧凑的游戏感觉。

结束游戏

我们可以利用 Playerhit 信号来结束游戏。我们所要做的就是将它连接到 Main 节点上,在处理时停止 MobTimer

打开 Main.tscn 场景,选中 Player 节点,然后在节点面板中把 hit 信号连接到 Main 节点。

image5

_on_Player_hit() 函数中获取并停止计时器。

GDScriptC#

  1. func _on_Player_hit():
  2. $MobTimer.stop()
  1. // We also specified this function name in PascalCase in the editor's connection window
  2. public void OnPlayerHit()
  3. {
  4. GetNode<Timer>("MobTimer").Stop();
  5. }

如果你现在试玩游戏,你死亡后就会停止刷怪,现有的怪物会离开屏幕。

你可以鼓励鼓励自己了:你做出了完整 3D 游戏的原型,虽说还有点粗糙。

在此基础上,我们将会添加计分、重启游戏的选项,你还会看到如何使用简单的动画让游戏变得更加活灵活现。

代码检查点

这些事 MainMobPlayer 节点的完整脚本,仅供参考。你可以把它们和你的代码进行对比检查。

首先是 Main.gd

GDScriptC#

  1. extends Node
  2. export(PackedScene) var mob_scene
  3. func _ready():
  4. randomize()
  5. func _on_MobTimer_timeout():
  6. # Create a new instance of the Mob scene.
  7. var mob = mob_scene.instance()
  8. # Choose a random location on the SpawnPath.
  9. var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
  10. # And give it a random offset.
  11. mob_spawn_location.unit_offset = randf()
  12. # Communicate the spawn location and the player's location to the mob.
  13. var player_position = $Player.transform.origin
  14. mob.initialize(mob_spawn_location.translation, player_position)
  15. # Spawn the mob by adding it to the Main scene.
  16. add_child(mob)
  17. func _on_Player_hit():
  18. $MobTimer.stop()
  1. public class Main : Node
  2. {
  3. #pragma warning disable 649
  4. [Export]
  5. public PackedScene MobScene;
  6. #pragma warning restore 649
  7. public override void _Ready()
  8. {
  9. GD.Randomize();
  10. }
  11. public void OnMobTimerTimeout()
  12. {
  13. // Create a new instance of the Mob scene.
  14. var mob = (Mob)MobScene.Instance();
  15. // Choose a random location on the SpawnPath.
  16. // We store the reference to the SpawnLocation node.
  17. var mobSpawnLocation = GetNode<PathFollow>("SpawnPath/SpawnLocation");
  18. // And give it a random offset.
  19. mobSpawnLocation.UnitOffset = GD.Randf();
  20. // Communicate the spawn location and the player's location to the mob.
  21. Vector3 playerPosition = GetNode<Player>("Player").Transform.origin;
  22. mob.Initialize(mobSpawnLocation.Translation, playerPosition);
  23. // Spawn the mob by adding it to the Main scene.
  24. AddChild(mob);
  25. }
  26. public void OnPlayerHit()
  27. {
  28. GetNode<Timer>("MobTimer").Stop();
  29. }
  30. }

然后是 Mob.gd

GDScriptC#

  1. extends KinematicBody
  2. # Emitted when the player jumped on the mob.
  3. signal squashed
  4. # Minimum speed of the mob in meters per second.
  5. export var min_speed = 10
  6. # Maximum speed of the mob in meters per second.
  7. export var max_speed = 18
  8. var velocity = Vector3.ZERO
  9. func _physics_process(_delta):
  10. move_and_slide(velocity)
  11. func initialize(start_position, player_position):
  12. look_at_from_position(start_position, player_position, Vector3.UP)
  13. rotate_y(rand_range(-PI / 4, PI / 4))
  14. var random_speed = rand_range(min_speed, max_speed)
  15. velocity = Vector3.FORWARD * random_speed
  16. velocity = velocity.rotated(Vector3.UP, rotation.y)
  17. func squash():
  18. emit_signal("squashed")
  19. queue_free()
  20. func _on_VisibilityNotifier_screen_exited():
  21. queue_free()
  1. public class Mob : KinematicBody
  2. {
  3. // Emitted when the played jumped on the mob.
  4. [Signal]
  5. public delegate void Squashed();
  6. // Minimum speed of the mob in meters per second
  7. [Export]
  8. public int MinSpeed = 10;
  9. // Maximum speed of the mob in meters per second
  10. [Export]
  11. public int MaxSpeed = 18;
  12. private Vector3 _velocity = Vector3.Zero;
  13. public override void _PhysicsProcess(float delta)
  14. {
  15. MoveAndSlide(_velocity);
  16. }
  17. public void Initialize(Vector3 startPosition, Vector3 playerPosition)
  18. {
  19. LookAtFromPosition(startPosition, playerPosition, Vector3.Up);
  20. RotateY((float)GD.RandRange(-Mathf.Pi / 4.0, Mathf.Pi / 4.0));
  21. float randomSpeed = (float)GD.RandRange(MinSpeed, MaxSpeed);
  22. _velocity = Vector3.Forward * randomSpeed;
  23. _velocity = _velocity.Rotated(Vector3.Up, Rotation.y);
  24. }
  25. public void Squash()
  26. {
  27. EmitSignal(nameof(Squashed));
  28. QueueFree();
  29. }
  30. public void OnVisibilityNotifierScreenExited()
  31. {
  32. QueueFree();
  33. }
  34. }

最后是最长的脚本 Player.gd

GDScriptC#

  1. extends KinematicBody
  2. # Emitted when a mob hit the player.
  3. signal hit
  4. # How fast the player moves in meters per second.
  5. export var speed = 14
  6. # The downward acceleration when in the air, in meters per second squared.
  7. export var fall_acceleration = 75
  8. # Vertical impulse applied to the character upon jumping in meters per second.
  9. export var jump_impulse = 20
  10. # Vertical impulse applied to the character upon bouncing over a mob in meters per second.
  11. export var bounce_impulse = 16
  12. var velocity = Vector3.ZERO
  13. func _physics_process(delta):
  14. var direction = Vector3.ZERO
  15. if Input.is_action_pressed("move_right"):
  16. direction.x += 1
  17. if Input.is_action_pressed("move_left"):
  18. direction.x -= 1
  19. if Input.is_action_pressed("move_back"):
  20. direction.z += 1
  21. if Input.is_action_pressed("move_forward"):
  22. direction.z -= 1
  23. if direction != Vector3.ZERO:
  24. direction = direction.normalized()
  25. $Pivot.look_at(translation + direction, Vector3.UP)
  26. velocity.x = direction.x * speed
  27. velocity.z = direction.z * speed
  28. # Jumping.
  29. if is_on_floor() and Input.is_action_just_pressed("jump"):
  30. velocity.y += jump_impulse
  31. velocity.y -= fall_acceleration * delta
  32. velocity = move_and_slide(velocity, Vector3.UP)
  33. for index in range(get_slide_count()):
  34. var collision = get_slide_collision(index)
  35. if collision.collider.is_in_group("mob"):
  36. var mob = collision.collider
  37. if Vector3.UP.dot(collision.normal) > 0.1:
  38. mob.squash()
  39. velocity.y = bounce_impulse
  40. func die():
  41. emit_signal("hit")
  42. queue_free()
  43. func _on_MobDetector_body_entered(_body):
  44. die()
  1. public class Player : KinematicBody
  2. {
  3. // Emitted when the player was hit by a mob.
  4. [Signal]
  5. public delegate void Hit();
  6. // How fast the player moves in meters per second.
  7. [Export]
  8. public int Speed = 14;
  9. // The downward acceleration when in the air, in meters per second squared.
  10. [Export]
  11. public int FallAcceleration = 75;
  12. // Vertical impulse applied to the character upon jumping in meters per second.
  13. [Export]
  14. public int JumpImpulse = 20;
  15. // Vertical impulse applied to the character upon bouncing over a mob in meters per second.
  16. [Export]
  17. public int BounceImpulse = 16;
  18. private Vector3 _velocity = Vector3.Zero;
  19. public override void _PhysicsProcess(float delta)
  20. {
  21. var direction = Vector3.Zero;
  22. if (Input.IsActionPressed("move_right"))
  23. {
  24. direction.x += 1f;
  25. }
  26. if (Input.IsActionPressed("move_left"))
  27. {
  28. direction.x -= 1f;
  29. }
  30. if (Input.IsActionPressed("move_back"))
  31. {
  32. direction.z += 1f;
  33. }
  34. if (Input.IsActionPressed("move_forward"))
  35. {
  36. direction.z -= 1f;
  37. }
  38. if (direction != Vector3.Zero)
  39. {
  40. direction = direction.Normalized();
  41. GetNode<Spatial>("Pivot").LookAt(Translation + direction, Vector3.Up);
  42. }
  43. _velocity.x = direction.x * Speed;
  44. _velocity.z = direction.z * Speed;
  45. // Jumping.
  46. if (IsOnFloor() && Input.IsActionJustPressed("jump"))
  47. {
  48. _velocity.y += JumpImpulse;
  49. }
  50. _velocity.y -= FallAcceleration * delta;
  51. _velocity = MoveAndSlide(_velocity, Vector3.Up);
  52. for (int index = 0; index < GetSlideCount(); index++)
  53. {
  54. KinematicCollision collision = GetSlideCollision(index);
  55. if (collision.Collider is Mob mob && mob.IsInGroup("mob"))
  56. {
  57. if (Vector3.Up.Dot(collision.Normal) > 0.1f)
  58. {
  59. mob.Squash();
  60. _velocity.y = BounceImpulse;
  61. }
  62. }
  63. }
  64. }
  65. private void Die()
  66. {
  67. EmitSignal(nameof(Hit));
  68. QueueFree();
  69. }
  70. public void OnMobDetectorBodyEntered(Node body)
  71. {
  72. Die();
  73. }
  74. }

在下一节课中我们会添加计分和重试选项,再见。