Godot 4 ソースコード分析 - 画像ファイルを動的にインポート

Godot 4で電子書籍ソフトをコンパイルしてみたところ、初期効果は出ていて、パイプライン通信インターフェースを介して属性の取得や設定、関数の呼び出しなどができ、いろいろ対応できそうです。

実は、外的要因が内的要因を介して作用し、内部を理解して機能を解放しなければ、実現できないニーズもあります。

たとえば、新しい書籍を動的にロードしたい場合、これは実際的な要件です。各電子書籍を 1 回エクスポートする必要がある場合、このソフトウェアは汎用的ではありません。

以前に画像をロードするとき、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;

別の画像を直接ロードすると、結果は失敗します

 これは勉強しなければなりません。

輸入 輸入

この画像を Godot 開発環境にコピーすると、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 クラスの 2 つの関数 _reimport_group と _reimport_file があることがわかります。そのため、_reimport_file を使用する必要があります。しかし、この機能にはアクセスできません。幸いなことに、ソース コードは手元にあり、直接公開されており、アクセスできます。

ただし、これを GDScript に呼び出す必要があり、いくつかの作業を行う必要があり、ClassDB に追加する必要があります。

簡単にするために、これを DllStream クラスに直接追加し [ Godot 4 ソース コード分析 - パイプライン通信の増加_DrGraph のブログ - CSDN ブログを参照]、インポート関数を追加します。

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;
}

この要求は少し過剰です。ただし、プログラムの処理には 2 つの方法があります。1 つはプロジェクト ディレクトリに画像を自動的にコピーする方法、もう 1 つはこれをバイパスする方法です。

1 つ試して、コインを投げて、最後尾の 2 つ目を選択してください。

バイパスを選択したため、EditorFileSystem::_reimport_file 関数を直接実装すると、このプライベート関数をパブリックにする必要がなくなります。いわゆる実装は、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 のインポーターが空であることがわかりました。ソース コードから 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 画像のみをインポートする必要があることがわかります。したがって、インポートできるのは 1 種類だけです。

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

実行すると、画像が正常に表示されます。

仕事の次のステップ

この時点で、核となる作業は完了しており、次のステップには 2 つの主なタスクがあります。

1 つはディレクトリインポートです。つまり、対象の電子書籍の画像が配置されているディレクトリ内のすべてのファイルを一度にインポートできます。

2 つ目は、必須の動的インポートです。非必須インポートは、対応する .import ファイルが存在する限り、再度インポートする必要がないことを意味します。必須インポートは、.import ファイルが存在するかどうかに関係なくインポートされることを意味します。これは動的な処理に使用され、たとえば、ファイル内でキーワードを検索した後、キーワードを強調表示する必要があるため、異なる画像が表示されます。これらの画像は動的ディレクトリに配置でき、強制的にインポートできます。

おすすめ

転載: blog.csdn.net/drgraph/article/details/132003507
おすすめ