Godot 4 源码分析 - 碰撞

news2024/11/26 11:59:34

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

基于属性设置,能碰撞的都具备这样的属性: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 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配置问题。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

在线文档协作工具有哪些推荐?

在现代工作和学习中&#xff0c;团队协作的重要性日益凸显。随着云计算和互联网技术的快速发展&#xff0c;越来越多的在线协作工具涌现出来&#xff0c;为我们提供了高效、便捷的协作方式。我根据日常工作经验&#xff0c;向大家推荐3款主流、且备受欢迎的在线协作工具&#x…

AI绘图实战(十二):让AI设计LOGO/图标/标识 | Stable Diffusion成为设计师生产力工具

S&#xff1a;AI能取代设计师么&#xff1f; I &#xff1a;至少在设计行业&#xff0c;目前AI扮演的主要角色还是超级工具&#xff0c;要顶替&#xff1f;除非甲方对设计效果无所畏惧~~ 预先学习&#xff1a; 安装及其问题解决参考&#xff1a;《Windows安装Stable Diffusion …

用C语言构建一个数字识别卷积神经网络

卷积神经网络的原理和对应的python例子参见: https://victorzhou.com/blog/intro-to-cnns-part-1/ 和...-2/ 这里仅叙述卷积神经网络的配置, 其余部分不做赘述&#xff0c;具体请参见上一篇: 用C语言构建一个手写数字识别神经网路 卷积网络同样采用简单的三层结构&#xff0c…

为何优秀的项目经理都在用鱼骨图?

什么是鱼骨图 鱼骨图&#xff0c;又被称为因果图或是石川图&#xff0c;是一种强大的视觉工具&#xff0c;用于识别&#xff0c;探索和显示在复杂过程中可能或已经出现的问题的所有可能原因。这个名字来源于这个图的形状&#xff0c;看起来像一条鱼的骨骼。 这种图表的起源可…

了解 CVSS:通用漏洞评分系统的应用

漏洞威胁管理至关重要&#xff0c;因为网络犯罪是一种持续存在的全球风险。网络犯罪分子愿意利用软件中的任何漏洞来访问网络和设备。对使用该软件的软件开发人员和组织的影响可能很严重。用户必须处理攻击的结果&#xff0c;例如赎金或数据盗窃&#xff0c;并且还可能面临法律…

笔记:Android 9系统启动流程

启动流程 1.按下电源&#xff0c;系统启动 当电源键按下时&#xff0c;引导芯片代码&#xff08;汇编指令&#xff09;会从预定的地方&#xff08;固化在ROM&#xff09;开始执行&#xff0c;将引导程序 BootLoader 加载到 RAM中&#xff0c;然后执行 2.引导程序 BootLoader …

箭头函数和普通函数的区别

1. 写法不同 const arrayFn (a, b) > a b; function add(a, b) > {return a b; }2. 内部this的指向不同 window.a 1;// 箭头函数中的this在定义时就已经固定&#xff0c;不会因为调用对象的不同而改变 const loga () > {const a 2;return this.a; }// this在该…

【C语言学习】逃逸字符(转义字符)

逃逸字符&#xff08;转义字符&#xff09; 1.\" 双引号 \" printf("请分别输入身高的英尺和英寸&#xff0c;""如输入\"5 7\"表示5英尺7英寸:");这里的"\就是双引号的作用&#xff0c;因为在双引号里面直接用双引号无意义&…

C++问题记录:VS中使用cout输出vsnprintf()产生的字符串有乱码

1. 问题介绍 使用Visual Studio开发一个C项目的时候&#xff0c;使用vsnprintf()生成格式化字符串&#xff0c;在使用std::cout输出该字符串的时候产生乱码&#xff0c;但是使用printf()输出的结果是正常的&#xff0c;下面是相关代码和结果&#xff0c;如有指教&#xff0c;欢…

量子力学的挑战和未来:未解决的问题和可能的发展方向

亲爱的读者&#xff0c; 欢迎回到我们的量子力学系列文章。在前面的几篇文章中&#xff0c;我们已经深入探讨了量子力学的起源、基本概念、实验验证以及应用领域&#xff0c;包括量子计算、量子通信和量子感应。今天&#xff0c;我们将探讨量子力学所面临的挑战以及未来可能的…

【雕爷学编程】 MicroPython动手做(34)——通用传感器的综合运用2

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

@ControllerAdvice注解使用及原理探究 | 京东物流技术团队

最近在新项目的开发过程中&#xff0c;遇到了个问题&#xff0c;需要将一些异常的业务流程返回给前端&#xff0c;需要提供给前端不同的响应码&#xff0c;前端再在次基础上做提示语言的国际化适配。这些异常流程涉及业务层和控制层的各个地方&#xff0c;如果每个地方都写一些…

【云原生】Docker中容器管理常用所有命令

1.docker 容器创建流程 2.容器运行本质 docker run [OPTIONS] IMAGE [COMMAND] [ARG...] 创建容器基本选项&#xff1a;--name&#xff1a;为容器命名 -i&#xff1a;交互式创建容器 -d&#xff1a;后台创建容器 -t&#xff1a;为容器分配伪终端 Docker 容器存在的意义就是为…

Demystifying Prompts in Language Models via Perplexity Estimation

Demystifying Prompts in Language Models via Perplexity Estimation 原文链接 Gonen H, Iyer S, Blevins T, et al. Demystifying prompts in language models via perplexity estimation[J]. arXiv preprint arXiv:2212.04037, 2022. 简单来说就是作者通过在不同LLM和不同…

如何提升自信更好地面对挑战

简而言之&#xff1a;扬长避短&#xff0c;做自己。 动态 - CSDN AI话痨&#xff1a; 提升自信是面对挑战的关键之一。以下是一些方法可以帮助你提升自信&#xff0c;更好地面对挑战&#xff1a; 自我认知&#xff1a;了解自己的优点和缺点&#xff0c;认识到自己的强项和弱…

无涯教程-Perl - Subroutines(子例程)

定义子程序 Perl编程语言中 Subroutine子程序定义的一般形式如下: sub subroutine_name {body of the subroutine } 调用该Perl Subroutine的典型方式如下- subroutine_name( list of arguments ); 在Perl 5.0之前的版本中&#xff0c;调用 Subroutine的语法略有不同&…

【LeetCode】相同的树、 翻转二叉树 、对称二叉树

100.相同的树 两棵树相同的条件就是根节点及他们的左右子树的值val相同&#xff0c;结构相同&#xff0c;就是一模一样&#xff0c;那这道题最终还是要同时遍历两个树的&#xff0c;并且还得遍历完&#xff0c;那如果我们在遍历的过程中&#xff0c;通过设置一些不满足相同的树…

【知识图谱】图数据库Neo4jDesktop的安装图文详解(小白适用)

neo4j 的安装需要有jdk环境的支持。因此在安装Neo4j之前&#xff0c;需要安装Java JDK。 一.安装JDK 参考文章https://blog.csdn.net/weixin_41824534/article/details/104147067?spm1001.2014.3001.5502 二.Neo4j下载 进入Neo4j官网 选择下载中心 下滑选择Neo4j Deskto…

Python渗透测试编程——AES与DES算法

一、AES简介 AES&#xff08;Advanced Encryption Standard&#xff0c;高级加密标准&#xff09;的出现&#xff0c;是因为以前使用的DES算法密钥长度较短&#xff0c;已经不适应当今数据加密安 全性的要求&#xff0c;因此2000年10月2日&#xff0c;美国政府宣布将比利时密码…

多雷达协同探测技术研究进展:认知跟踪与资源调度算法

源自&#xff1a;雷达学报 作者&#xff1a;易伟 袁野 刘光宏 葛建军 孔令讲 杨建宇 1. 引 言 雷达是信息感知的千里眼&#xff0c;具有极高的军用和民用价值&#xff0c;广泛应用在防空预警、遥感测绘、反恐维稳等领域[1–5]。雷达信息获取与探测技术也一直是大国竞相抢占…