Godot 4 源码分析 - 动态导入图片文件

用Godot 4尝试编一个电子书软件,初步效果已经出来,并且通过管道通信接口可以获取、设置属性、调用函数,貌似能处理各种事宜了。

其实不然,外因通过内因起作用,如果没把里面搞明白,功能没有开放出来,则有些需求就不能实现。

比如,现在想动态加载新的图书,这就是一个实际需求。如果每一本电子书,都需要导出一次,那这个软件就太不通用了。

之前加载图片时,GDScript代码:

# 目标对象上加载图片
func loadPng(obj, pngFileName) -> bool:
	var texture = load(pngFileName) as Texture2D;
	if(texture != null):
		obj.set_texture(texture);
		obj.scale.y = get_viewport_rect().size.y / texture.get_height();
		obj.scale.x = obj.scale.y;
		obj.position.y = get_viewport_rect().size.y / 2;	# 垂直居中
		adjustPos();
		return true;
	return false;

直接加载另一图片,结果不成功

 这就得研究一下了。

导入import

把这个图片拷贝到Godot开发环境中,发现切换回Godot时,会快速闪过一个import对话框,那想必Godot干了什么事

到资源管理器,看到多了一个对应的.import文件,打开看了一下内容

[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://rv5gm15xbcaf"
path="res://.godot/imported/DrGraph_Page24.png-1dd935fbb11807a645ea1ea79ec38662.ctex"
metadata={
"vram_texture": false
}

[deps]

source_file="res://Pages/DrGraph_Page24.png"
dest_files=["res://.godot/imported/DrGraph_Page24.png-1dd935fbb11807a645ea1ea79ec38662.ctex"]

[params]

compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

既然是动态生成的,那肯定是代码里写的。到源码中找,从哪里下手呢?查import、.import都是一大堆结果。再试试内容,查找[remap],结果很少

 定位一看,是EditorFileSystem类的两个函数_reimport_group、_reimport_file,那肯定是用_reimport_file。但这个函数是不可访问的。好在源码在手,直接public出来,可以访问了。

但这个要给GDScript调用,还得做一些工作,需要加入到ClassDB中去。

为了简单一些,直接加到DllStream类中【见Godot 4 源码分析 - 增加管道通信_DrGraph的博客-CSDN博客】,增加一个import函数

ClassDB::bind_method(D_METHOD("import", "fileName"), &DllStream::import);

String DllStream::import(String fileName) {
	if (FileAccess::exists(fileName + ".import") == false) 
		EditorFileSystem::get_singleton()->_reimport_file(fileName);
	return fileName;	
}

运行后,还是不成功。调试发现,_find_file(p_file, &fs, cpos)返回false。而_find_file居然要求文件位于"res://"目录以下,也就是说,得在工程目录下。

bool EditorFileSystem::_find_file(const String &p_file, EditorFileSystemDirectory **r_d, int &r_file_pos) const {
	//todo make faster

	if (!filesystem || scanning) {
		return false;
	}

	String f = ProjectSettings::get_singleton()->localize_path(p_file);

	if (!f.begins_with("res://")) {
		return false;
	}
	f = f.substr(6, f.length());
	f = f.replace("\\", "/");

	Vector<String> path = f.split("/");

	if (path.size() == 0) {
		return false;
	}
	String file = path[path.size() - 1];
	path.resize(path.size() - 1);

	EditorFileSystemDirectory *fs = filesystem;

	for (int i = 0; i < path.size(); i++) {
		if (path[i].begins_with(".")) {
			return false;
		}

		int idx = -1;
		for (int j = 0; j < fs->get_subdir_count(); j++) {
			if (fs->get_subdir(j)->get_name() == path[i]) {
				idx = j;
				break;
			}
		}

		if (idx == -1) {
			//does not exist, create i guess?
			EditorFileSystemDirectory *efsd = memnew(EditorFileSystemDirectory);

			efsd->name = path[i];
			efsd->parent = fs;

			int idx2 = 0;
			for (int j = 0; j < fs->get_subdir_count(); j++) {
				if (efsd->name.naturalnocasecmp_to(fs->get_subdir(j)->get_name()) < 0) {
					break;
				}
				idx2++;
			}

			if (idx2 == fs->get_subdir_count()) {
				fs->subdirs.push_back(efsd);
			} else {
				fs->subdirs.insert(idx2, efsd);
			}
			fs = efsd;
		} else {
			fs = fs->get_subdir(idx);
		}
	}

	int cpos = -1;
	for (int i = 0; i < fs->files.size(); i++) {
		if (fs->files[i]->file == file) {
			cpos = i;
			break;
		}
	}

	r_file_pos = cpos;
	*r_d = fs;

	return cpos != -1;
}

这个要求就有些过分了。不过程序处理就两种方法:一是将图片自动拷贝到工程目录下,二是绕过这个。

随便试一种吧,抛个硬币,背面选二。

既然选绕过,那就直接实现EditorFileSystem::_reimport_file函数,这样也省得再把这个私有函数public出来。所谓实现,就是把EditorFileSystem::_reimport_file的代码全部拷贝过来,然后改呗改呗:

Error DllStream::_import(String destFileName) {
	HashMap<StringName, Variant> params = HashMap<StringName, Variant>();	
	String importer_name; //empty by default though
	ResourceUID::ID uid = ResourceUID::INVALID_ID;
	Variant generator_parameters;

	Ref<ResourceImporter> importer = ResourceFormatImporter::get_singleton()->get_importer_by_extension(destFileName.get_extension());
	if (importer.is_null()) 
		ERR_FAIL_V_MSG(ERR_FILE_CANT_OPEN, "BUG: File queued for import, but can't be imported, importer for type '" + importer_name + "' not found.");

	//mix with default params, in case a parameter is missing

	List<ResourceImporter::ImportOption> opts;
	importer->get_import_options(destFileName, &opts);
	for (const ResourceImporter::ImportOption &E : opts) {
		if (!params.has(E.option.name)) { //this one is not present
			params[E.option.name] = E.default_value;
		}
	}

	if (ProjectSettings::get_singleton()->has_setting("importer_defaults/" + importer->get_importer_name())) {
		//use defaults if exist
		Dictionary d = GLOBAL_GET("importer_defaults/" + importer->get_importer_name());
		List<Variant> v;
		d.get_key_list(&v);

		for (const Variant &E : v) 
			params[E] = d[E];
	}

	//finally, perform import!!
	String base_path = ResourceFormatImporter::get_singleton()->get_import_base_path(destFileName);

	List<String> import_variants;
	List<String> gen_files;
	Variant meta;
	Error err = importer->import(destFileName, base_path, params, &import_variants, &gen_files, &meta);

	ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_UNRECOGNIZED, "Error importing '" + destFileName + "'.");

	//as import is complete, save the .import file

	Vector<String> dest_paths;
	{
		Ref<FileAccess> f = FileAccess::open(destFileName + ".import", FileAccess::WRITE);
		ERR_FAIL_COND_V_MSG(f.is_null(), ERR_FILE_CANT_OPEN, "Cannot open file from path '" + destFileName + ".import'.");

		//write manually, as order matters ([remap] has to go first for performance).
		f->store_line("[remap]");
		f->store_line("");
		f->store_line("importer=\"" + importer->get_importer_name() + "\"");
		int version = importer->get_format_version();
		if (version > 0) {
			f->store_line("importer_version=" + itos(version));
		}
		if (!importer->get_resource_type().is_empty()) {
			f->store_line("type=\"" + importer->get_resource_type() + "\"");
		}

		if (uid == ResourceUID::INVALID_ID) {
			uid = ResourceUID::get_singleton()->create_id();
		}

		f->store_line("uid=\"" + ResourceUID::get_singleton()->id_to_text(uid) + "\""); //store in readable format

		if (err == OK) {
			if (importer->get_save_extension().is_empty()) {
				//no path
			} else if (import_variants.size()) {
				//import with variants
				for (const String &E : import_variants) {
					String path = base_path.c_escape() + "." + E + "." + importer->get_save_extension();

					f->store_line("path." + E + "=\"" + path + "\"");
					dest_paths.push_back(path);
				}
			} else {
				String path = base_path + "." + importer->get_save_extension();
				f->store_line("path=\"" + path + "\"");
				dest_paths.push_back(path);
			}

		} else {
			f->store_line("valid=false");
		}

		if (meta != Variant()) {
			f->store_line("metadata=" + meta.get_construct_string());
		}

		if (generator_parameters != Variant()) {
			f->store_line("generator_parameters=" + generator_parameters.get_construct_string());
		}

		f->store_line("");

		f->store_line("[deps]\n");

		if (gen_files.size()) {
			Array genf;
			for (const String &E : gen_files) {
				genf.push_back(E);
				dest_paths.push_back(E);
			}

			String value;
			VariantWriter::write_to_string(genf, value);
			f->store_line("files=" + value);
			f->store_line("");
		}

		f->store_line("source_file=" + Variant(destFileName).get_construct_string());

		if (dest_paths.size()) {
			Array dp;
			for (int i = 0; i < dest_paths.size(); i++) {
				dp.push_back(dest_paths[i]);
			}
			f->store_line("dest_files=" + Variant(dp).get_construct_string() + "\n");
		}

		f->store_line("[params]");
		f->store_line("");

		//store options in provided order, to avoid file changing. Order is also important because first match is accepted first.

		for (const ResourceImporter::ImportOption &E : opts) {
			String base = E.option.name;
			String value;
			VariantWriter::write_to_string(params[base], value);
			f->store_line(base + "=" + value);
		}
	}

	// Store the md5's of the various files. These are stored separately so that the .import files can be version controlled.
	{
		Ref<FileAccess> md5s = FileAccess::open(base_path + ".md5", FileAccess::WRITE);
		ERR_FAIL_COND_V_MSG(md5s.is_null(), ERR_FILE_CANT_OPEN, "Cannot open MD5 file '" + base_path + ".md5'.");

		md5s->store_line("source_md5=\"" + FileAccess::get_md5(destFileName) + "\"");
		if (dest_paths.size()) {
			md5s->store_line("dest_md5=\"" + FileAccess::get_multiple_md5(dest_paths) + "\"\n");
		}
	}
	if (ResourceUID::get_singleton()->has_id(uid)) {
		ResourceUID::get_singleton()->set_id(uid, destFileName);
	} else {
		ResourceUID::get_singleton()->add_id(uid, destFileName);
	}
}

String DllStream::import(String fileName) {
	if (FileAccess::exists(fileName + ".import") == false) 
		_import(fileName);
	return fileName;	
}

运行后,还是失败,再跟进,发现是

importer->import(destFileName, base_path, params, &import_variants, &gen_files, &meta)

失败。单步调试发现,ResourceFormatImporter的importers为空。从源码中找到其add_importer函数会添加ResourceImporter,在EditorNode::EditorNode()构造函数中,添加了多个ResourceImporter。

	{
		// Register importers at the beginning, so dialogs are created with the right extensions.
		Ref<ResourceImporterTexture> import_texture;
		import_texture.instantiate();
		ResourceFormatImporter::get_singleton()->add_importer(import_texture);

		Ref<ResourceImporterLayeredTexture> import_cubemap;
		import_cubemap.instantiate();
		import_cubemap->set_mode(ResourceImporterLayeredTexture::MODE_CUBEMAP);
		ResourceFormatImporter::get_singleton()->add_importer(import_cubemap);

		Ref<ResourceImporterLayeredTexture> import_array;
		import_array.instantiate();
		import_array->set_mode(ResourceImporterLayeredTexture::MODE_2D_ARRAY);
		ResourceFormatImporter::get_singleton()->add_importer(import_array);

		Ref<ResourceImporterLayeredTexture> import_cubemap_array;
		import_cubemap_array.instantiate();
		import_cubemap_array->set_mode(ResourceImporterLayeredTexture::MODE_CUBEMAP_ARRAY);
		ResourceFormatImporter::get_singleton()->add_importer(import_cubemap_array);

		Ref<ResourceImporterLayeredTexture> import_3d;
		import_3d.instantiate();
		import_3d->set_mode(ResourceImporterLayeredTexture::MODE_3D);
		ResourceFormatImporter::get_singleton()->add_importer(import_3d);

		Ref<ResourceImporterImage> import_image;
		import_image.instantiate();
		ResourceFormatImporter::get_singleton()->add_importer(import_image);

		Ref<ResourceImporterTextureAtlas> import_texture_atlas;
		import_texture_atlas.instantiate();
		ResourceFormatImporter::get_singleton()->add_importer(import_texture_atlas);

		Ref<ResourceImporterDynamicFont> import_font_data_dynamic;
		import_font_data_dynamic.instantiate();
		ResourceFormatImporter::get_singleton()->add_importer(import_font_data_dynamic);

		Ref<ResourceImporterBMFont> import_font_data_bmfont;
		import_font_data_bmfont.instantiate();
		ResourceFormatImporter::get_singleton()->add_importer(import_font_data_bmfont);

		Ref<ResourceImporterImageFont> import_font_data_image;
		import_font_data_image.instantiate();
		ResourceFormatImporter::get_singleton()->add_importer(import_font_data_image);

		Ref<ResourceImporterCSVTranslation> import_csv_translation;
		import_csv_translation.instantiate();
		ResourceFormatImporter::get_singleton()->add_importer(import_csv_translation);

		Ref<ResourceImporterWAV> import_wav;
		import_wav.instantiate();
		ResourceFormatImporter::get_singleton()->add_importer(import_wav);

		Ref<ResourceImporterOBJ> import_obj;
		import_obj.instantiate();
		ResourceFormatImporter::get_singleton()->add_importer(import_obj);

		Ref<ResourceImporterShaderFile> import_shader_file;
		import_shader_file.instantiate();
		ResourceFormatImporter::get_singleton()->add_importer(import_shader_file);

		Ref<ResourceImporterScene> import_scene;
		import_scene.instantiate();
		ResourceFormatImporter::get_singleton()->add_importer(import_scene);

		Ref<ResourceImporterScene> import_animation;
		import_animation = Ref<ResourceImporterScene>(memnew(ResourceImporterScene(true)));
		ResourceFormatImporter::get_singleton()->add_importer(import_animation);

		{
			Ref<EditorSceneFormatImporterCollada> import_collada;
			import_collada.instantiate();
			ResourceImporterScene::add_importer(import_collada);

			Ref<EditorOBJImporter> import_obj2;
			import_obj2.instantiate();
			ResourceImporterScene::add_importer(import_obj2);

			Ref<EditorSceneFormatImporterESCN> import_escn;
			import_escn.instantiate();
			ResourceImporterScene::add_importer(import_escn);
		}

		Ref<ResourceImporterBitMap> import_bitmap;
		import_bitmap.instantiate();
		ResourceFormatImporter::get_singleton()->add_importer(import_bitmap);
	}

EditorNode会在编辑器模式下自动创建,但对于最终的运行程序而言,没有ProjectManager,也没有EditorNode,所以,想导入的话,需要自己创建。

从图片导入过程可知,电子书只需要导入png图片,是Texture格式,所以只导入一种即可

		Ref<ResourceImporterTexture> import_texture;
		import_texture.instantiate();
		ResourceFormatImporter::get_singleton()->add_importer(import_texture);

运行,图片成功显示。

下一步工作

至此,核心工作完成,下一步的工作主要有两个

一是目录导入,即可以将目标电子书的图片所在目录中的所有文件一次性导入

二是强制动态导入,非强制导入是指只要存在相应的.import文件,就不用再导入;强制导入是无论该.import文件是否存在,均导入。这个用于动态处理,比如文件中查找关键词后,关键词需要高亮显示,从而导致图片不一样。这些图片可置于动态目录下,强制导入即可。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【SpringCloud Alibaba】(六)使用 Sentinel 实现服务限流与容错

今天&#xff0c;我们就使用 Sentinel 实现接口的限流&#xff0c;并使用 Feign 整合 Sentinel 实现服务容错的功能&#xff0c;让我们体验下微服务使用了服务容错功能的效果。 因为内容仅仅围绕着 SpringCloud Alibaba技术栈展开&#xff0c;所以&#xff0c;这里我们使用的服…

详解Mybatis之分页插件【PageHelper】

编译软件&#xff1a;IntelliJ IDEA 2019.2.4 x64 操作系统&#xff1a;win10 x64 位 家庭版 Maven版本&#xff1a;apache-maven-3.6.3 Mybatis版本&#xff1a;3.5.6 文章目录 一. 什么是分页&#xff1f;二. 为什么使用分页&#xff1f;三. 如何设计一个Page类&#xff08;分…

【玩转python系列】【小白必看】使用Python爬虫技术获取代理IP并保存到文件中

文章目录 前言导入依赖库打开文件准备写入数据循环爬取多个页面完整代码运行效果结束语 前言 这篇文章介绍了如何使用 Python 爬虫技术获取代理IP并保存到文件中。通过使用第三方库 requests 发送HTTP请求&#xff0c;并使用 lxml 库解析HTML&#xff0c;我们可以从多个网页上…

《Kali渗透基础》11. 无线渗透(一)

kali渗透 1&#xff1a;无线技术特点2&#xff1a;IEEE 802.11 标准2.1&#xff1a;无线网络分层2.2&#xff1a;IEEE2.3&#xff1a;日常使用标准2.3.1&#xff1a;802.112.3.2&#xff1a;802.11b2.3.3&#xff1a;802.11a2.3.4&#xff1a;802.11g2.3.5&#xff1a;802.11n …

python的包管理器pip安装经常失败的解决办法:修改pip镜像源

pip 常用的国内镜像源&#xff1a; https://pypi.tuna.tsinghua.edu.cn/simple/ // 清华 http://mirrors.aliyun.com/pypi/simple/ // 阿里云 https://pypi.mirrors.ustc.edu.cn/simple/ // 中国科技大学 http://pypi.hustunique.com/ // 华中理…

stm32通过ESP8266接入原子云

1. ESP8266模块需要烧录原子云固件&#xff0c;此原子云固件和正常的ESP8266固件相比添加了ATATKCLDSTA 和 ATATKCLDCLS 这两条指令&#xff1a; 2. 原子云账号注册及设备建立 设备管理-新增设备-ESP8266 新建设备后新建分组&#xff0c;将设备加入到此分组中&#xff1a; 至此…

【算法基础:贪心】6. 贪心

文章目录 区间问题905. 区间选点&#xff08;排序 贪心&#xff09;908. 最大不相交区间数量&#xff08;排序 贪心&#xff09;906. 区间分组&#xff08;排序 优先队列 贪心&#xff09;⭐907. 区间覆盖&#xff08;排序 贪心&#xff09; Huffman树148. 合并果子&#…

安卓:Picasso——加载网络图片的库

目录 一、Picasso介绍及其优势 二、Picasso的使用方法 1、添加依赖&#xff1a; 2、Picasso常用方法&#xff1a; 1、加载图像&#xff1a; 2、图像显示&#xff1a; 3、图像处理&#xff1a; 4、图像占位符和错误处理&#xff1a; 5、缓存控制&#xff1a; 6、清除缓…

关于前端框架vue2升级为vue3的相关说明

一些框架需要升级 当前&#xff08;202306&#xff09; Vue 的最新稳定版本是 v3.3.4。Vue 框架升级为最新的3.0版本&#xff0c;涉及的相关依赖变更有&#xff1a; 前提条件&#xff1a;已安装 16.0 或更高版本的Node.js&#xff08;摘&#xff09; 必须的变更&#xff1a;核…

06-MySQL-基础篇-SQL之DCL语句

SQL之DCL语句 前言DCL 管理用户查询用户创建用户修改用户密码删除用户说明 权限控制常见权限描述查询权限授予权限撤销权限说明 前言 本篇来学习下SQL中的DCL语句 DCL DCL英文全称是Data Control Language(数据控制语言)&#xff0c;用来管理数据库用户、控制数据库的访问权…

附录1-将uni-app运行到微信开发者工具

目录 1 在manifest.json写入AppID 2 配置微信开发者工具的安装路径 3 微信开发者工具的安全设置 4 运行 5 修改一些配置项 1 在manifest.json写入AppID 2 配置微信开发者工具的安装路径 如果你忘了安装在哪里了&#xff0c;可以右键快捷方式看一下属性 在运行设置…

剑指offer42.连续子数组的最大和

这道题挺简单的&#xff0c;看完题脑子里出现的想法就是用一个sum来把数组从前往后加&#xff0c;如果sum小于0&#xff0c;那么对于和来说是会减小的&#xff0c;所以这个时候直接把sum归零&#xff0c;然后从这个位置再往后加&#xff0c;用一个max_sum来记录sum的最大值&…

SpringBoot整合ActiveMQ

ActiveMQ简单使用 JMS ActiveMQ 下载安装 https://activemq.apache.org/components/classic/download/解压缩文件。进入win64目录&#xff0c;双击运行activemq.bat文件&#xff0c;运行服务 将下面的网址输入到浏览器&#xff0c;用户名和密码都是admin SpringBoot整合Act…

MP的开发流程

MP的开发流程 1、添加依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.ap…

LeetCode 1857. Largest Color Value in a Directed Graph【拓扑排序,动态规划】困难

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…

自动化运维工具—Ansible概述

Ansible是什么&#xff1f; Ansible是一个基于Python开发的配置管理和应用部署工具&#xff0c;现在也在自动化管理领域大放异彩。它融合了众多老牌运维工具的优点&#xff0c;Pubbet和Saltstack能实现的功能&#xff0c;Ansible基本上都可以实现。 Ansible能批量配置、部署、…

linux | vscode | makefile | c++编译和调试

简单介绍环境&#xff1a; vscode 、centos、 gcc、g、makefile 简单来说就是&#xff0c;写好项目然后再自己写makefile脚本实现编译。所以看这篇博客的用户需要了解gcc编译的一些常用命令以及makefile语法。在网上看了很多教程&#xff0c;以及官网也看了很多次&#xff0c;最…

LabVIEW开发环境试验箱控制器

LabVIEW开发环境试验箱控制器 环境或气候试验箱是一种外壳&#xff0c;用于模拟各种材料&#xff08;包括工业产品、生物物质、复合材料、电子设备和航空航天部件&#xff09;的特定环境条件&#xff0c;并评估调节对这些材料的影响。 环境试验箱&#xff08;ETC&#xff09;…

短视频矩阵营销系统技术开发者开发笔记分享

一、开发短视频seo抖音矩阵系统需要遵循以下步骤&#xff1a; 1. 确定系统需求&#xff1a;根据客户的需求&#xff0c;确定系统的功能和特点&#xff0c;例如用户注册登录、视频上传、视频浏览、评论点赞等。 2. 设计系统架构&#xff1a;根据系统需求&#xff0c;设计系统的…

PDF.js实现搜索关键词高亮显示效果

在static\PDF\web\viewer.js找到定义setInitialView方法 大约是在1202行&#xff0c;不同的pdf.js版本不同 在方法体最后面添加如下代码&#xff1a; // 高亮显示关键词---------------------------------------- var keyword new URL(decodeURIComponent(location)).searchP…