Godot 4 源码分析 - Path2D与PathFollow2D
学习演示项目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_ENABLEDvirtual 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;
#endifvoid 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;
}
#endifvoid 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_ENABLEDconst real_t line_width = get_tree()->get_debug_paths_width() * EDSCALE;
#elseconst real_t line_width = get_tree()->get_debug_paths_width();
#endifreal_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的死角,玩家可以活到把用户送走

相关文章:
Godot 4 源码分析 - Path2D与PathFollow2D
学习演示项目dodge_the_creeps,发现里面多了一个Path2D与PathFollow2D 研究GDScript代码发现,它主要用于随机生成Mob var mob_spawn_location get_node(^"MobPath/MobSpawnLocation")mob_spawn_location.progress randi()# Set the mobs dir…...
ardupilot 中坐标变换矩阵和坐标系变换矩阵区别
目录 文章目录 目录摘要1.坐标变换矩阵与坐标系变换矩阵摘要 本节主要记录ardupilot 中坐标变换矩阵和坐标系变换矩阵的区别,这里非常重要,特别是进行姿态误差计算时,如果理解错误,很难搞明白后面算法。 1.坐标变换矩阵与坐标系变换矩阵 坐标变换矩阵的本质含义:是可以把…...
VR内容研发公司 | VR流感病毒实验虚拟现实课件
由广州华锐互动开发的《VR流感病毒实验虚拟现实课件》是一种新型的教学模式,可以为学生提供更加真实和直观的流感病毒分离鉴定实验操作体验,从而提高学生的实验技能和工作效率。 《VR流感病毒实验虚拟现实课件》涉及了生物安全二级实验室(BSL-2)和流感病…...
python——案例10:认识if、elif、else
案例10:认识if、elif、elsenumfloat(input("输入数值:")) #用户输入数字if num>0:print("正数")elif num0:print("零") else:print("负数")#输出结果如下:输入数值:-1 负数 输入数值…...
Hadoop中命令检查hdfs的文件是否存在
Hadoop中命令检查hdfs的文件是否存在 在Hadoop中,可以使用以下命令检查HDFS文件是否存在: hadoop fs -test -e 其中,是要检查的HDFS文件的路径。 如果文件存在,命令返回0;如果文件不存在,命令返回非0值…...
计算机网络用户接入层设计
用户接入层为用户提供访问核心网络的能力, 为用户提供共享/交换的带宽分配,按照业主要求,并考虑到端口密度的要求以及 设备的性能价格比,建议选用 Catalyst 3524XL和 Catalyst 3548XL 工作组交换 机,分别放置于配线间中。如同一配线间需两台以…...
全志F1C200S嵌入式驱动开发(应用程序开发)
【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 我们在开发soc驱动的时候,很多情况下也要验证下当前的驱动功能是否正确。当然除了验证驱动功能之外,我们还要编写业务代码和流程代码。这中间就和各行各业有关了,有的是算法,有…...
人工智能学习07--pytorch23--目标检测:Deformable-DETR训练自己的数据集
参考 https://blog.csdn.net/qq_44808827/article/details/125326909https://blog.csdn.net/dystsp/article/details/125949720?utm_mediumdistribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-0-125949720-blog-125326909.235^v38^pc_releva…...
Statefulset 实战 1
上一部分与大家分享到 Statefulset 与 RplicaSet 的区别,以及 Statefulset 的特点,能做的一些事情及一些注意事项 现在我们来尝试使用 Statefulset 来部署我们的应用,我们可以需要有应用程序,然后有持久化卷 开始使用 Statefuls…...
没有jodatime,rust怎么方便高效的操作时间呢?
关注我,学习Rust不迷路!! 当使用Rust进行日期操作时,可以使用 chrono 库。下面给出了二十个常见的日期操作的例子: 1. 获取当前日期和时间: use chrono::prelude::*;let current_datetime Local::now()…...
如何把pdf转成cad版本?这种转换方法非常简单
将PDF转换成CAD格式的优势在于,CAD格式通常是用于工程设计和绘图的标准格式。这种格式的文件可以在计算机上进行编辑和修改,而不需要纸质副本。此外,CAD文件通常可以与其他CAD软件进行交互,从而使得工程设计和绘图过程更加高效和精…...
MySQL常用函数方法
字符串函数 函数描述举例left(str, length)从左开始截取字符串,截取length个left(2023-08-04, 7) 2023-08right(str, length)从右开始截取字符串,截取length个 right(2023-08-04, 5) 08-04 substring(str, pos, length) substring(被截取字…...
Linux命令200例专栏导读:实用指南助你成为Linux大师
🏆作者简介,黑夜开发者,全栈领域新星创作者✌,阿里云社区专家博主,2023年6月csdn上海赛道top4。 🏆数年电商行业从业经验,历任核心研发工程师,项目技术负责人。 🏆本文已…...
ICN6202 MIPIDSI转LVDS桥接芯片的功能及特征 调试文档资料
产品特征功能: 输入:MIPI DSI 支持MIPI D-PHY Version 1.00.00 和 MIPI DSI Version 1.02.00. 可接收MIPI DSI 18bpp RGB666 and 24bpp RGB888 packets 4 lane data1 lane clock 4对数据线可以选择1、2、3、4lane data 每对差分数据传输线最大可…...
vscode 格式问题
1、EditorConfig for VS Code 插件 shift alt f 格式化文件(VS Code格式化按键),如下图,每个缩进4空格 代码如下 创建文件名 .editorconfig root true [*] charset utf-8 indent_style space indent_size 2 end_of_…...
OPENCV C++(三)二值化灰度函数+调用摄像头+鼠标响应+肤色检测
RGB转灰度函数 cvtColor(image, gray, COLOR_BGR2GRAY); 图像 目标图像 rgb转灰度 大津法二值化函数 threshold(gray, result1, 84, 255, THRESH_OTSU); 灰度图,目标图,阈值,大于阈值的转换的像素值,方法为大津法 自适应二值…...
zabbix简易入门:基本的网络监控、WEB监控
需求背景: 我们越来越发现:网络越来越复杂,网络、应用、云端……故障点随时可能发生,而我们不能人工盯着所有的问题,所以,网管软件是必须的。那么没有预算的情况下,我们只好自己布署简单的…...
51单片机学习--DS1302可调时钟
之前学习过用定时器做的时钟,但是那样不仅误差大还费CPU,接下来利用DS1302时钟模块做一个可调实时时钟 这一次直接编写DS1302模块,首先要在DS1392.c 中根据下面的模块原理图进行位声明: sbit DS1302_SCLK P3^6; sbit DS1302_IO …...
Matlab统计字符串中共有多少种字符以及每种字符出现次数的功能实现(Matlab R2021a)
在做2023年深圳杯B题的时候,需要使用隐写技术(将特定信息嵌入信息载体且不易被察觉,可被广泛地应用于著作权保护、数据附加等领域)将《中华人民共和国著作权法》全篇10314个字符写入图片,首先我想到的是利用霍夫曼编码…...
HTTPS文件传输
目录 0.https概述1.单钥匙锁2.双钥匙锁 - 防篡改3.双钥匙锁 - 防泄漏4.单双钥匙锁相互配合 0.https概述 HTTPS其实就是HTTP协议加上TLS/SSL,SSL是个加密套件,负责对HTTP的数据进行加密,TLS是SSL的升级版,现在提到HTTPS࿰…...
微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
Java 二维码
Java 二维码 **技术:**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...
iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈
在日常iOS开发过程中,性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期,开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发,但背后往往隐藏着系统资源调度不当…...
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要: 近期,在使用较新版本的OpenSSH客户端连接老旧SSH服务器时,会遇到 "no matching key exchange method found", "n…...
TCP/IP 网络编程 | 服务端 客户端的封装
设计模式 文章目录 设计模式一、socket.h 接口(interface)二、socket.cpp 实现(implementation)三、server.cpp 使用封装(main 函数)四、client.cpp 使用封装(main 函数)五、退出方法…...
Linux基础开发工具——vim工具
文章目录 vim工具什么是vimvim的多模式和使用vim的基础模式vim的三种基础模式三种模式的初步了解 常用模式的详细讲解插入模式命令模式模式转化光标的移动文本的编辑 底行模式替换模式视图模式总结 使用vim的小技巧vim的配置(了解) vim工具 本文章仍然是继续讲解Linux系统下的…...
Django RBAC项目后端实战 - 03 DRF权限控制实现
项目背景 在上一篇文章中,我们完成了JWT认证系统的集成。本篇文章将实现基于Redis的RBAC权限控制系统,为系统提供细粒度的权限控制。 开发目标 实现基于Redis的权限缓存机制开发DRF权限控制类实现权限管理API配置权限白名单 前置配置 在开始开发权限…...
