Jelajahi Sumber

Create AudioStreamPlayer when dropping AudioStream

- Create AudioStreamPlayer if dropped in between nodes in the Scene dock
- Create AudioStreamPlayer2D if dropped into 2D editor
- Create AudioStreamPlayer3D if dropped into 3D editor
Haoyu Qiu 1 tahun lalu
induk
melakukan
e5c321448e

+ 17 - 4
editor/gui/scene_tree_editor.cpp

@@ -1368,22 +1368,35 @@ bool SceneTreeEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_d
 		}
 
 		bool scene_drop = true;
+		bool audio_drop = true;
 		for (int i = 0; i < files.size(); i++) {
 			String ftype = EditorFileSystem::get_singleton()->get_file_type(files[i]);
 			if (ftype != "PackedScene") {
 				scene_drop = false;
-				break;
+			}
+			if (audio_drop && !ClassDB::is_parent_class(ftype, "AudioStream")) {
+				audio_drop = false;
 			}
 		}
 
 		if (scene_drop) {
 			tree->set_drop_mode_flags(Tree::DROP_MODE_INBETWEEN | Tree::DROP_MODE_ON_ITEM);
-		} else {
+			return true;
+		}
+
+		if (audio_drop) {
 			if (files.size() > 1) {
-				return false;
+				tree->set_drop_mode_flags(Tree::DROP_MODE_INBETWEEN);
+			} else {
+				tree->set_drop_mode_flags(Tree::DROP_MODE_INBETWEEN | Tree::DROP_MODE_ON_ITEM);
 			}
-			tree->set_drop_mode_flags(Tree::DROP_MODE_ON_ITEM);
+			return true;
+		}
+
+		if (files.size() > 1) {
+			return false;
 		}
+		tree->set_drop_mode_flags(Tree::DROP_MODE_ON_ITEM);
 
 		return true;
 	}

+ 227 - 139
editor/plugins/canvas_item_editor_plugin.cpp

@@ -46,10 +46,12 @@
 #include "editor/scene_tree_dock.h"
 #include "editor/themes/editor_scale.h"
 #include "editor/themes/editor_theme_manager.h"
+#include "scene/2d/audio_stream_player_2d.h"
 #include "scene/2d/polygon_2d.h"
 #include "scene/2d/skeleton_2d.h"
 #include "scene/2d/sprite_2d.h"
 #include "scene/2d/touch_screen_button.h"
+#include "scene/gui/base_button.h"
 #include "scene/gui/flow_container.h"
 #include "scene/gui/grid_container.h"
 #include "scene/gui/separator.h"
@@ -966,7 +968,7 @@ void CanvasItemEditor::_add_node_pressed(int p_result) {
 	}
 }
 
-void CanvasItemEditor::_node_created(Node *p_node) {
+void CanvasItemEditor::_adjust_new_node_position(Node *p_node) {
 	if (node_create_position == Point2()) {
 		return;
 	}
@@ -5161,7 +5163,7 @@ CanvasItemEditor::CanvasItemEditor() {
 	editor_selection->connect("selection_changed", callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw));
 	editor_selection->connect("selection_changed", callable_mp(this, &CanvasItemEditor::_selection_changed));
 
-	SceneTreeDock::get_singleton()->connect("node_created", callable_mp(this, &CanvasItemEditor::_node_created));
+	SceneTreeDock::get_singleton()->connect("node_created", callable_mp(this, &CanvasItemEditor::_adjust_new_node_position));
 	SceneTreeDock::get_singleton()->connect("add_node_used", callable_mp(this, &CanvasItemEditor::_reset_create_position));
 
 	// Add some margin to the sides for better esthetics.
@@ -5697,15 +5699,15 @@ CanvasItemEditorPlugin::~CanvasItemEditorPlugin() {
 }
 
 void CanvasItemEditorViewport::_on_mouse_exit() {
-	if (!selector->is_visible()) {
+	if (!texture_node_type_selector->is_visible()) {
 		_remove_preview();
 	}
 }
 
-void CanvasItemEditorViewport::_on_select_type(Object *selected) {
+void CanvasItemEditorViewport::_on_select_texture_node_type(Object *selected) {
 	CheckBox *check = Object::cast_to<CheckBox>(selected);
 	String type = check->get_text();
-	selector->set_title(vformat(TTR("Add %s"), type));
+	texture_node_type_selector->set_title(vformat(TTR("Add %s"), type));
 	label->set_text(vformat(TTR("Adding %s..."), type));
 }
 
@@ -5717,7 +5719,7 @@ void CanvasItemEditorViewport::_on_change_type_confirmed() {
 	CheckBox *check = Object::cast_to<CheckBox>(button_group->get_pressed_button());
 	default_texture_node_type = check->get_text();
 	_perform_drop_data();
-	selector->hide();
+	texture_node_type_selector->hide();
 }
 
 void CanvasItemEditorViewport::_on_change_type_closed() {
@@ -5728,35 +5730,35 @@ void CanvasItemEditorViewport::_create_preview(const Vector<String> &files) cons
 	bool add_preview = false;
 	for (int i = 0; i < files.size(); i++) {
 		Ref<Resource> res = ResourceLoader::load(files[i]);
-		ERR_FAIL_COND(res.is_null());
-		Ref<Texture2D> texture = Ref<Texture2D>(Object::cast_to<Texture2D>(*res));
-		Ref<PackedScene> scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*res));
-		if (texture != nullptr || scene != nullptr) {
-			String desc = TTR("Drag and drop to add as sibling of selected node (except when root is selected).") +
-					"\n" + TTR("Hold Shift when dropping to add as child of selected node.") +
-					"\n" + TTR("Hold Alt when dropping to add as child of root node.");
-
-			if (texture != nullptr) {
-				Sprite2D *sprite = memnew(Sprite2D);
-				sprite->set_texture(texture);
-				sprite->set_modulate(Color(1, 1, 1, 0.7f));
-				preview_node->add_child(sprite);
-				label->show();
-				label_desc->show();
-				desc += "\n" + TTR("Hold Alt + Shift when dropping to add as a different node type.");
-				label_desc->set_text(desc);
-			} else {
-				if (scene.is_valid()) {
-					Node *instance = scene->instantiate();
-					if (instance) {
-						preview_node->add_child(instance);
-						label_desc->show();
-						label_desc->set_text(desc);
-					}
-				}
+		ERR_CONTINUE(res.is_null());
+
+		Ref<Texture2D> texture = res;
+		if (texture.is_valid()) {
+			Sprite2D *sprite = memnew(Sprite2D);
+			sprite->set_texture(texture);
+			sprite->set_modulate(Color(1, 1, 1, 0.7f));
+			preview_node->add_child(sprite);
+			add_preview = true;
+		}
+
+		Ref<PackedScene> scene = res;
+		if (scene.is_valid()) {
+			Node *instance = scene->instantiate();
+			if (instance) {
+				preview_node->add_child(instance);
 			}
 			add_preview = true;
 		}
+
+		Ref<AudioStream> audio = res;
+		if (audio.is_valid()) {
+			Sprite2D *sprite = memnew(Sprite2D);
+			sprite->set_texture(get_editor_theme_icon(SNAME("AudioStreamPlayer2D")));
+			sprite->set_modulate(Color(1, 1, 1, 0.7f));
+			sprite->set_position(Vector2(0, -sprite->get_texture()->get_size().height) * EDSCALE);
+			preview_node->add_child(sprite);
+			add_preview = true;
+		}
 	}
 
 	if (add_preview) {
@@ -5797,44 +5799,46 @@ bool CanvasItemEditorViewport::_cyclical_dependency_exists(const String &p_targe
 	return false;
 }
 
-void CanvasItemEditorViewport::_create_nodes(Node *parent, Node *child, String &path, const Point2 &p_point) {
+void CanvasItemEditorViewport::_create_texture_node(Node *p_parent, Node *p_child, const String &p_path, const Point2 &p_point) {
 	// Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others.
-	String name = path.get_file().get_basename();
-	child->set_name(Node::adjust_name_casing(name));
+	const String &node_name = Node::adjust_name_casing(p_path.get_file().get_basename());
+	if (!node_name.is_empty()) {
+		p_child->set_name(node_name);
+	}
 
 	EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
-	Ref<Texture2D> texture = ResourceCache::get_ref(path);
+	Ref<Texture2D> texture = ResourceCache::get_ref(p_path);
 
-	if (parent) {
-		undo_redo->add_do_method(parent, "add_child", child, true);
-		undo_redo->add_do_method(child, "set_owner", EditorNode::get_singleton()->get_edited_scene());
-		undo_redo->add_do_reference(child);
-		undo_redo->add_undo_method(parent, "remove_child", child);
+	if (p_parent) {
+		undo_redo->add_do_method(p_parent, "add_child", p_child, true);
+		undo_redo->add_do_method(p_child, "set_owner", EditorNode::get_singleton()->get_edited_scene());
+		undo_redo->add_do_reference(p_child);
+		undo_redo->add_undo_method(p_parent, "remove_child", p_child);
 	} else { // If no parent is selected, set as root node of the scene.
-		undo_redo->add_do_method(EditorNode::get_singleton(), "set_edited_scene", child);
-		undo_redo->add_do_method(child, "set_owner", EditorNode::get_singleton()->get_edited_scene());
-		undo_redo->add_do_reference(child);
+		undo_redo->add_do_method(EditorNode::get_singleton(), "set_edited_scene", p_child);
+		undo_redo->add_do_method(p_child, "set_owner", EditorNode::get_singleton()->get_edited_scene());
+		undo_redo->add_do_reference(p_child);
 		undo_redo->add_undo_method(EditorNode::get_singleton(), "set_edited_scene", (Object *)nullptr);
 	}
 
-	if (parent) {
-		String new_name = parent->validate_child_name(child);
+	if (p_parent) {
+		String new_name = p_parent->validate_child_name(p_child);
 		EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();
-		undo_redo->add_do_method(ed, "live_debug_create_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(parent), child->get_class(), new_name);
-		undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(parent)) + "/" + new_name));
+		undo_redo->add_do_method(ed, "live_debug_create_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent), p_child->get_class(), new_name);
+		undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent)) + "/" + new_name));
 	}
 
-	if (Object::cast_to<TouchScreenButton>(child) || Object::cast_to<TextureButton>(child)) {
-		undo_redo->add_do_property(child, "texture_normal", texture);
+	if (Object::cast_to<TouchScreenButton>(p_child) || Object::cast_to<TextureButton>(p_child)) {
+		undo_redo->add_do_property(p_child, "texture_normal", texture);
 	} else {
-		undo_redo->add_do_property(child, "texture", texture);
+		undo_redo->add_do_property(p_child, "texture", texture);
 	}
 
 	// make visible for certain node type
-	if (Object::cast_to<Control>(child)) {
+	if (Object::cast_to<Control>(p_child)) {
 		Size2 texture_size = texture->get_size();
-		undo_redo->add_do_property(child, "size", texture_size);
-	} else if (Object::cast_to<Polygon2D>(child)) {
+		undo_redo->add_do_property(p_child, "size", texture_size);
+	} else if (Object::cast_to<Polygon2D>(p_child)) {
 		Size2 texture_size = texture->get_size();
 		Vector<Vector2> list = {
 			Vector2(0, 0),
@@ -5842,7 +5846,7 @@ void CanvasItemEditorViewport::_create_nodes(Node *parent, Node *child, String &
 			Vector2(texture_size.width, texture_size.height),
 			Vector2(0, texture_size.height)
 		};
-		undo_redo->add_do_property(child, "polygon", list);
+		undo_redo->add_do_property(p_child, "polygon", list);
 	}
 
 	// Compute the global position
@@ -5850,21 +5854,68 @@ void CanvasItemEditorViewport::_create_nodes(Node *parent, Node *child, String &
 	Point2 target_position = xform.affine_inverse().xform(p_point);
 
 	// Adjust position for Control and TouchScreenButton
-	if (Object::cast_to<Control>(child) || Object::cast_to<TouchScreenButton>(child)) {
+	if (Object::cast_to<Control>(p_child) || Object::cast_to<TouchScreenButton>(p_child)) {
 		target_position -= texture->get_size() / 2;
 	}
 
-	// there's nothing to be used as source position so snapping will work as absolute if enabled
+	// There's nothing to be used as source position, so snapping will work as absolute if enabled.
+	target_position = canvas_item_editor->snap_point(target_position);
+
+	CanvasItem *parent_ci = Object::cast_to<CanvasItem>(p_parent);
+	Point2 local_target_pos = parent_ci ? parent_ci->get_global_transform().affine_inverse().xform(target_position) : target_position;
+
+	undo_redo->add_do_method(p_child, "set_position", local_target_pos);
+}
+
+void CanvasItemEditorViewport::_create_audio_node(Node *p_parent, const String &p_path, const Point2 &p_point) {
+	AudioStreamPlayer2D *child = memnew(AudioStreamPlayer2D);
+	child->set_stream(ResourceCache::get_ref(p_path));
+
+	// Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others.
+	const String &node_name = Node::adjust_name_casing(p_path.get_file().get_basename());
+	if (!node_name.is_empty()) {
+		child->set_name(node_name);
+	}
+
+	EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+
+	if (p_parent) {
+		undo_redo->add_do_method(p_parent, "add_child", child, true);
+		undo_redo->add_do_method(child, "set_owner", EditorNode::get_singleton()->get_edited_scene());
+		undo_redo->add_do_reference(child);
+		undo_redo->add_undo_method(p_parent, "remove_child", child);
+	} else { // If no parent is selected, set as root node of the scene.
+		undo_redo->add_do_method(EditorNode::get_singleton(), "set_edited_scene", child);
+		undo_redo->add_do_method(child, "set_owner", EditorNode::get_singleton()->get_edited_scene());
+		undo_redo->add_do_reference(child);
+		undo_redo->add_undo_method(EditorNode::get_singleton(), "set_edited_scene", (Object *)nullptr);
+	}
+
+	if (p_parent) {
+		String new_name = p_parent->validate_child_name(child);
+		EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();
+		undo_redo->add_do_method(ed, "live_debug_create_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent), child->get_class(), new_name);
+		undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent)) + "/" + new_name));
+	}
+
+	// Compute the global position
+	Transform2D xform = canvas_item_editor->get_canvas_transform();
+	Point2 target_position = xform.affine_inverse().xform(p_point);
+
+	// There's nothing to be used as source position, so snapping will work as absolute if enabled.
 	target_position = canvas_item_editor->snap_point(target_position);
 
-	CanvasItem *parent_ci = Object::cast_to<CanvasItem>(parent);
+	CanvasItem *parent_ci = Object::cast_to<CanvasItem>(p_parent);
 	Point2 local_target_pos = parent_ci ? parent_ci->get_global_transform().affine_inverse().xform(target_position) : target_position;
 
 	undo_redo->add_do_method(child, "set_position", local_target_pos);
+
+	EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection();
+	undo_redo->add_do_method(editor_selection, "add_node", child);
 }
 
-bool CanvasItemEditorViewport::_create_instance(Node *parent, String &path, const Point2 &p_point) {
-	Ref<PackedScene> sdata = ResourceLoader::load(path);
+bool CanvasItemEditorViewport::_create_instance(Node *p_parent, const String &p_path, const Point2 &p_point) {
+	Ref<PackedScene> sdata = ResourceLoader::load(p_path);
 	if (!sdata.is_valid()) { // invalid scene
 		return false;
 	}
@@ -5883,27 +5934,27 @@ bool CanvasItemEditorViewport::_create_instance(Node *parent, String &path, cons
 		}
 	}
 
-	instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(path));
+	instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(p_path));
 
 	EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
 	EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection();
-	undo_redo->add_do_method(parent, "add_child", instantiated_scene, true);
+	undo_redo->add_do_method(p_parent, "add_child", instantiated_scene, true);
 	undo_redo->add_do_method(instantiated_scene, "set_owner", edited_scene);
 	undo_redo->add_do_reference(instantiated_scene);
-	undo_redo->add_undo_method(parent, "remove_child", instantiated_scene);
+	undo_redo->add_undo_method(p_parent, "remove_child", instantiated_scene);
 	undo_redo->add_do_method(editor_selection, "add_node", instantiated_scene);
 
-	String new_name = parent->validate_child_name(instantiated_scene);
+	String new_name = p_parent->validate_child_name(instantiated_scene);
 	EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();
-	undo_redo->add_do_method(ed, "live_debug_instantiate_node", edited_scene->get_path_to(parent), path, new_name);
-	undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(parent)) + "/" + new_name));
+	undo_redo->add_do_method(ed, "live_debug_instantiate_node", edited_scene->get_path_to(p_parent), p_path, new_name);
+	undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(p_parent)) + "/" + new_name));
 
 	CanvasItem *instance_ci = Object::cast_to<CanvasItem>(instantiated_scene);
 	if (instance_ci) {
 		Vector2 target_pos = canvas_item_editor->get_canvas_transform().affine_inverse().xform(p_point);
 		target_pos = canvas_item_editor->snap_point(target_pos);
 
-		CanvasItem *parent_ci = Object::cast_to<CanvasItem>(parent);
+		CanvasItem *parent_ci = Object::cast_to<CanvasItem>(p_parent);
 		if (parent_ci) {
 			target_pos = parent_ci->get_global_transform_with_canvas().affine_inverse().xform(target_pos);
 		}
@@ -5922,12 +5973,8 @@ void CanvasItemEditorViewport::_perform_drop_data() {
 	_remove_preview();
 
 	if (!target_node) {
-		// Without root dropping multiple files is not allowed
-		if (selected_files.size() > 1) {
-			accept->set_text(TTR("Cannot instantiate multiple nodes without root."));
-			accept->popup_centered();
-			return;
-		}
+		// Should already be handled by `can_drop_data`.
+		ERR_FAIL_COND_MSG(selected_files.size() > 1, "Can't instantiate multiple nodes without root.");
 
 		const String &path = selected_files[0];
 		Ref<Resource> res = ResourceLoader::load(path);
@@ -5973,9 +6020,14 @@ void CanvasItemEditorViewport::_perform_drop_data() {
 		Ref<Texture2D> texture = res;
 		if (texture.is_valid()) {
 			Node *child = Object::cast_to<Node>(ClassDB::instantiate(default_texture_node_type));
-			_create_nodes(target_node, child, path, drop_pos);
+			_create_texture_node(target_node, child, path, drop_pos);
 			undo_redo->add_do_method(editor_selection, "add_node", child);
 		}
+
+		Ref<AudioStream> audio = res;
+		if (audio.is_valid()) {
+			_create_audio_node(target_node, path, drop_pos);
+		}
 	}
 
 	undo_redo->commit_action();
@@ -5994,71 +6046,108 @@ bool CanvasItemEditorViewport::can_drop_data(const Point2 &p_point, const Varian
 	}
 
 	Vector<String> files = d["files"];
-	bool can_instantiate = false;
-	bool is_cyclical_dep = false;
-	String error_file;
 
-	// Check if at least one of the dragged files is a texture or scene.
-	for (int i = 0; i < files.size(); i++) {
-		bool is_scene = ClassDB::is_parent_class(ResourceLoader::get_resource_type(files[i]), "PackedScene");
-		bool is_texture = ClassDB::is_parent_class(ResourceLoader::get_resource_type(files[i]), "Texture2D");
+	const Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();
+	if (!edited_scene && files.size() > 1) {
+		canvas_item_editor->message = TTR("Can't instantiate multiple nodes without root.");
+		canvas_item_editor->update_viewport();
+		return false;
+	}
+
+	enum {
+		SCENE = 1 << 0,
+		TEXTURE = 1 << 1,
+		AUDIO = 1 << 2,
+	};
+	int instantiate_type = 0;
 
-		if (is_scene || is_texture) {
-			Ref<Resource> res = ResourceLoader::load(files[i]);
-			if (res.is_null()) {
+	for (const String &path : files) {
+		const String &res_type = ResourceLoader::get_resource_type(path);
+		String error_message;
+
+		if (ClassDB::is_parent_class(res_type, "PackedScene")) {
+			Ref<PackedScene> scn = ResourceLoader::load(path);
+			ERR_CONTINUE(scn.is_null());
+
+			Node *instantiated_scene = scn->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);
+			if (!instantiated_scene) {
 				continue;
 			}
-			Ref<PackedScene> scn = res;
-			if (scn.is_valid()) {
-				Node *instantiated_scene = scn->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);
-				if (!instantiated_scene) {
-					continue;
-				}
-
-				Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();
-				if (edited_scene && !edited_scene->get_scene_file_path().is_empty() && _cyclical_dependency_exists(edited_scene->get_scene_file_path(), instantiated_scene)) {
-					memdelete(instantiated_scene);
-					can_instantiate = false;
-					is_cyclical_dep = true;
-					error_file = files[i].get_file();
-					break;
-				}
-				memdelete(instantiated_scene);
+			if (edited_scene && !edited_scene->get_scene_file_path().is_empty() && _cyclical_dependency_exists(edited_scene->get_scene_file_path(), instantiated_scene)) {
+				error_message = vformat(TTR("Circular dependency found at %s."), path.get_file());
 			}
-			can_instantiate = true;
+			memdelete(instantiated_scene);
+			instantiate_type |= SCENE;
+		}
+		if (ClassDB::is_parent_class(res_type, "Texture2D")) {
+			instantiate_type |= TEXTURE;
+		}
+		if (ClassDB::is_parent_class(res_type, "AudioStream")) {
+			instantiate_type |= AUDIO;
+		}
+
+		if (!error_message.is_empty()) {
+			// TRANSLATORS: The placeholder is the error message.
+			canvas_item_editor->message = vformat(TTR("Can't instantiate: %s"), error_message);
+			canvas_item_editor->update_viewport();
+			return false;
 		}
 	}
-	if (is_cyclical_dep) {
-		canvas_item_editor->message = vformat(TTR("Can't instantiate: %s."), vformat(TTR("Circular dependency found at %s"), error_file));
-		canvas_item_editor->update_viewport();
+	if (instantiate_type == 0) {
 		return false;
 	}
-	if (can_instantiate) {
-		if (!preview_node->get_parent()) { // create preview only once
-			_create_preview(files);
-		}
-		ERR_FAIL_COND_V(preview_node->get_child_count() == 0, false);
 
-		Transform2D trans = canvas_item_editor->get_canvas_transform();
-		preview_node->set_position((p_point - trans.get_origin()) / trans.get_scale().x);
+	if (!preview_node->get_parent()) { // create preview only once
+		_create_preview(files);
+	}
+	ERR_FAIL_COND_V(preview_node->get_child_count() == 0, false);
+
+	const Transform2D trans = canvas_item_editor->get_canvas_transform();
+	preview_node->set_position((p_point - trans.get_origin()) / trans.get_scale().x);
+
+	if (!edited_scene && instantiate_type & SCENE) {
 		String scene_file_path = preview_node->get_child(0)->get_scene_file_path();
-		if (scene_file_path.is_empty() || preview_node->get_tree()->get_edited_scene_root()) {
-			double snap = EDITOR_GET("interface/inspector/default_float_step");
-			int snap_step_decimals = Math::range_step_decimals(snap);
+		// TRANSLATORS: The placeholder is the file path of the scene being instantiated.
+		canvas_item_editor->message = vformat(TTR("Creating inherited scene from: %s"), scene_file_path);
+	} else {
+		double snap = EDITOR_GET("interface/inspector/default_float_step");
+		int snap_step_decimals = Math::range_step_decimals(snap);
 #define FORMAT(value) (TS->format_number(String::num(value, snap_step_decimals)))
-			Vector2 preview_node_pos = preview_node->get_global_position();
-			canvas_item_editor->message = TTR("Instantiating:") + " (" + FORMAT(preview_node_pos.x) + ", " + FORMAT(preview_node_pos.y) + ") px";
-			label->set_text(vformat(TTR("Adding %s..."), default_texture_node_type));
-		} else {
-			canvas_item_editor->message = TTR("Creating inherited scene from: ") + scene_file_path;
+		Vector2 preview_node_pos = preview_node->get_global_position();
+		canvas_item_editor->message = TTR("Instantiating: ") + "(" + FORMAT(preview_node_pos.x) + ", " + FORMAT(preview_node_pos.y) + ") px";
+	}
+	canvas_item_editor->update_viewport();
+
+	if (instantiate_type & TEXTURE && instantiate_type & AUDIO) {
+		// TRANSLATORS: The placeholders are the types of nodes being instantiated.
+		label->set_text(vformat(TTR("Adding %s and %s..."), default_texture_node_type, "AudioStreamPlayer2D"));
+	} else {
+		String node_type;
+		if (instantiate_type & TEXTURE) {
+			node_type = default_texture_node_type;
+		} else if (instantiate_type & AUDIO) {
+			node_type = "AudioStreamPlayer2D";
+		}
+		if (!node_type.is_empty()) {
+			// TRANSLATORS: The placeholder is the type of node being instantiated.
+			label->set_text(vformat(TTR("Adding %s..."), node_type));
 		}
+	}
+	label->set_visible(instantiate_type & ~SCENE);
 
-		canvas_item_editor->update_viewport();
+	String desc = TTR("Drag and drop to add as sibling of selected node (except when root is selected).") +
+			"\n" + TTR("Hold Shift when dropping to add as child of selected node.") +
+			"\n" + TTR("Hold Alt when dropping to add as child of root node.");
+	if (instantiate_type & TEXTURE) {
+		desc += "\n" + TTR("Hold Alt + Shift when dropping to add as different node type.");
 	}
-	return can_instantiate;
+	label_desc->set_text(desc);
+	label_desc->show();
+
+	return true;
 }
 
-void CanvasItemEditorViewport::_show_resource_type_selector() {
+void CanvasItemEditorViewport::_show_texture_node_type_selector() {
 	_remove_preview();
 	List<BaseButton *> btn_list;
 	button_group->get_buttons(&btn_list);
@@ -6067,18 +6156,17 @@ void CanvasItemEditorViewport::_show_resource_type_selector() {
 		CheckBox *check = Object::cast_to<CheckBox>(btn);
 		check->set_pressed(check->get_text() == default_texture_node_type);
 	}
-	selector->set_title(vformat(TTR("Add %s"), default_texture_node_type));
-	selector->popup_centered();
+	texture_node_type_selector->set_title(vformat(TTR("Add %s"), default_texture_node_type));
+	texture_node_type_selector->popup_centered();
 }
 
-bool CanvasItemEditorViewport::_only_packed_scenes_selected() const {
+bool CanvasItemEditorViewport::_is_any_texture_selected() const {
 	for (int i = 0; i < selected_files.size(); ++i) {
-		if (ResourceLoader::load(selected_files[i])->get_class() != "PackedScene") {
-			return false;
+		if (ClassDB::is_parent_class(ResourceLoader::get_resource_type(selected_files[i]), "Texture2D")) {
+			return true;
 		}
 	}
-
-	return true;
+	return false;
 }
 
 void CanvasItemEditorViewport::drop_data(const Point2 &p_point, const Variant &p_data) {
@@ -6115,8 +6203,8 @@ void CanvasItemEditorViewport::drop_data(const Point2 &p_point, const Variant &p
 
 	drop_pos = p_point;
 
-	if (is_alt && is_shift && !_only_packed_scenes_selected()) {
-		_show_resource_type_selector();
+	if (is_alt && is_shift && _is_any_texture_selected()) {
+		_show_texture_node_type_selector();
 	} else {
 		_perform_drop_data();
 	}
@@ -6176,19 +6264,19 @@ CanvasItemEditorViewport::CanvasItemEditorViewport(CanvasItemEditor *p_canvas_it
 	accept = memnew(AcceptDialog);
 	EditorNode::get_singleton()->get_gui_base()->add_child(accept);
 
-	selector = memnew(AcceptDialog);
-	EditorNode::get_singleton()->get_gui_base()->add_child(selector);
-	selector->set_title(TTR("Change Default Type"));
-	selector->connect("confirmed", callable_mp(this, &CanvasItemEditorViewport::_on_change_type_confirmed));
-	selector->connect("canceled", callable_mp(this, &CanvasItemEditorViewport::_on_change_type_closed));
+	texture_node_type_selector = memnew(AcceptDialog);
+	EditorNode::get_singleton()->get_gui_base()->add_child(texture_node_type_selector);
+	texture_node_type_selector->set_title(TTR("Change Default Type"));
+	texture_node_type_selector->connect("confirmed", callable_mp(this, &CanvasItemEditorViewport::_on_change_type_confirmed));
+	texture_node_type_selector->connect("canceled", callable_mp(this, &CanvasItemEditorViewport::_on_change_type_closed));
 
 	VBoxContainer *vbc = memnew(VBoxContainer);
-	selector->add_child(vbc);
+	texture_node_type_selector->add_child(vbc);
 	vbc->set_h_size_flags(Control::SIZE_EXPAND_FILL);
 	vbc->set_v_size_flags(Control::SIZE_EXPAND_FILL);
 	vbc->set_custom_minimum_size(Size2(240, 260) * EDSCALE);
 
-	btn_group = memnew(VBoxContainer);
+	VBoxContainer *btn_group = memnew(VBoxContainer);
 	vbc->add_child(btn_group);
 	btn_group->set_h_size_flags(SIZE_EXPAND_FILL);
 
@@ -6197,7 +6285,7 @@ CanvasItemEditorViewport::CanvasItemEditorViewport(CanvasItemEditor *p_canvas_it
 		CheckBox *check = memnew(CheckBox);
 		btn_group->add_child(check);
 		check->set_text(texture_node_types[i]);
-		check->connect("button_down", callable_mp(this, &CanvasItemEditorViewport::_on_select_type).bind(check));
+		check->connect("button_down", callable_mp(this, &CanvasItemEditorViewport::_on_select_texture_node_type).bind(check));
 		check->set_button_group(button_group);
 	}
 

+ 10 - 10
editor/plugins/canvas_item_editor_plugin.h

@@ -32,10 +32,11 @@
 #define CANVAS_ITEM_EDITOR_PLUGIN_H
 
 #include "editor/plugins/editor_plugin.h"
-#include "scene/gui/base_button.h"
 #include "scene/gui/box_container.h"
 
 class AcceptDialog;
+class Button;
+class ButtonGroup;
 class CanvasItemEditorViewport;
 class ConfirmationDialog;
 class EditorData;
@@ -409,7 +410,7 @@ private:
 	void _selection_result_pressed(int);
 	void _selection_menu_hide();
 	void _add_node_pressed(int p_result);
-	void _node_created(Node *p_node);
+	void _adjust_new_node_position(Node *p_node);
 	void _reset_create_position();
 	void _update_editor_settings();
 	bool _is_grid_visible() const;
@@ -634,15 +635,13 @@ class CanvasItemEditorViewport : public Control {
 	CanvasItemEditor *canvas_item_editor = nullptr;
 	Control *preview_node = nullptr;
 	AcceptDialog *accept = nullptr;
-	AcceptDialog *selector = nullptr;
-	Label *selector_label = nullptr;
+	AcceptDialog *texture_node_type_selector = nullptr;
 	Label *label = nullptr;
 	Label *label_desc = nullptr;
-	VBoxContainer *btn_group = nullptr;
 	Ref<ButtonGroup> button_group;
 
 	void _on_mouse_exit();
-	void _on_select_type(Object *selected);
+	void _on_select_texture_node_type(Object *selected);
 	void _on_change_type_confirmed();
 	void _on_change_type_closed();
 
@@ -650,11 +649,12 @@ class CanvasItemEditorViewport : public Control {
 	void _remove_preview();
 
 	bool _cyclical_dependency_exists(const String &p_target_scene_path, Node *p_desired_node) const;
-	bool _only_packed_scenes_selected() const;
-	void _create_nodes(Node *parent, Node *child, String &path, const Point2 &p_point);
-	bool _create_instance(Node *parent, String &path, const Point2 &p_point);
+	bool _is_any_texture_selected() const;
+	void _create_texture_node(Node *p_parent, Node *p_child, const String &p_path, const Point2 &p_point);
+	void _create_audio_node(Node *p_parent, const String &p_path, const Point2 &p_point);
+	bool _create_instance(Node *p_parent, const String &p_path, const Point2 &p_point);
 	void _perform_drop_data();
-	void _show_resource_type_selector();
+	void _show_texture_node_type_selector();
 	void _update_theme();
 
 protected:

+ 107 - 41
editor/plugins/node_3d_editor_plugin.cpp

@@ -78,12 +78,14 @@
 #include "editor/plugins/gizmos/voxel_gi_gizmo_plugin.h"
 #include "editor/plugins/node_3d_editor_gizmos.h"
 #include "editor/scene_tree_dock.h"
+#include "scene/3d/audio_stream_player_3d.h"
 #include "scene/3d/camera_3d.h"
 #include "scene/3d/decal.h"
 #include "scene/3d/light_3d.h"
 #include "scene/3d/mesh_instance_3d.h"
 #include "scene/3d/physics/collision_shape_3d.h"
 #include "scene/3d/physics/physics_body_3d.h"
+#include "scene/3d/sprite_3d.h"
 #include "scene/3d/visual_instance_3d.h"
 #include "scene/3d/world_environment.h"
 #include "scene/gui/center_container.h"
@@ -4192,27 +4194,37 @@ Node *Node3DEditorViewport::_sanitize_preview_node(Node *p_node) const {
 
 void Node3DEditorViewport::_create_preview_node(const Vector<String> &files) const {
 	bool add_preview = false;
-	for (int i = 0; i < files.size(); i++) {
-		Ref<Resource> res = ResourceLoader::load(files[i]);
+	for (const String &path : files) {
+		Ref<Resource> res = ResourceLoader::load(path);
 		ERR_CONTINUE(res.is_null());
-		Ref<PackedScene> scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*res));
-		Ref<Mesh> mesh = Ref<Mesh>(Object::cast_to<Mesh>(*res));
-		if (mesh != nullptr || scene != nullptr) {
-			if (mesh != nullptr) {
-				MeshInstance3D *mesh_instance = memnew(MeshInstance3D);
-				mesh_instance->set_mesh(mesh);
-				preview_node->add_child(mesh_instance);
-			} else {
-				if (scene.is_valid()) {
-					Node *instance = scene->instantiate();
-					if (instance) {
-						instance = _sanitize_preview_node(instance);
-						preview_node->add_child(instance);
-					}
-				}
+
+		Ref<PackedScene> scene = res;
+		if (scene.is_valid()) {
+			Node *instance = scene->instantiate();
+			if (instance) {
+				instance = _sanitize_preview_node(instance);
+				preview_node->add_child(instance);
 			}
 			add_preview = true;
 		}
+
+		Ref<Mesh> mesh = res;
+		if (mesh.is_valid()) {
+			MeshInstance3D *mesh_instance = memnew(MeshInstance3D);
+			mesh_instance->set_mesh(mesh);
+			preview_node->add_child(mesh_instance);
+			add_preview = true;
+		}
+
+		Ref<AudioStream> audio = res;
+		if (audio.is_valid()) {
+			Sprite3D *sprite = memnew(Sprite3D);
+			sprite->set_texture(get_editor_theme_icon(SNAME("Gizmo3DSamplePlayer")));
+			sprite->set_billboard_mode(StandardMaterial3D::BILLBOARD_ENABLED);
+			sprite->set_pixel_size(0.005);
+			preview_node->add_child(sprite);
+			add_preview = true;
+		}
 	}
 	if (add_preview) {
 		EditorNode::get_singleton()->get_scene_root()->add_child(preview_node);
@@ -4346,12 +4358,12 @@ bool Node3DEditorViewport::_cyclical_dependency_exists(const String &p_target_sc
 	return false;
 }
 
-bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Point2 &p_point) {
-	Ref<Resource> res = ResourceLoader::load(path);
+bool Node3DEditorViewport::_create_instance(Node *p_parent, const String &p_path, const Point2 &p_point) {
+	Ref<Resource> res = ResourceLoader::load(p_path);
 	ERR_FAIL_COND_V(res.is_null(), false);
 
-	Ref<PackedScene> scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*res));
-	Ref<Mesh> mesh = Ref<Mesh>(Object::cast_to<Mesh>(*res));
+	Ref<PackedScene> scene = res;
+	Ref<Mesh> mesh = res;
 
 	Node *instantiated_scene = nullptr;
 
@@ -4361,8 +4373,10 @@ bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Po
 			mesh_instance->set_mesh(mesh);
 
 			// Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others.
-			String name = path.get_file().get_basename();
-			mesh_instance->set_name(Node::adjust_name_casing(name));
+			const String &node_name = Node::adjust_name_casing(p_path.get_file().get_basename());
+			if (!node_name.is_empty()) {
+				mesh_instance->set_name(node_name);
+			}
 
 			instantiated_scene = mesh_instance;
 		} else {
@@ -4386,25 +4400,25 @@ bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Po
 	}
 
 	if (scene != nullptr) {
-		instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(path));
+		instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(p_path));
 	}
 
 	EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
-	undo_redo->add_do_method(parent, "add_child", instantiated_scene, true);
+	undo_redo->add_do_method(p_parent, "add_child", instantiated_scene, true);
 	undo_redo->add_do_method(instantiated_scene, "set_owner", EditorNode::get_singleton()->get_edited_scene());
 	undo_redo->add_do_reference(instantiated_scene);
-	undo_redo->add_undo_method(parent, "remove_child", instantiated_scene);
+	undo_redo->add_undo_method(p_parent, "remove_child", instantiated_scene);
 	undo_redo->add_do_method(editor_selection, "add_node", instantiated_scene);
 
-	String new_name = parent->validate_child_name(instantiated_scene);
+	String new_name = p_parent->validate_child_name(instantiated_scene);
 	EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();
-	undo_redo->add_do_method(ed, "live_debug_instantiate_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(parent), path, new_name);
-	undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(parent)) + "/" + new_name));
+	undo_redo->add_do_method(ed, "live_debug_instantiate_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent), p_path, new_name);
+	undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent)) + "/" + new_name));
 
 	Node3D *node3d = Object::cast_to<Node3D>(instantiated_scene);
 	if (node3d) {
 		Transform3D parent_tf;
-		Node3D *parent_node3d = Object::cast_to<Node3D>(parent);
+		Node3D *parent_node3d = Object::cast_to<Node3D>(p_parent);
 		if (parent_node3d) {
 			parent_tf = parent_node3d->get_global_gizmo_transform();
 		}
@@ -4419,6 +4433,46 @@ bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Po
 	return true;
 }
 
+bool Node3DEditorViewport::_create_audio_node(Node *p_parent, const String &p_path, const Point2 &p_point) {
+	Ref<AudioStream> audio = ResourceLoader::load(p_path);
+	ERR_FAIL_COND_V(audio.is_null(), false);
+
+	AudioStreamPlayer3D *audio_player = memnew(AudioStreamPlayer3D);
+	audio_player->set_stream(audio);
+
+	// Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others.
+	const String &node_name = Node::adjust_name_casing(p_path.get_file().get_basename());
+	if (!node_name.is_empty()) {
+		audio_player->set_name(node_name);
+	}
+
+	EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+	undo_redo->add_do_method(p_parent, "add_child", audio_player, true);
+	undo_redo->add_do_method(audio_player, "set_owner", EditorNode::get_singleton()->get_edited_scene());
+	undo_redo->add_do_reference(audio_player);
+	undo_redo->add_undo_method(p_parent, "remove_child", audio_player);
+	undo_redo->add_do_method(editor_selection, "add_node", audio_player);
+
+	const String new_name = p_parent->validate_child_name(audio_player);
+	EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();
+	undo_redo->add_do_method(ed, "live_debug_create_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent), audio_player->get_class(), new_name);
+	undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent)) + "/" + new_name));
+
+	Transform3D parent_tf;
+	Node3D *parent_node3d = Object::cast_to<Node3D>(p_parent);
+	if (parent_node3d) {
+		parent_tf = parent_node3d->get_global_gizmo_transform();
+	}
+
+	Transform3D new_tf = audio_player->get_transform();
+	new_tf.origin = parent_tf.affine_inverse().xform(preview_node_pos + audio_player->get_position());
+	new_tf.basis = parent_tf.affine_inverse().basis * new_tf.basis;
+
+	undo_redo->add_do_method(audio_player, "set_transform", new_tf);
+
+	return true;
+}
+
 void Node3DEditorViewport::_perform_drop_data() {
 	EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
 	if (spatial_editor->get_preview_material_target().is_valid()) {
@@ -4453,11 +4507,18 @@ void Node3DEditorViewport::_perform_drop_data() {
 		if (res.is_null()) {
 			continue;
 		}
-		Ref<PackedScene> scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*res));
-		Ref<Mesh> mesh = Ref<Mesh>(Object::cast_to<Mesh>(*res));
-		if (mesh != nullptr || scene != nullptr) {
-			bool success = _create_instance(target_node, path, drop_pos);
-			if (!success) {
+
+		Ref<PackedScene> scene = res;
+		Ref<Mesh> mesh = res;
+		if (mesh.is_valid() || scene.is_valid()) {
+			if (!_create_instance(target_node, path, drop_pos)) {
+				error_files.push_back(path.get_file());
+			}
+		}
+
+		Ref<AudioStream> audio = res;
+		if (audio.is_valid()) {
+			if (!_create_audio_node(target_node, path, drop_pos)) {
 				error_files.push_back(path.get_file());
 			}
 		}
@@ -4488,12 +4549,14 @@ bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant
 			bool is_other_valid = false;
 			// Check if at least one of the dragged files is a mesh, material, texture or scene.
 			for (int i = 0; i < files.size(); i++) {
-				bool is_scene = ClassDB::is_parent_class(ResourceLoader::get_resource_type(files[i]), "PackedScene");
-				bool is_mesh = ClassDB::is_parent_class(ResourceLoader::get_resource_type(files[i]), "Mesh");
-				bool is_material = ClassDB::is_parent_class(ResourceLoader::get_resource_type(files[i]), "Material");
-				bool is_texture = ClassDB::is_parent_class(ResourceLoader::get_resource_type(files[i]), "Texture");
-
-				if (is_mesh || is_scene || is_material || is_texture) {
+				const String &res_type = ResourceLoader::get_resource_type(files[i]);
+				bool is_scene = ClassDB::is_parent_class(res_type, "PackedScene");
+				bool is_mesh = ClassDB::is_parent_class(res_type, "Mesh");
+				bool is_material = ClassDB::is_parent_class(res_type, "Material");
+				bool is_texture = ClassDB::is_parent_class(res_type, "Texture");
+				bool is_audio = ClassDB::is_parent_class(res_type, "AudioStream");
+
+				if (is_mesh || is_scene || is_material || is_texture || is_audio) {
 					Ref<Resource> res = ResourceLoader::load(files[i]);
 					if (res.is_null()) {
 						continue;
@@ -4502,6 +4565,7 @@ bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant
 					Ref<Mesh> mesh = res;
 					Ref<Material> mat = res;
 					Ref<Texture2D> tex = res;
+					Ref<AudioStream> audio = res;
 					if (scn.is_valid()) {
 						Node *instantiated_scene = scn->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);
 						if (!instantiated_scene) {
@@ -4537,6 +4601,8 @@ bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant
 						spatial_editor->set_preview_material(new_mat);
 						is_other_valid = true;
 						continue;
+					} else if (!is_other_valid && audio.is_valid()) {
+						is_other_valid = true;
 					} else {
 						continue;
 					}

+ 2 - 1
editor/plugins/node_3d_editor_plugin.h

@@ -443,7 +443,8 @@ private:
 	void _reset_preview_material() const;
 	void _remove_preview_material();
 	bool _cyclical_dependency_exists(const String &p_target_scene_path, Node *p_desired_node) const;
-	bool _create_instance(Node *parent, String &path, const Point2 &p_point);
+	bool _create_instance(Node *p_parent, const String &p_path, const Point2 &p_point);
+	bool _create_audio_node(Node *p_parent, const String &p_path, const Point2 &p_point);
 	void _perform_drop_data();
 
 	bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);

+ 99 - 19
editor/scene_tree_dock.cpp

@@ -56,6 +56,7 @@
 #include "editor/shader_create_dialog.h"
 #include "editor/themes/editor_scale.h"
 #include "scene/animation/animation_tree.h"
+#include "scene/audio/audio_stream_player.h"
 #include "scene/gui/check_box.h"
 #include "scene/property_utils.h"
 #include "scene/resources/packed_scene.h"
@@ -254,8 +255,8 @@ void SceneTreeDock::instantiate_scenes(const Vector<String> &p_files, Node *p_pa
 	_perform_instantiate_scenes(p_files, parent, -1);
 }
 
-void SceneTreeDock::_perform_instantiate_scenes(const Vector<String> &p_files, Node *parent, int p_pos) {
-	ERR_FAIL_NULL(parent);
+void SceneTreeDock::_perform_instantiate_scenes(const Vector<String> &p_files, Node *p_parent, int p_pos) {
+	ERR_FAIL_NULL(p_parent);
 
 	Vector<Node *> instances;
 
@@ -302,25 +303,25 @@ void SceneTreeDock::_perform_instantiate_scenes(const Vector<String> &p_files, N
 	}
 
 	EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
-	undo_redo->create_action(TTR("Instantiate Scene(s)"));
+	undo_redo->create_action_for_history(TTRN("Instantiate Scene", "Instantiate Scenes", instances.size()), editor_data->get_current_edited_scene_history_id());
+	undo_redo->add_do_method(editor_selection, "clear");
 
 	for (int i = 0; i < instances.size(); i++) {
 		Node *instantiated_scene = instances[i];
 
-		undo_redo->add_do_method(parent, "add_child", instantiated_scene, true);
+		undo_redo->add_do_method(p_parent, "add_child", instantiated_scene, true);
 		if (p_pos >= 0) {
-			undo_redo->add_do_method(parent, "move_child", instantiated_scene, p_pos + i);
+			undo_redo->add_do_method(p_parent, "move_child", instantiated_scene, p_pos + i);
 		}
 		undo_redo->add_do_method(instantiated_scene, "set_owner", edited_scene);
-		undo_redo->add_do_method(editor_selection, "clear");
 		undo_redo->add_do_method(editor_selection, "add_node", instantiated_scene);
 		undo_redo->add_do_reference(instantiated_scene);
-		undo_redo->add_undo_method(parent, "remove_child", instantiated_scene);
+		undo_redo->add_undo_method(p_parent, "remove_child", instantiated_scene);
 
-		String new_name = parent->validate_child_name(instantiated_scene);
+		String new_name = p_parent->validate_child_name(instantiated_scene);
 		EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();
-		undo_redo->add_do_method(ed, "live_debug_instantiate_node", edited_scene->get_path_to(parent), p_files[i], new_name);
-		undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(parent)).path_join(new_name)));
+		undo_redo->add_do_method(ed, "live_debug_instantiate_node", edited_scene->get_path_to(p_parent), p_files[i], new_name);
+		undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(p_parent)).path_join(new_name)));
 	}
 
 	undo_redo->commit_action();
@@ -330,6 +331,75 @@ void SceneTreeDock::_perform_instantiate_scenes(const Vector<String> &p_files, N
 	}
 }
 
+void SceneTreeDock::_perform_create_audio_stream_players(const Vector<String> &p_files, Node *p_parent, int p_pos) {
+	ERR_FAIL_NULL(p_parent);
+
+	StringName node_type = "AudioStreamPlayer";
+	if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
+		if (Object::cast_to<Node2D>(p_parent)) {
+			node_type = "AudioStreamPlayer2D";
+		} else if (Object::cast_to<Node3D>(p_parent)) {
+			node_type = "AudioStreamPlayer3D";
+		}
+	}
+
+	Vector<Node *> nodes;
+	bool error = false;
+
+	for (const String &path : p_files) {
+		Ref<AudioStream> stream = ResourceLoader::load(path);
+		if (stream.is_null()) {
+			current_option = -1;
+			accept->set_text(vformat(TTR("Error loading audio stream from %s"), path));
+			accept->popup_centered();
+			error = true;
+			break;
+		}
+
+		Node *player = Object::cast_to<Node>(ClassDB::instantiate(node_type));
+		player->set("stream", stream);
+
+		// Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others.
+		const String &node_name = Node::adjust_name_casing(path.get_file().get_basename());
+		if (!node_name.is_empty()) {
+			player->set_name(node_name);
+		}
+
+		nodes.push_back(player);
+	}
+
+	if (error) {
+		for (Node *node : nodes) {
+			memdelete(node);
+		}
+		return;
+	}
+
+	EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+	undo_redo->create_action_for_history(TTRN("Create AudioStreamPlayer", "Create AudioStreamPlayers", nodes.size()), editor_data->get_current_edited_scene_history_id());
+	undo_redo->add_do_method(editor_selection, "clear");
+
+	for (int i = 0; i < nodes.size(); i++) {
+		Node *node = nodes[i];
+
+		undo_redo->add_do_method(p_parent, "add_child", node, true);
+		if (p_pos >= 0) {
+			undo_redo->add_do_method(p_parent, "move_child", node, p_pos + i);
+		}
+		undo_redo->add_do_method(node, "set_owner", edited_scene);
+		undo_redo->add_do_method(editor_selection, "add_node", node);
+		undo_redo->add_do_reference(node);
+		undo_redo->add_undo_method(p_parent, "remove_child", node);
+
+		String new_name = p_parent->validate_child_name(node);
+		EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();
+		undo_redo->add_do_method(ed, "live_debug_create_node", edited_scene->get_path_to(p_parent), node->get_class(), new_name);
+		undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(p_parent)).path_join(new_name)));
+	}
+
+	undo_redo->commit_action();
+}
+
 void SceneTreeDock::_replace_with_branch_scene(const String &p_file, Node *base) {
 	// `move_child` + `get_index` doesn't really work for internal nodes.
 	ERR_FAIL_COND_MSG(base->get_internal_mode() != INTERNAL_MODE_DISABLED, "Trying to replace internal node, this is not supported.");
@@ -3196,15 +3266,13 @@ void SceneTreeDock::_normalize_drop(Node *&to_node, int &to_pos, int p_type) {
 void SceneTreeDock::_files_dropped(const Vector<String> &p_files, NodePath p_to, int p_type) {
 	Node *node = get_node(p_to);
 	ERR_FAIL_NULL(node);
+	ERR_FAIL_COND(p_files.is_empty());
 
-	if (scene_tree->get_scene_tree()->get_drop_mode_flags() & Tree::DROP_MODE_INBETWEEN) {
-		// Dropped PackedScene, instance it.
-		int to_pos = -1;
-		_normalize_drop(node, to_pos, p_type);
-		_perform_instantiate_scenes(p_files, node, to_pos);
-	} else {
-		const String &res_path = p_files[0];
-		StringName res_type = EditorFileSystem::get_singleton()->get_file_type(res_path);
+	const String &res_path = p_files[0];
+	const StringName res_type = EditorFileSystem::get_singleton()->get_file_type(res_path);
+
+	// Dropping as property when possible.
+	if (p_type == 0 && p_files.size() == 1) {
 		List<String> valid_properties;
 
 		List<PropertyInfo> pinfo;
@@ -3238,10 +3306,22 @@ void SceneTreeDock::_files_dropped(const Vector<String> &p_files, NodePath p_to,
 			menu_properties->reset_size();
 			menu_properties->set_position(get_screen_position() + get_local_mouse_position());
 			menu_properties->popup();
-		} else if (!valid_properties.is_empty()) {
+			return;
+		}
+		if (!valid_properties.is_empty()) {
 			_perform_property_drop(node, valid_properties.front()->get(), ResourceLoader::load(res_path));
+			return;
 		}
 	}
+
+	// Either instantiate scenes or create AudioStreamPlayers.
+	int to_pos = -1;
+	_normalize_drop(node, to_pos, p_type);
+	if (res_type == "PackedScene") {
+		_perform_instantiate_scenes(p_files, node, to_pos);
+	} else {
+		_perform_create_audio_stream_players(p_files, node, to_pos);
+	}
 }
 
 void SceneTreeDock::_script_dropped(const String &p_file, NodePath p_to) {

+ 2 - 1
editor/scene_tree_dock.h

@@ -272,7 +272,8 @@ class SceneTreeDock : public VBoxContainer {
 	void _filter_option_selected(int option);
 	void _append_filter_options_to(PopupMenu *p_menu, bool p_include_separator = true);
 
-	void _perform_instantiate_scenes(const Vector<String> &p_files, Node *parent, int p_pos);
+	void _perform_instantiate_scenes(const Vector<String> &p_files, Node *p_parent, int p_pos);
+	void _perform_create_audio_stream_players(const Vector<String> &p_files, Node *p_parent, int p_pos);
 	void _replace_with_branch_scene(const String &p_file, Node *base);
 
 	void _remote_tree_selected();