同一节点中的碰撞检测和重叠检测?
Collision detection and overlapping detection in same node?
是否有可能使单个节点像 RigidBody2D
一样发生碰撞,但同时在禁用碰撞时它像 Area2D
节点一样工作并且可以检测重叠?
例如:
我有一个附属物,它的行为就像一个 Area2D
节点,但是当它被切断时,它就像它自己的 RigidBody2D
节点一样。
我考虑过的一种方法是在剪切时创建 RigidBody2D
节点的新附属物,并从具有 Area2D
节点的旧附属物转移 CollisionShape2D
。
但我不知道哪种方法耗电最少
有没有更好的方法来实现这个?
blade 和头部 RigidBody2D
,
两者最初都将 CollisionPolygon2D
禁用设置为 true,一旦 blade 掉落并且头部检测到重叠退出,disabled
将设置为 false 并且头部被切碎关闭(与 parent 节点分开)并弹开。
“人造Body2D”
我们将定制一个 Node
,我称之为 FauxBody2D
。它将作为 RigidBody2D
与 CollisionShape
一起工作,作为 Area2D
与 相同 CollisionShape
。为了存档,我们将使用 Physics2DServer
.
虽然RigidBody2D
和Area2D
的第一个共同祖先class是CollisionObject2D
,但不方便扩展CollisionObject2D
。所以 FauxBody2D
将是 Node2D
.
类型
因此创建一个新脚本faux_body.gd
。然而,它并不打算直接在场景树中使用(你可以,但你不能扩展它的代码),而是使用它添加一个 Node2D
和一个新脚本并将它设置为 extends FauxBody2D
.
您将能够使用 FauxBody2D
的变量并以不受欢迎的方式对其进行处理。事实上,即使我声明了 setter,如果您不使用 self
,您的脚本也会绕过它们。例如,不要设置 applied_force
,而是设置 self.applied_force
。顺便说一句,有些方法留空供您在脚本中覆盖(它们是“虚拟的”)。
这些是我们在 faux_body.gd
中的第一行代码:
class_name FauxBody2D
extends Node2D
我会避免重复代码。
模仿RigidBody2D
我正在跳过 rough
、absorbent
。同样在这个答案中,我只显示了区域的监控和信号。见 the followup answer.
我们将在 _enter_tree
中创建一个 body 并在 _exit_tree
中释放它:
var _body:RID
var _invalid_rid:RID
func _enter_tree() -> void:
_body = Physics2DServer.body_create()
func _exit_tree() -> void:
Physics2DServer.free_rid(_body)
_body = _invalid_rid
没有表达式可以清零 RID
。我会声明一个 _invalid_rid
并且从不设置它,所以它总是归零。
此外 body 应该与 FauxBody2D
相同 space:
func _enter_tree() -> void:
# …
Physics2DServer.body_set_space(_body, get_world_2d().space)
模仿CollisionShape2D
接下来让我们实现 CollisionShape2D
:
的逻辑
export var shape:Shape2D setget set_shape
export var disabled:bool setget set_disabled
export var one_way_collision:bool setget set_one_way_collision
export(float, 0.0, 128.0) var one_way_collision_margin:float setget set_one_way_collision_margin
var _shape:RID
func _enter_tree() -> void:
# …
_update_shape()
func _update_shape() -> void:
var new_shape = _invalid_rid if shape == null else shape.get_rid()
if new_shape == _shape:
return
if _shape.get_id() != 0:
Physics2DServer.body_remove_shape(_body, 0)
_shape = new_shape
if _shape.get_id() != 0:
Physics2DServer.body_add_shape(_body, _shape, Transform2D.IDENTITY, disabled)
Physics2DServer.body_set_shape_as_one_way_collision(_body, 0, one_way_collision, one_way_collision_margin)
func set_shape(new_value:Shape2D) -> void:
if shape == new_value:
return
shape = new_value
if _body.get_id() == 0:
return
_update_shape()
func set_disabled(new_value:bool) -> void:
if disabled == new_value:
return
disabled = new_value
if _body.get_id() == 0:
return
if _shape.get_id() != 0:
Physics2DServer.body_set_shape_disabled(_body, 0, disabled)
func set_one_way_collision(new_value:bool) -> void:
if one_way_collision == new_value:
return
one_way_collision = new_value
if _body.get_id() == 0:
return
if _shape.get_id() != 0:
Physics2DServer.body_set_shape_as_one_way_collision(_body, 0, one_way_collision, one_way_collision_margin)
func set_one_way_collision_margin(new_value:float) -> void:
if one_way_collision_margin == new_value:
return
one_way_collision_margin = new_value
if _body.get_id() == 0:
return
if _shape.get_id() != 0:
Physics2DServer.body_set_shape_as_one_way_collision(_body, 0, one_way_collision, one_way_collision_margin)
此处我在 shape
无效时使用 _invalid_rid
。请注意,我们不负责释放形状 RID
.
州
完成后 body 将作为 RigidBody2D
使用,但 FauxBody2D
的 children 不是 body 的 children .我们将利用集成力,同时设置 body.
的状态
signal sleeping_state_changed()
export var linear_velocity:Vector2 setget set_linear_velocity
export var angular_velocity:float setget set_angular_velocity
export var can_sleep:bool = true setget set_can_sleep
export var sleeping:bool setget set_sleeping
export var custom_integrator:bool setget set_custom_integrator
func _enter_tree() -> void:
# …
Physics2DServer.body_set_state(_body, Physics2DServer.BODY_STATE_TRANSFORM, global_transform)
Physics2DServer.body_set_state(_body, Physics2DServer.BODY_STATE_ANGULAR_VELOCITY, angular_velocity)
Physics2DServer.body_set_state(_body, Physics2DServer.BODY_STATE_CAN_SLEEP, can_sleep)
Physics2DServer.body_set_state(_body, Physics2DServer.BODY_STATE_LINEAR_VELOCITY, linear_velocity)
Physics2DServer.body_set_state(_body, Physics2DServer.BODY_STATE_SLEEPING, sleeping)
Physics2DServer.body_set_force_integration_callback(_body, self, "_body_moved", 0)
Physics2DServer.body_set_omit_force_integration(_body, custom_integrator)
func _body_moved(state:Physics2DDirectBodyState, _user_data) -> void:
_integrate_forces(state)
global_transform = state.transform
angular_velocity = state.angular_velocity
linear_velocity = state.linear_velocity
if sleeping != state.sleeping:
sleeping = state.sleeping
emit_signal("sleeping_state_changed")
# warning-ignore:unused_argument
func _integrate_forces(state:Physics2DDirectBodyState) -> void:
pass
func set_linear_velocity(new_value:Vector2) -> void:
if linear_velocity == new_value:
return
linear_velocity = new_value
if _body.get_id() == 0:
return
Physics2DServer.body_set_state(_body, Physics2DServer.BODY_STATE_LINEAR_VELOCITY, linear_velocity)
func set_angular_velocity(new_value:float) -> void:
if angular_velocity == new_value:
return
angular_velocity = new_value
if _body.get_id() == 0:
return
Physics2DServer.body_set_state(_body, Physics2DServer.BODY_STATE_ANGULAR_VELOCITY, angular_velocity)
func set_can_sleep(new_value:bool) -> void:
if can_sleep == new_value:
return
can_sleep = new_value
if _body.get_id() == 0:
return
Physics2DServer.body_set_state(_body, Physics2DServer.BODY_STATE_CAN_SLEEP, can_sleep)
func set_sleeping(new_value:bool) -> void:
if sleeping == new_value:
return
sleeping = new_value
if _body.get_id() == 0:
return
Physics2DServer.body_set_state(_body, Physics2DServer.BODY_STATE_SLEEPING, sleeping)
func set_custom_integrator(new_value:bool) -> void:
if custom_integrator == new_value:
return
custom_integrator = new_value
if _body.get_id() == 0:
return
Physics2DServer.body_set_omit_force_integration(_body, custom_integrator)
body 将从我们 FauxBody2D
的 global_transform
开始,当 body 移动时,我们在 _body_moved
中获得回调,更新FauxBody2D
的属性以匹配 body 的状态,包括 FauxBody2D
的 global_transform
。现在您可以将 children 添加到 FauxBody2D
并且它们将根据 body.
移动
然而,当 FauxBody2D
移动时,它不会移动 body。我会用 NOTIFICATION_TRANSFORM_CHANGED
:
来解决
func _enter_tree() -> void:
# …
set_notify_transform(true)
func _notification(what: int) -> void:
if what == NOTIFICATION_TRANSFORM_CHANGED:
if _body.get_id() != 0:
Physics2DServer.body_set_state(_body, Physics2DServer.BODY_STATE_TRANSFORM, global_transform)
顺便说一句 if _body.get_id() != 0:
应该和 if _body:
一样,但我更喜欢明确一点。
现在,当 FauxBody2D
移动时(不是在设置变换时),它将更新 body 的变换。
参数
接下来我将处理body个参数:
export(float, EXP, 0.01, 65535.0) var mass:float = 1.0 setget set_mass
export(float, EXP, 0.0, 65535.0) var inertia:float = 1.0 setget set_inertia
export(float, 0.0, 1.0) var bounce:float = 0.0 setget set_bounce
export(float, 0.0, 1.0) var friction:float = 1.0 setget set_friction
export(float, -128.0, 128.0) var gravity_scale:float = 1.0 setget set_gravity_scale
export(float, -1.0, 100.0) var linear_damp:float = -1 setget set_linear_damp
export(float, -1.0, 100.0) var angular_damp:float = -1 setget set_angular_damp
func _enter_tree() -> void:
# …
Physics2DServer.body_set_param(_body, Physics2DServer.BODY_PARAM_ANGULAR_DAMP, angular_damp)
Physics2DServer.body_set_param(_body, Physics2DServer.BODY_PARAM_GRAVITY_SCALE, gravity_scale)
Physics2DServer.body_set_param(_body, Physics2DServer.BODY_PARAM_INERTIA, inertia)
Physics2DServer.body_set_param(_body, Physics2DServer.BODY_PARAM_LINEAR_DAMP, linear_damp)
Physics2DServer.body_set_param(_body, Physics2DServer.BODY_PARAM_MASS, mass)
Physics2DServer.body_set_param(_body, Physics2DServer.BODY_PARAM_BOUNCE, bounce)
Physics2DServer.body_set_param(_body, Physics2DServer.BODY_PARAM_FRICTION, friction)
# …
func set_mass(new_value:float) -> void:
if mass == new_value:
return
mass = new_value
if _body.get_id() == 0:
return
Physics2DServer.body_set_param(_body, Physics2DServer.BODY_PARAM_MASS, mass)
func set_inertia(new_value:float) -> void:
if inertia == new_value:
return
inertia = new_value
if _body.get_id() == 0:
return
Physics2DServer.body_set_param(_body, Physics2DServer.BODY_PARAM_INERTIA, inertia)
func set_bounce(new_value:float) -> void:
if bounce == new_value:
return
bounce = new_value
if _body.get_id() == 0:
return
Physics2DServer.body_set_param(_body, Physics2DServer.BODY_PARAM_BOUNCE, bounce)
func set_friction(new_value:float) -> void:
if friction == new_value:
return
friction = new_value
if _body.get_id() == 0:
return
Physics2DServer.body_set_param(_body, Physics2DServer.BODY_PARAM_FRICTION, friction)
func set_gravity_scale(new_value:float) -> void:
if gravity_scale == new_value:
return
gravity_scale = new_value
if _body.get_id() == 0:
return
Physics2DServer.body_set_param(_body, Physics2DServer.BODY_PARAM_GRAVITY_SCALE, gravity_scale)
func set_linear_damp(new_value:float) -> void:
if linear_damp == new_value:
return
linear_damp = new_value
if _body.get_id() == 0:
return
Physics2DServer.body_set_param(_body, Physics2DServer.BODY_PARAM_LINEAR_DAMP, linear_damp)
func set_angular_damp(new_value:float) -> void:
if angular_damp == new_value:
return
angular_damp = new_value
if _body.get_id() == 0:
return
Physics2DServer.body_set_param(_body, Physics2DServer.BODY_PARAM_ANGULAR_DAMP, angular_damp)
我相信模式很清楚。
顺便说一下,inertia
没有在 Godot 的 RigidBody2D
中公开 3.x 但它在 Godot 4.0 中,所以继续在这里添加它。
持续集成
export(int, "Disabled", "Cast Ray", "Cast Shape") var continuous_cd
func _enter_tree() -> void:
# …
Physics2DServer.body_set_continuous_collision_detection_mode(_body, continuous_cd)
# …
func set_continuous_cd(new_value:int) -> void:
if continuous_cd == new_value:
return
continuous_cd = new_value
if _body.get_id() == 0:
return
Physics2DServer.body_set_continuous_collision_detection_mode(_body, continuous_cd)
力和扭矩
我们将在applied_force
和torque
中积累这些。我将使用 Godot 4.0 的另一个页面并有一个 center_of_mass
。因此,我不会使用 body_add_force
,而是执行等效的 body_add_center_force
和 body_add_torque
调用,因此我可以使用自定义 center_of_mass
计算 torque
。
此外,Godot 在 2D 和 3D 之间存在差异,在 3D 中每个物理帧都会重置力,但在 2D 中不会。所以我希望它是可配置的。为此,我添加了 auto_reset_forces
属性.
export var applied_force:Vector2 setget set_applied_force
export var applied_torque:float setget set_applied_torque
export var center_of_mass:Vector2
export var auto_reset_forces:bool
func _enter_tree() -> void:
# …
Physics2DServer.body_add_central_force(_body, applied_force)
Physics2DServer.body_add_torque(_body, applied_torque)
func _body_moved(state:Physics2DDirectBodyState, _user_data) -> void:
# …
if auto_reset_forces:
Physics2DServer.body_add_central_force(_body, -applied_force)
Physics2DServer.body_add_torque(_body, -applied_torque)
applied_force = Vector2.ZERO
applied_torque = 0
func add_central_force(force:Vector2) -> void:
applied_force += force
if _body.get_id() != 0:
Physics2DServer.body_add_central_force(_body, force)
func add_force(force:Vector2, offset:Vector2) -> void:
var torque := (offset - center_of_mass).cross(force)
applied_force += force
applied_torque += torque
if _body.get_id() != 0:
Physics2DServer.body_add_central_force(_body, force)
Physics2DServer.body_add_torque(_body, torque)
func add_torque(torque:float) -> void:
applied_torque += torque
if _body.get_id() != 0:
Physics2DServer.body_add_torque(_body, torque)
func apply_central_impulse(impulse:Vector2) -> void:
if _body.get_id() != 0:
Physics2DServer.body_apply_central_impulse(_body, impulse)
func apply_impulse(offset:Vector2, impulse:Vector2) -> void:
if _body.get_id() != 0:
Physics2DServer.body_apply_impulse(_body, offset, impulse)
func apply_torque_impulse(torque:float) -> void:
if _body.get_id() != 0:
Physics2DServer.body_apply_torque_impulse(_body, torque)
func set_applied_force(new_value:Vector2) -> void:
if applied_force == new_value:
return
if _body.get_id() != 0:
var difference := new_value - applied_force
Physics2DServer.body_add_central_force(_body, difference)
applied_force = new_value
func set_applied_torque(new_value:float) -> void:
if applied_torque == new_value:
return
if _body.get_id() != 0:
var difference := new_value - applied_torque
Physics2DServer.body_add_torque(_body, difference)
applied_torque = new_value
顺便说一句,在将物理体添加到场景树之前,我还没有真正尝试过对物理体施加力和扭矩(我不知道为什么要这样做)。然而,当 body 进入场景树时,施加的力和扭矩将被存储和应用对我来说是有意义的。顺便说一句,当 body 退出场景树时,我不会擦除它们。
碰撞异常
并且我们 运行 进入了一个未暴露给脚本的函数:body_get_collision_exceptions
。所以我们必须保留碰撞异常的清单。这很好,这意味着我可以在创建 body.
之前存储它们
var collision_exceptions:Array
func add_collision_exception_with(body:Node) -> void:
var collision_object := body as PhysicsBody2D
if not is_instance_valid(collision_object):
push_error( "Collision exception only works between two objects of PhysicsBody type.")
return
var rid = collision_object.get_rid()
if rid.get_id() == 0:
return
if collision_exceptions.has(collision_object):
return
collision_exceptions.append(collision_object)
if _body.get_id() != 0:
Physics2DServer.body_add_collision_exception(_body, rid)
func get_collision_exceptions() -> Array:
return collision_exceptions
func remove_collision_exception_with(body:Node) -> void:
var collision_object := body as PhysicsBody2D
if not is_instance_valid(collision_object):
push_error( "Collision exception only works between two objects of PhysicsBody type.")
return
var rid = collision_object.get_rid()
if rid.get_id() == 0:
return
if not collision_exceptions.has(collision_object):
return
collision_exceptions.erase(collision_object)
if _body.get_id() != 0:
Physics2DServer.body_remove_collision_exception(_body, rid)
测试动作
这个很简单:
func test_motion(motion:Vector2, infinite_inertia:bool = true, margin:float = 0.08, result:Physics2DTestMotionResult = null) -> bool:
if _body.get_id() == 0:
push_error("body is not inside the scene tree")
return false
return Physics2DServer.body_test_motion(_body, global_transform, motion, infinite_inertia, margin, result)
顺便说一句,如果你想传递body_test_motion
的exclude
参数,知道它想要RID
s。为了以防万一,我还要提到 get_collision_exceptions
记录在 return Node
中,这就是我在这里实现它的方式。
轴速度
虽然我很想像这样实现它:
func set_axis_velocity(axis_velocity:Vector2) -> void:
Physics2DServer.body_set_axis_velocity(_body, axis_velocity)
不是很方便。原因是我想继续存储属性的想法,并在 body 进入场景树时应用它们。
对于一个实现这个的替代方法,我们应该明白它做了什么:它改变了linear_velocity
但只在axis_velocity
的方向上,任何垂直速度都不会受到影响。换句话说,我们分解 linear_velocity
沿 axis_velocity
的速度和垂直 axis_velocity
的速度,然后我们从 axis_velocity
加上分量计算一个新的 linear_velocity
与 axis_velocity
垂直的旧 linear_velocity
。
所以,像这样:
func set_axis_velocity(axis_velocity:Vector2) -> void:
self.linear_velocity = axis_velocity + linear_velocity.slide(axis_velocity.normalized())
对了,官方文档之所以说axis_velocity
对跳跃有用,是因为它可以让你设置垂直速度,而不影响水平速度。
模仿Area2D
我不会实施 space 覆盖 et.al。希望您现在已经对如何与 Physics2DServer
交互有了一个很好的了解,只要说您想要使用 area_*
方法而不是 body_*
方法就够了。因此,您可以使用 area_set_param
设置区域参数,使用 area_set_space_override_mode
设置 space 覆盖模式。
接下来,让我们创建一个区域:
var _area:RID
func _enter_tree() -> void:
_area = Physics2DServer.area_create()
Physics2DServer.area_set_space(_area, get_world_2d().space)
Physics2DServer.area_set_transform(_area, global_transform)
# …
func _exit_tree() -> void:
Physics2DServer.free_rid(_area)
_area = _invalid_rid
# …
注意:我也给area_set_transform
的区域定位了。
然后让我们将形状附加到该区域:
func _update_shape() -> void:
# …
if _shape.get_id() != 0:
Physics2DServer.area_add_shape(_area, _shape, Transform2D.IDENTITY, disabled)
# …
移动区域
body移动时,我们也应该移动区域:
func _notification(what: int) -> void:
if what == NOTIFICATION_TRANSFORM_CHANGED:
# …
if _area.get_id() != 0:
Physics2DServer.area_set_transform(_area, global_transform)
模式
我想复制 Godot 4.0 设计并使用 freeze
和 freeze_mode
而不是使用 mode
。那么转换我们的Node2D
最终会多出一个freeze_mode
。 它也可能是一个额外的mode
如果我做的更像戈多3.x。
export var lock_rotation:bool setget set_lock_rotation
export var freeze:bool setget set_freeze
export(int, "Static", "Kinematic", "Area") var freeze_mode:int setget set_freeze_mode
func _enter_tree() -> void:
# …
_update_body_mode()
func _update_body_mode() -> void:
if freeze:
if freeze_mode == 1:
Physics2DServer.body_set_mode(_body, Physics2DServer.BODY_MODE_KINEMATIC)
else:
Physics2DServer.body_set_mode(_body, Physics2DServer.BODY_MODE_STATIC)
else:
if lock_rotation:
Physics2DServer.body_set_mode(_body, Physics2DServer.BODY_MODE_CHARACTER)
else:
Physics2DServer.body_set_mode(_body, Physics2DServer.BODY_MODE_RIGID)
func set_lock_rotation(new_value:bool) -> void:
if lock_rotation == new_value:
return
lock_rotation = new_value
if _body.get_id() == 0:
return
_update_body_mode()
func set_freeze(new_value:bool) -> void:
if freeze == new_value:
return
freeze = new_value
if _body.get_id() == 0:
return
_update_body_mode()
func set_freeze_mode(new_value:int) -> void:
if freeze_mode == new_value:
return
freeze_mode = new_value
if _body.get_id() == 0:
return
_update_body_mode()
自从我实施 _update_body_mode
检查 freeze_mode
是否为 Kinematic
后,"Area"
将表现得像 "Static"
,这就是我们想要的。 好吧,差不多了,我们会讲到的。
输入可拾取
遗憾的是 body_set_pickable
没有公开脚本。所以我们将不得不重新创建此功能。
signal input_event(viewport, event, shape_idx)
signal mouse_entered()
signal mouse_exited()
export var input_pickable:bool setget set_input_pickable
var _mouse_is_inside:bool
func _enter_tree() -> void:
# …
_update_pickable()
# …
func _notification(what: int) -> void:
# …
if what == NOTIFICATION_VISIBILITY_CHANGED:
_update_pickable()
func _update_pickable() -> void:
set_process_input(input_pickable and _body.get_id() != 0 and is_visible_in_tree())
func _input(event: InputEvent) -> void:
if (
not (event is InputEventScreenDrag)
and not (event is InputEventScreenTouch)
and not (event is InputEventMouse)
):
return
var viewport := get_viewport()
var position:Vector2 = viewport.get_canvas_transform().affine_inverse().xform(event.position)
var objects := get_world_2d().direct_space_state.intersect_point(position, 32, [], 0x7FFFFFFF, false, true)
var is_inside := false
for object in objects:
if object.rid == _area:
is_inside = true
break
if is_inside:
_input_event(viewport, event, 0)
emit_signal("input_event", viewport, event, 0)
if event is InputEventMouse and _mouse_is_inside != is_inside:
_mouse_is_inside = is_inside
if _mouse_is_inside:
emit_signal("mouse_entered")
else:
emit_signal("mouse_exited")
# warning-ignore:unused_argument
# warning-ignore:unused_argument
# warning-ignore:unused_argument
func _input_event(viewport:Object, event:InputEvent, shape_idx:int) -> void:
pass
func set_input_pickable(new_value:bool) -> void:
if input_pickable == new_value:
return
input_pickable = new_value
_update_pickable()
Body进入和退出
似乎还有一个错误需要 area_set_monitorable
进行监控。所以我们不能做一个可以监控但不可监控的区域。
signal body_entered(body)
signal body_exited(body)
signal body_shape_entered(body_rid, body, body_shape_index, local_shape_index)
signal body_shape_exited(body_rid, body, body_shape_index, local_shape_index)
signal area_entered(area)
signal area_exited(area)
signal area_shape_entered(area_rid, area, area_shape_index, local_shape_index)
signal area_shape_exited(area_rid, area, area_shape_index, local_shape_index)
export var monitorable:bool setget set_monitorable
export var monitoring:bool setget set_monitoring
var overlapping_body_instances:Dictionary
var overlapping_area_instances:Dictionary
var overlapping_bodies:Dictionary
var overlapping_areas:Dictionary
func _enter_tree() -> void:
# …
_update_monitoring()
# …
func _update_monitoring() -> void:
Physics2DServer.area_set_monitorable(_area, monitorable or monitoring)
if monitoring:
Physics2DServer.area_set_monitor_callback(_area, self, "_body_monitor")
Physics2DServer.area_set_area_monitor_callback(_area, self, "_area_monitor")
else:
Physics2DServer.area_set_monitor_callback(_area, null, "")
Physics2DServer.area_set_area_monitor_callback(_area, null, "")
func _body_monitor(status:int, body:RID, instance_id:int, body_shape_index:int, local_shape_index:int) -> void:
if _body == body:
return
var instance := instance_from_id(instance_id)
if status == 0:
# entered
if not overlapping_bodies.has(body):
overlapping_bodies[body] = 0
overlapping_body_instances[instance] = instance
emit_signal("body_entered", instance)
overlapping_bodies[body] += 1
emit_signal("body_shape_entered", body, instance, body_shape_index, local_shape_index)
else:
# exited
emit_signal("body_shape_exited", body, instance, body_shape_index, local_shape_index)
overlapping_bodies[body] -= 1
if overlapping_bodies[body] == 0:
overlapping_bodies.erase(body)
overlapping_body_instances.erase(instance)
emit_signal("body_exited", instance)
func _area_monitor(status:int, area:RID, instance_id:int, area_shape_index:int, local_shape_index:int) -> void:
var instance := instance_from_id(instance_id)
if status == 0:
# entered
if not overlapping_areas.has(area):
overlapping_areas[area] = 0
overlapping_area_instances[instance] = instance
emit_signal("area_entered", instance)
overlapping_areas[area] += 1
emit_signal("area_shape_entered", area, instance, area_shape_index, local_shape_index)
else:
# exited
emit_signal("area_shape_exited", area, instance, area_shape_index, local_shape_index)
overlapping_areas[area] -= 1
if overlapping_areas[area] == 0:
overlapping_areas.erase(area)
overlapping_area_instances.erase(instance)
emit_signal("area_exited", instance)
func get_overlapping_bodies() -> Array:
if not monitoring:
push_error("monitoring is false")
return []
return overlapping_body_instances.keys()
func get_overlapping_areas() -> Array:
if not monitoring:
push_error("monitoring is false")
return []
return overlapping_area_instances.keys()
func overlaps_body(body:Node) -> bool:
if not monitoring:
return false
return overlapping_body_instances.has(body)
func overlaps_area(area:Node) -> bool:
if not monitoring:
return false
return overlapping_area_instances.has(area)
func set_monitoring(new_value:bool) -> void:
if monitoring == new_value:
return
monitoring = new_value
if _area.get_id() == 0:
return
_update_monitoring()
func set_monitorable(new_value:bool) -> void:
if monitorable == new_value:
return
monitorable = new_value
if _area.get_id() == 0:
return
_update_monitoring()
我在这里使用 area_set_monitor_callback
和 area_set_area_monitor_callback
。该文档声称 area_set_monitor_callback
适用于区域和身体。但这也不正确。 area_set_monitor_callback
只针对body,无证的area_set_area_monitor_callback
针对area
我需要跟踪每个进入和存在的形状。这就是为什么我使用 overlapping_areas
和 overlapping_bodies
的字典。键将是 RID
s,值将是形状重叠的数量。
我们快完成了。
碰撞层和遮罩
我希望区域和 body 共享碰撞层和遮罩。除了 "Area"
模式,我会将 body 的碰撞层和遮罩设置为 0,这样它就不会与任何东西发生碰撞。
export(int, LAYERS_2D_PHYSICS) var collision_layer:int = 1 setget set_collision_layer
export(int, LAYERS_2D_PHYSICS) var collision_mask:int = 1 setget set_collision_mask
func _enter_tree() -> void:
# …
_update_collision_layer_and_mask()
# …
func _update_collision_layer_and_mask() -> void:
Physics2DServer.area_set_collision_layer(_area, collision_layer)
Physics2DServer.body_set_collision_layer(_body, collision_layer if not freeze or freeze_mode != 2 else 0)
Physics2DServer.area_set_collision_mask(_area, collision_mask)
Physics2DServer.body_set_collision_mask(_body, collision_mask if not freeze or freeze_mode != 2 else 0)
func set_collision_layer(new_value:int) -> void:
if collision_layer == new_value:
return
collision_layer = new_value
if _body.get_id() == 0:
return
_update_collision_layer_and_mask()
func set_collision_mask(new_value:int) -> void:
if collision_mask == new_value:
return
collision_mask = new_value
if _body.get_id() == 0:
return
_update_collision_layer_and_mask()
同时让我们实现 get_collision_layer_bit
、get_collision_mask_bit
、set_collision_layer_bit
和 set_collision_mask_bit
:
func get_collision_layer_bit(bit:int) -> bool:
if bit < 0 or bit > 31:
push_error("Collision layer bit must be between 0 and 31 inclusive.")
return false
return collision_layer & (1 << bit) != 0
func get_collision_mask_bit(bit:int) -> bool:
if bit < 0 or bit > 31:
push_error("Collision mask bit must be between 0 and 31 inclusive.")
return false
return collision_mask & (1 << bit) != 0
func set_collision_layer_bit(bit:int, value:bool) -> void:
if bit < 0 or bit > 31:
push_error("Collision layer bit must be between 0 and 31 inclusive.")
return
if value:
self.collision_layer = collision_layer | 1 << bit
else:
self.collision_layer = collision_layer & ~(1 << bit)
func set_collision_mask_bit(bit:int, value:bool) -> void:
if bit < 0 or bit > 31:
push_error("Collision mask bit must be between 0 and 31 inclusive.")
return
if value:
self.collision_mask = collision_mask | 1 << bit
else:
self.collision_mask = collision_mask & ~(1 << bit)
并在_update_body_mode
中添加对_update_collision_layer_and_mask()
的调用:
func _update_body_mode() -> void:
_update_collision_layer_and_mask()
# …
我们完成了。 我觉得。
是否有可能使单个节点像 RigidBody2D
一样发生碰撞,但同时在禁用碰撞时它像 Area2D
节点一样工作并且可以检测重叠?
例如:
我有一个附属物,它的行为就像一个 Area2D
节点,但是当它被切断时,它就像它自己的 RigidBody2D
节点一样。
我考虑过的一种方法是在剪切时创建 RigidBody2D
节点的新附属物,并从具有 Area2D
节点的旧附属物转移 CollisionShape2D
。
但我不知道哪种方法耗电最少
有没有更好的方法来实现这个?
blade 和头部 RigidBody2D
,
两者最初都将 CollisionPolygon2D
禁用设置为 true,一旦 blade 掉落并且头部检测到重叠退出,disabled
将设置为 false 并且头部被切碎关闭(与 parent 节点分开)并弹开。
“人造Body2D”
我们将定制一个 Node
,我称之为 FauxBody2D
。它将作为 RigidBody2D
与 CollisionShape
一起工作,作为 Area2D
与 相同 CollisionShape
。为了存档,我们将使用 Physics2DServer
.
虽然RigidBody2D
和Area2D
的第一个共同祖先class是CollisionObject2D
,但不方便扩展CollisionObject2D
。所以 FauxBody2D
将是 Node2D
.
因此创建一个新脚本faux_body.gd
。然而,它并不打算直接在场景树中使用(你可以,但你不能扩展它的代码),而是使用它添加一个 Node2D
和一个新脚本并将它设置为 extends FauxBody2D
.
您将能够使用 FauxBody2D
的变量并以不受欢迎的方式对其进行处理。事实上,即使我声明了 setter,如果您不使用 self
,您的脚本也会绕过它们。例如,不要设置 applied_force
,而是设置 self.applied_force
。顺便说一句,有些方法留空供您在脚本中覆盖(它们是“虚拟的”)。
这些是我们在 faux_body.gd
中的第一行代码:
class_name FauxBody2D
extends Node2D
我会避免重复代码。
模仿RigidBody2D
我正在跳过 rough
、absorbent
。同样在这个答案中,我只显示了区域的监控和信号。见 the followup answer.
我们将在 _enter_tree
中创建一个 body 并在 _exit_tree
中释放它:
var _body:RID
var _invalid_rid:RID
func _enter_tree() -> void:
_body = Physics2DServer.body_create()
func _exit_tree() -> void:
Physics2DServer.free_rid(_body)
_body = _invalid_rid
没有表达式可以清零 RID
。我会声明一个 _invalid_rid
并且从不设置它,所以它总是归零。
此外 body 应该与 FauxBody2D
相同 space:
func _enter_tree() -> void:
# …
Physics2DServer.body_set_space(_body, get_world_2d().space)
模仿CollisionShape2D
接下来让我们实现 CollisionShape2D
:
export var shape:Shape2D setget set_shape
export var disabled:bool setget set_disabled
export var one_way_collision:bool setget set_one_way_collision
export(float, 0.0, 128.0) var one_way_collision_margin:float setget set_one_way_collision_margin
var _shape:RID
func _enter_tree() -> void:
# …
_update_shape()
func _update_shape() -> void:
var new_shape = _invalid_rid if shape == null else shape.get_rid()
if new_shape == _shape:
return
if _shape.get_id() != 0:
Physics2DServer.body_remove_shape(_body, 0)
_shape = new_shape
if _shape.get_id() != 0:
Physics2DServer.body_add_shape(_body, _shape, Transform2D.IDENTITY, disabled)
Physics2DServer.body_set_shape_as_one_way_collision(_body, 0, one_way_collision, one_way_collision_margin)
func set_shape(new_value:Shape2D) -> void:
if shape == new_value:
return
shape = new_value
if _body.get_id() == 0:
return
_update_shape()
func set_disabled(new_value:bool) -> void:
if disabled == new_value:
return
disabled = new_value
if _body.get_id() == 0:
return
if _shape.get_id() != 0:
Physics2DServer.body_set_shape_disabled(_body, 0, disabled)
func set_one_way_collision(new_value:bool) -> void:
if one_way_collision == new_value:
return
one_way_collision = new_value
if _body.get_id() == 0:
return
if _shape.get_id() != 0:
Physics2DServer.body_set_shape_as_one_way_collision(_body, 0, one_way_collision, one_way_collision_margin)
func set_one_way_collision_margin(new_value:float) -> void:
if one_way_collision_margin == new_value:
return
one_way_collision_margin = new_value
if _body.get_id() == 0:
return
if _shape.get_id() != 0:
Physics2DServer.body_set_shape_as_one_way_collision(_body, 0, one_way_collision, one_way_collision_margin)
此处我在 shape
无效时使用 _invalid_rid
。请注意,我们不负责释放形状 RID
.
州
完成后 body 将作为 RigidBody2D
使用,但 FauxBody2D
的 children 不是 body 的 children .我们将利用集成力,同时设置 body.
signal sleeping_state_changed()
export var linear_velocity:Vector2 setget set_linear_velocity
export var angular_velocity:float setget set_angular_velocity
export var can_sleep:bool = true setget set_can_sleep
export var sleeping:bool setget set_sleeping
export var custom_integrator:bool setget set_custom_integrator
func _enter_tree() -> void:
# …
Physics2DServer.body_set_state(_body, Physics2DServer.BODY_STATE_TRANSFORM, global_transform)
Physics2DServer.body_set_state(_body, Physics2DServer.BODY_STATE_ANGULAR_VELOCITY, angular_velocity)
Physics2DServer.body_set_state(_body, Physics2DServer.BODY_STATE_CAN_SLEEP, can_sleep)
Physics2DServer.body_set_state(_body, Physics2DServer.BODY_STATE_LINEAR_VELOCITY, linear_velocity)
Physics2DServer.body_set_state(_body, Physics2DServer.BODY_STATE_SLEEPING, sleeping)
Physics2DServer.body_set_force_integration_callback(_body, self, "_body_moved", 0)
Physics2DServer.body_set_omit_force_integration(_body, custom_integrator)
func _body_moved(state:Physics2DDirectBodyState, _user_data) -> void:
_integrate_forces(state)
global_transform = state.transform
angular_velocity = state.angular_velocity
linear_velocity = state.linear_velocity
if sleeping != state.sleeping:
sleeping = state.sleeping
emit_signal("sleeping_state_changed")
# warning-ignore:unused_argument
func _integrate_forces(state:Physics2DDirectBodyState) -> void:
pass
func set_linear_velocity(new_value:Vector2) -> void:
if linear_velocity == new_value:
return
linear_velocity = new_value
if _body.get_id() == 0:
return
Physics2DServer.body_set_state(_body, Physics2DServer.BODY_STATE_LINEAR_VELOCITY, linear_velocity)
func set_angular_velocity(new_value:float) -> void:
if angular_velocity == new_value:
return
angular_velocity = new_value
if _body.get_id() == 0:
return
Physics2DServer.body_set_state(_body, Physics2DServer.BODY_STATE_ANGULAR_VELOCITY, angular_velocity)
func set_can_sleep(new_value:bool) -> void:
if can_sleep == new_value:
return
can_sleep = new_value
if _body.get_id() == 0:
return
Physics2DServer.body_set_state(_body, Physics2DServer.BODY_STATE_CAN_SLEEP, can_sleep)
func set_sleeping(new_value:bool) -> void:
if sleeping == new_value:
return
sleeping = new_value
if _body.get_id() == 0:
return
Physics2DServer.body_set_state(_body, Physics2DServer.BODY_STATE_SLEEPING, sleeping)
func set_custom_integrator(new_value:bool) -> void:
if custom_integrator == new_value:
return
custom_integrator = new_value
if _body.get_id() == 0:
return
Physics2DServer.body_set_omit_force_integration(_body, custom_integrator)
body 将从我们 FauxBody2D
的 global_transform
开始,当 body 移动时,我们在 _body_moved
中获得回调,更新FauxBody2D
的属性以匹配 body 的状态,包括 FauxBody2D
的 global_transform
。现在您可以将 children 添加到 FauxBody2D
并且它们将根据 body.
然而,当 FauxBody2D
移动时,它不会移动 body。我会用 NOTIFICATION_TRANSFORM_CHANGED
:
func _enter_tree() -> void:
# …
set_notify_transform(true)
func _notification(what: int) -> void:
if what == NOTIFICATION_TRANSFORM_CHANGED:
if _body.get_id() != 0:
Physics2DServer.body_set_state(_body, Physics2DServer.BODY_STATE_TRANSFORM, global_transform)
顺便说一句 if _body.get_id() != 0:
应该和 if _body:
一样,但我更喜欢明确一点。
现在,当 FauxBody2D
移动时(不是在设置变换时),它将更新 body 的变换。
参数
接下来我将处理body个参数:
export(float, EXP, 0.01, 65535.0) var mass:float = 1.0 setget set_mass
export(float, EXP, 0.0, 65535.0) var inertia:float = 1.0 setget set_inertia
export(float, 0.0, 1.0) var bounce:float = 0.0 setget set_bounce
export(float, 0.0, 1.0) var friction:float = 1.0 setget set_friction
export(float, -128.0, 128.0) var gravity_scale:float = 1.0 setget set_gravity_scale
export(float, -1.0, 100.0) var linear_damp:float = -1 setget set_linear_damp
export(float, -1.0, 100.0) var angular_damp:float = -1 setget set_angular_damp
func _enter_tree() -> void:
# …
Physics2DServer.body_set_param(_body, Physics2DServer.BODY_PARAM_ANGULAR_DAMP, angular_damp)
Physics2DServer.body_set_param(_body, Physics2DServer.BODY_PARAM_GRAVITY_SCALE, gravity_scale)
Physics2DServer.body_set_param(_body, Physics2DServer.BODY_PARAM_INERTIA, inertia)
Physics2DServer.body_set_param(_body, Physics2DServer.BODY_PARAM_LINEAR_DAMP, linear_damp)
Physics2DServer.body_set_param(_body, Physics2DServer.BODY_PARAM_MASS, mass)
Physics2DServer.body_set_param(_body, Physics2DServer.BODY_PARAM_BOUNCE, bounce)
Physics2DServer.body_set_param(_body, Physics2DServer.BODY_PARAM_FRICTION, friction)
# …
func set_mass(new_value:float) -> void:
if mass == new_value:
return
mass = new_value
if _body.get_id() == 0:
return
Physics2DServer.body_set_param(_body, Physics2DServer.BODY_PARAM_MASS, mass)
func set_inertia(new_value:float) -> void:
if inertia == new_value:
return
inertia = new_value
if _body.get_id() == 0:
return
Physics2DServer.body_set_param(_body, Physics2DServer.BODY_PARAM_INERTIA, inertia)
func set_bounce(new_value:float) -> void:
if bounce == new_value:
return
bounce = new_value
if _body.get_id() == 0:
return
Physics2DServer.body_set_param(_body, Physics2DServer.BODY_PARAM_BOUNCE, bounce)
func set_friction(new_value:float) -> void:
if friction == new_value:
return
friction = new_value
if _body.get_id() == 0:
return
Physics2DServer.body_set_param(_body, Physics2DServer.BODY_PARAM_FRICTION, friction)
func set_gravity_scale(new_value:float) -> void:
if gravity_scale == new_value:
return
gravity_scale = new_value
if _body.get_id() == 0:
return
Physics2DServer.body_set_param(_body, Physics2DServer.BODY_PARAM_GRAVITY_SCALE, gravity_scale)
func set_linear_damp(new_value:float) -> void:
if linear_damp == new_value:
return
linear_damp = new_value
if _body.get_id() == 0:
return
Physics2DServer.body_set_param(_body, Physics2DServer.BODY_PARAM_LINEAR_DAMP, linear_damp)
func set_angular_damp(new_value:float) -> void:
if angular_damp == new_value:
return
angular_damp = new_value
if _body.get_id() == 0:
return
Physics2DServer.body_set_param(_body, Physics2DServer.BODY_PARAM_ANGULAR_DAMP, angular_damp)
我相信模式很清楚。
顺便说一下,inertia
没有在 Godot 的 RigidBody2D
中公开 3.x 但它在 Godot 4.0 中,所以继续在这里添加它。
持续集成
export(int, "Disabled", "Cast Ray", "Cast Shape") var continuous_cd
func _enter_tree() -> void:
# …
Physics2DServer.body_set_continuous_collision_detection_mode(_body, continuous_cd)
# …
func set_continuous_cd(new_value:int) -> void:
if continuous_cd == new_value:
return
continuous_cd = new_value
if _body.get_id() == 0:
return
Physics2DServer.body_set_continuous_collision_detection_mode(_body, continuous_cd)
力和扭矩
我们将在applied_force
和torque
中积累这些。我将使用 Godot 4.0 的另一个页面并有一个 center_of_mass
。因此,我不会使用 body_add_force
,而是执行等效的 body_add_center_force
和 body_add_torque
调用,因此我可以使用自定义 center_of_mass
计算 torque
。
此外,Godot 在 2D 和 3D 之间存在差异,在 3D 中每个物理帧都会重置力,但在 2D 中不会。所以我希望它是可配置的。为此,我添加了 auto_reset_forces
属性.
export var applied_force:Vector2 setget set_applied_force
export var applied_torque:float setget set_applied_torque
export var center_of_mass:Vector2
export var auto_reset_forces:bool
func _enter_tree() -> void:
# …
Physics2DServer.body_add_central_force(_body, applied_force)
Physics2DServer.body_add_torque(_body, applied_torque)
func _body_moved(state:Physics2DDirectBodyState, _user_data) -> void:
# …
if auto_reset_forces:
Physics2DServer.body_add_central_force(_body, -applied_force)
Physics2DServer.body_add_torque(_body, -applied_torque)
applied_force = Vector2.ZERO
applied_torque = 0
func add_central_force(force:Vector2) -> void:
applied_force += force
if _body.get_id() != 0:
Physics2DServer.body_add_central_force(_body, force)
func add_force(force:Vector2, offset:Vector2) -> void:
var torque := (offset - center_of_mass).cross(force)
applied_force += force
applied_torque += torque
if _body.get_id() != 0:
Physics2DServer.body_add_central_force(_body, force)
Physics2DServer.body_add_torque(_body, torque)
func add_torque(torque:float) -> void:
applied_torque += torque
if _body.get_id() != 0:
Physics2DServer.body_add_torque(_body, torque)
func apply_central_impulse(impulse:Vector2) -> void:
if _body.get_id() != 0:
Physics2DServer.body_apply_central_impulse(_body, impulse)
func apply_impulse(offset:Vector2, impulse:Vector2) -> void:
if _body.get_id() != 0:
Physics2DServer.body_apply_impulse(_body, offset, impulse)
func apply_torque_impulse(torque:float) -> void:
if _body.get_id() != 0:
Physics2DServer.body_apply_torque_impulse(_body, torque)
func set_applied_force(new_value:Vector2) -> void:
if applied_force == new_value:
return
if _body.get_id() != 0:
var difference := new_value - applied_force
Physics2DServer.body_add_central_force(_body, difference)
applied_force = new_value
func set_applied_torque(new_value:float) -> void:
if applied_torque == new_value:
return
if _body.get_id() != 0:
var difference := new_value - applied_torque
Physics2DServer.body_add_torque(_body, difference)
applied_torque = new_value
顺便说一句,在将物理体添加到场景树之前,我还没有真正尝试过对物理体施加力和扭矩(我不知道为什么要这样做)。然而,当 body 进入场景树时,施加的力和扭矩将被存储和应用对我来说是有意义的。顺便说一句,当 body 退出场景树时,我不会擦除它们。
碰撞异常
并且我们 运行 进入了一个未暴露给脚本的函数:body_get_collision_exceptions
。所以我们必须保留碰撞异常的清单。这很好,这意味着我可以在创建 body.
var collision_exceptions:Array
func add_collision_exception_with(body:Node) -> void:
var collision_object := body as PhysicsBody2D
if not is_instance_valid(collision_object):
push_error( "Collision exception only works between two objects of PhysicsBody type.")
return
var rid = collision_object.get_rid()
if rid.get_id() == 0:
return
if collision_exceptions.has(collision_object):
return
collision_exceptions.append(collision_object)
if _body.get_id() != 0:
Physics2DServer.body_add_collision_exception(_body, rid)
func get_collision_exceptions() -> Array:
return collision_exceptions
func remove_collision_exception_with(body:Node) -> void:
var collision_object := body as PhysicsBody2D
if not is_instance_valid(collision_object):
push_error( "Collision exception only works between two objects of PhysicsBody type.")
return
var rid = collision_object.get_rid()
if rid.get_id() == 0:
return
if not collision_exceptions.has(collision_object):
return
collision_exceptions.erase(collision_object)
if _body.get_id() != 0:
Physics2DServer.body_remove_collision_exception(_body, rid)
测试动作
这个很简单:
func test_motion(motion:Vector2, infinite_inertia:bool = true, margin:float = 0.08, result:Physics2DTestMotionResult = null) -> bool:
if _body.get_id() == 0:
push_error("body is not inside the scene tree")
return false
return Physics2DServer.body_test_motion(_body, global_transform, motion, infinite_inertia, margin, result)
顺便说一句,如果你想传递body_test_motion
的exclude
参数,知道它想要RID
s。为了以防万一,我还要提到 get_collision_exceptions
记录在 return Node
中,这就是我在这里实现它的方式。
轴速度
虽然我很想像这样实现它:
func set_axis_velocity(axis_velocity:Vector2) -> void:
Physics2DServer.body_set_axis_velocity(_body, axis_velocity)
不是很方便。原因是我想继续存储属性的想法,并在 body 进入场景树时应用它们。
对于一个实现这个的替代方法,我们应该明白它做了什么:它改变了linear_velocity
但只在axis_velocity
的方向上,任何垂直速度都不会受到影响。换句话说,我们分解 linear_velocity
沿 axis_velocity
的速度和垂直 axis_velocity
的速度,然后我们从 axis_velocity
加上分量计算一个新的 linear_velocity
与 axis_velocity
垂直的旧 linear_velocity
。
所以,像这样:
func set_axis_velocity(axis_velocity:Vector2) -> void:
self.linear_velocity = axis_velocity + linear_velocity.slide(axis_velocity.normalized())
对了,官方文档之所以说axis_velocity
对跳跃有用,是因为它可以让你设置垂直速度,而不影响水平速度。
模仿Area2D
我不会实施 space 覆盖 et.al。希望您现在已经对如何与 Physics2DServer
交互有了一个很好的了解,只要说您想要使用 area_*
方法而不是 body_*
方法就够了。因此,您可以使用 area_set_param
设置区域参数,使用 area_set_space_override_mode
设置 space 覆盖模式。
接下来,让我们创建一个区域:
var _area:RID
func _enter_tree() -> void:
_area = Physics2DServer.area_create()
Physics2DServer.area_set_space(_area, get_world_2d().space)
Physics2DServer.area_set_transform(_area, global_transform)
# …
func _exit_tree() -> void:
Physics2DServer.free_rid(_area)
_area = _invalid_rid
# …
注意:我也给area_set_transform
的区域定位了。
然后让我们将形状附加到该区域:
func _update_shape() -> void:
# …
if _shape.get_id() != 0:
Physics2DServer.area_add_shape(_area, _shape, Transform2D.IDENTITY, disabled)
# …
移动区域
body移动时,我们也应该移动区域:
func _notification(what: int) -> void:
if what == NOTIFICATION_TRANSFORM_CHANGED:
# …
if _area.get_id() != 0:
Physics2DServer.area_set_transform(_area, global_transform)
模式
我想复制 Godot 4.0 设计并使用 freeze
和 freeze_mode
而不是使用 mode
。那么转换我们的Node2D
最终会多出一个freeze_mode
。 它也可能是一个额外的mode
如果我做的更像戈多3.x。
export var lock_rotation:bool setget set_lock_rotation
export var freeze:bool setget set_freeze
export(int, "Static", "Kinematic", "Area") var freeze_mode:int setget set_freeze_mode
func _enter_tree() -> void:
# …
_update_body_mode()
func _update_body_mode() -> void:
if freeze:
if freeze_mode == 1:
Physics2DServer.body_set_mode(_body, Physics2DServer.BODY_MODE_KINEMATIC)
else:
Physics2DServer.body_set_mode(_body, Physics2DServer.BODY_MODE_STATIC)
else:
if lock_rotation:
Physics2DServer.body_set_mode(_body, Physics2DServer.BODY_MODE_CHARACTER)
else:
Physics2DServer.body_set_mode(_body, Physics2DServer.BODY_MODE_RIGID)
func set_lock_rotation(new_value:bool) -> void:
if lock_rotation == new_value:
return
lock_rotation = new_value
if _body.get_id() == 0:
return
_update_body_mode()
func set_freeze(new_value:bool) -> void:
if freeze == new_value:
return
freeze = new_value
if _body.get_id() == 0:
return
_update_body_mode()
func set_freeze_mode(new_value:int) -> void:
if freeze_mode == new_value:
return
freeze_mode = new_value
if _body.get_id() == 0:
return
_update_body_mode()
自从我实施 _update_body_mode
检查 freeze_mode
是否为 Kinematic
后,"Area"
将表现得像 "Static"
,这就是我们想要的。 好吧,差不多了,我们会讲到的。
输入可拾取
遗憾的是 body_set_pickable
没有公开脚本。所以我们将不得不重新创建此功能。
signal input_event(viewport, event, shape_idx)
signal mouse_entered()
signal mouse_exited()
export var input_pickable:bool setget set_input_pickable
var _mouse_is_inside:bool
func _enter_tree() -> void:
# …
_update_pickable()
# …
func _notification(what: int) -> void:
# …
if what == NOTIFICATION_VISIBILITY_CHANGED:
_update_pickable()
func _update_pickable() -> void:
set_process_input(input_pickable and _body.get_id() != 0 and is_visible_in_tree())
func _input(event: InputEvent) -> void:
if (
not (event is InputEventScreenDrag)
and not (event is InputEventScreenTouch)
and not (event is InputEventMouse)
):
return
var viewport := get_viewport()
var position:Vector2 = viewport.get_canvas_transform().affine_inverse().xform(event.position)
var objects := get_world_2d().direct_space_state.intersect_point(position, 32, [], 0x7FFFFFFF, false, true)
var is_inside := false
for object in objects:
if object.rid == _area:
is_inside = true
break
if is_inside:
_input_event(viewport, event, 0)
emit_signal("input_event", viewport, event, 0)
if event is InputEventMouse and _mouse_is_inside != is_inside:
_mouse_is_inside = is_inside
if _mouse_is_inside:
emit_signal("mouse_entered")
else:
emit_signal("mouse_exited")
# warning-ignore:unused_argument
# warning-ignore:unused_argument
# warning-ignore:unused_argument
func _input_event(viewport:Object, event:InputEvent, shape_idx:int) -> void:
pass
func set_input_pickable(new_value:bool) -> void:
if input_pickable == new_value:
return
input_pickable = new_value
_update_pickable()
Body进入和退出
似乎还有一个错误需要 area_set_monitorable
进行监控。所以我们不能做一个可以监控但不可监控的区域。
signal body_entered(body)
signal body_exited(body)
signal body_shape_entered(body_rid, body, body_shape_index, local_shape_index)
signal body_shape_exited(body_rid, body, body_shape_index, local_shape_index)
signal area_entered(area)
signal area_exited(area)
signal area_shape_entered(area_rid, area, area_shape_index, local_shape_index)
signal area_shape_exited(area_rid, area, area_shape_index, local_shape_index)
export var monitorable:bool setget set_monitorable
export var monitoring:bool setget set_monitoring
var overlapping_body_instances:Dictionary
var overlapping_area_instances:Dictionary
var overlapping_bodies:Dictionary
var overlapping_areas:Dictionary
func _enter_tree() -> void:
# …
_update_monitoring()
# …
func _update_monitoring() -> void:
Physics2DServer.area_set_monitorable(_area, monitorable or monitoring)
if monitoring:
Physics2DServer.area_set_monitor_callback(_area, self, "_body_monitor")
Physics2DServer.area_set_area_monitor_callback(_area, self, "_area_monitor")
else:
Physics2DServer.area_set_monitor_callback(_area, null, "")
Physics2DServer.area_set_area_monitor_callback(_area, null, "")
func _body_monitor(status:int, body:RID, instance_id:int, body_shape_index:int, local_shape_index:int) -> void:
if _body == body:
return
var instance := instance_from_id(instance_id)
if status == 0:
# entered
if not overlapping_bodies.has(body):
overlapping_bodies[body] = 0
overlapping_body_instances[instance] = instance
emit_signal("body_entered", instance)
overlapping_bodies[body] += 1
emit_signal("body_shape_entered", body, instance, body_shape_index, local_shape_index)
else:
# exited
emit_signal("body_shape_exited", body, instance, body_shape_index, local_shape_index)
overlapping_bodies[body] -= 1
if overlapping_bodies[body] == 0:
overlapping_bodies.erase(body)
overlapping_body_instances.erase(instance)
emit_signal("body_exited", instance)
func _area_monitor(status:int, area:RID, instance_id:int, area_shape_index:int, local_shape_index:int) -> void:
var instance := instance_from_id(instance_id)
if status == 0:
# entered
if not overlapping_areas.has(area):
overlapping_areas[area] = 0
overlapping_area_instances[instance] = instance
emit_signal("area_entered", instance)
overlapping_areas[area] += 1
emit_signal("area_shape_entered", area, instance, area_shape_index, local_shape_index)
else:
# exited
emit_signal("area_shape_exited", area, instance, area_shape_index, local_shape_index)
overlapping_areas[area] -= 1
if overlapping_areas[area] == 0:
overlapping_areas.erase(area)
overlapping_area_instances.erase(instance)
emit_signal("area_exited", instance)
func get_overlapping_bodies() -> Array:
if not monitoring:
push_error("monitoring is false")
return []
return overlapping_body_instances.keys()
func get_overlapping_areas() -> Array:
if not monitoring:
push_error("monitoring is false")
return []
return overlapping_area_instances.keys()
func overlaps_body(body:Node) -> bool:
if not monitoring:
return false
return overlapping_body_instances.has(body)
func overlaps_area(area:Node) -> bool:
if not monitoring:
return false
return overlapping_area_instances.has(area)
func set_monitoring(new_value:bool) -> void:
if monitoring == new_value:
return
monitoring = new_value
if _area.get_id() == 0:
return
_update_monitoring()
func set_monitorable(new_value:bool) -> void:
if monitorable == new_value:
return
monitorable = new_value
if _area.get_id() == 0:
return
_update_monitoring()
我在这里使用 area_set_monitor_callback
和 area_set_area_monitor_callback
。该文档声称 area_set_monitor_callback
适用于区域和身体。但这也不正确。 area_set_monitor_callback
只针对body,无证的area_set_area_monitor_callback
针对area
我需要跟踪每个进入和存在的形状。这就是为什么我使用 overlapping_areas
和 overlapping_bodies
的字典。键将是 RID
s,值将是形状重叠的数量。
我们快完成了。
碰撞层和遮罩
我希望区域和 body 共享碰撞层和遮罩。除了 "Area"
模式,我会将 body 的碰撞层和遮罩设置为 0,这样它就不会与任何东西发生碰撞。
export(int, LAYERS_2D_PHYSICS) var collision_layer:int = 1 setget set_collision_layer
export(int, LAYERS_2D_PHYSICS) var collision_mask:int = 1 setget set_collision_mask
func _enter_tree() -> void:
# …
_update_collision_layer_and_mask()
# …
func _update_collision_layer_and_mask() -> void:
Physics2DServer.area_set_collision_layer(_area, collision_layer)
Physics2DServer.body_set_collision_layer(_body, collision_layer if not freeze or freeze_mode != 2 else 0)
Physics2DServer.area_set_collision_mask(_area, collision_mask)
Physics2DServer.body_set_collision_mask(_body, collision_mask if not freeze or freeze_mode != 2 else 0)
func set_collision_layer(new_value:int) -> void:
if collision_layer == new_value:
return
collision_layer = new_value
if _body.get_id() == 0:
return
_update_collision_layer_and_mask()
func set_collision_mask(new_value:int) -> void:
if collision_mask == new_value:
return
collision_mask = new_value
if _body.get_id() == 0:
return
_update_collision_layer_and_mask()
同时让我们实现 get_collision_layer_bit
、get_collision_mask_bit
、set_collision_layer_bit
和 set_collision_mask_bit
:
func get_collision_layer_bit(bit:int) -> bool:
if bit < 0 or bit > 31:
push_error("Collision layer bit must be between 0 and 31 inclusive.")
return false
return collision_layer & (1 << bit) != 0
func get_collision_mask_bit(bit:int) -> bool:
if bit < 0 or bit > 31:
push_error("Collision mask bit must be between 0 and 31 inclusive.")
return false
return collision_mask & (1 << bit) != 0
func set_collision_layer_bit(bit:int, value:bool) -> void:
if bit < 0 or bit > 31:
push_error("Collision layer bit must be between 0 and 31 inclusive.")
return
if value:
self.collision_layer = collision_layer | 1 << bit
else:
self.collision_layer = collision_layer & ~(1 << bit)
func set_collision_mask_bit(bit:int, value:bool) -> void:
if bit < 0 or bit > 31:
push_error("Collision mask bit must be between 0 and 31 inclusive.")
return
if value:
self.collision_mask = collision_mask | 1 << bit
else:
self.collision_mask = collision_mask & ~(1 << bit)
并在_update_body_mode
中添加对_update_collision_layer_and_mask()
的调用:
func _update_body_mode() -> void:
_update_collision_layer_and_mask()
# …
我们完成了。 我觉得。