Godot 4 源码分析 - Path2D与PathFollow2D

news2024/11/23 17:05:13

学习演示项目dodge_the_creeps,发现里面多了一个Path2D与PathFollow2D

 研究GDScript代码发现,它主要用于随机生成Mob

	var mob_spawn_location = get_node(^"MobPath/MobSpawnLocation")
	mob_spawn_location.progress = randi()

	# Set the mob's direction perpendicular to the path direction.
	var direction = mob_spawn_location.rotation + PI / 2

	# Set the mob's position to a random location.
	mob.position = mob_spawn_location.position

	# Add some randomness to the direction.
	direction += randf_range(-PI / 4, PI / 4)
	mob.rotation = direction

	# Choose the velocity for the mob.
	var velocity = Vector2(randf_range(150.0, 250.0), 0.0)
	mob.linear_velocity = velocity.rotated(direction)

这个有这么大的作用,不明觉厉

但不知道如何下手

查看源码,有编辑器及类源码

先从应用角度,到B站上找找有没有视频,结果发现这个

Godot塔防游戏 - 01 -核心路径制作 Path2D_哔哩哔哩_bilibili

看了之后,就知道使用方法了:

  • 添加Path2D
  • 在编辑器中设置路径各关键点,形成路径

  • 在Path2D下增加PathFollow2D

这就OK了。剩下的就是使用

所谓使用,输入为PathFollow2D的progress,输出为路径上的点信息(position, rotation...),然后用户再根据这些信息去确定相应的属性

比如演示项目中,Path2D定制了一个外框路径(左上角 > 右上角 > 右下角 > 左下角 > 左上角),在生成MOB时,随机指定其下的PathFollow2D的progress值为randi(),即为0 ~ 2^32 - 1的随机整数。因为路径是有长度的,本例中为2400,randi()值将按2400取模得到最终的随机值0 - 2399,当然也可以归一化,设置其progress_ratio值为0.0 - 1.0,意思一样。

查看源码,set_progress的逻辑不只是取模,还有限制范围。即PathFollow2D还有一个Loop属性,如果Loop为真,才会取模,为false时,会直接限制在路径长度范围内 progress = CLAMP(progress, 0, path_length); 之后统一更新_update_transform

void PathFollow2D::set_progress(real_t p_progress) {
	ERR_FAIL_COND(!isfinite(p_progress));
	progress = p_progress;
	if (path) {
		if (path->get_curve().is_valid()) {
			real_t path_length = path->get_curve()->get_baked_length();

			if (loop && path_length) {
				progress = Math::fposmod(progress, path_length);
				if (!Math::is_zero_approx(p_progress) && Math::is_zero_approx(progress)) {
					progress = path_length;
				}
			} else {
				progress = CLAMP(progress, 0, path_length);
			}
		}

		_update_transform();
	}
}

void PathFollow2D::_update_transform() {
	if (!path) {
		return;
	}

	Ref<Curve2D> c = path->get_curve();
	if (!c.is_valid()) {
		return;
	}

	real_t path_length = c->get_baked_length();
	if (path_length == 0) {
		return;
	}

	if (rotates) {
		Transform2D xform = c->sample_baked_with_rotation(progress, cubic);
		xform.translate_local(v_offset, h_offset);
		set_rotation(xform[1].angle());
		set_position(xform[2]);
	} else {
		Vector2 pos = c->sample_baked(progress, cubic);
		pos.x += h_offset;
		pos.y += v_offset;
		set_position(pos);
	}
}

从PathFollow2D代码来看,它派生于Node2D,所以具备transform属性:Position、Rotation、Scale、Skew,对于路径上的点使用而言,这些信息就足够了,能够确定这些点的位置、方向,其实就是一个矢量 

Loop属性值的含义前面已明确,Rotates、Cubic、H Offsets、V Offsets都是在_update_transform中起作用,具体算法可以不深究。但lookahead没找到具体用处,感觉影响不大。

class PathFollow2D : public Node2D {
	GDCLASS(PathFollow2D, Node2D);

public:
private:
	Path2D *path = nullptr;
	real_t progress = 0.0;
	Timer *update_timer = nullptr;
	real_t h_offset = 0.0;
	real_t v_offset = 0.0;
	real_t lookahead = 4.0;
	bool cubic = true;
	bool loop = true;
	bool rotates = true;

	void _update_transform();

protected:
	void _validate_property(PropertyInfo &p_property) const;

	void _notification(int p_what);
	static void _bind_methods();

public:
	void path_changed();

	void set_progress(real_t p_progress);
	real_t get_progress() const;

	void set_h_offset(real_t p_h_offset);
	real_t get_h_offset() const;

	void set_v_offset(real_t p_v_offset);
	real_t get_v_offset() const;

	void set_progress_ratio(real_t p_ratio);
	real_t get_progress_ratio() const;

	void set_lookahead(real_t p_lookahead);
	real_t get_lookahead() const;

	void set_loop(bool p_loop);
	bool has_loop() const;

	void set_rotates(bool p_rotates);
	bool is_rotating() const;

	void set_cubic_interpolation(bool p_enable);
	bool get_cubic_interpolation() const;

	PackedStringArray get_configuration_warnings() const override;

	PathFollow2D() {}
};


void PathFollow2D::path_changed() {
	if (update_timer && !update_timer->is_stopped()) {
		update_timer->start();
	} else {
		_update_transform();
	}
}

void PathFollow2D::_update_transform() {
	if (!path) {
		return;
	}

	Ref<Curve2D> c = path->get_curve();
	if (!c.is_valid()) {
		return;
	}

	real_t path_length = c->get_baked_length();
	if (path_length == 0) {
		return;
	}

	if (rotates) {
		Transform2D xform = c->sample_baked_with_rotation(progress, cubic);
		xform.translate_local(v_offset, h_offset);
		set_rotation(xform[1].angle());
		set_position(xform[2]);
	} else {
		Vector2 pos = c->sample_baked(progress, cubic);
		pos.x += h_offset;
		pos.y += v_offset;
		set_position(pos);
	}
}

void PathFollow2D::_notification(int p_what) {
	switch (p_what) {
		case NOTIFICATION_READY: {
			if (Engine::get_singleton()->is_editor_hint()) {
				update_timer = memnew(Timer);
				update_timer->set_wait_time(0.2);
				update_timer->set_one_shot(true);
				update_timer->connect("timeout", callable_mp(this, &PathFollow2D::_update_transform));
				add_child(update_timer, false, Node::INTERNAL_MODE_BACK);
			}
		} break;

		case NOTIFICATION_ENTER_TREE: {
			path = Object::cast_to<Path2D>(get_parent());
			if (path) {
				_update_transform();
			}
		} break;

		case NOTIFICATION_EXIT_TREE: {
			path = nullptr;
		} break;
	}
}

void PathFollow2D::set_cubic_interpolation(bool p_enable) {
	cubic = p_enable;
}

bool PathFollow2D::get_cubic_interpolation() const {
	return cubic;
}

void PathFollow2D::_validate_property(PropertyInfo &p_property) const {
	if (p_property.name == "offset") {
		real_t max = 10000.0;
		if (path && path->get_curve().is_valid()) {
			max = path->get_curve()->get_baked_length();
		}

		p_property.hint_string = "0," + rtos(max) + ",0.01,or_less,or_greater";
	}
}

PackedStringArray PathFollow2D::get_configuration_warnings() const {
	PackedStringArray warnings = Node::get_configuration_warnings();

	if (is_visible_in_tree() && is_inside_tree()) {
		if (!Object::cast_to<Path2D>(get_parent())) {
			warnings.push_back(RTR("PathFollow2D only works when set as a child of a Path2D node."));
		}
	}

	return warnings;
}

void PathFollow2D::_bind_methods() {
	ClassDB::bind_method(D_METHOD("set_progress", "progress"), &PathFollow2D::set_progress);
	ClassDB::bind_method(D_METHOD("get_progress"), &PathFollow2D::get_progress);

	ClassDB::bind_method(D_METHOD("set_h_offset", "h_offset"), &PathFollow2D::set_h_offset);
	ClassDB::bind_method(D_METHOD("get_h_offset"), &PathFollow2D::get_h_offset);

	ClassDB::bind_method(D_METHOD("set_v_offset", "v_offset"), &PathFollow2D::set_v_offset);
	ClassDB::bind_method(D_METHOD("get_v_offset"), &PathFollow2D::get_v_offset);

	ClassDB::bind_method(D_METHOD("set_progress_ratio", "ratio"), &PathFollow2D::set_progress_ratio);
	ClassDB::bind_method(D_METHOD("get_progress_ratio"), &PathFollow2D::get_progress_ratio);

	ClassDB::bind_method(D_METHOD("set_rotates", "enable"), &PathFollow2D::set_rotates);
	ClassDB::bind_method(D_METHOD("is_rotating"), &PathFollow2D::is_rotating);

	ClassDB::bind_method(D_METHOD("set_cubic_interpolation", "enable"), &PathFollow2D::set_cubic_interpolation);
	ClassDB::bind_method(D_METHOD("get_cubic_interpolation"), &PathFollow2D::get_cubic_interpolation);

	ClassDB::bind_method(D_METHOD("set_loop", "loop"), &PathFollow2D::set_loop);
	ClassDB::bind_method(D_METHOD("has_loop"), &PathFollow2D::has_loop);

	ClassDB::bind_method(D_METHOD("set_lookahead", "lookahead"), &PathFollow2D::set_lookahead);
	ClassDB::bind_method(D_METHOD("get_lookahead"), &PathFollow2D::get_lookahead);

	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "progress", PROPERTY_HINT_RANGE, "0,10000,0.01,or_less,or_greater,suffix:px"), "set_progress", "get_progress");
	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "progress_ratio", PROPERTY_HINT_RANGE, "0,1,0.0001,or_less,or_greater", PROPERTY_USAGE_EDITOR), "set_progress_ratio", "get_progress_ratio");
	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "h_offset"), "set_h_offset", "get_h_offset");
	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "v_offset"), "set_v_offset", "get_v_offset");
	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "rotates"), "set_rotates", "is_rotating");
	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cubic_interp"), "set_cubic_interpolation", "get_cubic_interpolation");
	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "has_loop");
	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lookahead", PROPERTY_HINT_RANGE, "0.001,1024.0,0.001"), "set_lookahead", "get_lookahead");
}

void PathFollow2D::set_progress(real_t p_progress) {
	ERR_FAIL_COND(!isfinite(p_progress));
	progress = p_progress;
	if (path) {
		if (path->get_curve().is_valid()) {
			real_t path_length = path->get_curve()->get_baked_length();

			if (loop && path_length) {
				progress = Math::fposmod(progress, path_length);
				if (!Math::is_zero_approx(p_progress) && Math::is_zero_approx(progress)) {
					progress = path_length;
				}
			} else {
				progress = CLAMP(progress, 0, path_length);
			}
		}

		_update_transform();
	}
}

void PathFollow2D::set_h_offset(real_t p_h_offset) {
	h_offset = p_h_offset;
	if (path) {
		_update_transform();
	}
}

real_t PathFollow2D::get_h_offset() const {
	return h_offset;
}

void PathFollow2D::set_v_offset(real_t p_v_offset) {
	v_offset = p_v_offset;
	if (path) {
		_update_transform();
	}
}

real_t PathFollow2D::get_v_offset() const {
	return v_offset;
}

real_t PathFollow2D::get_progress() const {
	return progress;
}

void PathFollow2D::set_progress_ratio(real_t p_ratio) {
	if (path && path->get_curve().is_valid() && path->get_curve()->get_baked_length()) {
		set_progress(p_ratio * path->get_curve()->get_baked_length());
	}
}

real_t PathFollow2D::get_progress_ratio() const {
	if (path && path->get_curve().is_valid() && path->get_curve()->get_baked_length()) {
		return get_progress() / path->get_curve()->get_baked_length();
	} else {
		return 0;
	}
}

void PathFollow2D::set_lookahead(real_t p_lookahead) {
	lookahead = p_lookahead;
}

real_t PathFollow2D::get_lookahead() const {
	return lookahead;
}

void PathFollow2D::set_rotates(bool p_rotates) {
	rotates = p_rotates;
	_update_transform();
}

bool PathFollow2D::is_rotating() const {
	return rotates;
}

void PathFollow2D::set_loop(bool p_loop) {
	loop = p_loop;
}

bool PathFollow2D::has_loop() const {
	return loop;
}

从代码与用途来看,Path2D就没啥看头了,就负责提供一条曲线路径

class Path2D : public Node2D {
	GDCLASS(Path2D, Node2D);

	Ref<Curve2D> curve;

	void _curve_changed();

protected:
	void _notification(int p_what);
	static void _bind_methods();

public:
#ifdef TOOLS_ENABLED
	virtual Rect2 _edit_get_rect() const override;
	virtual bool _edit_use_rect() const override;
	virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override;
#endif

	void set_curve(const Ref<Curve2D> &p_curve);
	Ref<Curve2D> get_curve() const;

	Path2D() {}
};

#ifdef TOOLS_ENABLED
Rect2 Path2D::_edit_get_rect() const {
	if (!curve.is_valid() || curve->get_point_count() == 0) {
		return Rect2(0, 0, 0, 0);
	}

	Rect2 aabb = Rect2(curve->get_point_position(0), Vector2(0, 0));

	for (int i = 0; i < curve->get_point_count(); i++) {
		for (int j = 0; j <= 8; j++) {
			real_t frac = j / 8.0;
			Vector2 p = curve->sample(i, frac);
			aabb.expand_to(p);
		}
	}

	return aabb;
}

bool Path2D::_edit_use_rect() const {
	return curve.is_valid() && curve->get_point_count() != 0;
}

bool Path2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
	if (curve.is_null()) {
		return false;
	}

	for (int i = 0; i < curve->get_point_count(); i++) {
		Vector2 s[2];
		s[0] = curve->get_point_position(i);

		for (int j = 1; j <= 8; j++) {
			real_t frac = j / 8.0;
			s[1] = curve->sample(i, frac);

			Vector2 p = Geometry2D::get_closest_point_to_segment(p_point, s);
			if (p.distance_to(p_point) <= p_tolerance) {
				return true;
			}

			s[0] = s[1];
		}
	}

	return false;
}
#endif

void Path2D::_notification(int p_what) {
	switch (p_what) {
		// Draw the curve if path debugging is enabled.
		case NOTIFICATION_DRAW: {
			if (!curve.is_valid()) {
				break;
			}

			if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_paths_hint()) {
				return;
			}

			if (curve->get_point_count() < 2) {
				return;
			}

#ifdef TOOLS_ENABLED
			const real_t line_width = get_tree()->get_debug_paths_width() * EDSCALE;
#else
			const real_t line_width = get_tree()->get_debug_paths_width();
#endif
			real_t interval = 10;
			const real_t length = curve->get_baked_length();

			if (length > CMP_EPSILON) {
				const int sample_count = int(length / interval) + 2;
				interval = length / (sample_count - 1); // Recalculate real interval length.

				Vector<Transform2D> frames;
				frames.resize(sample_count);

				{
					Transform2D *w = frames.ptrw();

					for (int i = 0; i < sample_count; i++) {
						w[i] = curve->sample_baked_with_rotation(i * interval, false);
					}
				}

				const Transform2D *r = frames.ptr();
				// Draw curve segments
				{
					PackedVector2Array v2p;
					v2p.resize(sample_count);
					Vector2 *w = v2p.ptrw();

					for (int i = 0; i < sample_count; i++) {
						w[i] = r[i].get_origin();
					}
					draw_polyline(v2p, get_tree()->get_debug_paths_color(), line_width, false);
				}

				// Draw fish bones
				{
					PackedVector2Array v2p;
					v2p.resize(3);
					Vector2 *w = v2p.ptrw();

					for (int i = 0; i < sample_count; i++) {
						const Vector2 p = r[i].get_origin();
						const Vector2 side = r[i].columns[0];
						const Vector2 forward = r[i].columns[1];

						// Fish Bone.
						w[0] = p + (side - forward) * 5;
						w[1] = p;
						w[2] = p + (-side - forward) * 5;

						draw_polyline(v2p, get_tree()->get_debug_paths_color(), line_width * 0.5, false);
					}
				}
			}
		} break;
	}
}

void Path2D::_curve_changed() {
	if (!is_inside_tree()) {
		return;
	}

	if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_paths_hint()) {
		return;
	}

	queue_redraw();
	for (int i = 0; i < get_child_count(); i++) {
		PathFollow2D *follow = Object::cast_to<PathFollow2D>(get_child(i));
		if (follow) {
			follow->path_changed();
		}
	}
}

void Path2D::set_curve(const Ref<Curve2D> &p_curve) {
	if (curve.is_valid()) {
		curve->disconnect("changed", callable_mp(this, &Path2D::_curve_changed));
	}

	curve = p_curve;

	if (curve.is_valid()) {
		curve->connect("changed", callable_mp(this, &Path2D::_curve_changed));
	}

	_curve_changed();
}

Ref<Curve2D> Path2D::get_curve() const {
	return curve;
}

void Path2D::_bind_methods() {
	ClassDB::bind_method(D_METHOD("set_curve", "curve"), &Path2D::set_curve);
	ClassDB::bind_method(D_METHOD("get_curve"), &Path2D::get_curve);

	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve2D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT), "set_curve", "get_curve");
}

当然,也不是啥用处没有,比如动态指定路径的时候,就可以设置一条Curve2D,然后赋给Path2D,后面就照此行事。

比如,该演示项目中,

var curve = Curve2D.new()
curve.add_point(Vector2i(100, 100))
curve.add_point(Vector2i(400, 600))
$MobPath.curve = curve

然后,玩家呆在右上角,这就是那些MOB的死角,玩家可以活到把用户送走

 

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

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

相关文章

面试热题(x的平方根)

给你一个非负整数 x &#xff0c;计算并返回 x 的 算术平方根 。 由于返回类型是整数&#xff0c;结果只保留 整数部分 &#xff0c;小数部分将被 舍去 。 注意&#xff1a;不允许使用任何内置指数函数和算符&#xff0c;例如 pow(x, 0.5) 或者 x ** 0.5 。 这道题虽然是简单题…

Laravel 5 报错信息存在严重漏洞

靠自己生活&#xff0c;灵魂都是安宁的。 简介 Laravel是一套简洁、优雅的PHPweb开发程序框架&#xff0c;并且具有简洁的表达&#xff0c;是一个比较容易理解且强大的&#xff0c;它提供了强大的工具用以开发大型网站的应用。 漏洞复现 使用Whoops错误库来显示\Whoops\Han…

mysql报错:name ‘_mysql‘ is not defined

原因是&#xff1a; Mysqldb 不兼容 python3.5 以后的版本 解决办法&#xff1a; 使用pymysql代替MySQLdb 在项目应用下的__init__.py 添加上去 import pymysqlpymysql.version_info (1, 4, 13, "final", 0) pymysql.install_as_MySQLdb()

找不到org.apache.http.annotation.NotThreadSafe的类文件

问题现象 最近在做一个调用包含请求体的GET请求功能的时候&#xff0c;引用了 httpclient-4.5.2.jar 和 httpcore-4.4.5.jar。 在工程编译环节报错如下&#xff1a; 原因分析 httpcore 和 httpclient 的版本不匹配。 解决方案 将 httpcore 的版本由 4.4.5 改为 4.4.4&am…

常用工具的常用操作1

写在前面 记录可能用到的各种工具常见技巧。 1&#xff1a;sublime 1.1&#xff1a;操作多列 首先选中要操作的列所在的行&#xff1a; 然后点击selection&#xff0c;spit lines&#xff1a; 接下来移动左右键就可以操作了&#xff0c;删除或者批量添加内容&#xff1a; 1…

客户流失分析预测案例 -- 机器学习项目基础篇(7)

客户流失 它是指现有的客户、用户、订阅者或任何类型的回头客停止与公司开展业务或结束与公司的关系。 客户流失的类型 合同客户流失&#xff1a;当客户签订了服务合同并决定取消服务时&#xff0c;例如有线电视&#xff0c;SaaS。自愿流失&#xff1a;当用户自愿取消服务时…

QtWebApp同时开启http服务和https服务,接受来自客户端的不同请求并进行相应的处理

零、前言 在 QtWebApp开发https服务器&#xff0c;完成客户端与服务器基于ssl的双向认证&#xff0c;纯代码操作 一文中已经用纯代码的形式完成了客户端和服务端的 https 协议交互。 不过&#xff0c;只是开放了https服务&#xff0c;更多情况下&#xff0c;http服务和https服…

解决 Android Studio 的 Gradle 面板上只有关于测试的 task

文章目录 问题描述解决办法 笔者出问题时的运行环境&#xff1a; Android Studio Flamingo | 2022.2.1 Android SDK 33 Gradle 8.0.1 JDK 17 问题描述 笔者最近发现一个奇怪的事情。笔者的 Android Studio 的 Gradle 面板上居然除了用于测试的 task 之外&#xff0c;其它什…

Spring Security6入门及自定义登录

一、前言 Spring Security已经更新到了6.x,通过本专栏记录以下Spring Security6学习过程&#xff0c;当然大家可参考Spring Security5专栏对比学习 Spring Security5专栏地址&#xff1a;security5 Spring Security是spring家族产品中的一个安全框架&#xff0c;核心功能包括…

驱动工作原理

驱动原理 在Linux操作系统中&#xff0c;硬件驱动程序中实现对硬件直接操作&#xff0c;而用户空间&#xff0c;通过通用的系统调用接口&#xff08;open() 打开相应的驱动设备,ioctl()控制相应的功能等&#xff09;&#xff0c;实现对硬件操作&#xff0c;应用程序没有直接操作…

百度Apollo规划算法——OBB障碍物检测代码解析

百度Apollo规划算法——Box障碍物检测代码解析 前言代码代码分析f1f2f3f4f5f6 参考 前言 本文主要分析Apollo代码中函数bool Box::HasOverlap(const Box2d &box) const {}的数学原理。 在阅读此部分代码时&#xff0c;第一遍没看懂return的一堆什么意思&#xff0c;百度之后…

work weekly

每周汇报&#xff1a;围绕着项目范围及需求内容完成情况多少、人力资源情况、整体进度情况、成本情况、【范围】多少工作、【资源】投入多少人、【时间】花费多少时间、【成本】花了多少钱 【质量】一般没有特别要求的默认软件开发过程规范要求响应时间 【沟通】这里不说了 …

31 对集合中的字符串,按照长度降序排列

思路&#xff1a;使用集合的sort方法&#xff0c;新建一个Comparator接口&#xff0c;泛型是<String>&#xff0c;重写里面的compare方法。 package jiang.com; import java.util.Arrays; import java.util.Comparator; import java.util.List;public class Practice4 {…

ppt压缩文件怎么压缩最小?文件压缩技巧分享

在日常的工作和学习中&#xff0c;难免会遇到PPT太大&#xff0c;需要将其压缩变小的情况&#xff0c;但很多朋友还不知道怎么压缩PPT文件&#xff0c;下面就给大家分享几个简单的方法&#xff0c;分分钟缩小过大的PPT文件。 一、PowerPoint PowerPoint就是微软公司的演示文稿…

微信小程序的自定义TabBar及Vant的使用

一、安装Vant 1、在 资源管理器 空白位置&#xff0c;点右键打开 在外部终端窗口打开 2、初始化NPM npm init -y 3、安装命令 npm i vant/weapp1.3.3 -S --production 4、构建NPM包 在 工具 里选择构建NPM包 5、删除style:v2 在app.json里&#xff0c;删除"style"…

【中断机制】什么是中断?使用中断的原因、注意事项

目录 一、为什么需要中断 二、什么是中断 1、中断的概念 2、中断的分类 3、中断的处理流程 三、中断处理程序要少用延时的原因 一、为什么需要中断 以网卡为例&#xff0c;CPU 如果要从网卡获取数据&#xff0c;不可能时时盯着网卡啥时候会有数据。当网卡收到数据时&…

炼钢工艺流程(2)

1. 轧制单元 更换前后两个工作辊之间的轧制对象称为轧制单元&#xff0c;对应一个轧制计划。两个 支撑辊之间的轧制对象是由多个轧制单元组成&#xff0c;称为轧制单元组&#xff0c;对应多个轧制计 划。 轧制单元的结构 每个计划开始的部分板坯按照宽度非减的方向排列来加热轧…

Linux中安装Node

安装 先从 官方网站 下载安装包&#xff0c;有时 node 版本太新会导致失败&#xff0c;详见下方的常见问题第2点 cd /home // 创建目录&#xff0c;将下载好的 node 安装包上传到此目录 mkdir Download mkdir /usr/local/lib/node解压 // 解压&#xff0c;前面是文件当前路径…

jenkins pipeline项目

回到目录 将练习jenkins使用pipeline项目&#xff0c;结合k8s发布一个简单的springboot项目 前提&#xff1a;jenkins的环境和k8s环境都已经安装完成&#xff0c;提前准备了gitlab和一个简单的springboot项目 创建一个流水线项目 流水线中选择git&#xff0c;并选择gitlab的…

运放电路之比较器的分析笔记

一、比较器 比较器分为同相比较器和反相比较器。 反相比较器电路如下&#xff1a; V11为固定值&#xff0c;这里设定的是2.5V&#xff0c;接到了运放的输入正&#xff08;V&#xff09;&#xff1b; V10为输入值&#xff0c;值可变&#xff0c;接到了运放的输入负&#xff08;V…