move_and_collide()与move_and_slide()

KinematicBody2D有move_and_collide()与move_and_slide()两个关于移动与碰撞方法。
image.png
你可以看到两者的返回值类型是不同的。

move_and_slide()

平常使用时,move_and_slide()更像是一个简易函数,用于快速的实现KinematicBody2D移动和碰撞。它的行为就是:

  • 以给定的线速度进行移动
  • 如果指定了上的方向,就同时界定了什么是地板、墙和天花板
  • 用它进行移动,如果碰到地面、墙体和天花板等地方,它既不会像小球一样反弹,也不会像一块石头一样掉在地上就不动了。如果遇到斜坡等地方,它会尽可能的滑动。

    move_and_slide()的写法及其参数意义

    代码来自:官方文档 - 使用KinematicBody2D - 平台运动
    image.png ```swift extends KinematicBody2D

export (int)var run speed = 100 export (int)var jump_speed = -400 export (int)var gravity = 1200

var velocity = Vector2() var jumping = false # 是否正在跳跃

func 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’)

  1. if jump and is_on_floor(): # 按下跳跃键但还未起跳时
  2. jumping = true
  3. velocity.y = jump_speed
  4. if right:
  5. velocity.x += run_speed
  6. if left:
  7. velocity.x -= run_speed

func _physics_process(delta): get_input() velocity.y + =gravity * delta # 模拟垂直方向上受重力下落 if jumping and is_on_floor(): # 也就是上次跳跃完落地之时,此时jumpingi还处于true,is_on_floor()也是true jumping = false velocity = move_and_slide(velocity) # 没有限定up_direction,则默认为Vector2(0,0),则四面皆是墙

  1. <a name="igDX4"></a>
  2. ## 关于墙(wall)、天花板(ceiling)、地板(floor)
  3. move_and_slide()有一个参数up_direction<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/8438332/1652857326935-2c222546-d770-4ec8-8108-54b175defa3d.png#clientId=uc6d55e0e-e7e0-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=102&id=B4w3E&margin=%5Bobject%20Object%5D&name=image.png&originHeight=246&originWidth=1373&originalType=binary&ratio=1&rotation=0&showTitle=false&size=74477&status=done&style=none&taskId=u0d479454-541a-41cd-b630-860c3674d13&title=&width=572.083310600785)
  4. | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/8438332/1652871793481-6f77a7ee-0ecc-4c8e-8ccc-69102d0e3cca.png#clientId=u0adef509-b4d2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=176&id=rgPW1&margin=%5Bobject%20Object%5D&name=image.png&originHeight=422&originWidth=622&originalType=binary&ratio=1&rotation=0&showTitle=false&size=24555&status=done&style=none&taskId=u7085e3bb-15f8-4f78-ba28-ebc6d3c4d76&title=&width=259.166656368309) | 默认情况下,可以被看做是俯视角游戏,四面都是墙,没有天花板和地板。 |
  5. | --- | --- |
  6. | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/8438332/1652871804992-4beb0035-ca61-44be-b0ed-600fbda62bd4.png#clientId=u0adef509-b4d2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=175&id=VuM62&margin=%5Bobject%20Object%5D&name=image.png&originHeight=421&originWidth=628&originalType=binary&ratio=1&rotation=0&showTitle=false&size=31586&status=done&style=none&taskId=u664658b0-bcff-463d-b0f3-b8ec29fc8df&title=&width=261.6666562689679) | 2D平台游戏中,通常设定up_direction为Vector2.UP,此时,下面是地板,上面是天花板,左右是墙。 |
  7. | --- | --- |
  8. | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/8438332/1652872889810-e0e4df1c-1064-440d-b9eb-09f4a43ff058.png#clientId=u0adef509-b4d2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=360&id=g7vR1&margin=%5Bobject%20Object%5D&name=image.png&originHeight=865&originWidth=1030&originalType=binary&ratio=1&rotation=0&showTitle=false&size=133245&status=done&style=none&taskId=uadf9d991-ef7c-4c13-8337-68fa29ad34c&title=&width=429.1666496131162) | 并且还有另一个参数floor_max_angle来设置墙和地板之间的临界值。<br />默认为45°,也就是说>45°的斜坡会被认为是墙,而<45°则会被认为是地板。 |
  9. | --- | --- |
  10. | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/8438332/1652857767725-a705f4c2-54e9-40df-9966-89b11d710dd3.png#clientId=uc6d55e0e-e7e0-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=102&id=l4yVY&margin=%5Bobject%20Object%5D&name=image.png&originHeight=246&originWidth=882&originalType=binary&ratio=1&rotation=0&showTitle=false&size=24255&status=done&style=none&taskId=u3938989d-8fa4-434f-9f4d-f50dee66a16&title=&width=367.4999853968626) | KinematicBody2D有三个方法,用于判断当前KinematicBody2D到底是碰撞到了什么,是墙、是地面还是天花板。 |
  11. | --- | --- |
  12. | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/8438332/1652857774590-dded0eb3-d264-4c30-b3d3-d27e528dbfc3.png#clientId=uc6d55e0e-e7e0-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=201&id=KWddi&margin=%5Bobject%20Object%5D&name=image.png&originHeight=712&originWidth=1360&originalType=binary&ratio=1&rotation=0&showTitle=false&size=73256&status=done&style=none&taskId=ub2263cad-0fae-406b-ac97-5e93c26a8e7&title=&width=383.03125) | 对于平台跳跃游戏,可以有一个更直观的图解。 |
  13. | --- | --- |
  14. <a name="Kt44j"></a>
  15. ## move_and_collide()
  16. | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/8438332/1652873384084-e44b5ba1-5ed1-4f93-96cd-6a3fa073ca84.png#clientId=u0adef509-b4d2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=356&id=BjQ4M&margin=%5Bobject%20Object%5D&name=image.png&originHeight=854&originWidth=1336&originalType=binary&ratio=1&rotation=0&showTitle=false&size=128189&status=done&style=none&taskId=u47ad1ce2-111b-4620-b1ba-538a4b5d35a&title=&width=556.6666445467216) | move_and_collide()返回的是一个 KinematicCollision2D类型,它提供KinematicBody2D发生碰撞后的数据,可以在发生碰撞后,获取到被碰撞物、碰撞点以及法线等的信息,并以此为基础进行高度自定义的碰撞响应,自由的返回新的速度向量。 |
  17. | --- | --- |
  18. <a name="g7ejh"></a>
  19. ## 实现碰撞后滑动、反弹以及反射
  20. 由于move_and_collide()是基于速度向量进行移动和碰撞检测,所以我们实现效果是基于对速度向量的修改。<br />而2D的速度向量是Vector2类型,也就是二维向量。因此我们要借助的就是Vector2的三个方法。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/8438332/1652857052581-3eb82b0b-dba9-43e8-80a1-1030a44d642f.png#clientId=uc6d55e0e-e7e0-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=175&id=P8F02&margin=%5Bobject%20Object%5D&name=image.png&originHeight=420&originWidth=887&originalType=binary&ratio=1&rotation=0&showTitle=false&size=59658&status=done&style=none&taskId=ud475f47d-5b92-4ffe-a3e4-5c4779bc26d&title=&width=369.5833186474117)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/8438332/1652857195683-55673745-3bbc-4691-8c79-d3a0ed629143.png#clientId=uc6d55e0e-e7e0-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=129&id=DMTwL&margin=%5Bobject%20Object%5D&name=image.png&originHeight=310&originWidth=1360&originalType=binary&ratio=1&rotation=0&showTitle=false&size=31601&status=done&style=none&taskId=u94a9eafa-eab8-4a7c-b1d0-10ad719a9e1&title=&width=566.6666441493574)<br />其中slide()就类似于move_and_slide(),而bounce()进行反弹,而reflect()进行反射。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/8438332/1652857003312-1871b784-a89a-4b10-b9be-78a3ff001f7a.png#clientId=uc6d55e0e-e7e0-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=187&id=EueqO&margin=%5Bobject%20Object%5D&name=image.png&originHeight=448&originWidth=853&originalType=binary&ratio=1&rotation=0&showTitle=false&size=59771&status=done&style=none&taskId=ufd54638a-1cb5-4916-9f12-1715812050a&title=&width=355.4166525436778)<br />关于Vector2的reflect()<br />通过timothyqiu兄指正,reflect()的参数为反射平面,而非平面的法线,官方文档和内置文档存在谬误.<br />那么问题来了,对于同一个假想平面,有两个方向相反的向量,那么Vector2的reflect()对于这两个向量,是否会返回一致的向量呢?实际测试结果是:两者是一样的。笔者采用水平方向的Vector2.RIGHT和Vector2.LEFT测试,reflect()返回的结果向量都是一样的.<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/8438332/1652857217484-8c7d20ff-fea7-4763-90fc-32ee3eacaa90.png#clientId=uc6d55e0e-e7e0-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=257&id=Rxoui&margin=%5Bobject%20Object%5D&name=image.png&originHeight=617&originWidth=864&originalType=binary&ratio=1&rotation=0&showTitle=false&size=33729&status=done&style=none&taskId=u61305981-a0c3-4334-bf1d-7b5d9e7d635&title=&width=359.9999856948858)
  21. ```swift
  22. func _physics_process(delta):
  23. if can_move:
  24. var collision = collider.move_and_collide(velocity * delta)
  25. if collision:#发生碰撞
  26. velocity = velocity.slide(collision.normal)
  1. extends Node2D
  2. var direction = Vector2.ONE
  3. var speed = 100.0
  4. var velocity = Vector2.ZERO
  5. onready var collider = $碰撞体
  6. onready var plane = $平面
  7. func _ready()
  8. velocity = direction * speed # 初始的速度向量
  9. func _physics_process(delta):
  10. var collision = collider.move_and_collide(velocity * delta)
  11. if collision: # 发生碰撞
  12. velocity = velocity.bounce(collision.normal) # 实现基于被碰撞物体的碰撞形状上碰撞,点法线进行反弹

image.pngimage.png

move_and_slide()获取碰撞对象

当使用 move_and_slide() 时, 有可能发生多次碰撞, 因为滑动响应是计算出来的.
要处理这些碰撞, 可以使用 get_slide_count() 和 get_slide_collision():

  1. velocity = move_and_slide(velocity)
  2. for i in get_slide_count():
  3. var collision = get_slide_collision(i)
  4. print("I collided with ", collision.collider.name)

image.png