Browse Source

Merge pull request #53957 from fabriceci/new-template-workflow

Rémi Verschelde 3 years ago
parent
commit
98b3ba1842
29 changed files with 940 additions and 524 deletions
  1. 21 2
      core/object/script_language.h
  2. 0 40
      editor/editor_settings.cpp
  3. 10 44
      editor/plugin_config_dialog.cpp
  4. 361 214
      editor/script_create_dialog.cpp
  5. 15 18
      editor/script_create_dialog.h
  6. 95 0
      editor/template_builders.py
  7. 2 0
      modules/gdscript/SCsub
  8. 29 0
      modules/gdscript/editor_templates/CharacterBody2D/basic_movement.gd
  9. 32 0
      modules/gdscript/editor_templates/CharacterBody3D/basic_movement.gd
  10. 11 0
      modules/gdscript/editor_templates/EditorPlugin/plugin.gd
  11. 7 0
      modules/gdscript/editor_templates/EditorScript/basic_editor_script.gd
  12. 11 0
      modules/gdscript/editor_templates/Node/default.gd
  13. 3 0
      modules/gdscript/editor_templates/Object/empty.gd
  14. 16 0
      modules/gdscript/editor_templates/SCsub
  15. 12 2
      modules/gdscript/gdscript.cpp
  16. 50 51
      modules/gdscript/gdscript.h
  17. 34 57
      modules/gdscript/gdscript_editor.cpp
  18. 2 0
      modules/mono/SCsub
  19. 21 44
      modules/mono/csharp_script.cpp
  20. 2 2
      modules/mono/csharp_script.h
  21. 41 0
      modules/mono/editor_templates/CharacterBody2D/basic_movement.cs
  22. 44 0
      modules/mono/editor_templates/CharacterBody3D/basic_movement.cs
  23. 19 0
      modules/mono/editor_templates/EditorPlugin/plugin.cs
  24. 14 0
      modules/mono/editor_templates/EditorScript/basic_editor_script.cs
  25. 19 0
      modules/mono/editor_templates/Node/default.cs
  26. 9 0
      modules/mono/editor_templates/Object/empty.cs
  27. 16 0
      modules/mono/editor_templates/SCsub
  28. 5 10
      modules/visual_script/visual_script.cpp
  29. 39 40
      modules/visual_script/visual_script.h

+ 21 - 2
core/object/script_language.h

@@ -274,13 +274,32 @@ public:
 		String message;
 	};
 
+	enum TemplateLocation {
+		TEMPLATE_BUILT_IN,
+		TEMPLATE_EDITOR,
+		TEMPLATE_PROJECT
+	};
+
+	struct ScriptTemplate {
+		String inherit = "Object";
+		String name;
+		String description;
+		String content;
+		int id = 0;
+		TemplateLocation origin = TemplateLocation::TEMPLATE_BUILT_IN;
+
+		String get_hash() const {
+			return itos(origin) + inherit + name;
+		}
+	};
+
 	void get_core_type_words(List<String> *p_core_type_words) const;
 	virtual void get_reserved_words(List<String> *p_words) const = 0;
 	virtual bool is_control_flow_keyword(String p_string) const = 0;
 	virtual void get_comment_delimiters(List<String> *p_delimiters) const = 0;
 	virtual void get_string_delimiters(List<String> *p_delimiters) const = 0;
-	virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const = 0;
-	virtual void make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script) {}
+	virtual Ref<Script> make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const { return Ref<Script>(); }
+	virtual Vector<ScriptTemplate> get_built_in_templates(StringName p_object) { return Vector<ScriptTemplate>(); }
 	virtual bool is_using_templates() { return false; }
 	virtual bool validate(const String &p_script, const String &p_path = "", List<String> *r_functions = nullptr, List<ScriptError> *r_errors = nullptr, List<Warning> *r_warnings = nullptr, Set<int> *r_safe_lines = nullptr) const = 0;
 	virtual String validate_path(const String &p_path) const { return ""; }

+ 0 - 40
editor/editor_settings.cpp

@@ -781,43 +781,6 @@ bool EditorSettings::_is_default_text_editor_theme(String p_theme_name) {
 	return p_theme_name == "default" || p_theme_name == "godot 2" || p_theme_name == "custom";
 }
 
-static Dictionary _get_builtin_script_templates() {
-	Dictionary templates;
-
-	// No Comments
-	templates["no_comments.gd"] =
-			"extends %BASE%\n"
-			"\n"
-			"\n"
-			"func _ready()%VOID_RETURN%:\n"
-			"%TS%pass\n";
-
-	// Empty
-	templates["empty.gd"] =
-			"extends %BASE%"
-			"\n"
-			"\n";
-
-	return templates;
-}
-
-static void _create_script_templates(const String &p_path) {
-	Dictionary templates = _get_builtin_script_templates();
-	List<Variant> keys;
-	templates.get_key_list(&keys);
-	FileAccessRef file = FileAccess::create(FileAccess::ACCESS_FILESYSTEM);
-	DirAccessRef dir = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
-	dir->change_dir(p_path);
-	for (int i = 0; i < keys.size(); i++) {
-		if (!dir->file_exists(keys[i])) {
-			Error err = file->reopen(p_path.plus_file((String)keys[i]), FileAccess::WRITE);
-			ERR_FAIL_COND(err != OK);
-			file->store_string(templates[keys[i]]);
-			file->close();
-		}
-	}
-}
-
 // PUBLIC METHODS
 
 EditorSettings *EditorSettings::get_singleton() {
@@ -852,10 +815,7 @@ void EditorSettings::create() {
 	}
 
 	if (EditorPaths::get_singleton()->are_paths_valid()) {
-		_create_script_templates(EditorPaths::get_singleton()->get_config_dir().plus_file("script_templates"));
-
 		// Validate editor config file.
-
 		DirAccessRef dir = DirAccess::open(EditorPaths::get_singleton()->get_config_dir());
 		String config_file_name = "editor_settings-" + itos(VERSION_MAJOR) + ".tres";
 		config_file_path = EditorPaths::get_singleton()->get_config_dir().plus_file(config_file_name);

+ 10 - 44
editor/plugin_config_dialog.cpp

@@ -36,12 +36,6 @@
 #include "editor/editor_plugin.h"
 #include "editor/editor_scale.h"
 #include "editor/project_settings_editor.h"
-#include "scene/gui/grid_container.h"
-
-#include "modules/modules_enabled.gen.h" // For gdscript.
-#ifdef MODULE_GDSCRIPT_ENABLED
-#include "modules/gdscript/gdscript.h"
-#endif
 
 void PluginConfigDialog::_clear_fields() {
 	name_edit->set_text("");
@@ -76,42 +70,16 @@ void PluginConfigDialog::_on_confirmed() {
 		String lang_name = ScriptServer::get_language(lang_idx)->get_name();
 
 		Ref<Script> script;
-
-		// TODO Use script templates. Right now, this code won't add the 'tool' annotation to other languages.
-		// TODO Better support script languages with named classes (has_named_classes).
-
-		// FIXME: It's hacky to have hardcoded access to the GDScript module here.
-		// The editor code should not have to know what languages are enabled.
-#ifdef MODULE_GDSCRIPT_ENABLED
-		if (lang_name == GDScriptLanguage::get_singleton()->get_name()) {
-			// Hard-coded GDScript template to keep usability until we use script templates.
-			Ref<Script> gdscript = memnew(GDScript);
-			gdscript->set_source_code(
-					"@tool\n"
-					"extends EditorPlugin\n"
-					"\n"
-					"\n"
-					"func _enter_tree()%VOID_RETURN%:\n"
-					"%TS%pass\n"
-					"\n"
-					"\n"
-					"func _exit_tree()%VOID_RETURN%:\n"
-					"%TS%pass\n");
-			GDScriptLanguage::get_singleton()->make_template("", "", gdscript);
-			String script_path = path.plus_file(script_edit->get_text());
-			gdscript->set_path(script_path);
-			ResourceSaver::save(script_path, gdscript);
-			script = gdscript;
-		} else {
-#endif
-			String script_path = path.plus_file(script_edit->get_text());
-			String class_name = script_path.get_file().get_basename();
-			script = ScriptServer::get_language(lang_idx)->get_template(class_name, "EditorPlugin");
-			script->set_path(script_path);
-			ResourceSaver::save(script_path, script);
-#ifdef MODULE_GDSCRIPT_ENABLED
+		String script_path = path.plus_file(script_edit->get_text());
+		String class_name = script_path.get_file().get_basename();
+		String template_content = "";
+		Vector<ScriptLanguage::ScriptTemplate> templates = ScriptServer::get_language(lang_idx)->get_built_in_templates("EditorPlugin");
+		if (templates.size() > 0) {
+			template_content = templates.get(0).content;
 		}
-#endif
+		script = ScriptServer::get_language(lang_idx)->make_template(template_content, class_name, "EditorPlugin");
+		script->set_path(script_path);
+		ResourceSaver::save(script_path, script);
 
 		emit_signal(SNAME("plugin_ready"), script.operator->(), active_edit->is_pressed() ? _to_absolute_plugin_path(subfolder_edit->get_text()) : "");
 	} else {
@@ -331,11 +299,9 @@ PluginConfigDialog::PluginConfigDialog() {
 	for (int i = 0; i < ScriptServer::get_language_count(); i++) {
 		ScriptLanguage *lang = ScriptServer::get_language(i);
 		script_option_edit->add_item(lang->get_name());
-#ifdef MODULE_GDSCRIPT_ENABLED
-		if (lang == GDScriptLanguage::get_singleton()) {
+		if (lang->get_name() == "GDScript") {
 			default_lang = i;
 		}
-#endif
 	}
 	script_option_edit->select(default_lang);
 	grid->add_child(script_option_edit);

+ 361 - 214
editor/script_create_dialog.cpp

@@ -33,7 +33,6 @@
 #include "core/config/project_settings.h"
 #include "core/io/file_access.h"
 #include "core/io/resource_saver.h"
-#include "core/object/script_language.h"
 #include "core/string/string_builder.h"
 #include "editor/create_dialog.h"
 #include "editor/editor_node.h"
@@ -45,17 +44,16 @@ void ScriptCreateDialog::_notification(int p_what) {
 		case NOTIFICATION_ENTER_TREE:
 		case NOTIFICATION_THEME_CHANGED: {
 			for (int i = 0; i < ScriptServer::get_language_count(); i++) {
-				String lang = ScriptServer::get_language(i)->get_type();
-				Ref<Texture2D> lang_icon = get_theme_icon(lang, SNAME("EditorIcons"));
-				if (lang_icon.is_valid()) {
-					language_menu->set_item_icon(i, lang_icon);
+				Ref<Texture2D> language_icon = get_theme_icon(ScriptServer::get_language(i)->get_type(), SNAME("EditorIcons"));
+				if (language_icon.is_valid()) {
+					language_menu->set_item_icon(i, language_icon);
 				}
 			}
 
-			String last_lang = EditorSettings::get_singleton()->get_project_metadata("script_setup", "last_selected_language", "");
-			if (!last_lang.is_empty()) {
+			String last_language = EditorSettings::get_singleton()->get_project_metadata("script_setup", "last_selected_language", "");
+			if (!last_language.is_empty()) {
 				for (int i = 0; i < language_menu->get_item_count(); i++) {
-					if (language_menu->get_item_text(i) == last_lang) {
+					if (language_menu->get_item_text(i) == last_language) {
 						language_menu->select(i);
 						current_language = i;
 						break;
@@ -64,6 +62,10 @@ void ScriptCreateDialog::_notification(int p_what) {
 			} else {
 				language_menu->select(default_language);
 			}
+			if (EditorSettings::get_singleton()->has_meta("script_setup/use_script_templates")) {
+				is_using_templates = bool(EditorSettings::get_singleton()->get_meta("script_setup/use_script_templates"));
+				use_templates->set_pressed(is_using_templates);
+			}
 
 			path_button->set_icon(get_theme_icon(SNAME("Folder"), SNAME("EditorIcons")));
 			parent_browse_button->set_icon(get_theme_icon(SNAME("Folder"), SNAME("EditorIcons")));
@@ -114,7 +116,7 @@ void ScriptCreateDialog::config(const String &p_base_name, const String &p_base_
 	built_in_enabled = p_built_in_enabled;
 	load_enabled = p_load_enabled;
 
-	_lang_changed(current_language);
+	_language_changed(current_language);
 	_class_name_changed("");
 	_path_changed(file_path->get_text());
 }
@@ -145,8 +147,9 @@ bool ScriptCreateDialog::_validate_class(const String &p_string) {
 
 	for (int i = 0; i < p_string.length(); i++) {
 		if (i == 0) {
+			// Cannot start with a number.
 			if (p_string[0] >= '0' && p_string[0] <= '9') {
-				return false; // no start with number plz
+				return false;
 			}
 		}
 
@@ -170,6 +173,10 @@ String ScriptCreateDialog::_validate_path(const String &p_path, bool p_file_must
 		return TTR("Filename is empty.");
 	}
 
+	if (!p.get_file().get_basename().is_valid_filename()) {
+		return TTR("Filename is invalid.");
+	}
+
 	p = ProjectSettings::get_singleton()->localize_path(p);
 	if (!p.begins_with("res://")) {
 		return TTR("Path is not local.");
@@ -178,11 +185,11 @@ String ScriptCreateDialog::_validate_path(const String &p_path, bool p_file_must
 	DirAccess *d = DirAccess::create(DirAccess::ACCESS_RESOURCES);
 	if (d->change_dir(p.get_base_dir()) != OK) {
 		memdelete(d);
-		return TTR("Invalid base path.");
+		return TTR("Base path is invalid.");
 	}
 	memdelete(d);
 
-	/* Does file already exist */
+	// Check if file exists.
 	DirAccess *f = DirAccess::create(DirAccess::ACCESS_RESOURCES);
 	if (f->dir_exists(p)) {
 		memdelete(f);
@@ -193,11 +200,11 @@ String ScriptCreateDialog::_validate_path(const String &p_path, bool p_file_must
 	}
 	memdelete(f);
 
-	/* Check file extension */
+	// Check file extension.
 	String extension = p.get_extension();
 	List<String> extensions;
 
-	// get all possible extensions for script
+	// Get all possible extensions for script.
 	for (int l = 0; l < language_menu->get_item_count(); l++) {
 		ScriptServer::get_language(l)->get_recognized_extensions(&extensions);
 	}
@@ -207,8 +214,6 @@ String ScriptCreateDialog::_validate_path(const String &p_path, bool p_file_must
 	int index = 0;
 	for (const String &E : extensions) {
 		if (E.nocasecmp_to(extension) == 0) {
-			//FIXME (?) - changing language this way doesn't update controls, needs rework
-			//language_menu->select(index); // change Language option by extension
 			found = true;
 			if (E == ScriptServer::get_language(language_menu->get_selected())->get_extension()) {
 				match = true;
@@ -222,16 +227,16 @@ String ScriptCreateDialog::_validate_path(const String &p_path, bool p_file_must
 		return TTR("Invalid extension.");
 	}
 	if (!match) {
-		return TTR("Wrong extension chosen.");
+		return TTR("Extension doesn't match chosen language.");
 	}
 
-	/* Let ScriptLanguage do custom validation */
+	// Let ScriptLanguage do custom validation.
 	String path_error = ScriptServer::get_language(language_menu->get_selected())->validate_path(p);
 	if (!path_error.is_empty()) {
 		return path_error;
 	}
 
-	/* All checks passed */
+	// All checks passed.
 	return "";
 }
 
@@ -244,40 +249,49 @@ String ScriptCreateDialog::_get_class_name() const {
 }
 
 void ScriptCreateDialog::_class_name_changed(const String &p_name) {
-	if (_validate_class(class_name->get_text())) {
-		is_class_name_valid = true;
-	} else {
-		is_class_name_valid = false;
-	}
+	is_class_name_valid = _validate_class(class_name->get_text());
 	_update_dialog();
 }
 
 void ScriptCreateDialog::_parent_name_changed(const String &p_parent) {
-	if (_validate_parent(parent_name->get_text())) {
-		is_parent_name_valid = true;
-	} else {
-		is_parent_name_valid = false;
-	}
+	is_parent_name_valid = _validate_parent(parent_name->get_text());
 	_update_dialog();
 }
 
 void ScriptCreateDialog::_template_changed(int p_template) {
-	String selected_template = p_template == 0 ? "" : template_menu->get_item_text(p_template);
-	EditorSettings::get_singleton()->set_project_metadata("script_setup", "last_selected_template", selected_template);
-	if (p_template == 0) {
-		//default
-		script_template = "";
-		return;
-	}
-	int selected_id = template_menu->get_selected_id();
-
-	for (int i = 0; i < template_list.size(); i++) {
-		const ScriptTemplateInfo &sinfo = template_list[i];
-		if (sinfo.id == selected_id) {
-			script_template = sinfo.dir.plus_file(sinfo.name + "." + sinfo.extension);
-			break;
+	const ScriptLanguage::ScriptTemplate &sinfo = _get_current_template();
+	// Update last used dictionaries
+	if (is_using_templates && !parent_name->get_text().begins_with("\"res:")) {
+		if (sinfo.origin == ScriptLanguage::TemplateLocation::TEMPLATE_PROJECT) {
+			// Save the last used template for this node into the project dictionary.
+			Dictionary dic_templates_project = EditorSettings::get_singleton()->get_project_metadata("script_setup", "templates_dictionary", Dictionary());
+			dic_templates_project[parent_name->get_text()] = sinfo.get_hash();
+			EditorSettings::get_singleton()->set_project_metadata("script_setup", "templates_dictionary", dic_templates_project);
+		} else {
+			// Save template into to editor dictionary (not a project template).
+			Dictionary dic_templates;
+			if (EditorSettings::get_singleton()->has_meta("script_setup/templates_dictionary")) {
+				dic_templates = (Dictionary)EditorSettings::get_singleton()->get_meta("script_setup/templates_dictionary");
+			}
+			dic_templates[parent_name->get_text()] = sinfo.get_hash();
+			EditorSettings::get_singleton()->set_meta("script_setup/templates_dictionary", dic_templates);
+			// Remove template from project dictionary as we last used an editor level template.
+			Dictionary dic_templates_project = EditorSettings::get_singleton()->get_project_metadata("script_setup", "templates_dictionary", Dictionary());
+			if (dic_templates_project.has(parent_name->get_text())) {
+				dic_templates_project.erase(parent_name->get_text());
+				EditorSettings::get_singleton()->set_project_metadata("script_setup", "templates_dictionary", dic_templates_project);
+			}
 		}
 	}
+	// Update template label information.
+	String template_info = String::utf8("•  ");
+	template_info += TTR("Template:");
+	template_info += " " + sinfo.name;
+	if (!sinfo.description.is_empty()) {
+		template_info += " - " + sinfo.description;
+	}
+	template_info_label->set_text(template_info);
+	template_info_label->add_theme_color_override("font_color", get_theme_color(SNAME("success_color"), SNAME("Editor")));
 }
 
 void ScriptCreateDialog::ok_pressed() {
@@ -287,6 +301,7 @@ void ScriptCreateDialog::ok_pressed() {
 		_load_exist();
 	}
 
+	EditorSettings::get_singleton()->save();
 	is_new_script_created = true;
 	_update_dialog();
 }
@@ -295,18 +310,10 @@ void ScriptCreateDialog::_create_new() {
 	String cname_param = _get_class_name();
 
 	Ref<Script> scr;
-	if (!script_template.is_empty()) {
-		scr = ResourceLoader::load(script_template);
-		if (scr.is_null()) {
-			alert->set_text(vformat(TTR("Error loading template '%s'"), script_template));
-			alert->popup_centered();
-			return;
-		}
-		scr = scr->duplicate();
-		ScriptServer::get_language(language_menu->get_selected())->make_template(cname_param, parent_name->get_text(), scr);
-	} else {
-		scr = ScriptServer::get_language(language_menu->get_selected())->get_template(cname_param, parent_name->get_text());
-	}
+
+	const ScriptLanguage::ScriptTemplate sinfo = _get_current_template();
+
+	scr = ScriptServer::get_language(language_menu->get_selected())->make_template(sinfo.content, cname_param, parent_name->get_text());
 
 	if (has_named_classes) {
 		String cname = class_name->get_text();
@@ -345,8 +352,20 @@ void ScriptCreateDialog::_load_exist() {
 	hide();
 }
 
-void ScriptCreateDialog::_lang_changed(int l) {
-	ScriptLanguage *language = ScriptServer::get_language(l);
+Vector<String> ScriptCreateDialog::get_hierarchy(String p_object) const {
+	Vector<String> hierachy;
+	hierachy.append(p_object);
+
+	String parent_class = ClassDB::get_parent_class(p_object);
+	while (parent_class.is_valid_identifier()) {
+		hierachy.append(parent_class);
+		parent_class = ClassDB::get_parent_class(parent_class);
+	}
+	return hierachy;
+}
+
+void ScriptCreateDialog::_language_changed(int l) {
+	language = ScriptServer::get_language(l);
 
 	has_named_classes = language->has_named_classes();
 	can_inherit_from_file = language->can_inherit_from_file();
@@ -364,13 +383,13 @@ void ScriptCreateDialog::_lang_changed(int l) {
 		}
 
 		if (extension.length() == 0) {
-			// add extension if none
+			// Add extension if none.
 			path += selected_ext;
 			_path_changed(path);
 		} else {
-			// change extension by selected language
+			// Change extension by selected language.
 			List<String> extensions;
-			// get all possible extensions for script
+			// Get all possible extensions for script.
 			for (int m = 0; m < language_menu->get_item_count(); m++) {
 				ScriptServer::get_language(m)->get_recognized_extensions(&extensions);
 			}
@@ -389,123 +408,12 @@ void ScriptCreateDialog::_lang_changed(int l) {
 	}
 	file_path->set_text(path);
 
-	bool use_templates = language->is_using_templates();
-	template_menu->set_disabled(!use_templates);
-	template_menu->clear();
-
-	if (use_templates) {
-		_update_script_templates(language->get_extension());
-
-		String last_lang = EditorSettings::get_singleton()->get_project_metadata("script_setup", "last_selected_language", "");
-		String last_template = EditorSettings::get_singleton()->get_project_metadata("script_setup", "last_selected_template", "");
-
-		template_menu->add_item(TTR("Default"));
-
-		ScriptTemplateInfo *templates = template_list.ptrw();
-
-		Vector<String> origin_names;
-		origin_names.push_back(TTR("Project"));
-		origin_names.push_back(TTR("Editor"));
-		int cur_origin = -1;
-
-		// Populate script template items previously sorted and now grouped by origin
-		for (int i = 0; i < template_list.size(); i++) {
-			if (int(templates[i].origin) != cur_origin) {
-				template_menu->add_separator();
-
-				String origin_name = origin_names[templates[i].origin];
-
-				int last_index = template_menu->get_item_count() - 1;
-				template_menu->set_item_text(last_index, origin_name);
-
-				cur_origin = templates[i].origin;
-			}
-			String item_name = templates[i].name.capitalize();
-			template_menu->add_item(item_name);
-
-			int new_id = template_menu->get_item_count() - 1;
-			templates[i].id = new_id;
-		}
-		// Disable overridden
-		for (const KeyValue<String, Vector<int>> &E : template_overrides) {
-			const Vector<int> &overrides = E.value;
-
-			if (overrides.size() == 1) {
-				continue; // doesn't override anything
-			}
-			const ScriptTemplateInfo &extended = template_list[overrides[0]];
-
-			StringBuilder override_info;
-			override_info += TTR("Overrides");
-			override_info += ": ";
-
-			for (int i = 1; i < overrides.size(); i++) {
-				const ScriptTemplateInfo &overridden = template_list[overrides[i]];
-
-				int disable_index = template_menu->get_item_index(overridden.id);
-				template_menu->set_item_disabled(disable_index, true);
-
-				override_info += origin_names[overridden.origin];
-				if (i < overrides.size() - 1) {
-					override_info += ", ";
-				}
-			}
-			template_menu->set_item_icon(extended.id, get_theme_icon(SNAME("Override"), SNAME("EditorIcons")));
-			template_menu->get_popup()->set_item_tooltip(extended.id, override_info.as_string());
-		}
-		// Reselect last selected template
-		for (int i = 0; i < template_menu->get_item_count(); i++) {
-			const String &ti = template_menu->get_item_text(i);
-			if (language_menu->get_item_text(language_menu->get_selected()) == last_lang && last_template == ti) {
-				template_menu->select(i);
-				break;
-			}
-		}
-	} else {
-		template_menu->add_item(TTR("N/A"));
-		script_template = "";
-	}
-
-	_template_changed(template_menu->get_selected());
 	EditorSettings::get_singleton()->set_project_metadata("script_setup", "last_selected_language", language_menu->get_item_text(language_menu->get_selected()));
 
 	_parent_name_changed(parent_name->get_text());
 	_update_dialog();
 }
 
-void ScriptCreateDialog::_update_script_templates(const String &p_extension) {
-	template_list.clear();
-	template_overrides.clear();
-
-	Vector<String> dirs;
-
-	// Ordered from local to global for correct override mechanism
-	dirs.push_back(EditorSettings::get_singleton()->get_project_script_templates_dir());
-	dirs.push_back(EditorSettings::get_singleton()->get_script_templates_dir());
-
-	for (int i = 0; i < dirs.size(); i++) {
-		Vector<String> list = EditorSettings::get_singleton()->get_script_templates(p_extension, dirs[i]);
-
-		for (int j = 0; j < list.size(); j++) {
-			ScriptTemplateInfo sinfo;
-			sinfo.origin = ScriptOrigin(i);
-			sinfo.dir = dirs[i];
-			sinfo.name = list[j];
-			sinfo.extension = p_extension;
-			template_list.push_back(sinfo);
-
-			if (!template_overrides.has(sinfo.name)) {
-				Vector<int> overrides;
-				overrides.push_back(template_list.size() - 1); // first one
-				template_overrides.insert(sinfo.name, overrides);
-			} else {
-				Vector<int> &overrides = template_overrides[sinfo.name];
-				overrides.push_back(template_list.size() - 1);
-			}
-		}
-	}
-}
-
 void ScriptCreateDialog::_built_in_pressed() {
 	if (internal->is_pressed()) {
 		is_built_in = true;
@@ -517,6 +425,12 @@ void ScriptCreateDialog::_built_in_pressed() {
 	_update_dialog();
 }
 
+void ScriptCreateDialog::_use_template_pressed() {
+	is_using_templates = use_templates->is_pressed();
+	EditorSettings::get_singleton()->set_meta("script_setup/use_script_templates", is_using_templates);
+	_update_dialog();
+}
+
 void ScriptCreateDialog::_browse_path(bool browse_parent, bool p_save) {
 	is_browsing_parent = browse_parent;
 
@@ -545,16 +459,16 @@ void ScriptCreateDialog::_browse_path(bool browse_parent, bool p_save) {
 }
 
 void ScriptCreateDialog::_file_selected(const String &p_file) {
-	String p = ProjectSettings::get_singleton()->localize_path(p_file);
+	String path = ProjectSettings::get_singleton()->localize_path(p_file);
 	if (is_browsing_parent) {
-		parent_name->set_text("\"" + p + "\"");
+		parent_name->set_text("\"" + path + "\"");
 		_parent_name_changed(parent_name->get_text());
 	} else {
-		file_path->set_text(p);
-		_path_changed(p);
+		file_path->set_text(path);
+		_path_changed(path);
 
-		String filename = p.get_file().get_basename();
-		int select_start = p.rfind(filename);
+		String filename = path.get_file().get_basename();
+		int select_start = path.rfind(filename);
 		file_path->select(select_start, select_start + filename.length());
 		file_path->set_caret_column(select_start + filename.length());
 		file_path->grab_focus();
@@ -588,7 +502,7 @@ void ScriptCreateDialog::_path_changed(const String &p_path) {
 		return;
 	}
 
-	/* Does file already exist */
+	// Check if file exists.
 	DirAccess *f = DirAccess::create(DirAccess::ACCESS_RESOURCES);
 	String p = ProjectSettings::get_singleton()->localize_path(p_path.strip_edges());
 	if (f->file_exists(p)) {
@@ -623,9 +537,98 @@ void ScriptCreateDialog::_msg_path_valid(bool valid, const String &p_msg) {
 	}
 }
 
-void ScriptCreateDialog::_update_dialog() {
-	/* "Add Script Dialog" GUI logic and script checks. */
+void ScriptCreateDialog::_update_template_menu() {
+	bool is_language_using_templates = language->is_using_templates();
+	template_menu->set_disabled(false);
+	template_menu->clear();
+	template_list.clear();
 
+	if (is_language_using_templates) {
+		// Get the lastest templates used for each type of node from project settings then global settings.
+		Dictionary last_local_templates = EditorSettings::get_singleton()->get_project_metadata("script_setup", "templates_dictionary", Dictionary());
+		Dictionary last_global_templates;
+		if (EditorSettings::get_singleton()->has_meta("script_setup/templates_dictionary")) {
+			last_global_templates = (Dictionary)EditorSettings::get_singleton()->get_meta("script_setup/templates_dictionary");
+		}
+		String inherits_base_type = parent_name->get_text();
+
+		// If it inherits from a script, select Object instead.
+		if (inherits_base_type[0] == '"') {
+			inherits_base_type = "Object";
+		}
+
+		// Get all ancestor node for selected base node.
+		// There templates will also fit the base node.
+		Vector<String> hierarchy = get_hierarchy(inherits_base_type);
+		int last_used_template = -1;
+		int preselected_template = -1;
+		int previous_ancestor_level = -1;
+
+		// Templates can be stored in tree different locations.
+		Vector<ScriptLanguage::TemplateLocation> template_locations;
+		template_locations.append(ScriptLanguage::TEMPLATE_PROJECT);
+		template_locations.append(ScriptLanguage::TEMPLATE_EDITOR);
+		template_locations.append(ScriptLanguage::TEMPLATE_BUILT_IN);
+
+		for (const ScriptLanguage::TemplateLocation &template_location : template_locations) {
+			String display_name = _get_script_origin_label(template_location);
+			bool separator = false;
+			int ancestor_level = 0;
+			for (const String &current_node : hierarchy) {
+				Vector<ScriptLanguage::ScriptTemplate> templates_found;
+				if (template_location == ScriptLanguage::TEMPLATE_BUILT_IN) {
+					templates_found = language->get_built_in_templates(current_node);
+				} else {
+					String template_directory;
+					if (template_location == ScriptLanguage::TEMPLATE_PROJECT) {
+						template_directory = EditorSettings::get_singleton()->get_project_script_templates_dir();
+					} else {
+						template_directory = EditorSettings::get_singleton()->get_script_templates_dir();
+					}
+					templates_found = _get_user_templates(language, current_node, template_directory, template_location);
+				}
+				if (!templates_found.is_empty()) {
+					if (!separator) {
+						template_menu->add_separator();
+						template_menu->set_item_text(template_menu->get_item_count() - 1, display_name);
+						separator = true;
+					}
+					for (ScriptLanguage::ScriptTemplate &t : templates_found) {
+						template_menu->add_item(t.inherit + ": " + t.name);
+						int id = template_menu->get_item_count() - 1;
+						// Check if this template should be preselected if node isn't in the last used dictionary.
+						if (ancestor_level < previous_ancestor_level || previous_ancestor_level == -1) {
+							previous_ancestor_level = ancestor_level;
+							preselected_template = id;
+						}
+						// Check for last used template for this node in project settings then in global settings.
+						if (last_local_templates.has(parent_name->get_text()) && t.get_hash() == String(last_local_templates[parent_name->get_text()])) {
+							last_used_template = id;
+						} else if (last_used_template == -1 && last_global_templates.has(parent_name->get_text()) && t.get_hash() == String(last_global_templates[parent_name->get_text()])) {
+							last_used_template = id;
+						}
+						t.id = id;
+						template_list.push_back(t);
+						String icon = has_theme_icon(t.inherit, SNAME("EditorIcons")) ? t.inherit : "Object";
+						template_menu->set_item_icon(id, get_theme_icon(icon, SNAME("EditorIcons")));
+					}
+				}
+				ancestor_level++;
+			}
+		}
+
+		if (last_used_template != -1) {
+			template_menu->select(last_used_template);
+		} else if (preselected_template != -1) {
+			template_menu->select(preselected_template);
+		}
+	}
+	_template_changed(template_menu->get_selected());
+}
+
+void ScriptCreateDialog::_update_dialog() {
+	// "Add Script Dialog" GUI logic and script checks.
+	_update_template_menu();
 	bool script_ok = true;
 
 	// Is script path/name valid (order from top to bottom)?
@@ -697,41 +700,51 @@ void ScriptCreateDialog::_update_dialog() {
 	// This warning isn't relevant if the script is built-in.
 	script_name_warning_label->set_visible(!is_built_in && _get_class_name() == parent_name->get_text());
 
-	if (is_built_in) {
-		get_ok_button()->set_text(TTR("Create"));
-		parent_name->set_editable(true);
-		parent_search_button->set_disabled(false);
-		parent_browse_button->set_disabled(!can_inherit_from_file);
-		_msg_path_valid(true, TTR("Built-in script (into scene file)."));
-	} else if (is_new_script_created) {
-		// New script created.
-
-		get_ok_button()->set_text(TTR("Create"));
-		parent_name->set_editable(true);
-		parent_search_button->set_disabled(false);
-		parent_browse_button->set_disabled(!can_inherit_from_file);
-		if (is_path_valid) {
+	bool is_new_file = is_built_in || is_new_script_created;
+
+	parent_name->set_editable(is_new_file);
+	parent_search_button->set_disabled(!is_new_file);
+	parent_browse_button->set_disabled(!is_new_file || !can_inherit_from_file);
+	template_inactive_message = "";
+	String button_text = is_new_file ? TTR("Create") : TTR("Load");
+	get_ok_button()->set_text(button_text);
+
+	if (is_new_file) {
+		if (is_built_in) {
+			_msg_path_valid(true, TTR("Built-in script (into scene file)."));
+		}
+		if (is_new_script_created && is_path_valid) {
 			_msg_path_valid(true, TTR("Will create a new script file."));
 		}
-	} else if (load_enabled) {
-		// Script loaded.
-
-		get_ok_button()->set_text(TTR("Load"));
-		parent_name->set_editable(false);
-		parent_search_button->set_disabled(true);
-		parent_browse_button->set_disabled(true);
-		if (is_path_valid) {
-			_msg_path_valid(true, TTR("Will load an existing script file."));
+	} else {
+		if (load_enabled) {
+			template_inactive_message = TTR("Using existing script file.");
+			if (is_path_valid) {
+				_msg_path_valid(true, TTR("Will load an existing script file."));
+			}
+		} else {
+			template_inactive_message = TTR("Using existing script file.");
+			_msg_path_valid(false, TTR("Script file already exists."));
+			script_ok = false;
+		}
+	}
+
+	// Show templates list if needed.
+	if (is_using_templates) {
+		// Check if at least one suitable template has been found.
+		if (template_menu->get_item_count() == 0 && template_inactive_message.is_empty()) {
+			template_inactive_message = TTR("No suitable template.");
 		}
 	} else {
-		get_ok_button()->set_text(TTR("Create"));
-		parent_name->set_editable(true);
-		parent_search_button->set_disabled(false);
-		parent_browse_button->set_disabled(!can_inherit_from_file);
-		_msg_path_valid(false, TTR("Script file already exists."));
+		template_inactive_message = TTR("Empty");
+	}
 
-		script_ok = false;
+	if (!template_inactive_message.is_empty()) {
+		template_menu->set_disabled(true);
+		template_menu->clear();
+		template_menu->add_item(template_inactive_message);
 	}
+	template_info_label->set_visible(!template_menu->is_disabled());
 
 	get_ok_button()->set_disabled(!script_ok);
 
@@ -745,6 +758,122 @@ void ScriptCreateDialog::_update_dialog() {
 	}
 }
 
+ScriptLanguage::ScriptTemplate ScriptCreateDialog::_get_current_template() const {
+	int selected_id = template_menu->get_selected_id();
+	for (const ScriptLanguage::ScriptTemplate &t : template_list) {
+		if (is_using_templates) {
+			if (t.id == selected_id) {
+				return t;
+			}
+		} else {
+			// Using empty built-in template if templates are disabled.
+			if (t.origin == ScriptLanguage::TemplateLocation::TEMPLATE_BUILT_IN && t.name == "Empty") {
+				return t;
+			}
+		}
+	}
+	return ScriptLanguage::ScriptTemplate();
+}
+
+Vector<ScriptLanguage::ScriptTemplate> ScriptCreateDialog::_get_user_templates(const ScriptLanguage *language, const StringName &p_object, const String &p_dir, const ScriptLanguage::TemplateLocation &p_origin) const {
+	Vector<ScriptLanguage::ScriptTemplate> user_templates;
+	String extension = language->get_extension();
+
+	String dir_path = p_dir.plus_file(p_object);
+
+	DirAccess *d = DirAccess::open(dir_path);
+	if (d) {
+		d->list_dir_begin();
+		String file = d->get_next();
+		while (file != String()) {
+			if (file.get_extension() == extension) {
+				user_templates.append(_parse_template(language, dir_path, file, p_origin, p_object));
+			}
+			file = d->get_next();
+		}
+		d->list_dir_end();
+		memdelete(d);
+	}
+	return user_templates;
+}
+
+ScriptLanguage::ScriptTemplate ScriptCreateDialog::_parse_template(const ScriptLanguage *language, const String &p_path, const String &p_filename, const ScriptLanguage::TemplateLocation &p_origin, const String &p_inherits) const {
+	ScriptLanguage::ScriptTemplate script_template = ScriptLanguage::ScriptTemplate();
+	script_template.origin = p_origin;
+	script_template.inherit = p_inherits;
+	String space_indent = "    ";
+	// Get meta delimiter
+	String meta_delimiter = String();
+	List<String> comment_delimiters;
+	language->get_comment_delimiters(&comment_delimiters);
+	for (const String &script_delimiter : comment_delimiters) {
+		if (script_delimiter.find(" ") == -1) {
+			meta_delimiter = script_delimiter;
+			break;
+		}
+	}
+	String meta_prefix = meta_delimiter + " meta-";
+
+	// Parse file for meta-information and script content
+	Error err;
+	FileAccess *file = FileAccess::open(p_path.plus_file(p_filename), FileAccess::READ, &err);
+	if (!err) {
+		while (!file->eof_reached()) {
+			String line = file->get_line();
+			if (line.begins_with(meta_prefix)) {
+				// Store meta information
+				line = line.substr(meta_prefix.length(), -1);
+				if (line.begins_with("name")) {
+					script_template.name = line.substr(5, -1).strip_edges();
+				}
+				if (line.begins_with("description")) {
+					script_template.description = line.substr(12, -1).strip_edges();
+				}
+				if (line.begins_with("space-indent")) {
+					String indent_value = line.substr(17, -1).strip_edges();
+					if (indent_value.is_valid_int()) {
+						space_indent = "";
+						for (int i = 0; i < indent_value.to_int(); i++) {
+							space_indent += " ";
+						}
+					} else {
+						WARN_PRINT(vformat("Template meta-use_space_indent need to be a valid integer value. Found %s.", indent_value));
+					}
+				}
+			} else {
+				// Store script
+				if (space_indent != "") {
+					line = line.replace(space_indent, "_TS_");
+				}
+				script_template.content += line.replace("\t", "_TS_") + "\n";
+			}
+		}
+		file->close();
+		memdelete(file);
+	}
+
+	script_template.content = script_template.content.lstrip("\n");
+
+	// Get name from file name if no name in meta information
+	if (script_template.name == String()) {
+		script_template.name = p_filename.get_basename().replace("_", " ").capitalize();
+	}
+
+	return script_template;
+}
+
+String ScriptCreateDialog::_get_script_origin_label(const ScriptLanguage::TemplateLocation &p_origin) const {
+	switch (p_origin) {
+		case ScriptLanguage::TEMPLATE_BUILT_IN:
+			return TTR("Built-in");
+		case ScriptLanguage::TEMPLATE_EDITOR:
+			return TTR("Editor");
+		case ScriptLanguage::TEMPLATE_PROJECT:
+			return TTR("Project");
+	}
+	return "";
+}
+
 void ScriptCreateDialog::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("config", "inherits", "path", "built_in_enabled", "load_enabled"), &ScriptCreateDialog::config, DEFVAL(true), DEFVAL(true));
 
@@ -757,7 +886,7 @@ ScriptCreateDialog::ScriptCreateDialog() {
 	GridContainer *gc = memnew(GridContainer);
 	gc->set_columns(2);
 
-	/* Error Messages Field */
+	/* Information Messages Field */
 
 	VBoxContainer *vb = memnew(VBoxContainer);
 
@@ -782,6 +911,10 @@ ScriptCreateDialog::ScriptCreateDialog() {
 	script_name_warning_label->set_autowrap_mode(Label::AUTOWRAP_WORD_SMART);
 	script_name_warning_label->hide();
 
+	template_info_label = memnew(Label);
+	vb->add_child(template_info_label);
+	template_info_label->set_autowrap_mode(Label::AUTOWRAP_WORD_SMART);
+
 	status_panel = memnew(PanelContainer);
 	status_panel->set_h_size_flags(Control::SIZE_FILL);
 	status_panel->set_v_size_flags(Control::SIZE_EXPAND_FILL);
@@ -801,7 +934,7 @@ ScriptCreateDialog::ScriptCreateDialog() {
 	/* Language */
 
 	language_menu = memnew(OptionButton);
-	language_menu->set_custom_minimum_size(Size2(250, 0) * EDSCALE);
+	language_menu->set_custom_minimum_size(Size2(350, 0) * EDSCALE);
 	language_menu->set_h_size_flags(Control::SIZE_EXPAND_FILL);
 	gc->add_child(memnew(Label(TTR("Language:"))));
 	gc->add_child(language_menu);
@@ -819,7 +952,7 @@ ScriptCreateDialog::ScriptCreateDialog() {
 	}
 	current_language = default_language;
 
-	language_menu->connect("item_selected", callable_mp(this, &ScriptCreateDialog::_lang_changed));
+	language_menu->connect("item_selected", callable_mp(this, &ScriptCreateDialog::_language_changed));
 
 	/* Inherits */
 
@@ -851,10 +984,24 @@ ScriptCreateDialog::ScriptCreateDialog() {
 
 	/* Templates */
 
-	template_menu = memnew(OptionButton);
+	is_using_templates = true;
 	gc->add_child(memnew(Label(TTR("Template:"))));
-	gc->add_child(template_menu);
+	HBoxContainer *template_hb = memnew(HBoxContainer);
+	template_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+
+	use_templates = memnew(CheckBox);
+	use_templates->set_pressed(is_using_templates);
+	use_templates->connect("pressed", callable_mp(this, &ScriptCreateDialog::_use_template_pressed));
+	template_hb->add_child(use_templates);
+
+	template_inactive_message = "";
+
+	template_menu = memnew(OptionButton);
+	template_menu->set_h_size_flags(Control::SIZE_EXPAND_FILL);
 	template_menu->connect("item_selected", callable_mp(this, &ScriptCreateDialog::_template_changed));
+	template_hb->add_child(template_menu);
+
+	gc->add_child(template_hb);
 
 	/* Built-in Script */
 

+ 15 - 18
editor/script_create_dialog.h

@@ -31,6 +31,7 @@
 #ifndef SCRIPT_CREATE_DIALOG_H
 #define SCRIPT_CREATE_DIALOG_H
 
+#include "core/object/script_language.h"
 #include "editor/editor_file_dialog.h"
 #include "editor/editor_settings.h"
 #include "scene/gui/check_box.h"
@@ -50,6 +51,7 @@ class ScriptCreateDialog : public ConfirmationDialog {
 	Label *path_error_label;
 	Label *builtin_warning_label;
 	Label *script_name_warning_label;
+	Label *template_info_label;
 	PanelContainer *status_panel;
 	LineEdit *parent_name;
 	Button *parent_browse_button;
@@ -61,12 +63,14 @@ class ScriptCreateDialog : public ConfirmationDialog {
 	Button *path_button;
 	EditorFileDialog *file_browse;
 	CheckBox *internal;
+	CheckBox *use_templates;
 	VBoxContainer *path_vb;
 	AcceptDialog *alert;
 	CreateDialog *select_class;
 	bool path_valid;
 	bool create_new;
 	bool is_browsing_parent;
+	String template_inactive_message;
 	String initial_bp;
 	bool is_new_script_created;
 	bool is_path_valid;
@@ -76,6 +80,7 @@ class ScriptCreateDialog : public ConfirmationDialog {
 	bool is_parent_name_valid;
 	bool is_class_name_valid;
 	bool is_built_in;
+	bool is_using_templates;
 	bool built_in_enabled;
 	bool load_enabled;
 	int current_language;
@@ -85,23 +90,8 @@ class ScriptCreateDialog : public ConfirmationDialog {
 	Control *path_controls[2];
 	Control *name_controls[2];
 
-	enum ScriptOrigin {
-		SCRIPT_ORIGIN_PROJECT,
-		SCRIPT_ORIGIN_EDITOR,
-	};
-	struct ScriptTemplateInfo {
-		int id = 0;
-		ScriptOrigin origin = ScriptOrigin::SCRIPT_ORIGIN_EDITOR;
-		String dir;
-		String name;
-		String extension;
-	};
-
-	String script_template;
-	Vector<ScriptTemplateInfo> template_list;
-	Map<String, Vector<int>> template_overrides; // name : indices
-
-	void _update_script_templates(const String &p_extension);
+	Vector<ScriptLanguage::ScriptTemplate> template_list;
+	ScriptLanguage *language;
 
 	String base_type;
 
@@ -109,8 +99,9 @@ class ScriptCreateDialog : public ConfirmationDialog {
 	bool _can_be_built_in();
 	void _path_changed(const String &p_path = String());
 	void _path_submitted(const String &p_path = String());
-	void _lang_changed(int l = 0);
+	void _language_changed(int l = 0);
 	void _built_in_pressed();
+	void _use_template_pressed();
 	bool _validate_parent(const String &p_string);
 	bool _validate_class(const String &p_string);
 	String _validate_path(const String &p_path, bool p_file_must_exist);
@@ -125,9 +116,15 @@ class ScriptCreateDialog : public ConfirmationDialog {
 	virtual void ok_pressed() override;
 	void _create_new();
 	void _load_exist();
+	Vector<String> get_hierarchy(String p_object) const;
 	void _msg_script_valid(bool valid, const String &p_msg = String());
 	void _msg_path_valid(bool valid, const String &p_msg = String());
+	void _update_template_menu();
 	void _update_dialog();
+	ScriptLanguage::ScriptTemplate _get_current_template() const;
+	Vector<ScriptLanguage::ScriptTemplate> _get_user_templates(const ScriptLanguage *language, const StringName &p_object, const String &p_dir, const ScriptLanguage::TemplateLocation &p_origin) const;
+	ScriptLanguage::ScriptTemplate _parse_template(const ScriptLanguage *language, const String &p_path, const String &p_filename, const ScriptLanguage::TemplateLocation &p_origin, const String &p_inherits) const;
+	String _get_script_origin_label(const ScriptLanguage::TemplateLocation &p_origin) const;
 
 protected:
 	void _notification(int p_what);

+ 95 - 0
editor/template_builders.py

@@ -0,0 +1,95 @@
+"""Functions used to generate source files during build time
+All such functions are invoked in a subprocess on Windows to prevent build flakiness.
+"""
+
+import os
+from io import StringIO
+from platform_methods import subprocess_main
+
+
+def parse_template(inherits, source, delimiter):
+    script_template = {
+        "inherits": inherits,
+        "name": "",
+        "description": "",
+        "version": "",
+        "script": "",
+        "space-indent": "4",
+    }
+    meta_prefix = delimiter + " meta-"
+    meta = ["name", "description", "version", "space-indent"]
+
+    with open(source) as f:
+        lines = f.readlines()
+        for line in lines:
+            if line.startswith(meta_prefix):
+                line = line[len(meta_prefix) :]
+                for m in meta:
+                    if line.startswith(m):
+                        strip_lenght = len(m) + 1
+                        script_template[m] = line[strip_lenght:].strip()
+            else:
+                script_template["script"] += line
+        if script_template["space-indent"] != "":
+            indent = " " * int(script_template["space-indent"])
+            script_template["script"] = script_template["script"].replace(indent, "_TS_")
+        if script_template["name"] == "":
+            script_template["name"] = os.path.splitext(os.path.basename(source))[0].replace("_", " ").title()
+        script_template["script"] = (
+            script_template["script"].replace('"', '\\"').lstrip().replace("\n", "\\n").replace("\t", "_TS_")
+        )
+        return (
+            '{ String("'
+            + script_template["inherits"]
+            + '"), String("'
+            + script_template["name"]
+            + '"),  String("'
+            + script_template["description"]
+            + '"),  String("'
+            + script_template["script"]
+            + '")'
+            + " },\n"
+        )
+
+
+def make_templates(target, source, env):
+    dst = target[0]
+    s = StringIO()
+    s.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n\n")
+    s.write("#ifndef _CODE_TEMPLATES_H\n")
+    s.write("#define _CODE_TEMPLATES_H\n\n")
+    s.write('#include "core/object/object.h"\n')
+    s.write('#include "core/object/script_language.h"\n')
+
+    delimiter = "#"  # GDScript single line comment delimiter by default.
+    if source:
+        ext = os.path.splitext(source[0])[1]
+        if ext == ".cs":
+            delimiter = "//"
+
+    parsed_template_string = ""
+    number_of_templates = 0
+
+    for filepath in source:
+        node_name = os.path.basename(os.path.dirname(filepath))
+        parsed_template = parse_template(node_name, filepath, delimiter)
+        parsed_template_string += "\t" + parsed_template
+        number_of_templates += 1
+
+    s.write("\nstatic const int TEMPLATES_ARRAY_SIZE = " + str(number_of_templates) + ";\n")
+    s.write("\nstatic const struct ScriptLanguage::ScriptTemplate TEMPLATES[" + str(number_of_templates) + "] = {\n")
+
+    s.write(parsed_template_string)
+
+    s.write("};\n")
+
+    s.write("\n#endif\n")
+
+    with open(dst, "w") as f:
+        f.write(s.getvalue())
+
+    s.close()
+
+
+if __name__ == "__main__":
+    subprocess_main(globals())

+ 2 - 0
modules/gdscript/SCsub

@@ -21,3 +21,5 @@ if env["tools"]:
 if env["tests"]:
     env_gdscript.Append(CPPDEFINES=["TESTS_ENABLED"])
     env_gdscript.add_source_files(env.modules_sources, "./tests/*.cpp")
+
+SConscript("editor_templates/SCsub")

+ 29 - 0
modules/gdscript/editor_templates/CharacterBody2D/basic_movement.gd

@@ -0,0 +1,29 @@
+# meta-description: Classic movement for gravity games (platformer, ...)
+
+extends _BASE_
+
+const SPEED: float = 300.0
+const JUMP_FORCE: float = -400.0
+
+# Get the gravity from the project settings to be synced with RigidDynamicBody nodes.
+var gravity: int = ProjectSettings.get_setting("physics/2d/default_gravity")
+
+
+func _physics_process(delta: float) -> void:
+	# Add the gravity.
+	if not is_on_floor():
+		motion_velocity.y += gravity * delta
+
+	# Handle Jump.
+	if Input.is_action_just_pressed("ui_accept") and is_on_floor():
+		motion_velocity.y = JUMP_FORCE
+
+	# Get the input direction and handle the movement/deceleration.
+	# As good practice, you should replace UI actions with custom gameplay actions.
+	var direction := Input.get_axis("ui_left", "ui_right")
+	if direction:
+		motion_velocity.x = direction * SPEED
+	else:
+		motion_velocity.x = move_toward(motion_velocity.x, 0, SPEED)
+
+	move_and_slide()

+ 32 - 0
modules/gdscript/editor_templates/CharacterBody3D/basic_movement.gd

@@ -0,0 +1,32 @@
+# meta-description: Classic movement for gravity games (FPS, TPS, ...)
+
+extends _BASE_
+
+const SPEED: float = 5.0
+const JUMP_FORCE: float = 4.5
+
+# Get the gravity from the project settings to be synced with RigidDynamicBody nodes.
+var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")
+
+
+func _physics_process(delta: float) -> void:
+	# Add the gravity.
+	if not is_on_floor():
+		motion_velocity.y -= gravity * delta
+
+	# Handle Jump.
+	if Input.is_action_just_pressed("ui_accept") and is_on_floor():
+		motion_velocity.y = JUMP_FORCE
+
+	# Get the input direction and handle the movement/deceleration.
+	# As good practice, you should replace UI actions with custom gameplay actions.
+	var input_dir := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
+	var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
+	if direction:
+		motion_velocity.x = direction.x * SPEED
+		motion_velocity.z = direction.z * SPEED
+	else:
+		motion_velocity.x = move_toward(motion_velocity.x, 0, SPEED)
+		motion_velocity.z = move_toward(motion_velocity.z, 0, SPEED)
+
+	move_and_slide()

+ 11 - 0
modules/gdscript/editor_templates/EditorPlugin/plugin.gd

@@ -0,0 +1,11 @@
+# meta-description: Basic plugin template
+@tool
+extends EditorPlugin
+
+func _enter_tree() -> void:
+    # Initialization of the plugin goes here.
+    pass
+
+func _exit_tree() -> void:
+    # Clean-up of the plugin goes here.
+    pass

+ 7 - 0
modules/gdscript/editor_templates/EditorScript/basic_editor_script.gd

@@ -0,0 +1,7 @@
+# meta-description: Basic editor script template
+@tool
+extends EditorScript
+
+func _run() -> void:
+    # Called when the script is executed (using File -> Run in Script Editor).
+    pass

+ 11 - 0
modules/gdscript/editor_templates/Node/default.gd

@@ -0,0 +1,11 @@
+# meta-description: Base template for Node with default Godot cycle methods
+
+extends _BASE_
+
+# Called when the node enters the scene tree for the first time.
+func _ready() -> void:
+	pass # Replace with function body.
+
+# Called every frame. 'delta' is the elapsed time since the previous frame.
+func _process(delta: float) -> void:
+	pass

+ 3 - 0
modules/gdscript/editor_templates/Object/empty.gd

@@ -0,0 +1,3 @@
+# meta-description: Empty template suitable for all Objects
+
+extends _BASE_

+ 16 - 0
modules/gdscript/editor_templates/SCsub

@@ -0,0 +1,16 @@
+#!/usr/bin/env python
+
+Import("env")
+
+import editor.template_builders as build_template_gd
+
+env["BUILDERS"]["MakeGDTemplateBuilder"] = Builder(
+    action=env.Run(build_template_gd.make_templates, "Generating GDScript templates header."),
+    suffix=".h",
+    src_suffix=".gd",
+)
+
+# Template files
+templates_sources = Glob("*/*.gd")
+
+env.Alias("editor_template_gd", [env.MakeGDTemplateBuilder("templates.gen.h", templates_sources)])

+ 12 - 2
modules/gdscript/gdscript.cpp

@@ -49,6 +49,10 @@
 #include "tests/gdscript_test_runner.h"
 #endif
 
+#ifdef TOOLS_ENABLED
+#include "editor/editor_settings.h"
+#endif
+
 ///////////////////////////
 
 GDScriptNativeClass::GDScriptNativeClass(const StringName &p_name) {
@@ -817,10 +821,16 @@ Error GDScript::reload(bool p_keep_state) {
 		basedir = basedir.get_base_dir();
 	}
 
-	if (source.find("%BASE%") != -1) {
-		//loading a template, don't parse
+// Loading a template, don't parse.
+#ifdef TOOLS_ENABLED
+	if (basedir.begins_with(EditorSettings::get_singleton()->get_project_script_templates_dir())) {
 		return OK;
 	}
+#else
+	if (source.find("_BASE_") != -1) {
+		return OK;
+	}
+#endif
 
 	{
 		String source_path = path;

+ 50 - 51
modules/gdscript/gdscript.h

@@ -396,7 +396,7 @@ public:
 		_debug_call_stack_pos--;
 	}
 
-	virtual Vector<StackInfo> debug_get_current_stack_info() {
+	virtual Vector<StackInfo> debug_get_current_stack_info() override {
 		if (Thread::get_main_id() != Thread::get_caller_id()) {
 			return Vector<StackInfo>();
 		}
@@ -430,77 +430,76 @@ public:
 
 	_FORCE_INLINE_ static GDScriptLanguage *get_singleton() { return singleton; }
 
-	virtual String get_name() const;
+	virtual String get_name() const override;
 
 	/* LANGUAGE FUNCTIONS */
-	virtual void init();
-	virtual String get_type() const;
-	virtual String get_extension() const;
-	virtual Error execute_file(const String &p_path);
-	virtual void finish();
+	virtual void init() override;
+	virtual String get_type() const override;
+	virtual String get_extension() const override;
+	virtual Error execute_file(const String &p_path) override;
+	virtual void finish() override;
 
 	/* EDITOR FUNCTIONS */
-	virtual void get_reserved_words(List<String> *p_words) const;
-	virtual bool is_control_flow_keyword(String p_keywords) const;
-	virtual void get_comment_delimiters(List<String> *p_delimiters) const;
-	virtual void get_string_delimiters(List<String> *p_delimiters) const;
-	virtual String _get_processed_template(const String &p_template, const String &p_base_class_name) const;
-	virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const;
-	virtual bool is_using_templates();
-	virtual void make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script);
-	virtual bool validate(const String &p_script, const String &p_path = "", List<String> *r_functions = nullptr, List<ScriptLanguage::ScriptError> *r_errors = nullptr, List<ScriptLanguage::Warning> *r_warnings = nullptr, Set<int> *r_safe_lines = nullptr) const;
-	virtual Script *create_script() const;
-	virtual bool has_named_classes() const;
-	virtual bool supports_builtin_mode() const;
-	virtual bool supports_documentation() const;
-	virtual bool can_inherit_from_file() const { return true; }
-	virtual int find_function(const String &p_function, const String &p_code) const;
-	virtual String make_function(const String &p_class, const String &p_name, const PackedStringArray &p_args) const;
-	virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint);
+	virtual void get_reserved_words(List<String> *p_words) const override;
+	virtual bool is_control_flow_keyword(String p_keywords) const override;
+	virtual void get_comment_delimiters(List<String> *p_delimiters) const override;
+	virtual void get_string_delimiters(List<String> *p_delimiters) const override;
+	virtual bool is_using_templates() override;
+	virtual Ref<Script> make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const override;
+	virtual Vector<ScriptTemplate> get_built_in_templates(StringName p_object) override;
+	virtual bool validate(const String &p_script, const String &p_path = "", List<String> *r_functions = nullptr, List<ScriptLanguage::ScriptError> *r_errors = nullptr, List<ScriptLanguage::Warning> *r_warnings = nullptr, Set<int> *r_safe_lines = nullptr) const override;
+	virtual Script *create_script() const override;
+	virtual bool has_named_classes() const override;
+	virtual bool supports_builtin_mode() const override;
+	virtual bool supports_documentation() const override;
+	virtual bool can_inherit_from_file() const override { return true; }
+	virtual int find_function(const String &p_function, const String &p_code) const override;
+	virtual String make_function(const String &p_class, const String &p_name, const PackedStringArray &p_args) const override;
+	virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint) override;
 #ifdef TOOLS_ENABLED
-	virtual Error lookup_code(const String &p_code, const String &p_symbol, const String &p_path, Object *p_owner, LookupResult &r_result);
+	virtual Error lookup_code(const String &p_code, const String &p_symbol, const String &p_path, Object *p_owner, LookupResult &r_result) override;
 #endif
 	virtual String _get_indentation() const;
-	virtual void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const;
-	virtual void add_global_constant(const StringName &p_variable, const Variant &p_value);
-	virtual void add_named_global_constant(const StringName &p_name, const Variant &p_value);
-	virtual void remove_named_global_constant(const StringName &p_name);
+	virtual void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const override;
+	virtual void add_global_constant(const StringName &p_variable, const Variant &p_value) override;
+	virtual void add_named_global_constant(const StringName &p_name, const Variant &p_value) override;
+	virtual void remove_named_global_constant(const StringName &p_name) override;
 
 	/* DEBUGGER FUNCTIONS */
 
-	virtual String debug_get_error() const;
-	virtual int debug_get_stack_level_count() const;
-	virtual int debug_get_stack_level_line(int p_level) const;
-	virtual String debug_get_stack_level_function(int p_level) const;
-	virtual String debug_get_stack_level_source(int p_level) const;
-	virtual void debug_get_stack_level_locals(int p_level, List<String> *p_locals, List<Variant> *p_values, int p_max_subitems = -1, int p_max_depth = -1);
-	virtual void debug_get_stack_level_members(int p_level, List<String> *p_members, List<Variant> *p_values, int p_max_subitems = -1, int p_max_depth = -1);
-	virtual ScriptInstance *debug_get_stack_level_instance(int p_level);
-	virtual void debug_get_globals(List<String> *p_globals, List<Variant> *p_values, int p_max_subitems = -1, int p_max_depth = -1);
-	virtual String debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems = -1, int p_max_depth = -1);
+	virtual String debug_get_error() const override;
+	virtual int debug_get_stack_level_count() const override;
+	virtual int debug_get_stack_level_line(int p_level) const override;
+	virtual String debug_get_stack_level_function(int p_level) const override;
+	virtual String debug_get_stack_level_source(int p_level) const override;
+	virtual void debug_get_stack_level_locals(int p_level, List<String> *p_locals, List<Variant> *p_values, int p_max_subitems = -1, int p_max_depth = -1) override;
+	virtual void debug_get_stack_level_members(int p_level, List<String> *p_members, List<Variant> *p_values, int p_max_subitems = -1, int p_max_depth = -1) override;
+	virtual ScriptInstance *debug_get_stack_level_instance(int p_level) override;
+	virtual void debug_get_globals(List<String> *p_globals, List<Variant> *p_values, int p_max_subitems = -1, int p_max_depth = -1) override;
+	virtual String debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems = -1, int p_max_depth = -1) override;
 
-	virtual void reload_all_scripts();
-	virtual void reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload);
+	virtual void reload_all_scripts() override;
+	virtual void reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) override;
 
-	virtual void frame();
+	virtual void frame() override;
 
-	virtual void get_public_functions(List<MethodInfo> *p_functions) const;
-	virtual void get_public_constants(List<Pair<String, Variant>> *p_constants) const;
+	virtual void get_public_functions(List<MethodInfo> *p_functions) const override;
+	virtual void get_public_constants(List<Pair<String, Variant>> *p_constants) const override;
 
-	virtual void profiling_start();
-	virtual void profiling_stop();
+	virtual void profiling_start() override;
+	virtual void profiling_stop() override;
 
-	virtual int profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int p_info_max);
-	virtual int profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max);
+	virtual int profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int p_info_max) override;
+	virtual int profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max) override;
 
 	/* LOADER FUNCTIONS */
 
-	virtual void get_recognized_extensions(List<String> *p_extensions) const;
+	virtual void get_recognized_extensions(List<String> *p_extensions) const override;
 
 	/* GLOBAL CLASSES */
 
-	virtual bool handles_global_class_type(const String &p_type) const;
-	virtual String get_global_class_name(const String &p_path, String *r_base_type = nullptr, String *r_icon_path = nullptr) const;
+	virtual bool handles_global_class_type(const String &p_type) const override;
+	virtual String get_global_class_name(const String &p_path, String *r_base_type = nullptr, String *r_icon_path = nullptr) const override;
 
 	void add_orphan_subclass(const String &p_qualified_name, const ObjectID &p_subclass);
 	Ref<GDScript> get_orphan_subclass(const String &p_qualified_name);

+ 34 - 57
modules/gdscript/gdscript_editor.cpp

@@ -33,6 +33,7 @@
 #include "core/config/engine.h"
 #include "core/core_constants.h"
 #include "core/io/file_access.h"
+#include "editor_templates/templates.gen.h"
 #include "gdscript_analyzer.h"
 #include "gdscript_compiler.h"
 #include "gdscript_parser.h"
@@ -55,68 +56,44 @@ void GDScriptLanguage::get_string_delimiters(List<String> *p_delimiters) const {
 	p_delimiters->push_back("\"\"\" \"\"\"");
 }
 
-String GDScriptLanguage::_get_processed_template(const String &p_template, const String &p_base_class_name) const {
-	String processed_template = p_template;
+bool GDScriptLanguage::is_using_templates() {
+	return true;
+}
 
+Ref<Script> GDScriptLanguage::make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const {
+	Ref<GDScript> script;
+	script.instantiate();
+	String processed_template = p_template;
 #ifdef TOOLS_ENABLED
-	if (EDITOR_DEF("text_editor/completion/add_type_hints", false)) {
-		processed_template = processed_template.replace("%INT_TYPE%", ": int");
-		processed_template = processed_template.replace("%STRING_TYPE%", ": String");
-		processed_template = processed_template.replace("%FLOAT_TYPE%", ": float");
-		processed_template = processed_template.replace("%VOID_RETURN%", " -> void");
-	} else {
-		processed_template = processed_template.replace("%INT_TYPE%", "");
-		processed_template = processed_template.replace("%STRING_TYPE%", "");
-		processed_template = processed_template.replace("%FLOAT_TYPE%", "");
-		processed_template = processed_template.replace("%VOID_RETURN%", "");
+	if (!EDITOR_DEF("text_editor/completion/add_type_hints", false)) {
+		processed_template = processed_template.replace(": int", "")
+									 .replace(": String", "")
+									 .replace(": float", "")
+									 .replace(":=", "=")
+									 .replace(" -> void", "");
 	}
 #else
-	processed_template = processed_template.replace("%INT_TYPE%", "");
-	processed_template = processed_template.replace("%STRING_TYPE%", "");
-	processed_template = processed_template.replace("%FLOAT_TYPE%", "");
-	processed_template = processed_template.replace("%VOID_RETURN%", "");
+	processed_template = processed_template.replace(": int", "")
+								 .replace(": String", "")
+								 .replace(": float", "")
+								 .replace(" -> void", "");
 #endif
 
-	processed_template = processed_template.replace("%BASE%", p_base_class_name);
-	processed_template = processed_template.replace("%TS%", _get_indentation());
-
-	return processed_template;
-}
-
-Ref<Script> GDScriptLanguage::get_template(const String &p_class_name, const String &p_base_class_name) const {
-	String _template = "extends %BASE%\n"
-					   "\n"
-					   "\n"
-					   "# Declare member variables here. Examples:\n"
-					   "# var a%INT_TYPE% = 2\n"
-					   "# var b%STRING_TYPE% = \"text\"\n"
-					   "\n"
-					   "\n"
-					   "# Called when the node enters the scene tree for the first time.\n"
-					   "func _ready()%VOID_RETURN%:\n"
-					   "%TS%pass # Replace with function body.\n"
-					   "\n"
-					   "\n"
-					   "# Called every frame. 'delta' is the elapsed time since the previous frame.\n"
-					   "#func _process(delta%FLOAT_TYPE%)%VOID_RETURN%:\n"
-					   "#%TS%pass\n";
-
-	_template = _get_processed_template(_template, p_base_class_name);
-
-	Ref<GDScript> script;
-	script.instantiate();
-	script->set_source_code(_template);
-
+	processed_template = processed_template.replace("_BASE_", p_base_class_name)
+								 .replace("_CLASS_", p_class_name)
+								 .replace("_TS_", _get_indentation());
+	script->set_source_code(processed_template);
 	return script;
 }
 
-bool GDScriptLanguage::is_using_templates() {
-	return true;
-}
-
-void GDScriptLanguage::make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script) {
-	String _template = _get_processed_template(p_script->get_source_code(), p_base_class_name);
-	p_script->set_source_code(_template);
+Vector<ScriptLanguage::ScriptTemplate> GDScriptLanguage::get_built_in_templates(StringName p_object) {
+	Vector<ScriptLanguage::ScriptTemplate> templates;
+	for (int i = 0; i < TEMPLATES_ARRAY_SIZE; i++) {
+		if (TEMPLATES[i].inherit == p_object) {
+			templates.append(TEMPLATES[i]);
+		}
+	}
+	return templates;
 }
 
 static void get_function_names_recursively(const GDScriptParser::ClassNode *p_class, const String &p_prefix, Map<int, String> &r_funcs) {
@@ -236,7 +213,7 @@ Script *GDScriptLanguage::create_script() const {
 /* DEBUGGER FUNCTIONS */
 
 bool GDScriptLanguage::debug_break_parse(const String &p_file, int p_line, const String &p_error) {
-	//break because of parse error
+	// break because of parse error
 
 	if (EngineDebugger::is_active() && Thread::get_caller_id() == Thread::get_main_id()) {
 		_debug_parse_err_line = p_line;
@@ -1383,8 +1360,8 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
 														}
 
 														if (!script.ends_with(".gd")) {
-															//not a script, try find the script anyway,
-															//may have some success
+															// not a script, try find the script anyway,
+															// may have some success
 															script = script.get_basename() + ".gd";
 														}
 
@@ -2770,7 +2747,7 @@ void GDScriptLanguage::auto_indent_code(String &p_code, int p_from_line, int p_t
 			}
 
 			if (indent_stack.size() && indent_stack.back()->get() != tc) {
-				indent_stack.push_back(tc); //this is not right but gets the job done
+				indent_stack.push_back(tc); // this is not right but gets the job done
 			}
 		}
 

+ 2 - 0
modules/mono/SCsub

@@ -63,3 +63,5 @@ elif env["platform"] == "android":
 
 if env["tools"]:
     env_mono.add_source_files(env.modules_sources, "editor/*.cpp")
+
+SConscript("editor_templates/SCsub")

+ 21 - 44
modules/mono/csharp_script.cpp

@@ -55,6 +55,7 @@
 #endif
 
 #include "editor/editor_internal_calls.h"
+#include "editor_templates/templates.gen.h"
 #include "godotsharp_dirs.h"
 #include "mono_gd/gd_mono_cache.h"
 #include "mono_gd/gd_mono_class.h"
@@ -351,57 +352,33 @@ static String get_base_class_name(const String &p_base_class_name, const String
 	return base_class;
 }
 
-Ref<Script> CSharpLanguage::get_template(const String &p_class_name, const String &p_base_class_name) const {
-	String script_template = "using " BINDINGS_NAMESPACE ";\n"
-							 "using System;\n"
-							 "\n"
-							 "public partial class %CLASS% : %BASE%\n"
-							 "{\n"
-							 "    // Declare member variables here. Examples:\n"
-							 "    // private int a = 2;\n"
-							 "    // private string b = \"text\";\n"
-							 "\n"
-							 "    // Called when the node enters the scene tree for the first time.\n"
-							 "    public override void _Ready()\n"
-							 "    {\n"
-							 "        \n"
-							 "    }\n"
-							 "\n"
-							 "//  // Called every frame. 'delta' is the elapsed time since the previous frame.\n"
-							 "//  public override void _Process(float delta)\n"
-							 "//  {\n"
-							 "//      \n"
-							 "//  }\n"
-							 "}\n";
-
-	// Replaces all spaces in p_class_name with underscores to prevent
-	// invalid C# Script templates from being generated when the object name
-	// has spaces in it.
-	String class_name_no_spaces = p_class_name.replace(" ", "_");
-	String base_class_name = get_base_class_name(p_base_class_name, class_name_no_spaces);
-	script_template = script_template.replace("%BASE%", base_class_name)
-							  .replace("%CLASS%", class_name_no_spaces);
+bool CSharpLanguage::is_using_templates() {
+	return true;
+}
 
+Ref<Script> CSharpLanguage::make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const {
 	Ref<CSharpScript> script;
 	script.instantiate();
-	script->set_source_code(script_template);
-	script->set_name(class_name_no_spaces);
 
+	String class_name_no_spaces = p_class_name.replace(" ", "_");
+	String base_class_name = get_base_class_name(p_base_class_name, class_name_no_spaces);
+	String processed_template = p_template;
+	processed_template = processed_template.replace("_BINDINGS_NAMESPACE_", BINDINGS_NAMESPACE)
+								 .replace("_BASE_", base_class_name)
+								 .replace("_CLASS_", class_name_no_spaces)
+								 .replace("_TS_", _get_indentation());
+	script->set_source_code(processed_template);
 	return script;
 }
 
-bool CSharpLanguage::is_using_templates() {
-	return true;
-}
-
-void CSharpLanguage::make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script) {
-	String src = p_script->get_source_code();
-	String class_name_no_spaces = p_class_name.replace(" ", "_");
-	String base_class_name = get_base_class_name(p_base_class_name, class_name_no_spaces);
-	src = src.replace("%BASE%", base_class_name)
-				  .replace("%CLASS%", class_name_no_spaces)
-				  .replace("%TS%", _get_indentation());
-	p_script->set_source_code(src);
+Vector<ScriptLanguage::ScriptTemplate> CSharpLanguage::get_built_in_templates(StringName p_object) {
+	Vector<ScriptLanguage::ScriptTemplate> templates;
+	for (int i = 0; i < TEMPLATES_ARRAY_SIZE; i++) {
+		if (TEMPLATES[i].inherit == p_object) {
+			templates.append(TEMPLATES[i]);
+		}
+	}
+	return templates;
 }
 
 String CSharpLanguage::validate_path(const String &p_path) const {

+ 2 - 2
modules/mono/csharp_script.h

@@ -463,9 +463,9 @@ public:
 	bool is_control_flow_keyword(String p_keyword) const override;
 	void get_comment_delimiters(List<String> *p_delimiters) const override;
 	void get_string_delimiters(List<String> *p_delimiters) const override;
-	Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const override;
 	bool is_using_templates() override;
-	void make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script) override;
+	virtual Ref<Script> make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const override;
+	virtual Vector<ScriptTemplate> get_built_in_templates(StringName p_object) override;
 	/* TODO */ bool validate(const String &p_script, const String &p_path, List<String> *r_functions,
 			List<ScriptLanguage::ScriptError> *r_errors = nullptr, List<ScriptLanguage::Warning> *r_warnings = nullptr, Set<int> *r_safe_lines = nullptr) const override {
 		return true;

+ 41 - 0
modules/mono/editor_templates/CharacterBody2D/basic_movement.cs

@@ -0,0 +1,41 @@
+// meta-description: Classic movement for gravity games (platformer, ...)
+
+using _BINDINGS_NAMESPACE_;
+using System;
+
+public partial class _CLASS_ : _BASE_
+{
+    public const float Speed = 300.0f;
+    public const float JumpForce = -400.0f;
+
+    // Get the gravity from the project settings to be synced with RigidDynamicBody nodes.
+    public float gravity = (float)ProjectSettings.GetSetting("physics/2d/default_gravity");
+
+    public override void _PhysicsProcess(float delta)
+    {
+        Vector2 motionVelocity = MotionVelocity;
+
+        // Add the gravity.
+        if (!IsOnFloor())
+            motionVelocity.y += gravity * delta;
+
+        // Handle Jump.
+        if (Input.IsActionJustPressed("ui_accept") && IsOnFloor())
+            motionVelocity.y = JumpForce;
+
+        // Get the input direction and handle the movement/deceleration.
+        // As good practice, you should replace UI actions with custom gameplay actions.
+        Vector2 direction = Input.GetVector("ui_left", "ui_right", "ui_up", "ui_down");
+        if (direction != Vector2.Zero)
+        {
+            motionVelocity.x = direction.x * Speed;
+        }
+        else
+        {
+            motionVelocity.x = Mathf.MoveToward(MotionVelocity.x, 0, Speed);
+        }
+
+        MotionVelocity = motionVelocity;
+        MoveAndSlide();
+    }
+}

+ 44 - 0
modules/mono/editor_templates/CharacterBody3D/basic_movement.cs

@@ -0,0 +1,44 @@
+// meta-description: Classic movement for gravity games (FPS, TPS, ...)
+
+using _BINDINGS_NAMESPACE_;
+using System;
+
+public partial class _CLASS_ : _BASE_
+{
+    public const float Speed = 5.0f;
+    public const float JumpForce = 4.5f;
+
+    // Get the gravity from the project settings to be synced with RigidDynamicBody nodes.
+    public float gravity = (float)ProjectSettings.GetSetting("physics/3d/default_gravity");
+
+    public override void _PhysicsProcess(float delta)
+    {
+        Vector3 motionVelocity = MotionVelocity;
+
+        // Add the gravity.
+        if (!IsOnFloor())
+            motionVelocity.y -= gravity * delta;
+
+        // Handle Jump.
+        if (Input.IsActionJustPressed("ui_accept") && IsOnFloor())
+             motionVelocity.y = JumpForce;
+
+        // Get the input direction and handle the movement/deceleration.
+        // As good practice, you should replace UI actions with custom gameplay actions.
+        Vector2 inputDir = Input.GetVector("ui_left", "ui_right", "ui_up", "ui_down");
+        Vector3 direction = Transform.basis.Xform(new Vector3(inputDir.x, 0, inputDir.y)).Normalized();
+        if (direction != Vector3.Zero)
+        {
+            motionVelocity.x = direction.x * Speed;
+            motionVelocity.z = direction.z * Speed;
+        }
+        else
+        {
+            motionVelocity.x = Mathf.MoveToward(MotionVelocity.x, 0, Speed);
+            motionVelocity.z = Mathf.MoveToward(MotionVelocity.z, 0, Speed);
+        }
+
+        MotionVelocity = motionVelocity;
+        MoveAndSlide();
+    }
+}

+ 19 - 0
modules/mono/editor_templates/EditorPlugin/plugin.cs

@@ -0,0 +1,19 @@
+// meta-description: Basic plugin template
+#if TOOLS
+using _BINDINGS_NAMESPACE_;
+using System;
+
+[Tool]
+public partial class _CLASS_ : _BASE_
+{
+    public override void _EnterTree()
+    {
+        // Initialization of the plugin goes here.
+    }
+
+    public override void _ExitTree()
+    {
+        // Clean-up of the plugin goes here.
+    }
+}
+#endif

+ 14 - 0
modules/mono/editor_templates/EditorScript/basic_editor_script.cs

@@ -0,0 +1,14 @@
+// meta-description: Basic editor script template
+#if TOOLS
+using _BINDINGS_NAMESPACE_;
+using System;
+
+[Tool]
+public partial class _CLASS_ : _BASE_
+{
+    public override void _Run()
+    {
+        // Called when the script is executed (using File -> Run in Script Editor).
+    }
+}
+#endif

+ 19 - 0
modules/mono/editor_templates/Node/default.cs

@@ -0,0 +1,19 @@
+// meta-description: Base template for Node with default Godot cycle methods
+
+using _BINDINGS_NAMESPACE_;
+using System;
+
+public partial class _CLASS_ : _BASE_
+{
+    // Called when the node enters the scene tree for the first time.
+    public override void _Ready()
+    {
+
+    }
+
+    // Called every frame. 'delta' is the elapsed time since the previous frame.
+    public override void _Process(float delta)
+    {
+
+    }
+}

+ 9 - 0
modules/mono/editor_templates/Object/empty.cs

@@ -0,0 +1,9 @@
+// meta-description: Empty template suitable for all Objects
+
+using _BINDINGS_NAMESPACE_;
+using System;
+
+public partial class _CLASS_ : _BASE_
+{
+
+}

+ 16 - 0
modules/mono/editor_templates/SCsub

@@ -0,0 +1,16 @@
+#!/usr/bin/env python
+
+Import("env")
+
+import editor.template_builders as build_template_cs
+
+env["BUILDERS"]["MakeCSharpTemplateBuilder"] = Builder(
+    action=env.Run(build_template_cs.make_templates, "Generating C# templates header."),
+    suffix=".h",
+    src_suffix=".cs",
+)
+
+# Template files
+templates_sources = Glob("*/*.cs")
+
+env.Alias("editor_template_cs", [env.MakeCSharpTemplateBuilder("templates.gen.h", templates_sources)])

+ 5 - 10
modules/visual_script/visual_script.cpp

@@ -2241,20 +2241,15 @@ void VisualScriptLanguage::get_comment_delimiters(List<String> *p_delimiters) co
 void VisualScriptLanguage::get_string_delimiters(List<String> *p_delimiters) const {
 }
 
-Ref<Script> VisualScriptLanguage::get_template(const String &p_class_name, const String &p_base_class_name) const {
-	Ref<VisualScript> script;
-	script.instantiate();
-	script->set_instance_base_type(p_base_class_name);
-	return script;
-}
-
 bool VisualScriptLanguage::is_using_templates() {
-	return true;
+	return false;
 }
 
-void VisualScriptLanguage::make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script) {
-	Ref<VisualScript> script = p_script;
+Ref<Script> VisualScriptLanguage::make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const {
+	Ref<VisualScript> script;
+	script.instantiate();
 	script->set_instance_base_type(p_base_class_name);
+	return script;
 }
 
 bool VisualScriptLanguage::validate(const String &p_script, const String &p_path, List<String> *r_functions, List<ScriptLanguage::ScriptError> *r_errors, List<ScriptLanguage::Warning> *r_warnings, Set<int> *r_safe_lines) const {

+ 39 - 40
modules/visual_script/visual_script.h

@@ -554,57 +554,56 @@ public:
 
 	//////////////////////////////////////
 
-	virtual String get_name() const;
+	virtual String get_name() const override;
 
 	/* LANGUAGE FUNCTIONS */
-	virtual void init();
-	virtual String get_type() const;
-	virtual String get_extension() const;
-	virtual Error execute_file(const String &p_path);
-	virtual void finish();
+	virtual void init() override;
+	virtual String get_type() const override;
+	virtual String get_extension() const override;
+	virtual Error execute_file(const String &p_path) override;
+	virtual void finish() override;
 
 	/* EDITOR FUNCTIONS */
-	virtual void get_reserved_words(List<String> *p_words) const;
-	virtual bool is_control_flow_keyword(String p_keyword) const;
-	virtual void get_comment_delimiters(List<String> *p_delimiters) const;
-	virtual void get_string_delimiters(List<String> *p_delimiters) const;
-	virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const;
-	virtual bool is_using_templates();
-	virtual void make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script);
-	virtual bool validate(const String &p_script, const String &p_path = "", List<String> *r_functions = nullptr, List<ScriptLanguage::ScriptError> *r_errors = nullptr, List<ScriptLanguage::Warning> *r_warnings = nullptr, Set<int> *r_safe_lines = nullptr) const;
-	virtual Script *create_script() const;
-	virtual bool has_named_classes() const;
-	virtual bool supports_builtin_mode() const;
-	virtual int find_function(const String &p_function, const String &p_code) const;
-	virtual String make_function(const String &p_class, const String &p_name, const PackedStringArray &p_args) const;
-	virtual void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const;
-	virtual void add_global_constant(const StringName &p_variable, const Variant &p_value);
+	virtual void get_reserved_words(List<String> *p_words) const override;
+	virtual bool is_control_flow_keyword(String p_keyword) const override;
+	virtual void get_comment_delimiters(List<String> *p_delimiters) const override;
+	virtual void get_string_delimiters(List<String> *p_delimiters) const override;
+	virtual bool is_using_templates() override;
+	virtual Ref<Script> make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const override;
+	virtual bool validate(const String &p_script, const String &p_path = "", List<String> *r_functions = nullptr, List<ScriptLanguage::ScriptError> *r_errors = nullptr, List<ScriptLanguage::Warning> *r_warnings = nullptr, Set<int> *r_safe_lines = nullptr) const override;
+	virtual Script *create_script() const override;
+	virtual bool has_named_classes() const override;
+	virtual bool supports_builtin_mode() const override;
+	virtual int find_function(const String &p_function, const String &p_code) const override;
+	virtual String make_function(const String &p_class, const String &p_name, const PackedStringArray &p_args) const override;
+	virtual void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const override;
+	virtual void add_global_constant(const StringName &p_variable, const Variant &p_value) override;
 
 	/* DEBUGGER FUNCTIONS */
 
-	virtual String debug_get_error() const;
-	virtual int debug_get_stack_level_count() const;
-	virtual int debug_get_stack_level_line(int p_level) const;
-	virtual String debug_get_stack_level_function(int p_level) const;
-	virtual String debug_get_stack_level_source(int p_level) const;
-	virtual void debug_get_stack_level_locals(int p_level, List<String> *p_locals, List<Variant> *p_values, int p_max_subitems = -1, int p_max_depth = -1);
-	virtual void debug_get_stack_level_members(int p_level, List<String> *p_members, List<Variant> *p_values, int p_max_subitems = -1, int p_max_depth = -1);
-	virtual void debug_get_globals(List<String> *p_locals, List<Variant> *p_values, int p_max_subitems = -1, int p_max_depth = -1);
-	virtual String debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems = -1, int p_max_depth = -1);
-
-	virtual void reload_all_scripts();
-	virtual void reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload);
+	virtual String debug_get_error() const override;
+	virtual int debug_get_stack_level_count() const override;
+	virtual int debug_get_stack_level_line(int p_level) const override;
+	virtual String debug_get_stack_level_function(int p_level) const override;
+	virtual String debug_get_stack_level_source(int p_level) const override;
+	virtual void debug_get_stack_level_locals(int p_level, List<String> *p_locals, List<Variant> *p_values, int p_max_subitems = -1, int p_max_depth = -1) override;
+	virtual void debug_get_stack_level_members(int p_level, List<String> *p_members, List<Variant> *p_values, int p_max_subitems = -1, int p_max_depth = -1) override;
+	virtual void debug_get_globals(List<String> *p_locals, List<Variant> *p_values, int p_max_subitems = -1, int p_max_depth = -1) override;
+	virtual String debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems = -1, int p_max_depth = -1) override;
+
+	virtual void reload_all_scripts() override;
+	virtual void reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) override;
 	/* LOADER FUNCTIONS */
 
-	virtual void get_recognized_extensions(List<String> *p_extensions) const;
-	virtual void get_public_functions(List<MethodInfo> *p_functions) const;
-	virtual void get_public_constants(List<Pair<String, Variant>> *p_constants) const;
+	virtual void get_recognized_extensions(List<String> *p_extensions) const override;
+	virtual void get_public_functions(List<MethodInfo> *p_functions) const override;
+	virtual void get_public_constants(List<Pair<String, Variant>> *p_constants) const override;
 
-	virtual void profiling_start();
-	virtual void profiling_stop();
+	virtual void profiling_start() override;
+	virtual void profiling_stop() override;
 
-	virtual int profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int p_info_max);
-	virtual int profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max);
+	virtual int profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int p_info_max) override;
+	virtual int profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max) override;
 
 	void add_register_func(const String &p_name, VisualScriptNodeRegisterFunc p_func);
 	void remove_register_func(const String &p_name);