Godot 4 源码分析 - 碰撞

碰撞功能应该是一个核心功能,它能自动产生相应的数据,比如目标对象进入、离开本对象的检测区域。

基于属性设置,能碰撞的都具备这样的属性:Layer、Mask.

在Godot 4中,Collision属性中的Layer和Mask属性是用于定义碰撞过滤的重要参数。它们允许控制哪些物体可以与该节点进行碰撞检测。

  1. Layer(图层):

    • Layer是所有节点都具有的属性,用于将节点分组到不同的图层中。Layer是一组位掩码(bitmask),每个位代表一个特定的碰撞图层。每个物体都可以分配一个或多个碰撞图层。通过将物体分配到特定的碰撞图层,可定义其所属的逻辑组
    • 每个节点可以属于一个或多个图层。你可以在节点的属性面板中的Layer部分选择一个或多个图层。
    • Layer属性定义了节点所属的图层。默认情况下,节点属于基本图层(Base Layer)。
    • 使用不同图层将场景中的节点分组,可以使你能够仅与特定图层上的节点进行碰撞检测。
  2. Mask(掩码):

    • Mask也是所有节点都具有的属性,用于指定该节点对碰撞的兴趣。Mask也是一组位掩码,用于指示物体可以与哪些碰撞图层的物体发生碰撞检测。每个物体都可以指定一个碰撞掩码。通过设置碰撞掩码,可定义物体与哪些碰撞图层的物体发生碰撞
    • 每个节点都有一个掩码值。可以在节点的属性面板中的Collision属性下设置掩码。
    • Mask属性定义了该节点对哪些图层的节点感兴趣,该节点将与这些图层中的其他节点进行碰撞检测。
    • 每个节点的掩码值是一个32位的整数,每一位代表一个图层。0表示不兴趣该图层,1表示兴趣该图层。可以使用位操作(如按位与和按位或)来设置和检查掩码值。

通过使用Layer和Mask属性,你可以灵活地控制碰撞检测,使得只特定图层中的节点相互交互。例如,你可以设置一个节点仅与属于某个特定图层的节点进行碰撞,而忽略其他图层的节点。

需要注意的是,为了让两个节点进行碰撞检测,它们的Layer和Mask需要同时满足一定条件。具体而言,一个节点的Layer值必须包含在另一个节点的Mask值中,同时另一个节点的Layer值必须包含在该节点的Mask值中。

通过合理设置Layer和Mask属性,可在Godot 4中创建精细且灵活的碰撞过滤系统,以实现各种复杂的物理效果和游戏机制。

假设有两个碰撞图层,一个是"Player",另一个是"Enemy"。有一个玩家角色和一些敌人,想要确保玩家和敌人之间发生碰撞,但是敌人之间不发生碰撞。

  • 对于玩家对象:将其分配到"Player"碰撞图层,并将其Collision Mask设置为"Enemy"的位掩码。这将使玩家只与"Enemy"图层的物体发生碰撞。

  • 对于敌人对象:将它们分配到"Enemy"碰撞图层,并将其Collision Mask设置为"Player"的位掩码。这将使敌人只与"Player"图层的物体发生碰撞。

对于敌人对象之间,可将它们分配到相同的碰撞图层并设置相应的碰撞掩码,以确保它们不会互相碰撞。

通过这种方式,可在复杂的场景中更精细地控制碰撞的交互,使碰撞逻辑更加清晰和可管理。

从理解角度来说,逻辑说起来复杂,技术上实现很简单

Layer与Mask应该都是32位整数,对象A有Layer与Mask属性,对象B也有Layer与Mask属性

如果 A.Layer & B.Mask 非零,则 A要与B发生碰撞

如果 A.Mask & B.Layer非零,则B要与A发生碰撞

还是有点绕,再多想一下,以下是我自己的想法,不一定正确:

其实,Layer与Mask都是逻辑概念,虚拟的。在真实场景中,A、B都占据相应的空间位置(3D)或平面位置(2D),在运行过程中,A、B至少有一个能动,它们的相对位置可能会变化,在某个时刻会有交叠。这个时候,godot引擎知道,因为它能实时计算。那么,godot发现两个对象发生交叠了,怎么办呢?

那就检查A、B的Layer与Mask。

  • 如果 A.Layer & B.Mask 非零,则B能检测到A,触发B的body_entered信号,实参为A。
  • 如果 B.Layer & A.Mask 非零,则A能检测到B,触发A的body_entered信号,实参为B。

其实上面的说法不严谨,因为body_entered信号不应该连续发送,而是有个进出状态。整体连起来应该就是:

  • A、B分别维护自己的body_map,知道与自己交叠的对象列表。这个工作不能交给godot引擎做,它多忙的,还是各自管好自己的事
  • 从源码看出,检测进出的状态标志,还是A、B自己完成,也就是说,遍历所有的监控对象monitored_bodies,看与自己的??状态E->value,若为0,则啥事没有,不再监控该对象。如果>0,则为AREA_BODY_ADDED,表示进入。如果<0,则为AREA_BODY_REMOVED,表示离开。
void GodotArea2D::call_queries() {if (!monitor_callback.is_null() && !monitored_bodies.is_empty()) {if (monitor_callback.is_valid()) {Variant res[5];Variant *resptr[5];for (int i = 0; i < 5; i++) {resptr[i] = &res[i];}for (HashMap<BodyKey, BodyState, BodyKey>::Iterator E = monitored_bodies.begin(); E;) {if (E->value.state == 0) { // Nothing happenedHashMap<BodyKey, BodyState, BodyKey>::Iterator next = E;++next;monitored_bodies.remove(E);E = next;continue;}res[0] = E->value.state > 0 ? PhysicsServer2D::AREA_BODY_ADDED : PhysicsServer2D::AREA_BODY_REMOVED;res[1] = E->key.rid;res[2] = E->key.instance_id;res[3] = E->key.body_shape;res[4] = E->key.area_shape;HashMap<BodyKey, BodyState, BodyKey>::Iterator next = E;++next;monitored_bodies.remove(E);E = next;Callable::CallError ce;Variant ret;monitor_callback.callp((const Variant **)resptr, 5, ret, ce);if (ce.error != Callable::CallError::CALL_OK) {ERR_PRINT_ONCE("Error calling event callback method " + Variant::get_callable_error_text(monitor_callback, (const Variant **)resptr, 5, ce));}}} else {monitored_bodies.clear();monitor_callback = Callable();}}if (!area_monitor_callback.is_null() && !monitored_areas.is_empty()) {if (area_monitor_callback.is_valid()) {Variant res[5];Variant *resptr[5];for (int i = 0; i < 5; i++) {resptr[i] = &res[i];}for (HashMap<BodyKey, BodyState, BodyKey>::Iterator E = monitored_areas.begin(); E;) {if (E->value.state == 0) { // Nothing happenedHashMap<BodyKey, BodyState, BodyKey>::Iterator next = E;++next;monitored_areas.remove(E);E = next;continue;}res[0] = E->value.state > 0 ? PhysicsServer2D::AREA_BODY_ADDED : PhysicsServer2D::AREA_BODY_REMOVED;res[1] = E->key.rid;res[2] = E->key.instance_id;res[3] = E->key.body_shape;res[4] = E->key.area_shape;HashMap<BodyKey, BodyState, BodyKey>::Iterator next = E;++next;monitored_areas.remove(E);E = next;Callable::CallError ce;Variant ret;area_monitor_callback.callp((const Variant **)resptr, 5, ret, ce);if (ce.error != Callable::CallError::CALL_OK) {ERR_PRINT_ONCE("Error calling event callback method " + Variant::get_callable_error_text(area_monitor_callback, (const Variant **)resptr, 5, ce));}}} else {monitored_areas.clear();area_monitor_callback = Callable();}}
}
  • 根据是否为AREA_BODY_ADDED确定body_in标志。若为body_in,则触发tree_entered、tree_exiting信号,如果本对象在工作场景中,则触发body_entered,参数为node。顺便还触发了body_shape_entered信号,看着用吧。
  • 若body_in为false,则触发tree_entered、tree_exiting。如果本对象在工作场景中,则触发body_exited、body_shape_exited
void Area2D::_body_inout(int p_status, const RID &p_body, ObjectID p_instance, int p_body_shape, int p_area_shape) {bool body_in = p_status == PhysicsServer2D::AREA_BODY_ADDED;ObjectID objid = p_instance;Object *obj = ObjectDB::get_instance(objid);Node *node = Object::cast_to<Node>(obj);HashMap<ObjectID, BodyState>::Iterator E = body_map.find(objid);if (!body_in && !E) {return; //does not exist because it was likely removed from the tree}lock_callback();locked = true;if (body_in) {if (!E) {E = body_map.insert(objid, BodyState());E->value.rid = p_body;E->value.rc = 0;E->value.in_tree = node && node->is_inside_tree();if (node) {node->connect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area2D::_body_enter_tree).bind(objid));node->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area2D::_body_exit_tree).bind(objid));if (E->value.in_tree) {emit_signal(SceneStringNames::get_singleton()->body_entered, node);}}}E->value.rc++;if (node) {E->value.shapes.insert(ShapePair(p_body_shape, p_area_shape));}if (!node || E->value.in_tree) {emit_signal(SceneStringNames::get_singleton()->body_shape_entered, p_body, node, p_body_shape, p_area_shape);}} else {E->value.rc--;if (node) {E->value.shapes.erase(ShapePair(p_body_shape, p_area_shape));}bool in_tree = E->value.in_tree;if (E->value.rc == 0) {body_map.remove(E);if (node) {node->disconnect(SceneStringNames::get_singleton()->tree_entered, callable_mp(this, &Area2D::_body_enter_tree));node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &Area2D::_body_exit_tree));if (in_tree) {emit_signal(SceneStringNames::get_singleton()->body_exited, obj);}}}if (!node || in_tree) {emit_signal(SceneStringNames::get_singleton()->body_shape_exited, p_body, obj, p_body_shape, p_area_shape);}}locked = false;unlock_callback();
}

到此,就该关注monitored_bodies,它在add_body_to_query、remove_body_from_query中维护


void GodotArea2D::add_body_to_query(GodotBody2D *p_body, uint32_t p_body_shape, uint32_t p_area_shape) {BodyKey bk(p_body, p_body_shape, p_area_shape);monitored_bodies[bk].inc();if (!monitor_query_list.in_list()) {_queue_monitor_update();}
}void GodotArea2D::remove_body_from_query(GodotBody2D *p_body, uint32_t p_body_shape, uint32_t p_area_shape) {BodyKey bk(p_body, p_body_shape, p_area_shape);monitored_bodies[bk].dec();if (!monitor_query_list.in_list()) {_queue_monitor_update();}
}

但这没看到Layer与Mask属性的作用。那就倒查。最终发现在GodotCollisionObject2D类中:

_FORCE_INLINE_ bool collides_with(GodotCollisionObject2D *p_other) const {return p_other->collision_layer & collision_mask;
}

果然,与猜测的一致。具体调用collides_with是在一些setup函数中。

bool GodotAreaPair2D::setup(real_t p_step) {bool result = false;if (area->collides_with(body) && GodotCollisionSolver2D::solve(body->get_shape(body_shape), body->get_transform() * body->get_shape_transform(body_shape), Vector2(), area->get_shape(area_shape), area->get_transform() * area->get_shape_transform(area_shape), Vector2(), nullptr, this)) {result = true;}process_collision = false;has_space_override = false;if (result != colliding) {if ((int)area->get_param(PhysicsServer2D::AREA_PARAM_GRAVITY_OVERRIDE_MODE) != PhysicsServer2D::AREA_SPACE_OVERRIDE_DISABLED) {has_space_override = true;} else if ((int)area->get_param(PhysicsServer2D::AREA_PARAM_LINEAR_DAMP_OVERRIDE_MODE) != PhysicsServer2D::AREA_SPACE_OVERRIDE_DISABLED) {has_space_override = true;} else if ((int)area->get_param(PhysicsServer2D::AREA_PARAM_ANGULAR_DAMP_OVERRIDE_MODE) != PhysicsServer2D::AREA_SPACE_OVERRIDE_DISABLED) {has_space_override = true;}process_collision = has_space_override;if (area->has_monitor_callback()) {process_collision = true;}colliding = result;}return process_collision;
}bool GodotBodyPair2D::setup(real_t p_step) {check_ccd = false;if (!A->interacts_with(B) || A->has_exception(B->get_self()) || B->has_exception(A->get_self())) {collided = false;return false;}collide_A = (A->get_mode() > PhysicsServer2D::BODY_MODE_KINEMATIC) && A->collides_with(B);collide_B = (B->get_mode() > PhysicsServer2D::BODY_MODE_KINEMATIC) && B->collides_with(A);report_contacts_only = false;if (!collide_A && !collide_B) {if ((A->get_max_contacts_reported() > 0) || (B->get_max_contacts_reported() > 0)) {report_contacts_only = true;} else {collided = false;return false;}}//use local A coordinates to avoid numerical issues on collision detectionoffset_B = B->get_transform().get_origin() - A->get_transform().get_origin();_validate_contacts();const Vector2 &offset_A = A->get_transform().get_origin();Transform2D xform_Au = A->get_transform().untranslated();Transform2D xform_A = xform_Au * A->get_shape_transform(shape_A);Transform2D xform_Bu = B->get_transform();xform_Bu.columns[2] -= offset_A;Transform2D xform_B = xform_Bu * B->get_shape_transform(shape_B);GodotShape2D *shape_A_ptr = A->get_shape(shape_A);GodotShape2D *shape_B_ptr = B->get_shape(shape_B);Vector2 motion_A, motion_B;if (A->get_continuous_collision_detection_mode() == PhysicsServer2D::CCD_MODE_CAST_SHAPE) {motion_A = A->get_motion();}if (B->get_continuous_collision_detection_mode() == PhysicsServer2D::CCD_MODE_CAST_SHAPE) {motion_B = B->get_motion();}bool prev_collided = collided;collided = GodotCollisionSolver2D::solve(shape_A_ptr, xform_A, motion_A, shape_B_ptr, xform_B, motion_B, _add_contact, this, &sep_axis);if (!collided) {oneway_disabled = false;if (A->get_continuous_collision_detection_mode() == PhysicsServer2D::CCD_MODE_CAST_RAY && collide_A) {check_ccd = true;return true;}if (B->get_continuous_collision_detection_mode() == PhysicsServer2D::CCD_MODE_CAST_RAY && collide_B) {check_ccd = true;return true;}return false;}if (oneway_disabled) {return false;}if (!prev_collided) {if (shape_B_ptr->allows_one_way_collision() && A->is_shape_set_as_one_way_collision(shape_A)) {Vector2 direction = xform_A.columns[1].normalized();bool valid = false;for (int i = 0; i < contact_count; i++) {Contact &c = contacts[i];if (c.normal.dot(direction) > -CMP_EPSILON) { // Greater (normal inverted).continue;}valid = true;break;}if (!valid) {collided = false;oneway_disabled = true;return false;}}if (shape_A_ptr->allows_one_way_collision() && B->is_shape_set_as_one_way_collision(shape_B)) {Vector2 direction = xform_B.columns[1].normalized();bool valid = false;for (int i = 0; i < contact_count; i++) {Contact &c = contacts[i];if (c.normal.dot(direction) < CMP_EPSILON) { // Less (normal ok).continue;}valid = true;break;}if (!valid) {collided = false;oneway_disabled = true;return false;}}}return true;
}

这就没有必要再跟下去了。至于这些setup函数什么时候被调用,可以用一个实际项目调试来看看。

主要核心思想是理解碰撞的Layer与Mask配置问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/83280.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

8个值得收藏的在线3D建模工具

如今&#xff0c;许多设计师、艺术家和建筑师尝试学习进行 3D 建模来表达他们的想法。 但 3D 建模并不总是看起来那样。 我们所有人都很难找到合适的工具&#xff0c;尤其是在学习阶段。 但不要害怕&#xff01; 你可以学习仅使用浏览器进行建模。 有许多基于浏览器的 3D 建模…

成功解决Android设备adb连接后显示device unauthorized

一、提出问题 在电脑通过USB连接新的Android设备&#xff0c;想要通过adb来进行一些操作时&#xff0c;却发现命令提示符上在输入下面命令后显示设备未授权的信息也就是"unauthorized" adb devices二、不可行的解决方案 有人提出的解决方案是打开Android设备的开发…

selenium获取b站视频标题

一、下载selenium 1. 下载对应版本的浏览器驱动 2. 安装selenium 3.把浏览器驱动放到使用的python内核的script目录中 二、测试效果模拟登录b站 from selenium import webdriver from selenium.webdriver.common.by import By import timebrowser webdriver.Chrome() # 打…

C++/Qt 读写文件

之前写过两篇跟文件操作相关的博客&#xff0c;有兴趣也可以看一下&#xff1a; C语言读写文件 Qt关于文件路径的处理 先讲一些关于基础文本文件和二进制文件的读写操作&#xff0c;后续将会整理C/Qt关于ini、xml、json、xlsx相关文件的读写操作。 C 相比于C语言使用FILE文…

冠达管理:股票注册制通俗理解?

目前我国A股商场正在进行股票注册制变革&#xff0c;相较之前的发行准则&#xff0c;股票注册制在理念上更为商场化&#xff0c;这意味着公司发行股票的门槛将下降&#xff0c;公司数量将添加&#xff0c;而股票流通的方式也将有所改变。那么股票注册制指的是什么&#xff0c;它…

ChatGPT会取代搜索引擎吗?BingChat、GoogleBard与ChatGPT区别

目前暂时不会&#xff0c;ChatGPT为代表的聊天机器人很可能会直接集成到搜索中&#xff0c;而不是取代它。微软已经通过Bing Chat和Bing做到了这一点&#xff0c;它将“聊天”选项卡直接放入Bing搜索的菜单中。Google、百度也分别开始尝试通过其AI生成技术将Google Bard、文心一…

栈和队列(二) 队列操作详解及栈与队列的相互实现

文章目录 四、队列1、什么是队列2、队列的基本操作Queue.hQueue.c初始化队列队尾入队列队头出队列获取队列头部元素获取队列队尾元素获取队列中有效元素个数检测队列是否为空&#xff0c;如果为空返回非零结果&#xff0c;如果非空返回0销毁队列 五、设计循环队列六、栈与队列的…

【Linux的开胃小菜】Linux系统安装后初始化配置操作

我们刚接手一台刚安装好服务器系统之后&#xff0c;可以对系统进行一些基础优化&#xff1a; 常规设定&#xff1a; centos: 1.关闭 iptables 2.关闭 selinux 3.设定 ChronyUbuntu: 4. /etc/security/limits.conf 5. /etc/sysctl.conf1.首先使用国内阿里云的yum源&#xff08…

Electron学习1 安装环境与第一个程序

Electron学习1 安装环境与第一个程序 一、 Electron 简介二、安装 nvm三、安装nodejs四、安装nrm五、安装electron1. npm 初始化2. 创建 package.json3. 安装electron4. 创建一个页面5. 创建文件main.js6. 创建预加载器文件 preload.js7. 启动程序 六、打包 一、 Electron 简介…

windows .gitignore 加入文件名后 依然可以从git status中看到文件问题

最近在学git&#xff0c;对着b站的视频操作&#xff0c;结果很简单的添加.gitignore文件操作&#xff0c;up主的正常隐藏&#xff0c;我的却一直出问题。 百思不得其解&#xff0c;网上各种啥啥啥清缓存都没讲到点上。 最后发现是.gitignore文件有问题&#xff0c;windows默认…

uniapp 实现滑动视图切换 顶部滚动导航栏

无论小程序的时候一般有这个功能,在页面处于首页时候,滑动视图,切换视图顶部滚动导航也跟着切换 1.想要实现这个功能就需要实现顶部导航栏,首先实现顶部滚导航栏 点击高亮颜色显示 模板代码 <scroll-view scroll-x"true" class"scroll-content" > …

IDEA离线安装插件

一、背景 有时&#xff0c;在ideal中我们无法获取到插件&#xff0c;可能是因为内网或者无法访问插件库等原因&#xff0c;此时我们需要离线安装插件 IDEA离线仓库&#xff1a;https://plugins.jetbrains.com/ 二、步骤 2.1 下载插件&#xff1a;https://plugins.jetbrains.…

护网行动 | AD360 在网络安全中的重要作用

随着数字化时代的来临&#xff0c;网络已经成为了人们生活和工作中不可或缺的一部分。然而&#xff0c;随之而来的是网络安全问题日益突出。为了应对这些安全威胁&#xff0c;护网行动应运而生&#xff0c;其中AD360在保障网络安全方面扮演着至关重要的角色。 AD360是一个集成的…

nginx 负载均衡

1.环境准备 我使用的说centos7的系统 1.20版本的nginx 另外还有3台虚拟机 主机&#xff1a;192.168.163.142 两台服务器&#xff1a;服务器A--192.168.163.140 服务器B---192.168.163.141 2.配置服务器A和B 找到nginx下的html目录&#xff0c;编辑其中的index.html(在此…

FreeRTOS(任务管理的创建、删除、挂起、恢复)

目录 一、任务的基本概念 二、任务状态的概念 1、Running—运行态&#xff1a; 2、Ready—就绪态 3、Blocked—阻塞态 4、Suspended—挂起态 三、任务状态的切换 四、系统启动 1、vTaskStartScheduler()函数 1.1 作用 1.2 启动函数介绍 2、空闲任务 2.1 空闲任务的作…

【ChatGPT 指令大全】销售怎么借力ChatGPT提高效率

目录 销售演说 电话销售 产出潜在客户清单 销售领域计划 销售培训计划 总结 随着人工智能技术的不断进步&#xff0c;我们现在有机会利用ChatGPT这样的智能助手来改进我们的销售工作。在接下来的时间里&#xff0c;我将为大家介绍如何运用ChatGPT提高销售效率并取得更好的…

nbcio-boot因升级mybatis-plus到3.5.3.1和JSQLParser 到4.6引起的online表单开发的数据库导入出错解决

更多功能看演示系统 gitee源代码地址 后端代码&#xff1a; https://gitee.com/nbacheng/nbcio-boot 前端代码&#xff1a;https://gitee.com/nbacheng/nbcio-vue.git 在线演示&#xff08;包括H5&#xff09; &#xff1a; http://122.227.135.243:9888 nbcio-boot因升级…

专业的ADAS测试记录仪ETHOS

随着ADAS驾驶辅助系统技术的快速发展及日臻成熟&#xff0c;近年来ADAS在全球汽车市场已开始快速普及和商业化&#xff0c;而如何确保ADAS系统的可靠和安全俨然成为汽车领域的重要问题。因此&#xff0c;ADAS驾驶辅助系统的测试也成为了各大整车厂及零部件厂商所关注的焦点。 一…

NodeJS原型链污染ctfshow_nodejs

文章目录 NodeJS原型链污染&ctfshow_nodejs前言0x01.原型与原型链0x02.prototype和__proto__分别是什么&#xff1f;0x03.原型链继承不同对象的原型链* 0x04.原型链污染原理0x05.merge()导致原型链污染0x06.ejs模板引擎RCEejs模板引擎另一处rce 0x07.jade模板引擎RCE【ctfs…

mysql数据库如何转移到oracle

mysql数据库转移到oracle 在研发过程中&#xff0c;可能会用到将表数据库中的表结构及数据迁移到另外一种数据库中&#xff0c; 比如说从mysql中迁移到oracle中&#xff0c; 常用的方法有好些&#xff0c;如下 1、使用powerdesigner&#xff0c;先连接mysql然后生成mysql的p…