Godot 4 源码分析 - 碰撞

这篇具有很好参考价值的文章主要介绍了Godot 4 源码分析 - 碰撞。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

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

Godot 4 源码分析 - 碰撞,godot,游戏引擎

在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 happened
					HashMap<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 happened
					HashMap<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 detection
	offset_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配置问题。文章来源地址https://www.toymoban.com/news/detail-627590.html

到了这里,关于Godot 4 源码分析 - 碰撞的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • GODOT游戏引擎简介,包含与unity性能对比测试,以及选型建议

    GODOT,是一个免费开源的3D引擎。本文以unity作对比,简述两者区别和选型建议。由于是很久以前写的ppt,技术原因视频和部分章节丢失了。建议当做业务参考。 GODOT目前为止遇到3个比较重大的机遇,第一个是oprea的合作奖,第二个是用支持c#换来的微软的投资,第三个是虚幻

    2024年02月14日
    浏览(79)
  • godot引擎c++源码深度解析系列二

    记录每次研究源码的突破,今天已经将打字练习的功能完成了一个基本模型,先来看下运行效果。 godot源码增加打字练习的demo 这个里面需要研究以下c++的控件页面的开发和熟悉,毕竟好久没有使用c++了,先来看以下代码吧。 就这样就实现了文本框,输入框和按钮的实现,以

    2024年02月15日
    浏览(42)
  • 使用js原生customElements.define()API 实现类似godot游戏引擎的colorRect类

    一共有两个方案,一个是基于div和css的dom渲染,一个是基于canvas的硬件绘图

    2024年02月08日
    浏览(61)
  • Godot 4 源码分析 - 初探

    准备研究GoDot 4源码。 获取源代码 在进入 SCons 构建系统并编译 Godot 之前,你需要将 Godot 的源代码下载到本地。 源代码位于 GitHub 上, 虽然你可以通过网站手动下载它, 但是通常你希望通过  git  版本控制系统来下载. 如果你是为了做贡献或拉动请求而进行编译,你应该遵循

    2024年02月06日
    浏览(36)
  • Godot 4 源码分析 - 获取脚本

    今天搂草打兔,取得了脚本内容 因为已能取得属性值,那就再进一步,取得属性名列表 相应地,可以取得函数名列表、子对象列表 其中,获取对象信息(GetObjectHint)是期望能显示对象的一些相应信息 测试一下,取得根节点(Book)的所有属性名: Book.propertyNames 看到script属性:

    2024年02月15日
    浏览(35)
  • Godot 4 源码分析 - 获取属性信息

    在管道通信基础上,可进行宿主程序与Godot的双向通信。 先拿属性信息试试手。  这已具备RTTI的雏形。 

    2024年02月16日
    浏览(30)
  • Godot 4 源码分析 - 增加格式化字符串功能

    Godot 4的主要字符串类型为String,已经设计得比较完善了,但有一个问题,格式化这块没怎么考虑。 String中有一个format函数,但这个函数只有两个参数,这咋用? 查找使用例子,都是这种效果 一看就懵。哪里有之前用的带%s %d...之类的格式化用得舒服。 动手实现一个 提供s

    2024年02月14日
    浏览(44)
  • Godot引擎 4.0 文档 - 入门介绍 - Godot简介

    本文旨在帮助您确定 Godot 是否适合您。我们将介绍该引擎的一些广泛功能,让您了解使用它可以实现什么,并回答诸如“我需要了解什么才能开始使用?”等问题。 这绝不是详尽的概述。我们将在本入门系列中介绍更多功能。 Godot 是一个通用的 2D 和 3D 游戏引擎,您还可以

    2024年02月05日
    浏览(73)
  • Godot 4 源码分析 - Path2D与PathFollow2D

    学习演示项目dodge_the_creeps,发现里面多了一个Path2D与PathFollow2D  研究GDScript代码发现,它主要用于随机生成Mob 这个有这么大的作用,不明觉厉 但不知道如何下手 查看源码,有编辑器及类源码 先从应用角度,到B站上找找有没有视频,结果发现这个 Godot塔防游戏 - 01 -核心路径

    2024年02月14日
    浏览(34)
  • Godot引擎 4.0 文档 - 手册 - 最佳实践

      本系列是一系列最佳实践,可帮助您高效地使用 Godot。 Godot 在构建项目代码库并将其分解为场景方面提供了极大的灵活性。每种方法都有其优点和缺点,在您使用该引擎足够长的时间之前,很难权衡它们。 总是有很多方法来构建代码和解决特定的编程问题。不可能在这里

    2024年02月09日
    浏览(52)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包