使用 KinematicBody2D
前言
KinematicBody2D 节点, 并展示一些如何使用它的例子. 注解 物理介绍 .
什么是运动体?
KinematicBody2D 用于实现通过代码控制的物体. 运动体在移动时可以检测到与其他物体的碰撞, 但不受引擎物理属性的影响, 比如重力或摩擦. 虽然这意味着你必须编写一些代码来创建它们的行为, 但这也意味着你可以更精确地控制它们如何移动和反应.
小技巧
KinematicBody2D 可以受到重力和其他力的影响, 但您必须在代码中计算它的运动. 物理引擎不会移动 KinematicBody2D.
运动与碰撞
KinematicBody2D 时, 你不应该直接设置它的 position 属性, 而是使用 move_and_collide() 或 move_and_slide() 方法. 这些方法沿着给定的向量移动物体, 如果检测到与另一个体发生碰撞, 则立即停止. 在KinematicBody2D发生碰撞后, 任何 碰撞响应 必须手动编码.
警告
_physics_process() 回调中做Kinematic物体运动.
这两种运动方法有不同的作用, 在后面的教程中, 你会看到它们如何工作的例子.
move_and_collide
Vector2 参数以表示物体的相对运动. 通常, 这是您的速度向量乘以帧时间步长( delta ). 如果在沿着此向量方向的任何位置, 引擎检测到碰撞, 则物体将立即停止移动. 如果发生这种情况, 该方法将返回 KinematicCollision2D 对象.
KinematicCollision2D 是一个包含碰撞和碰撞对象数据的对象. 使用这些数据, 你可以计算出你的碰撞响应.
move_and_slide
move_and_slide() 方法旨在简化常见情况下的碰撞响应, 即你希望一个物体沿着另一个物体滑动. 例如, 在平台游戏或自上而下的游戏中, 它特别有用.
小技巧
move_and_slide() 使用 delta 自动计算基于帧的运动. 在将速度向量传递给 move_and_slide() 之前, 请 不要 将速度向量乘以 delta.
move_and_slide() 还有许多其他参数, 允许您自定义滑动行为:
up_direction- 默认值:Vector2( 0, 0 )is_on_floor(),is_on_wall()和is_on_ceiling()方法来检测物体接触的表面类型. 默认值意味着所有的表面都被认为是墙壁.stop_on_slope- 默认值:false该参数可以防止物体站立不动时从斜坡上滑落.
max_slides- 默认值:4这个参数是物体停止移动前的最大碰撞次数. 设置太低可能会完全阻止移动.
floor_max_angle- 默认值:0.785398(以弧度表示, 相当于45度)这是表面不再被视为 “地板” 之前的最大角度
infinite_inertia- 默认值:truetrue时, 本体可以推动 RigidBody2D 节点, 忽略其质量, 不会检测到与它们的碰撞. 如果是false, 本体会与刚体发生碰撞而停止.move_and_slide_with_snapsap参数, 给move_and_slide()增加了一些额外的功能. 只要这个向量与地面接触, 物体就会保持在表面上. 注意, 这意味着你必须在例如跳跃时禁用捕捉. 你可以将sap设置为Vector2.ZERO或者使用move_and_slide()代替.检测碰撞
move_and_collide()时, 函数直接返回一个KinematicCollision2D, 你可以在代码中使用这个.move_and_slide()时, 有可能发生多次碰撞, 因为滑动响应是计算出来的. 要处理这些碰撞, 使用get_slide_count()和get_slide_collision(): GDScript
注解 get_slide_count() 只计算物体碰撞和改变方向的次数. KinematicCollision2D .# Using move_and_collide.var collision = move_and_collide(velocity * delta)if collision: print("I collided with ", collision.collider.name)# Using move_and_slide.velocity = move_and_slide(velocity)for i in get_slide_count(): var collision = get_slide_collision(i) print("I collided with ", collision.collider.name)
使用哪种运动方式?
move_and_slide(), 因为它 “更简单” , 但情况不一定如此. 有一种思路是,move_and_slide()是一种特殊情况, 而move_and_collide()更通用. 例如, 下面两个代码片段的结果是相同的碰撞响应: GDScript C## using move_and_collidevar collision = move_and_collide(velocity * delta)if collision: velocity = velocity.slide(collision.normal)# using move_and_slidevelocity = move_and_slide(velocity)
// using MoveAndCollidevar collision = MoveAndCollide(velocity * delta);if (collision != null){ velocity = velocity.Slide(collision.Normal);}// using MoveAndSlidevelocity = MoveAndSlide(velocity);
move_and_slide()做的任何事情都可以用move_and_collide()来完成, 但它可能需要更多的代码. 但是, 正如我们在下面的示例中将看到的, 有些情况下move_and_slide()不能提供您想要的响应.move_and_slide()返回的速度赋值给velocity变量. 这是因为当角色与环境发生碰撞时, 函数会在内部重新计算速度, 以反映减速的情况. 例如, 如果角色倒在地上, 不希望它因为重力的影响而积累垂直速度, 而希望它的垂直速度重置为零.move_and_slide()还可以在循环中多次重新计算运动体的速度, 为了产生一个平滑的运动, 它默认会移动角色, 并碰撞5次, 在这个过程结束时, 函数返回角色的新速度, 可以将其存储在velocity变量中, 并在下一帧中使用.示例
using_kinematic2d.zip.移动和墙壁
如果你已经下载了示例项目, 这个例子在 “BasicMovement.tscn” 中.KinematicBody2D, 有两个子级:Sprite和CollisionShape2D. 使用Godot “icon.png” 作为Sprite的纹理, 将其从文件系统栏拖到Sprite的 Texture 属性. 在CollisionShape2D的 Shape 属性中, 选择 “New RectangleShape2D” , 并将矩形的大小调整到适合sprite图像的大小. 注解 2D 运动概述 . 将脚本附加到KinematicBody2D并添加以下代码: GDScript C#extends KinematicBody2Dvar speed = 250var velocity = Vector2()func get_input(): # Detect up/down/left/right keystate and only move when pressed. velocity = Vector2() if Input.is_action_pressed('ui_right'): velocity.x += 1 if Input.is_action_pressed('ui_left'): velocity.x -= 1 if Input.is_action_pressed('ui_down'): velocity.y += 1 if Input.is_action_pressed('ui_up'): velocity.y -= 1 velocity = velocity.normalized() * speedfunc _physics_process(delta): get_input() move_and_collide(velocity * delta)
using Godot;using System;public class KBExample : KinematicBody2D{ public int Speed = 250; private Vector2 _velocity = new Vector2(); public void GetInput() { // Detect up/down/left/right keystate and only move when pressed _velocity = new Vector2(); if (Input.IsActionPressed("ui_right")) _velocity.x += 1; if (Input.IsActionPressed("ui_left")) _velocity.x -= 1; if (Input.IsActionPressed("ui_down")) _velocity.y += 1; if (Input.IsActionPressed("ui_up")) _velocity.y -= 1; } public override void _PhysicsProcess(float delta) { GetInput(); MoveAndCollide(_velocity * delta); }}
move_and_collide()按预期工作, 沿着速度向量方向移动物体. 现在让我们看看当您添加一些障碍时会发生什么. 添加一个具有矩形碰撞形状的 StaticBody2D . 为了可见性, 您可以使用精灵,Polygon2D, 或从 “调试” 菜单中打开 “可见碰撞形状”.KinematicBody2D无法穿透障碍物. 但是, 尝试以某个角度进入障碍物, 您会发现障碍物就像胶水一样 - 感觉物体被卡住了. 碰撞响应 .move_and_collide()在碰撞发生时停止物体的运动. 我们需要编写我们想要的碰撞响应.move_and_slide(velocity)并再次运行. 请注意, 我们从速度计算中删除了 “delta”.move_and_slide()提供了一个沿碰撞对象滑动物体的默认碰撞响应. 这对于许多游戏类型都很有用, 并且可能是获得所需行为所需的全部内容.弹跳/反射
如果您不想要滑动碰撞响应怎么办? 对于这个示例(示例项目中的 “BounceandCollide.tscn”), 我们有一个角色射击子弹, 我们希望子弹从墙上反弹. 此示例使用三个场景. 主场景包含游戏角色和墙壁. 子弹和墙是单独的场景, 以便它们可以实例化.move_and_slide(): GDScript C#extends KinematicBody2Dvar Bullet = preload("res://Bullet.tscn")var speed = 200var velocity = Vector2()func get_input(): # Add these actions in Project Settings -> Input Map. velocity = Vector2() if Input.is_action_pressed('backward'): velocity = Vector2(-speed/3, 0).rotated(rotation) if Input.is_action_pressed('forward'): velocity = Vector2(speed, 0).rotated(rotation) if Input.is_action_just_pressed('mouse_click'): shoot()func shoot(): # "Muzzle" is a Position2D placed at the barrel of the gun. var b = Bullet.instance() b.start($Muzzle.global_position, rotation) get_parent().add_child(b)func _physics_process(delta): get_input() var dir = get_global_mouse_position() - global_position # Don't move if too close to the mouse pointer. if dir.length() > 5: rotation = dir.angle() velocity = move_and_slide(velocity)
子弹的代码: GDScript C#using Godot;using System;public class KBExample : KinematicBody2D{ private PackedScene _bullet = (PackedScene)GD.Load("res://Bullet.tscn"); public int Speed = 200; private Vector2 _velocity = new Vector2(); public void GetInput() { // add these actions in Project Settings -> Input Map _velocity = new Vector2(); if (Input.IsActionPressed("backward")) { _velocity = new Vector2(-Speed/3, 0).Rotated(Rotation); } if (Input.IsActionPressed("forward")) { _velocity = new Vector2(Speed, 0).Rotated(Rotation); } if (Input.IsActionPressed("mouse_click")) { Shoot(); } } public void Shoot() { // "Muzzle" is a Position2D placed at the barrel of the gun var b = (Bullet)_bullet.Instance(); b.Start(GetNode<Node2D>("Muzzle").GlobalPosition, Rotation); GetParent().AddChild(b); } public override void _PhysicsProcess(float delta) { GetInput(); var dir = GetGlobalMousePosition() - GlobalPosition; // Don't move if too close to the mouse pointer if (dir.Length() > 5) { Rotation = dir.Angle(); _velocity = MoveAndSlide(_velocity); } }}
extends KinematicBody2Dvar speed = 750var velocity = Vector2()func start(pos, dir): rotation = dir position = pos velocity = Vector2(speed, 0).rotated(rotation)func _physics_process(delta): var collision = move_and_collide(velocity * delta) if collision: velocity = velocity.bounce(collision.normal) if collision.collider.has_method("hit"): collision.collider.hit()func _on_VisibilityNotifier2D_screen_exited(): queue_free()
using Godot;using System;public class Bullet : KinematicBody2D{ public int Speed = 750; private Vector2 _velocity = new Vector2(); public void Start(Vector2 pos, float dir) { Rotation = dir; Position = pos; _velocity = new Vector2(speed, 0).Rotated(Rotation); } public override void _PhysicsProcess(float delta) { var collision = MoveAndCollide(_velocity * delta); if (collision != null) { _velocity = _velocity.Bounce(collision.Normal); if (collision.Collider.HasMethod("Hit")) { collision.Collider.Call("Hit"); } } } public void OnVisibilityNotifier2DScreenExited() { QueueFree(); }}
_physics_process()中. 在使用move_and_collide()后, 如果发生碰撞, 将返回一个KinematicCollision2D对象, 否则, 返回Nil.normal来反映子弹的velocity和Vector2.bounce()方法.collider)有一个hit方法, 我们也调用它. 在示例项目中, 我们为墙壁添加了一个颜色闪烁效果来演示这一点.平台运动
move_and_slide()非常适合快速启动和运行功能字符控制器. 如果您已下载示例项目, 可以在 “Platformer.tscn” 中找到它.StaticBody2D对象构成的级别. 它们可以是任何形状和大小. 在示例项目中, 我们使用 Polygon2D 来创建平台形状. 这是游戏角色物体的代码: GDScript C#extends KinematicBody2Dexport (int) var run_speed = 100export (int) var jump_speed = -400export (int) var gravity = 1200var velocity = Vector2()var jumping = falsefunc get_input(): velocity.x = 0 var right = Input.is_action_pressed('ui_right') var left = Input.is_action_pressed('ui_left') var jump = Input.is_action_just_pressed('ui_select') if jump and is_on_floor(): jumping = true velocity.y = jump_speed if right: velocity.x += run_speed if left: velocity.x -= run_speedfunc _physics_process(delta): get_input() velocity.y += gravity * delta if jumping and is_on_floor(): jumping = false velocity = move_and_slide(velocity, Vector2(0, -1))
using Godot;using System;public class KBExample : KinematicBody2D{ [Export] public int RunSpeed = 100; [Export] public int JumpSpeed = -400; [Export] public int Gravity = 1200; Vector2 velocity = new Vector2(); bool jumping = false; public void GetInput() { velocity.x = 0; bool right = Input.IsActionPressed("ui_right"); bool left = Input.IsActionPressed("ui_left"); bool jump = Input.IsActionPressed("ui_select"); if (jump && IsOnFloor()) { jumping = true; velocity.y = JumpSpeed; } if (right) velocity.x += RunSpeed; if (left) velocity.x -= RunSpeed; } public override void _PhysicsProcess(float delta) { GetInput(); velocity.y += Gravity * delta; if (jumping && IsOnFloor()) jumping = false; velocity = MoveAndSlide(velocity, new Vector2(0, -1)); }}
move_and_slide()时, 该函数返回一个向量, 代表滑动碰撞发生后剩余的运动. 将该值设置返回角色的velocity, 我们就可以顺利地在斜坡上和斜坡下移动. 试着去掉velocity =, 看看如果不这样做会发生什么.Vector2(0, -1)作为地板法线. 这是向上指向的向量. 因此, 如果角色与具有该法线的物体相撞, 它将被视为地板.is_on_floor()来使跳跃工作. 这个函数只有在发生move_and_slide()碰撞后, 碰撞体的法线在给定的地板向量45度以内时才会返回true. 你可以通过设置floor_max_angle来控制最大角度.is_on_wall()实现其他功能, 比如墙面跳跃.
