运动学角色(2D)

前言

是的,名字听起来很奇怪。“运动学角色”是什么东西?使用这个名称的原因是物理引擎问世之处,它们被称为“动力学(Dynamics)”引擎(因为它们主要处理碰撞响应)。人们做了许多尝试,想使用动力学引擎创建角色控制器,但它并不像看起来那么容易。Godot 拥有您能找到的最好的动力学角色控制器(可以在 2d/platformer 演示中查看),但使用它需要相当高水平的技能和对物理引擎的理解(或者对试验和试错有足够的耐心)。

像 Havok 这样的物理引擎似乎认为力学角色控制器是最好的选择,而其他物理引擎(PhysX)则更愿意推广运动学(Kinematic)的角色控制器。

那么区别是什么呢?:

  • 动力学角色控制器使用的是一个具有无限惯性张量的刚体。这是一个不能旋转的刚体. 物理引擎总是让物体移动和碰撞, 然后一并解决它们的碰撞. 这使得动态角色控制器能够与其他物理对象无缝交互, 就像在平台游戏演示中看到的那样. 然而, 这些互动并不总是可预测的. 碰撞可能需要多于一帧的时间来解决, 所以几个碰撞可能看起来会有很小的位移. 这些问题是可以解决的, 但需要一定的技巧.

  • 运动学角色控制器总是假设以非碰撞状态开始,并将总是移动到非碰撞状态。如果它开始时处于碰撞状态, 将像刚体一样尝试释放自己, 但这是特例, 而不是规则. 这使得它们的控制和运动更可预测, 更容易编程. 然而, 有一个缺点, 它们不能直接与其他物理对象交互, 除非在代码中手动完成.

这个简短的教程将重点介绍运动学角色控制器。基本上,传统处理冲突的方法(它并不一定在底层更简单,但隐蔽性很好,并且呈现为一个简洁漂亮的 API)。

物理过程处理

为了管理运动物体或角色的逻辑, 总是建议使用物理过程处理, 因为它在物理步骤之前被调用并且它的执行与物理服务器同步, 它也被以被每秒相同的次数调用. 这使得物理和运动计算以比使用常规过程处理更可预测的方式工作, 如果帧速率太高或太低, 则可能具有尖峰或丢失精度.

GDScriptC#

  1. extends KinematicBody2D
  2. func _physics_process(delta):
  3. pass
  1. using Godot;
  2. using System;
  3. public class PhysicsScript : KinematicBody2D
  4. {
  5. public override void _PhysicsProcess(float delta)
  6. {
  7. }
  8. }

场景设置

要进行测试, 这里有场景(来自tilemap教程): kbscene.zip. 我们将为角色创造一个新场景. 使用机器人精灵并创建一个这样的场景:

../../_images/kbscene.png

您会注意到, 在 “二维碰撞形状 “(CollisionShape2D)节点旁边有一个警告图标;那是因为我们还没有定义它的形状. 在” 二维碰撞形状”(CollisionShape2D)的形状属性中创建一个新的二维圆形形状(CircleShape2D). 点击 <二维圆形形状>(CircleShape2D)进入选项, 将半径设置为30:

../../_images/kbradius.png

注意: 正如之前在物理教程中提到的, 物理引擎无法处理大多数类型形状的缩放, 只有碰撞多边形, 平面和段才有效, 所以, 总是改变形状的半径等参数, 而不是缩放它. 对于运动体或刚性体或静态体本身也是如此, 因为它们的比例会影响形状的比例.

现在, 为这个角色创建一个脚本, 上面作为例子的那个脚本可以作为基础.

最后, 实例化tilemap中的角色场景, 并使地图场景成为主场景, 因此在按下播放时运行.

../../_images/kbinstance.png

移动动态角色

回到角色场景, 打开脚本, 魔法开始了!动态物体默认不会做任何事情, 但它有一个有用的函数, 叫做 KinematicBody2D.move_and_collide() . 该函数以一个 Vector2 作为参数, 并尝试将该运动应用到动态物体. 如果发生了碰撞, 它就在碰撞的瞬间停止.

所以, 让我们向下移动我们的精灵, 直到它撞上地板:

GDScriptC#

  1. extends KinematicBody2D
  2. func _physics_process(delta):
  3. move_and_collide(Vector2(0, 1)) # Move down 1 pixel per physics frame
  1. using Godot;
  2. using System;
  3. public class PhysicsScript : KinematicBody2D
  4. {
  5. public override void _PhysicsProcess(float delta)
  6. {
  7. // Move down 1 pixel per physics frame
  8. MoveAndCollide(new Vector2(0, 1));
  9. }
  10. }

结果是角色会移动, 但在击中地板时会停止. 很酷, 对吧?

下一步将加入重力, 这样一来, 它的行为就更像一个常规的游戏角色:

GDScriptC#

  1. extends KinematicBody2D
  2. const GRAVITY = 200.0
  3. var velocity = Vector2()
  4. func _physics_process(delta):
  5. velocity.y += delta * GRAVITY
  6. var motion = velocity * delta
  7. move_and_collide(motion)
  1. using Godot;
  2. using System;
  3. public class PhysicsScript : KinematicBody2D
  4. {
  5. const float gravity = 200.0f;
  6. Vector2 velocity;
  7. public override void _PhysicsProcess(float delta)
  8. {
  9. velocity.y += delta * gravity;
  10. var motion = velocity * delta;
  11. MoveAndCollide(motion);
  12. }
  13. }

现在人物平滑下落. 我们让它向两边行走, 在按下方向键时向左或向右. 记住, 正在使用的值(至少对于速度)单位是像素/秒.

通过向左和向右按下可以增加简单的步行支持:

GDScriptC#

  1. extends KinematicBody2D
  2. const GRAVITY = 200.0
  3. const WALK_SPEED = 200
  4. var velocity = Vector2()
  5. func _physics_process(delta):
  6. velocity.y += delta * GRAVITY
  7. if Input.is_action_pressed("ui_left"):
  8. velocity.x = -WALK_SPEED
  9. elif Input.is_action_pressed("ui_right"):
  10. velocity.x = WALK_SPEED
  11. else:
  12. velocity.x = 0
  13. # We don't need to multiply velocity by delta because "move_and_slide" already takes delta time into account.
  14. # The second parameter of "move_and_slide" is the normal pointing up.
  15. # In the case of a 2D platformer, in Godot, upward is negative y, which translates to -1 as a normal.
  16. move_and_slide(velocity, Vector2(0, -1))
  1. using Godot;
  2. using System;
  3. public class PhysicsScript : KinematicBody2D
  4. {
  5. const float gravity = 200.0f;
  6. const int walkSpeed = 200;
  7. Vector2 velocity;
  8. public override void _PhysicsProcess(float delta)
  9. {
  10. velocity.y += delta * gravity;
  11. if (Input.IsActionPressed("ui_left"))
  12. {
  13. velocity.x = -walkSpeed;
  14. }
  15. else if (Input.IsActionPressed("ui_right"))
  16. {
  17. velocity.x = walkSpeed;
  18. }
  19. else
  20. {
  21. velocity.x = 0;
  22. }
  23. // We don't need to multiply velocity by delta because "MoveAndSlide" already takes delta time into account.
  24. // The second parameter of "MoveAndSlide" is the normal pointing up.
  25. // In the case of a 2D platformer, in Godot, upward is negative y, which translates to -1 as a normal.
  26. MoveAndSlide(velocity, new Vector2(0, -1));
  27. }
  28. }

试一试.

这是平台游戏的良好起点. 可以在随引擎分发的演示zip中找到更完整的演示, 或者在https://github.com/godotengine/godot-demo-projects/tree/master/2d/kinematic\_character中找到.