Work in progress

The content of this page was not yet updated for Godot 4.2 and may be outdated. If you know how to improve this page or you can confirm that it’s up to date, feel free to open a pull request.

杀死玩家

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

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

使用 Area 节点制作攻击框

回到 player.tscn 场景,添加一个新的 Area3D 子节点。把它命名为 MobDetector(小怪检测器)。添加一个 CollisionShape3D 节点作为它的一个子节点。

image0

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

image1

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

image2

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

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

接下来,再次选择 MobDetector 节点,并在检查器中, 关闭Monitorable 属性。这使得其他物理节点无法检测到这个区域。补充的 Monitoring 属性允许它检测碰撞。然后,清除 Collision -> Layer,并将掩码设置为“enemies”层。

image3

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

image4

当一个 CharacterBody3DRigidBody3D 节点进入它时,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. hit.emit()
  7. queue_free()
  8. func _on_mob_detector_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 HitEventHandler();
  5. // ...
  6. private void Die()
  7. {
  8. EmitSignal(SignalName.Hit);
  9. QueueFree();
  10. }
  11. // We also specified this function name in PascalCase in the editor's connection window
  12. private void OnMobDetectorBodyEntered(Node3D body)
  13. {
  14. Die();
  15. }

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

GDScriptC#

  1. var player_position = $Player.position
  1. Vector3 playerPosition = GetNode<Player>("Player").Position;

由于此处没有 $Player 导致的报错!

另外请注意,敌人与玩家碰撞并死亡取决于 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. private void OnPlayerHit()
  3. {
  4. GetNode<Timer>("MobTimer").Stop();
  5. }

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

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

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

代码检查点

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

首先是 main.gd

GDScriptC#

  1. extends Node
  2. @export var mob_scene: PackedScene
  3. func _on_mob_timer_timeout():
  4. # Create a new instance of the Mob scene.
  5. var mob = mob_scene.instantiate()
  6. # Choose a random location on the SpawnPath.
  7. # We store the reference to the SpawnLocation node.
  8. var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
  9. # And give it a random offset.
  10. mob_spawn_location.progress_ratio = randf()
  11. var player_position = $Player.position
  12. mob.initialize(mob_spawn_location.position, player_position)
  13. # Spawn the mob by adding it to the Main scene.
  14. add_child(mob)
  15. func _on_player_hit():
  16. $MobTimer.stop()
  1. using Godot;
  2. public partial class Main : Node
  3. {
  4. [Export]
  5. public PackedScene MobScene { get; set; }
  6. private void OnMobTimerTimeout()
  7. {
  8. // Create a new instance of the Mob scene.
  9. Mob mob = MobScene.Instantiate<Mob>();
  10. // Choose a random location on the SpawnPath.
  11. // We store the reference to the SpawnLocation node.
  12. var mobSpawnLocation = GetNode<PathFollow3D>("SpawnPath/SpawnLocation");
  13. // And give it a random offset.
  14. mobSpawnLocation.ProgressRatio = GD.Randf();
  15. Vector3 playerPosition = GetNode<Player>("Player").Position;
  16. mob.Initialize(mobSpawnLocation.Position, playerPosition);
  17. // Spawn the mob by adding it to the Main scene.
  18. AddChild(mob);
  19. }
  20. private void OnPlayerHit()
  21. {
  22. GetNode<Timer>("MobTimer").Stop();
  23. }
  24. }

然后是 Mob.gd

GDScriptC#

  1. extends CharacterBody3D
  2. # Minimum speed of the mob in meters per second.
  3. @export var min_speed = 10
  4. # Maximum speed of the mob in meters per second.
  5. @export var max_speed = 18
  6. # Emitted when the player jumped on the mob
  7. signal squashed
  8. func _physics_process(_delta):
  9. move_and_slide()
  10. # This function will be called from the Main scene.
  11. func initialize(start_position, player_position):
  12. # We position the mob by placing it at start_position
  13. # and rotate it towards player_position, so it looks at the player.
  14. look_at_from_position(start_position, player_position, Vector3.UP)
  15. # Rotate this mob randomly within range of -90 and +90 degrees,
  16. # so that it doesn't move directly towards the player.
  17. rotate_y(randf_range(-PI / 4, PI / 4))
  18. # We calculate a random speed (integer)
  19. var random_speed = randi_range(min_speed, max_speed)
  20. # We calculate a forward velocity that represents the speed.
  21. velocity = Vector3.FORWARD * random_speed
  22. # We then rotate the velocity vector based on the mob's Y rotation
  23. # in order to move in the direction the mob is looking.
  24. velocity = velocity.rotated(Vector3.UP, rotation.y)
  25. func _on_visible_on_screen_notifier_3d_screen_exited():
  26. queue_free()
  27. func squash():
  28. squashed.emit()
  29. queue_free() # Destroy this node
  1. using Godot;
  2. public partial class Mob : CharacterBody3D
  3. {
  4. // Emitted when the played jumped on the mob.
  5. [Signal]
  6. public delegate void SquashedEventHandler();
  7. // Minimum speed of the mob in meters per second
  8. [Export]
  9. public int MinSpeed { get; set; } = 10;
  10. // Maximum speed of the mob in meters per second
  11. [Export]
  12. public int MaxSpeed { get; set; } = 18;
  13. public override void _PhysicsProcess(double delta)
  14. {
  15. MoveAndSlide();
  16. }
  17. // This function will be called from the Main scene.
  18. public void Initialize(Vector3 startPosition, Vector3 playerPosition)
  19. {
  20. // We position the mob by placing it at startPosition
  21. // and rotate it towards playerPosition, so it looks at the player.
  22. LookAtFromPosition(startPosition, playerPosition, Vector3.Up);
  23. // Rotate this mob randomly within range of -90 and +90 degrees,
  24. // so that it doesn't move directly towards the player.
  25. RotateY((float)GD.RandRange(-Mathf.Pi / 4.0, Mathf.Pi / 4.0));
  26. // We calculate a random speed (integer)
  27. int randomSpeed = GD.RandRange(MinSpeed, MaxSpeed);
  28. // We calculate a forward velocity that represents the speed.
  29. Velocity = Vector3.Forward * randomSpeed;
  30. // We then rotate the velocity vector based on the mob's Y rotation
  31. // in order to move in the direction the mob is looking.
  32. Velocity = Velocity.Rotated(Vector3.Up, Rotation.Y);
  33. }
  34. public void Squash()
  35. {
  36. EmitSignal(SignalName.Squashed);
  37. QueueFree(); // Destroy this node
  38. }
  39. private void OnVisibilityNotifierScreenExited()
  40. {
  41. QueueFree();
  42. }
  43. }

最后是最长的脚本 Player.gd

GDScriptC#

  1. extends CharacterBody3D
  2. signal hit
  3. # How fast the player moves in meters per second
  4. @export var speed = 14
  5. # The downward acceleration while in the air, in meters per second squared.
  6. @export var fall_acceleration = 75
  7. # Vertical impulse applied to the character upon jumping in meters per second.
  8. @export var jump_impulse = 20
  9. # Vertical impulse applied to the character upon bouncing over a mob
  10. # in meters per second.
  11. @export var bounce_impulse = 16
  12. var target_velocity = Vector3.ZERO
  13. func _physics_process(delta):
  14. # We create a local variable to store the input direction
  15. var direction = Vector3.ZERO
  16. # We check for each move input and update the direction accordingly
  17. if Input.is_action_pressed("move_right"):
  18. direction.x = direction.x + 1
  19. if Input.is_action_pressed("move_left"):
  20. direction.x = direction.x - 1
  21. if Input.is_action_pressed("move_back"):
  22. # Notice how we are working with the vector's x and z axes.
  23. # In 3D, the XZ plane is the ground plane.
  24. direction.z = direction.z + 1
  25. if Input.is_action_pressed("move_forward"):
  26. direction.z = direction.z - 1
  27. # Prevent diagonal moving fast af
  28. if direction != Vector3.ZERO:
  29. direction = direction.normalized()
  30. $Pivot.look_at(position + direction, Vector3.UP)
  31. # Ground Velocity
  32. target_velocity.x = direction.x * speed
  33. target_velocity.z = direction.z * speed
  34. # Vertical Velocity
  35. if not is_on_floor(): # If in the air, fall towards the floor. Literally gravity
  36. target_velocity.y = target_velocity.y - (fall_acceleration * delta)
  37. # Jumping.
  38. if is_on_floor() and Input.is_action_just_pressed("jump"):
  39. target_velocity.y = jump_impulse
  40. # Iterate through all collisions that occurred this frame
  41. # in C this would be for(int i = 0; i < collisions.Count; i++)
  42. for index in range(get_slide_collision_count()):
  43. # We get one of the collisions with the player
  44. var collision = get_slide_collision(index)
  45. # If the collision is with ground
  46. if collision.get_collider() == null:
  47. continue
  48. # If the collider is with a mob
  49. if collision.get_collider().is_in_group("mob"):
  50. var mob = collision.get_collider()
  51. # we check that we are hitting it from above.
  52. if Vector3.UP.dot(collision.get_normal()) > 0.1:
  53. # If so, we squash it and bounce.
  54. mob.squash()
  55. target_velocity.y = bounce_impulse
  56. # Prevent further duplicate calls.
  57. break
  58. # Moving the Character
  59. velocity = target_velocity
  60. move_and_slide()
  61. # And this function at the bottom.
  62. func die():
  63. hit.emit()
  64. queue_free()
  65. func _on_mob_detector_body_entered(body):
  66. die()
  1. using Godot;
  2. public partial class Player : CharacterBody3D
  3. {
  4. // Emitted when the player was hit by a mob.
  5. [Signal]
  6. public delegate void HitEventHandler();
  7. // How fast the player moves in meters per second.
  8. [Export]
  9. public int Speed { get; set; } = 14;
  10. // The downward acceleration when in the air, in meters per second squared.
  11. [Export]
  12. public int FallAcceleration { get; set; } = 75;
  13. // Vertical impulse applied to the character upon jumping in meters per second.
  14. [Export]
  15. public int JumpImpulse { get; set; } = 20;
  16. // Vertical impulse applied to the character upon bouncing over a mob in meters per second.
  17. [Export]
  18. public int BounceImpulse { get; set; } = 16;
  19. private Vector3 _targetVelocity = Vector3.Zero;
  20. public override void _PhysicsProcess(double delta)
  21. {
  22. // We create a local variable to store the input direction.
  23. var direction = Vector3.Zero;
  24. // We check for each move input and update the direction accordingly.
  25. if (Input.IsActionPressed("move_right"))
  26. {
  27. direction.X += 1.0f;
  28. }
  29. if (Input.IsActionPressed("move_left"))
  30. {
  31. direction.X -= 1.0f;
  32. }
  33. if (Input.IsActionPressed("move_back"))
  34. {
  35. // Notice how we are working with the vector's X and Z axes.
  36. // In 3D, the XZ plane is the ground plane.
  37. direction.Z += 1.0f;
  38. }
  39. if (Input.IsActionPressed("move_forward"))
  40. {
  41. direction.Z -= 1.0f;
  42. }
  43. // Prevent diagonal moving fast af
  44. if (direction != Vector3.Zero)
  45. {
  46. direction = direction.Normalized();
  47. GetNode<Node3D>("Pivot").LookAt(Position + direction, Vector3.Up);
  48. }
  49. // Ground Velocity
  50. _targetVelocity.X = direction.X * Speed;
  51. _targetVelocity.Z = direction.Z * Speed;
  52. // Vertical Velocity
  53. if (!IsOnFloor()) // If in the air, fall towards the floor. Literally gravity
  54. {
  55. _targetVelocity.Y -= FallAcceleration * (float)delta;
  56. }
  57. // Jumping.
  58. if (IsOnFloor() && Input.IsActionJustPressed("jump"))
  59. {
  60. _targetVelocity.Y = JumpImpulse;
  61. }
  62. // Iterate through all collisions that occurred this frame.
  63. for (int index = 0; index < GetSlideCollisionCount(); index++)
  64. {
  65. // We get one of the collisions with the player.
  66. KinematicCollision3D collision = GetSlideCollision(index);
  67. // If the collision is with a mob.
  68. if (collision.GetCollider() is Mob mob)
  69. {
  70. // We check that we are hitting it from above.
  71. if (Vector3.Up.Dot(collision.GetNormal()) > 0.1f)
  72. {
  73. // If so, we squash it and bounce.
  74. mob.Squash();
  75. _targetVelocity.Y = BounceImpulse;
  76. // Prevent further duplicate calls.
  77. break;
  78. }
  79. }
  80. }
  81. // Moving the Character
  82. Velocity = _targetVelocity;
  83. MoveAndSlide();
  84. }
  85. private void Die()
  86. {
  87. EmitSignal(SignalName.Hit);
  88. QueueFree();
  89. }
  90. private void OnMobDetectorBodyEntered(Node3D body)
  91. {
  92. Die();
  93. }
  94. }

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

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.