浏览代码

Merge pull request #103418 from aaronfranke/adv-imp-attach-script

Allow attaching scripts to nodes in the Advanced Import Settings dialog
Rémi Verschelde 1 月之前
父节点
当前提交
033e55f4b1

+ 3 - 0
doc/classes/ResourceImporterScene.xml

@@ -80,6 +80,9 @@
 		<member name="nodes/root_scale" type="float" setter="" getter="" default="1.0">
 			The uniform scale to use for the scene root. The default value of [code]1.0[/code] will not perform any rescaling. See [member nodes/apply_root_scale] for details of how this scale is applied.
 		</member>
+		<member name="nodes/root_script" type="Script" setter="" getter="" default="null">
+			If set to a valid script, attaches the script to the root node of the imported scene. If the type of the root node is not compatible with the script, the root node will be replaced with a type that is compatible with the script. This setting can also be used on other non-mesh nodes in the scene to attach scripts to them.
+		</member>
 		<member name="nodes/root_type" type="String" setter="" getter="" default="&quot;&quot;">
 			Override for the root node type. If empty, the root node will use what the scene specifies, or [Node3D] if the scene does not specify a root type. Using a node type that inherits from [Node3D] is recommended. Otherwise, you'll lose the ability to position the node directly in the 3D editor.
 		</member>

+ 57 - 22
editor/import/3d/resource_importer_scene.cpp

@@ -1368,6 +1368,51 @@ Node *ResourceImporterScene::_post_fix_animations(Node *p_node, Node *p_root, co
 	return p_node;
 }
 
+Node *ResourceImporterScene::_replace_node_with_type_and_script(Node *p_node, String p_node_type, Ref<Script> p_script) {
+	p_node_type = p_node_type.get_slicec(' ', 0); // Full root_type is "ClassName (filename.gd)" for a script global class.
+	if (p_script.is_valid()) {
+		// Ensure the node type supports the script, or pick one that does.
+		String script_base_type = p_script->get_instance_base_type();
+		if (ClassDB::is_parent_class(script_base_type, "Node")) {
+			if (p_node_type.is_empty() || !ClassDB::is_parent_class(p_node_type, script_base_type)) {
+				p_node_type = script_base_type;
+			}
+		}
+	}
+	if (!p_node_type.is_empty() && ScriptServer::is_global_class(p_node_type)) {
+		// If the user specified a script class, we need to get the base node type.
+		if (p_script.is_null()) {
+			p_script = ResourceLoader::load(ScriptServer::get_global_class_path(p_node_type));
+		}
+		p_node_type = ScriptServer::get_global_class_base(p_node_type);
+	}
+	if (!p_node_type.is_empty() && p_node->get_class_name() != p_node_type) {
+		// If the user specified a Godot node type that does not match
+		// what the scene import gave us, replace the root node.
+		Node *new_base_node = Object::cast_to<Node>(ClassDB::instantiate(p_node_type));
+		if (new_base_node) {
+			List<PropertyInfo> old_properties;
+			p_node->get_property_list(&old_properties);
+			for (const PropertyInfo &prop : old_properties) {
+				if (!(prop.usage & PROPERTY_USAGE_STORAGE)) {
+					continue;
+				}
+				new_base_node->set(prop.name, p_node->get(prop.name));
+			}
+			new_base_node->set_name(p_node->get_name());
+			_copy_meta(p_node, new_base_node);
+			p_node->replace_by(new_base_node);
+			p_node->set_owner(nullptr);
+			memdelete(p_node);
+			p_node = new_base_node;
+		}
+	}
+	if (p_script.is_valid()) {
+		p_node->set_script(Variant(p_script));
+	}
+	return p_node;
+}
+
 Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, HashMap<Ref<ImporterMesh>, Vector<Ref<Shape3D>>> &collision_map, Pair<PackedVector3Array, PackedInt32Array> &r_occluder_arrays, HashSet<Ref<ImporterMesh>> &r_scanned_meshes, const Dictionary &p_node_data, const Dictionary &p_material_data, const Dictionary &p_animation_data, float p_animation_fps, float p_applied_root_scale, const String &p_source_file, const HashMap<StringName, Variant> &p_options) {
 	// children first
 	for (int i = 0; i < p_node->get_child_count(); i++) {
@@ -1842,6 +1887,10 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, HashMap<
 		}
 	}
 
+	String node_type = node_settings.get("node/node_type", "");
+	Ref<Script> node_script = node_settings.get("node/script", Ref<Script>());
+	p_node = _replace_node_with_type_and_script(p_node, node_type, node_script);
+
 	return p_node;
 }
 
@@ -2057,9 +2106,12 @@ void ResourceImporterScene::_compress_animations(AnimationPlayer *anim, int p_pa
 void ResourceImporterScene::get_internal_import_options(InternalImportCategory p_category, List<ImportOption> *r_options) const {
 	switch (p_category) {
 		case INTERNAL_IMPORT_CATEGORY_NODE: {
+			r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "node/node_type", PROPERTY_HINT_TYPE_STRING, "Node"), ""));
+			r_options->push_back(ImportOption(PropertyInfo(Variant::OBJECT, "node/script", PROPERTY_HINT_RESOURCE_TYPE, "Script"), Variant()));
 			r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "import/skip_import", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
 		} break;
 		case INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE: {
+			r_options->push_back(ImportOption(PropertyInfo(Variant::OBJECT, "node/script", PROPERTY_HINT_RESOURCE_TYPE, "Script"), Variant()));
 			r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "import/skip_import", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
 			r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "generate/physics", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
 			r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "generate/navmesh", PROPERTY_HINT_ENUM, "Disabled,Mesh + NavMesh,NavMesh Only"), 0));
@@ -2140,6 +2192,7 @@ void ResourceImporterScene::get_internal_import_options(InternalImportCategory p
 			}
 		} break;
 		case INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE: {
+			r_options->push_back(ImportOption(PropertyInfo(Variant::OBJECT, "node/script", PROPERTY_HINT_RESOURCE_TYPE, "Script"), Variant()));
 			r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "import/skip_import", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
 			r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "optimizer/enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), true));
 			r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "optimizer/max_velocity_error", PROPERTY_HINT_RANGE, "0,1,0.01"), 0.01));
@@ -2152,6 +2205,7 @@ void ResourceImporterScene::get_internal_import_options(InternalImportCategory p
 			r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "import_tracks/scale", PROPERTY_HINT_ENUM, "IfPresent,IfPresentForAll,Never"), 1));
 		} break;
 		case INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE: {
+			r_options->push_back(ImportOption(PropertyInfo(Variant::OBJECT, "node/script", PROPERTY_HINT_RESOURCE_TYPE, "Script"), Variant()));
 			r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "import/skip_import", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
 			r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "rest_pose/load_pose", PROPERTY_HINT_ENUM, "Default Pose,Use AnimationPlayer,Load External Animation", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 0));
 			r_options->push_back(ImportOption(PropertyInfo(Variant::OBJECT, "rest_pose/external_animation_library", PROPERTY_HINT_RESOURCE_TYPE, "Animation,AnimationLibrary", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), Variant()));
@@ -2436,6 +2490,7 @@ bool ResourceImporterScene::get_internal_option_update_view_required(InternalImp
 void ResourceImporterScene::get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset) const {
 	r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "nodes/root_type", PROPERTY_HINT_TYPE_STRING, "Node"), ""));
 	r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "nodes/root_name"), ""));
+	r_options->push_back(ImportOption(PropertyInfo(Variant::OBJECT, "nodes/root_script", PROPERTY_HINT_RESOURCE_TYPE, "Script"), Variant()));
 
 	List<String> script_extensions;
 	ResourceLoader::get_recognized_extensions_for_type("Script", &script_extensions);
@@ -3147,28 +3202,8 @@ Error ResourceImporterScene::import(ResourceUID::ID p_source_id, const String &p
 	_post_fix_animations(scene, scene, node_data, animation_data, fps, remove_immutable_tracks);
 
 	String root_type = p_options["nodes/root_type"];
-	if (!root_type.is_empty()) {
-		root_type = root_type.split(" ")[0]; // Full root_type is "ClassName (filename.gd)" for a script global class.
-		Ref<Script> root_script = nullptr;
-		if (ScriptServer::is_global_class(root_type)) {
-			root_script = ResourceLoader::load(ScriptServer::get_global_class_path(root_type));
-			root_type = ScriptServer::get_global_class_base(root_type);
-		}
-		if (scene->get_class_name() != root_type) {
-			// If the user specified a Godot node type that does not match
-			// what the scene import gave us, replace the root node.
-			Node *base_node = Object::cast_to<Node>(ClassDB::instantiate(root_type));
-			if (base_node) {
-				scene->replace_by(base_node);
-				scene->set_owner(nullptr);
-				memdelete(scene);
-				scene = base_node;
-			}
-		}
-		if (root_script.is_valid()) {
-			scene->set_script(Variant(root_script));
-		}
-	}
+	Ref<Script> root_script = p_options["nodes/root_script"];
+	scene = _replace_node_with_type_and_script(scene, root_type, root_script);
 
 	String root_name = p_options["nodes/root_name"];
 	if (!root_name.is_empty() && root_name != "Scene Root") {

+ 1 - 0
editor/import/3d/resource_importer_scene.h

@@ -216,6 +216,7 @@ class ResourceImporterScene : public ResourceImporter {
 	Node *_generate_meshes(Node *p_node, const Dictionary &p_mesh_data, bool p_generate_lods, bool p_create_shadow_meshes, LightBakeMode p_light_bake_mode, float p_lightmap_texel_size, const Vector<uint8_t> &p_src_lightmap_cache, Vector<Vector<uint8_t>> &r_lightmap_caches);
 	void _add_shapes(Node *p_node, const Vector<Ref<Shape3D>> &p_shapes);
 	void _copy_meta(Object *p_src_object, Object *p_dst_object);
+	Node *_replace_node_with_type_and_script(Node *p_node, String p_node_type, Ref<Script> p_script);
 
 	enum AnimationImportTracks {
 		ANIMATION_IMPORT_TRACKS_IF_PRESENT,

+ 1 - 0
modules/gltf/tests/test_gltf.h

@@ -78,6 +78,7 @@ static Node *gltf_import(const String &p_file) {
 	HashMap<StringName, Variant> options(21);
 	options["nodes/root_type"] = "";
 	options["nodes/root_name"] = "";
+	options["nodes/root_script"] = Variant();
 	options["nodes/apply_root_scale"] = true;
 	options["nodes/root_scale"] = 1.0;
 	options["meshes/ensure_tangents"] = true;