diff --git a/.gitignore b/.gitignore index c732858ee..967f211fa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +*.csv +!redirects.csv +.env + _build/ env/ __pycache__ diff --git a/_tools/redirects/README.md b/_tools/redirects/README.md new file mode 100644 index 000000000..8230f4d04 --- /dev/null +++ b/_tools/redirects/README.md @@ -0,0 +1,47 @@ +# ReadTheDocs redirect tools + +The scripts located in this directory help in creating and maintaining redirects on [Read the Docs](https://readthedocs.io). +Also refer to Read the Docs [API documentation](https://docs.readthedocs.io/en/stable/api/index.html). + +Note that RTD redirects only apply in case of 404 errors, and to all branches and languages: +. +If this ever changes, we need to rework how we manage these (likely adding per-branch logic). + +`convert_git_renames_to_csv.py` creates a list of renamed files in Git to create redirects for. +`create_redirects.py` is used to actually manage redirects on ReadTheDocs. + +For more information on the scripts themselves, see their help output. + +## Setup + +To install requirements: `pip3 install -r requirements.txt`. +Git is also required and needs to be available in the `PATH`. +To interact with the Read the Docs API, a valid API key must be set as +`RTD_AUTH_TOKEN` (either as a environment variable or in a [.env file](https://pypi.org/project/python-dotenv/)). + +## Usage + +Lets say we recently renamed some files in the Git branch `3.4` (compared to the `stable` branch), and now we want to create redirects for these. +For this, we would (after setting up the API token and requirements, see Setup above): + +> python convert_git_renames_to_csv.py stable 3.4 + +This should output a list of the redirects to create. Lets append these to the redirects file: + +> python convert_git_renames_to_csv.py stable 3.4 >> redirects.csv + +After this, redirects for renamed files should have been appended to `redirects.csv`. You may want to double check that! +Now lets submit these to ReadTheDocs and create redirects there: + +> python create_redirects.py + +And that should be it! + +The script takes care to not add duplicate redirects if the same ones already exist. +The created redirects are also valid for all branches and languages, which works out +as they only apply for actually missing files - when a user encounters a 404, that is. + +The script also only touches `page` type redirects, all other types may still be added +and managed manually on RTD or via other means. All `page` redirects need to +be managed with these tools however, as they will otherwise just overwrite any +changes made elsewhere. diff --git a/_tools/redirects/convert_git_renames_to_csv.py b/_tools/redirects/convert_git_renames_to_csv.py index a740d9855..4eb8efe40 100644 --- a/_tools/redirects/convert_git_renames_to_csv.py +++ b/_tools/redirects/convert_git_renames_to_csv.py @@ -1,4 +1,6 @@ -"""Uses git to list files that were renamed between two revisions and converts +#!/usr/bin/env python3 + +"""Uses Git to list files that were renamed between two revisions and converts that to a CSV table. Use it to prepare and double-check data for create_redirects.py. @@ -9,32 +11,40 @@ import argparse import csv import sys -try: - subprocess.check_output(["git", "--version"]) -except subprocess.CalledProcessError: - print("Git not found. It's required to run this program.") - def parse_command_line_args(): parser = argparse.ArgumentParser( - description="Uses git to list files that were renamed between two revisions and " + description="Uses Git to list files that were renamed between two revisions and " "converts that to a CSV table. Use it to prepare and double-check data for create_redirects.py." ) parser.add_argument( "revision1", type=str, - help="Start revision to get renamed files from.", + help="Start revision to get renamed files from (old).", ) parser.add_argument( "revision2", type=str, - help="End revision to get renamed files from.", + help="End revision to get renamed files from (new).", ) parser.add_argument("-f", "--output-file", type=str, help="Path to the output file") return parser.parse_args() +def dict_item_to_str(item): + s = "" + for key in item: + s += item[key] + return s + + def main(): + try: + subprocess.check_output(["git", "--version"]) + except subprocess.CalledProcessError: + print("Git not found. It's required to run this program.") + exit(1) + args = parse_command_line_args() assert args.revision1 != args.revision2, "Revisions must be different." for revision in [args.revision1, args.revision2]: @@ -49,7 +59,7 @@ def main(): except subprocess.CalledProcessError: print( f"Revision {revision} not found in this repository. " - "Please make sure that both revisions exist locally in your git repository." + "Please make sure that both revisions exist locally in your Git repository." ) exit(1) @@ -68,25 +78,34 @@ def main(): .decode("utf-8") .split("\n") ) - renamed_documents = [f for f in renamed_files if f.endswith(".rst")] + renamed_documents = [f for f in renamed_files if f.lower().endswith(".rst")] csv_data: list[dict] = [] - branch = args.revision2 + for document in renamed_documents: _, source, destination = document.split("\t") + source = source.replace(".rst", ".html") + destination = destination.replace(".rst", ".html") + if not source.startswith("/"): + source = "/" + source + if not destination.startswith("/"): + destination = "/" + destination csv_data.append( - {"source": source, "destination": destination, "branch": branch} + {"source": source, "destination": destination} ) - if args.output_file: - with open(args.output_file, "w") as f: - writer = csv.DictWriter(f, fieldnames=csv_data[0].keys()).writerows( - csv_data - ) - writer.writeheader() - writer.writerows(csv_data) - else: - writer = csv.DictWriter(sys.stdout, fieldnames=csv_data[0].keys()) + if len(csv_data) < 1: + print("No renames found for", args.revision1, "->", args.revision2) + return + + csv_data.sort(key=dict_item_to_str) + + out = args.output_file + if not out: + out = sys.stdout.fileno() + + with open(out, "w", encoding="utf-8", newline="") as f: + writer = csv.DictWriter(f, fieldnames=csv_data[0].keys()) writer.writeheader() writer.writerows(csv_data) diff --git a/_tools/redirects/create_redirects.py b/_tools/redirects/create_redirects.py index ad5725e54..b1ce58484 100644 --- a/_tools/redirects/create_redirects.py +++ b/_tools/redirects/create_redirects.py @@ -1,111 +1,324 @@ -"""Create page redirects for a specific branch of the docs. +#!/usr/bin/env python3 -Loads data from a CSV file with three columns: source, destination, branch - -Where the source and destination are paths to RST files in the repository. - -Pre-requisites: - -- You need the dotenv Python module installed. We use this to let you store your - API auth token privately. - - You can install it by running: pip3 install -r requirements.txt +"""Manages page redirects for the Godot documentation on ReadTheDocs. (https://docs.godotengine.org) +Note that RTD redirects only apply in case of 404 errors, and to all branches and languages: +https://docs.readthedocs.io/en/stable/user-defined-redirects.html. +If this ever changes, we need to rework how we manage these (likely adding per-branch logic). How to use: +- Install requirements: pip3 install -r requirements.txt +- Store your API token in RTD_API_TOKEN environment variable or + a .env file (the latter requires the package dotenv) +- Generate new redirects from two git revisions using convert_git_renames_to_csv.py +- Run this script -- Generate a CSV file from two git revisions using convert_git_renames_to_csv.py -- Store your API token in a .env variable in this directory like so: - RTD_API_TOKEN=your_token_here -- Run this script, passing it the path to your generated CSV file as an - argument. +Example: + python convert_git_renames_to_csv.py stable 3.4 >> redirects.csv + python create_redirects.py -The script directly creates redirects using the CSV data. It does not check if a -redirect already exist or if it's correct. +This would add all files that were renamed in 3.4 from stable to redirects.csv, +and then create the redirects on RTD accordingly. +Care is taken to not add redirects that already exist on RTD. """ import argparse import csv -import json import os +import time -import dotenv +import requests from requests.models import default_hooks +from requests.adapters import HTTPAdapter +from requests.packages.urllib3.util.retry import Retry -try: - import requests -except ImportError: - print( - "Required third-party module `requests` not found. " - "Please install it with `pip install requests` (or `pip3 install requests` on Linux)." - ) - - -dotenv.load_dotenv() -RTD_AUTH_TOKEN: str = os.environ.get("RTD_AUTH_TOKEN", "") -if RTD_AUTH_TOKEN == "": - print("Missing auth token in .env file or .env file not found. Aborting.") - exit(1) - -REDIRECT_URL = "https://readthedocs.org/api/v3/projects/pip/redirects/" -REQUEST_HEADERS = {"Authorization": f"token {RTD_AUTH_TOKEN}"} - +RTD_AUTH_TOKEN = "" +REQUEST_HEADERS = "" +REDIRECT_URL = "https://readthedocs.org/api/v3/projects/godot/redirects/" +USER_AGENT = "Godot RTD Redirects on Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36" +DEFAULT_PAGINATED_SIZE = 1024 +API_SLEEP_TIME = 0.2 # Seconds. +REDIRECT_SUFFIXES = [".html", "/"] +TIMEOUT_SECONDS = 5 +HTTP = None def parse_command_line_args(): - parser = argparse.ArgumentParser( - description="Create page redirects for a specific branch of the docs." - ) + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument( - "csv_file", + "-f", + "--file", + metavar="file", + default="redirects.csv", type=str, - help="Path to a CSV file with three columns: source, destination, branch.", + help="Path to a CSV file used to keep a list of redirects, containing two columns: source and destination.", + ) + parser.add_argument( + "--delete", + action="store_true", + help="Deletes all currently setup 'page' and 'exact' redirects on ReadTheDocs.", ) - # add dry-run argument parser.add_argument( - "-d", "--dry-run", action="store_true", - help="Run the program and output information without side effects.", + help="Safe mode: Run the program and output information without any calls to the ReadTheDocs API.", + ) + parser.add_argument( + "--dump", + action="store_true", + help="Only dumps or deletes (if --delete) existing RTD redirects, skips submission.", + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + help="Enables verbose output.", ) return parser.parse_args() -def make_redirect(source, destination, branch, args): - # Currently, the program only works for the EN version of the docs - trimmed_source = source.replace(".rst", "") - trimmed_destination = destination.replace(".rst", "") +def make_redirect(source, destination, args, retry=0): + json_data = {"from_url": source, "to_url": destination, "type": "page"} + headers = REQUEST_HEADERS + + if args.verbose: + print("POST " + REDIRECT_URL, headers, json_data) - source_slug = f"/en/{branch}/{trimmed_source}" - destination_slug = f"/en/{branch}/{trimmed_destination}" - json_data = {"from_url": source_slug, "to_url": destination_slug, "type": "page"} if args.dry_run: - print(f"{source_slug} -> {destination_slug}") + print(f"Created redirect {source} -> {destination} (DRY RUN)") + return + + response = HTTP.post( + REDIRECT_URL, + json=json_data, + headers=headers, + timeout=TIMEOUT_SECONDS + ) + + if response.status_code == 201: + print(f"Created redirect {source} -> {destination}") + elif response.status_code == 429 and retry<5: + retry += 1 + time.sleep(retry*retry) + make_redirect(source, destination, args, retry) + return else: - response = requests.post( - REDIRECT_URL, - json=json.dumps(json_data), - headers=REQUEST_HEADERS, + print( + f"Failed to create redirect {source} -> {destination}. " + f"Status code: {response.status_code}" ) - if response.status_code == 201: - print(f"Created redirect {source_slug} -> {destination_slug}") + exit(1) + + +def sleep(): + time.sleep(API_SLEEP_TIME) + + +def id(from_url, to_url): + return from_url + " -> " + to_url + + +def get_paginated(url, parameters={"limit": DEFAULT_PAGINATED_SIZE}): + entries = [] + count = -1 + while True: + data = HTTP.get( + url, + headers=REQUEST_HEADERS, + params=parameters, + timeout=TIMEOUT_SECONDS + ) + if data.status_code != 200: + if data.status_code == 401: + print("Access denied, check RTD API key in RTD_AUTH_TOKEN!") + print("Error accessing RTD API: " + url + ": " + str(data.status_code)) + exit(1) else: + json = data.json() + if json["count"] and count < 0: + count = json["count"] + entries.extend(json["results"]) + next = json["next"] + if next and len(next) > 0 and next != url: + url = next + sleep() + continue + if count > 0 and len(entries) != count: print( - f"Failed to create redirect {source_slug} -> {destination_slug}. " - f"Status code: {response.status_code}" + "Mismatch getting paginated content from " + url + ": " + + "expected " + str(count) + " items, got " + str(len(entries))) + exit(1) + return entries + + +def delete_redirect(id): + url = REDIRECT_URL + str(id) + data = HTTP.delete(url, headers=REQUEST_HEADERS, timeout=TIMEOUT_SECONDS) + if data.status_code != 204: + print("Error deleting redirect with ID", id, "- code:", data.status_code) + exit(1) + else: + print("Deleted redirect", id, "on RTD.") + + +def get_existing_redirects(delete=False): + redirs = get_paginated(REDIRECT_URL) + existing = [] + for redir in redirs: + if redir["type"] != "page": + print( + "Ignoring redirect (only type 'page' is handled): #" + + str(redir["pk"]) + " " + id(redir["from_url"], redir["to_url"]) + + " on ReadTheDocs is '" + redir["type"] + "'. " ) + continue + if delete: + delete_redirect(redir["pk"]) + sleep() + else: + existing.append([redir["from_url"], redir["to_url"]]) + return existing + + +def set_auth(token): + global RTD_AUTH_TOKEN + RTD_AUTH_TOKEN = token + global REQUEST_HEADERS + REQUEST_HEADERS = {"Authorization": f"token {RTD_AUTH_TOKEN}", "User-Agent": USER_AGENT} + + +def load_auth(): + try: + import dotenv + dotenv.load_dotenv() + except: + print("Failed to load dotenv. If you want to use .env files, install the dotenv.") + token = os.environ.get("RTD_AUTH_TOKEN", "") + if len(token) < 1: + print("Missing auth token in RTD_AUTH_TOKEN env var or .env file not found. Aborting.") + exit(1) + set_auth(token) + + +def has_suffix(s, suffixes): + for suffix in suffixes: + if s.endswith(suffix): + return True + return False + + +def is_valid_redirect_url(url): + if len(url) < len("/a"): + return False + + if not has_suffix(url.lower(), REDIRECT_SUFFIXES): + return False + + return True + + +def redirect_to_str(item): + return id(item[0], item[1]) def main(): args = parse_command_line_args() - redirect_data = [] - with open(args.csv_file, "r") as f: - redirect_data = list(csv.DictReader(f)) - assert redirect_data[0].keys() == { - "source", - "destination", - "branch", - }, "CSV file must have those three columns: source, destination, branch." - for row in redirect_data: - make_redirect(row["source"], row["destination"], row["branch"], args) + + if not args.dry_run: + load_auth() + + retry_strategy = Retry( + total=3, + status_forcelist=[429, 500, 502, 503, 504], + backoff_factor=2, + method_whitelist=["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE"] + ) + adapter = HTTPAdapter(max_retries=retry_strategy) + global HTTP + HTTP = requests.Session() + HTTP.mount("https://", adapter) + HTTP.mount("http://", adapter) + + to_add = [] + redirects_file = [] + with open(args.file, "r", encoding="utf-8") as f: + redirects_file = list(csv.DictReader(f)) + if len(redirects_file) > 0: + assert redirects_file[0].keys() == { + "source", + "destination", + }, "CSV file must have a header and two columns: source, destination." + + for row in redirects_file: + to_add.append([row["source"], row["destination"]]) + print("Loaded", len(redirects_file), "redirects from", args.file + ".") + + existing = [] + if not args.dry_run: + existing = get_existing_redirects(args.delete) + print("Loaded", len(existing), "existing redirects from RTD.") + + print("Total redirects:", str(len(to_add)) + + " (+" + str(len(existing)), "existing.)") + + redirects = [] + added = {} + for redirect in to_add: + if len(redirect) != 2: + print("Invalid redirect:", redirect, "- expected 2 elements, got:", len(redirect)) + continue + + if redirect[0] == redirect[1]: + print("Invalid redirect:", redirect, "- redirects to itself!") + continue + + if not is_valid_redirect_url(redirect[0]) or not is_valid_redirect_url(redirect[1]): + print("Invalid redirect:", redirect, "- invalid URL!") + continue + + if not redirect[0].startswith("/") or not redirect[1].startswith("/"): + print("Invalid redirect:", redirect, "- invalid URL: should start with slash!") + continue + + redirect_id = id(redirect[0], redirect[1]) + if redirect_id in added: + # Duplicate; skip. + continue + + reverse_id = id(redirect[1], redirect[0]) + if reverse_id in added: + # Duplicate; skip. + continue + + added[redirect_id] = True + redirects.append(redirect) + + redirects.sort(key=redirect_to_str) + + with open(args.file, "w", encoding="utf-8", newline="") as f: + writer = csv.writer(f) + writer.writerows([["source", "destination"]]) + writer.writerows(redirects) + + existing_ids = {} + for e in existing: + existing_ids[id(e[0], e[1])] = True + + if not args.dump: + print("Creating redirects.") + for redirect in redirects: + if not id(redirect[0], redirect[1]) in existing_ids: + make_redirect(redirect[0], redirect[1], args) + + if not id(redirect[1], redirect[0]) in existing_ids: + make_redirect(redirect[1], redirect[0], args) + + if not args.dry_run: + sleep() + + print("Finished creating", len(redirects), "redirects.") + print("(" + str(2*len(redirects)) + " including reverse redirects.)") + + if args.dry_run: + print("THIS WAS A DRY RUN, NOTHING WAS SUBMITTED TO READTHEDOCS!") if __name__ == "__main__": diff --git a/_tools/redirects/redirects.csv b/_tools/redirects/redirects.csv new file mode 100644 index 000000000..b6a89ed56 --- /dev/null +++ b/_tools/redirects/redirects.csv @@ -0,0 +1,390 @@ +source,destination +/classes/_classes.html,/classes/ +/classes/class_bulletphysicsserver.html,/classes/class_gltfdocument.html +/community/tutorials/3d/mesh_generation_with_heightmap_and_shaders.html,/tutorials/3d/mesh_generation_with_heightmap_and_shaders.html +/community/tutorials/gdnative/gdnative-c-example.html,/tutorials/plugins/gdnative/gdnative-c-example.html +/community/tutorials/gdnative/index.html,/tutorials/plugins/gdnative/index.html +/community/tutorials/vr/index.html,/tutorials/vr/index.html +/community/tutorials/vr/vr_primer.html,/tutorials/vr/vr_primer.html +/content/3d/making_trees.html,/tutorials/content/making_trees.html +/contributing/_contributing.html,/community/contributing/ +/contributing/bug_triage_guidelines.html,/community/contributing/bug_triage_guidelines.html +/contributing/doc_and_l10n_guidelines.html,/community/contributing/doc_and_l10n_guidelines.html +/contributing/updating_the_class_reference.html,/community/contributing/updating_the_class_reference.html +/development/consoles/consoles.html,/tutorials/platform/consoles.html +/development/plugins/import_plugins.html,/tutorials/plugins/editor/import_plugins.html +/development/plugins/index.html,/tutorials/plugins/editor/index.html +/development/plugins/making_plugins.html,/tutorials/plugins/editor/making_plugins.html +/getting_started/editor/command_line_tutorial.html,/tutorials/editor/command_line_tutorial.html +/getting_started/editor/default_key_mapping.html,/tutorials/editor/default_key_mapping.html +/getting_started/editor/external_editor.html,/tutorials/editor/external_editor.html +/getting_started/editor/using_the_web_editor.html,/tutorials/editor/using_the_web_editor.html +/getting_started/scripting/c_sharp/c_sharp_basics.html,/tutorials/scripting/c_sharp/c_sharp_basics.html +/getting_started/scripting/c_sharp/c_sharp_differences.html,/tutorials/scripting/c_sharp/c_sharp_differences.html +/getting_started/scripting/c_sharp/c_sharp_features.html,/tutorials/scripting/c_sharp/c_sharp_features.html +/getting_started/scripting/c_sharp/c_sharp_style_guide.html,/tutorials/scripting/c_sharp/c_sharp_style_guide.html +/getting_started/scripting/c_sharp/index.html,/tutorials/scripting/c_sharp/index.html +/getting_started/scripting/creating_script_templates.html,/tutorials/scripting/creating_script_templates.html +/getting_started/scripting/cross_language_scripting.html,/tutorials/scripting/cross_language_scripting.html +/getting_started/scripting/gdscript/gdscript_advanced.html,/tutorials/scripting/gdscript/gdscript_advanced.html +/getting_started/scripting/gdscript/gdscript_basics.html,/tutorials/scripting/gdscript/gdscript_basics.html +/getting_started/scripting/gdscript/gdscript_exports.html,/tutorials/scripting/gdscript/gdscript_exports.html +/getting_started/scripting/gdscript/gdscript_format_string.html,/tutorials/scripting/gdscript/gdscript_format_string.html +/getting_started/scripting/gdscript/gdscript_styleguide.html,/tutorials/scripting/gdscript/gdscript_styleguide.html +/getting_started/scripting/gdscript/index.html,/tutorials/scripting/gdscript/index.html +/getting_started/scripting/gdscript/static_typing.html,/tutorials/scripting/gdscript/static_typing.html +/getting_started/scripting/gdscript/warning_system.html,/tutorials/scripting/gdscript/warning_system.html +/getting_started/scripting/visual_script/custom_visualscript_nodes.html,/tutorials/scripting/visual_script/custom_visualscript_nodes.html +/getting_started/scripting/visual_script/getting_started.html,/tutorials/scripting/visual_script/getting_started.html +/getting_started/scripting/visual_script/index.html,/tutorials/scripting/visual_script/index.html +/getting_started/scripting/visual_script/nodes_purposes.html,/tutorials/scripting/visual_script/nodes_purposes.html +/getting_started/scripting/visual_script/what_is_visual_scripting.html,/tutorials/scripting/visual_script/what_is_visual_scripting.html +/getting_started/step_by_step/exporting.html,/tutorials/export/exporting_basics.html +/getting_started/step_by_step/filesystem.html,/tutorials/scripting/filesystem.html +/getting_started/step_by_step/godot_design_philosophy.html,/getting_started/introduction/godot_design_philosophy.html +/getting_started/step_by_step/resources.html,/tutorials/scripting/resources.html +/getting_started/step_by_step/scene_tree.html,/tutorials/scripting/scene_tree.html +/getting_started/step_by_step/singletons_autoload.html,/tutorials/scripting/singletons_autoload.html +/getting_started/workflow/assets/escn_exporter/animation.html,/tutorials/assets_pipeline/escn_exporter/animation.html +/getting_started/workflow/assets/escn_exporter/index.html,/tutorials/assets_pipeline/escn_exporter/index.html +/getting_started/workflow/assets/escn_exporter/lights.html,/tutorials/assets_pipeline/escn_exporter/lights.html +/getting_started/workflow/assets/escn_exporter/material.html,/tutorials/assets_pipeline/escn_exporter/material.html +/getting_started/workflow/assets/escn_exporter/mesh.html,/tutorials/assets_pipeline/escn_exporter/mesh.html +/getting_started/workflow/assets/escn_exporter/physics.html,/tutorials/assets_pipeline/escn_exporter/physics.html +/getting_started/workflow/assets/escn_exporter/skeleton.html,/tutorials/assets_pipeline/escn_exporter/skeleton.html +/getting_started/workflow/assets/import_process.html,/tutorials/assets_pipeline/import_process.html +/getting_started/workflow/assets/importing_audio_samples.html,/tutorials/assets_pipeline/importing_audio_samples.html +/getting_started/workflow/assets/importing_images.html,/tutorials/assets_pipeline/importing_images.html +/getting_started/workflow/assets/importing_scenes.html,/tutorials/assets_pipeline/importing_scenes.html +/getting_started/workflow/assets/importing_translations.html,/tutorials/assets_pipeline/importing_translations.html +/getting_started/workflow/assets/index.html,/tutorials/assets_pipeline/index.html +/getting_started/workflow/best_practices/autoloads_versus_internal_nodes.html,/tutorials/best_practices/autoloads_versus_internal_nodes.html +/getting_started/workflow/best_practices/data_preferences.html,/tutorials/best_practices/data_preferences.html +/getting_started/workflow/best_practices/godot_interfaces.html,/tutorials/best_practices/godot_interfaces.html +/getting_started/workflow/best_practices/godot_notifications.html,/tutorials/best_practices/godot_notifications.html +/getting_started/workflow/best_practices/index.html,/tutorials/best_practices/index.html +/getting_started/workflow/best_practices/introduction_best_practices.html,/tutorials/best_practices/introduction_best_practices.html +/getting_started/workflow/best_practices/logic_preferences.html,/tutorials/best_practices/logic_preferences.html +/getting_started/workflow/best_practices/node_alternatives.html,/tutorials/best_practices/node_alternatives.html +/getting_started/workflow/best_practices/scene_organization.html,/tutorials/best_practices/scene_organization.html +/getting_started/workflow/best_practices/scenes_versus_scripts.html,/tutorials/best_practices/scenes_versus_scripts.html +/getting_started/workflow/best_practices/what_are_godot_classes.html,/tutorials/best_practices/what_are_godot_classes.html +/getting_started/workflow/export/android_custom_build.html,/tutorials/export/android_custom_build.html +/getting_started/workflow/export/changing_application_icon_for_windows.html,/tutorials/export/changing_application_icon_for_windows.html +/getting_started/workflow/export/exporting_for_android.html,/tutorials/export/exporting_for_android.html +/getting_started/workflow/export/exporting_for_dedicated_servers.html,/tutorials/export/exporting_for_dedicated_servers.html +/getting_started/workflow/export/exporting_for_ios.html,/tutorials/export/exporting_for_ios.html +/getting_started/workflow/export/exporting_for_uwp.html,/tutorials/export/exporting_for_uwp.html +/getting_started/workflow/export/exporting_for_web.html,/tutorials/export/exporting_for_web.html +/getting_started/workflow/export/exporting_pcks.html,/tutorials/export/exporting_pcks.html +/getting_started/workflow/export/exporting_projects.html,/tutorials/export/exporting_projects.html +/getting_started/workflow/export/feature_tags.html,/tutorials/export/feature_tags.html +/getting_started/workflow/export/index.html,/tutorials/export/index.html +/getting_started/workflow/export/one-click_deploy.html,/tutorials/export/one-click_deploy.html +/getting_started/workflow/project_setup/project_organization.html,/tutorials/best_practices/project_organization.html +/getting_started/workflow/project_setup/version_control_systems.html,/tutorials/best_practices/version_control_systems.html +/learning/editor/2d_and_3d_keybindings.html,/getting_started/editor/2d_and_3d_keybindings.html +/learning/editor/command_line_tutorial.html,/getting_started/editor/command_line_tutorial.html +/learning/editor/index.html,/getting_started/editor/index.html +/learning/editor/unity_to_godot.html,/getting_started/editor/unity_to_godot.html +/learning/features/2d/2d_transforms.html,/tutorials/2d/2d_transforms.html +/learning/features/2d/canvas_layers.html,/tutorials/2d/canvas_layers.html +/learning/features/2d/custom_drawing_in_2d.html,/tutorials/2d/custom_drawing_in_2d.html +/learning/features/2d/index.html,/tutorials/2d/index.html +/learning/features/2d/particle_systems_2d.html,/tutorials/2d/particle_systems_2d.html +/learning/features/2d/using_tilemaps.html,/tutorials/2d/using_tilemaps.html +/learning/features/3d/3d_performance_and_limitations.html,/tutorials/3d/3d_performance_and_limitations.html +/learning/features/3d/baked_lightmaps.html,/tutorials/3d/baked_lightmaps.html +/learning/features/3d/environment_and_post_processing.html,/tutorials/3d/environment_and_post_processing.html +/learning/features/3d/gi_probes.html,/tutorials/3d/gi_probes.html +/learning/features/3d/high_dynamic_range.html,/tutorials/3d/high_dynamic_range.html +/learning/features/3d/index.html,/tutorials/3d/index.html +/learning/features/3d/introduction_to_3d.html,/tutorials/3d/introduction_to_3d.html +/learning/features/3d/lights_and_shadows.html,/tutorials/3d/lights_and_shadows.html +/learning/features/3d/reflection_probes.html,/tutorials/3d/reflection_probes.html +/learning/features/3d/spatial_material.html,/tutorials/3d/spatial_material.html +/learning/features/3d/using_gridmaps.html,/tutorials/3d/using_gridmaps.html +/learning/features/animation/cutout_animation.html,/tutorials/animation/cutout_animation.html +/learning/features/animation/index.html,/tutorials/animation/index.html +/learning/features/animation/introduction_2d.html,/tutorials/animation/introduction_2d.html +/learning/features/assetlib/index.html,/tutorials/assetlib/index.html +/learning/features/assetlib/uploading_to_assetlib.html,/tutorials/assetlib/uploading_to_assetlib.html +/learning/features/assetlib/using_assetlib.html,/tutorials/assetlib/using_assetlib.html +/learning/features/assetlib/what_is_assetlib.html,/tutorials/assetlib/what_is_assetlib.html +/learning/features/audio/audio_buses.html,/tutorials/audio/audio_buses.html +/learning/features/audio/audio_streams.html,/tutorials/audio/audio_streams.html +/learning/features/audio/index.html,/tutorials/audio/index.html +/learning/features/gui/bbcode_in_richtextlabel.html,/tutorials/gui/bbcode_in_richtextlabel.html +/learning/features/gui/custom_gui_controls.html,/tutorials/gui/custom_gui_controls.html +/learning/features/gui/gui_skinning.html,/tutorials/gui/gui_skinning.html +/learning/features/gui/index.html,/tutorials/gui/index.html +/learning/features/gui/size_and_anchors.html,/tutorials/gui/size_and_anchors.html +/learning/features/inputs/index.html,/tutorials/inputs/index.html +/learning/features/inputs/inputevent.html,/tutorials/inputs/inputevent.html +/learning/features/inputs/mouse_and_input_coordinates.html,/tutorials/inputs/mouse_and_input_coordinates.html +/learning/features/math/index.html,/tutorials/math/index.html +/learning/features/math/matrices_and_transforms.html,/tutorials/math/matrices_and_transforms.html +/learning/features/math/vector_math.html,/tutorials/math/vector_math.html +/learning/features/math/vectors_advanced.html,/tutorials/math/vectors_advanced.html +/learning/features/misc/background_loading.html,/tutorials/misc/background_loading.html +/learning/features/misc/binary_serialization_api.html,/tutorials/misc/binary_serialization_api.html +/learning/features/misc/data_paths.html,/tutorials/misc/data_paths.html +/learning/features/misc/encrypting_save_games.html,/tutorials/misc/encrypting_save_games.html +/learning/features/misc/handling_quit_requests.html,/tutorials/misc/handling_quit_requests.html +/learning/features/misc/index.html,/tutorials/misc/index.html +/learning/features/misc/internationalizing_games.html,/tutorials/misc/internationalizing_games.html +/learning/features/misc/locales.html,/tutorials/misc/locales.html +/learning/features/misc/pausing_games.html,/tutorials/misc/pausing_games.html +/learning/features/misc/saving_games.html,/tutorials/misc/saving_games.html +/learning/features/networking/high_level_multiplayer.html,/tutorials/networking/high_level_multiplayer.html +/learning/features/networking/http_client_class.html,/tutorials/networking/http_client_class.html +/learning/features/networking/index.html,/tutorials/networking/index.html +/learning/features/networking/ssl_certificates.html,/tutorials/networking/ssl_certificates.html +/learning/features/physics/index.html,/tutorials/physics/index.html +/learning/features/physics/kinematic_character_2d.html,/tutorials/physics/kinematic_character_2d.html +/learning/features/physics/physics_introduction.html,/tutorials/physics/physics_introduction.html +/learning/features/physics/ray-casting.html,/tutorials/physics/ray-casting.html +/learning/features/platform/android_in_app_purchases.html,/tutorials/platform/android_in_app_purchases.html +/learning/features/platform/index.html,/tutorials/platform/index.html +/learning/features/platform/services_for_ios.html,/tutorials/platform/services_for_ios.html +/learning/features/shading/index.html,/tutorials/shading/index.html +/learning/features/shading/screen-reading_shaders.html,/tutorials/shading/screen-reading_shaders.html +/learning/features/shading/shader_materials.html,/tutorials/shading/shader_materials.html +/learning/features/shading/shading_language.html,/tutorials/shading/shading_language.html +/learning/features/viewports/index.html,/tutorials/viewports/index.html +/learning/features/viewports/multiple_resolutions.html,/tutorials/viewports/multiple_resolutions.html +/learning/features/viewports/viewports.html,/tutorials/viewports/viewports.html +/learning/scripting/c_sharp/c_sharp_basics.html,/getting_started/scripting/c_sharp/c_sharp_basics.html +/learning/scripting/c_sharp/c_sharp_differences.html,/getting_started/scripting/c_sharp/c_sharp_differences.html +/learning/scripting/c_sharp/c_sharp_features.html,/getting_started/scripting/c_sharp/c_sharp_features.html +/learning/scripting/c_sharp/index.html,/getting_started/scripting/c_sharp/index.html +/learning/scripting/gdscript/gdscript_advanced.html,/getting_started/scripting/gdscript/gdscript_advanced.html +/learning/scripting/gdscript/gdscript_basics.html,/getting_started/scripting/gdscript/gdscript_basics.html +/learning/scripting/gdscript/gdscript_format_string.html,/getting_started/scripting/gdscript/gdscript_format_string.html +/learning/scripting/gdscript/gdscript_styleguide.html,/getting_started/scripting/gdscript/gdscript_styleguide.html +/learning/scripting/gdscript/index.html,/getting_started/scripting/gdscript/index.html +/learning/scripting/index.html,/getting_started/scripting/index.html +/learning/scripting/visual_script/getting_started.html,/getting_started/scripting/visual_script/getting_started.html +/learning/scripting/visual_script/index.html,/getting_started/scripting/visual_script/index.html +/learning/scripting/visual_script/nodes_purposes.html,/getting_started/scripting/visual_script/nodes_purposes.html +/learning/scripting/visual_script/what_is_visual_scripting.html,/getting_started/scripting/visual_script/what_is_visual_scripting.html +/learning/step_by_step/animations.html,/getting_started/step_by_step/animations.html +/learning/step_by_step/filesystem.html,/getting_started/step_by_step/filesystem.html +/learning/step_by_step/godot_design_philosophy.html,/getting_started/step_by_step/godot_design_philosophy.html +/learning/step_by_step/index.html,/getting_started/step_by_step/index.html +/learning/step_by_step/instancing.html,/getting_started/step_by_step/instancing.html +/learning/step_by_step/instancing_continued.html,/getting_started/step_by_step/instancing_continued.html +/learning/step_by_step/intro_to_the_editor_interface.html,/getting_started/step_by_step/intro_to_the_editor_interface.html +/learning/step_by_step/resources.html,/getting_started/step_by_step/resources.html +/learning/step_by_step/scene_tree.html,/getting_started/step_by_step/scene_tree.html +/learning/step_by_step/scenes_and_nodes.html,/getting_started/step_by_step/scenes_and_nodes.html +/learning/step_by_step/scripting.html,/getting_started/step_by_step/scripting.html +/learning/step_by_step/scripting_continued.html,/getting_started/step_by_step/scripting_continued.html +/learning/step_by_step/singletons_autoload.html,/getting_started/step_by_step/singletons_autoload.html +/learning/step_by_step/splash_screen.html,/getting_started/step_by_step/splash_screen.html +/learning/step_by_step/ui_code_a_life_bar.html,/getting_started/step_by_step/ui_code_a_life_bar.html +/learning/step_by_step/ui_game_user_interface.html,/getting_started/step_by_step/ui_game_user_interface.html +/learning/step_by_step/ui_introduction_to_the_ui_system.html,/getting_started/step_by_step/ui_introduction_to_the_ui_system.html +/learning/step_by_step/ui_main_menu.html,/getting_started/step_by_step/ui_main_menu.html +/learning/step_by_step/your_first_game.html,/getting_started/step_by_step/your_first_game.html +/learning/workflow/assets/import_process.html,/getting_started/workflow/assets/import_process.html +/learning/workflow/assets/importing_audio_samples.html,/getting_started/workflow/assets/importing_audio_samples.html +/learning/workflow/assets/importing_images.html,/getting_started/workflow/assets/importing_images.html +/learning/workflow/assets/importing_scenes.html,/getting_started/workflow/assets/importing_scenes.html +/learning/workflow/assets/importing_translations.html,/getting_started/workflow/assets/importing_translations.html +/learning/workflow/assets/index.html,/getting_started/workflow/assets/index.html +/learning/workflow/export/customizing_html5_shell.html,/getting_started/workflow/export/customizing_html5_shell.html +/learning/workflow/export/exporting_for_android.html,/getting_started/workflow/export/exporting_for_android.html +/learning/workflow/export/exporting_for_ios.html,/getting_started/workflow/export/exporting_for_ios.html +/learning/workflow/export/exporting_for_pc.html,/getting_started/workflow/export/exporting_for_pc.html +/learning/workflow/export/exporting_for_uwp.html,/getting_started/workflow/export/exporting_for_uwp.html +/learning/workflow/export/exporting_for_web.html,/getting_started/workflow/export/exporting_for_web.html +/learning/workflow/export/exporting_projects.html,/getting_started/workflow/export/exporting_projects.html +/learning/workflow/export/feature_tags.html,/getting_started/workflow/export/feature_tags.html +/learning/workflow/export/index.html,/getting_started/workflow/export/index.html +/learning/workflow/export/one-click_deploy.html,/getting_started/workflow/export/one-click_deploy.html +/learning/workflow/index.html,/getting_started/workflow/index.html +/learning/workflow/project_setup/index.html,/getting_started/workflow/project_setup/index.html +/learning/workflow/project_setup/project_organization.html,/getting_started/workflow/project_setup/project_organization.html +/reference/2d_and_3d_keybindings.html,/learning/editor/2d_and_3d_keybindings.html +/reference/_compiling.html,/development/compiling/ +/reference/_developing.html,/development/cpp/ +/reference/android_in_app_purchases.html,/learning/features/platform/android_in_app_purchases.html +/reference/batch_building_templates.html,/development/compiling/batch_building_templates.html +/reference/bbcode_in_richtextlabel.html,/learning/features/gui/bbcode_in_richtextlabel.html +/reference/binary_serialization_api.html,/learning/features/misc/binary_serialization_api.html +/reference/command_line_tutorial.html,/learning/editor/command_line_tutorial.html +/reference/compiling_for_android.html,/development/compiling/compiling_for_android.html +/reference/compiling_for_ios.html,/development/compiling/compiling_for_ios.html +/reference/compiling_for_osx.html,/development/compiling/compiling_for_osx.html +/reference/compiling_for_uwp.html,/development/compiling/compiling_for_uwp.html +/reference/compiling_for_web.html,/development/compiling/compiling_for_web.html +/reference/compiling_for_windows.html,/development/compiling/compiling_for_windows.html +/reference/compiling_for_x11.html,/development/compiling/compiling_for_x11.html +/reference/configuring_an_ide.html,/development/cpp/configuring_an_ide.html +/reference/core_types.html,/development/cpp/core_types.html +/reference/creating_android_modules.html,/development/cpp/creating_android_modules.html +/reference/cross-compiling_for_ios_on_linux.html,/development/compiling/cross-compiling_for_ios_on_linux.html +/reference/custom_modules_in_c++.html,/development/cpp/custom_modules_in_cpp.html +/reference/faq.html,/about/faq.html +/reference/gdscript.html,/learning/scripting/gdscript/gdscript_basics.html +/reference/gdscript_more_efficiently.html,/learning/scripting/gdscript/gdscript_advanced.html +/reference/gdscript_printf.html,/learning/scripting/gdscript/gdscript_format_string.html +/reference/inheritance_class_tree.html,/development/cpp/inheritance_class_tree.html +/reference/introduction_to_godot_development.html,/development/cpp/introduction_to_godot_development.html +/reference/introduction_to_the_buildsystem.html,/development/compiling/introduction_to_the_buildsystem.html +/reference/locales.html,/learning/features/misc/locales.html +/reference/object_class.html,/development/cpp/object_class.html +/reference/packaging_godot.html,/development/compiling/packaging_godot.html +/reference/services_for_ios.html,/learning/features/platform/services_for_ios.html +/reference/shading_language.html,/learning/features/shading/shading_language.html +/reference/unity_to_godot.html,/learning/editor/unity_to_godot.html +/reference/variant_class.html,/development/cpp/variant_class.html +/tutorials/2d/_2d.html,/learning/features/2d/ +/tutorials/2d/_2d_gui.html,/learning/features/gui/ +/tutorials/2d/_2d_physics.html,/learning/features/physics/ +/tutorials/2d/custom_gui_controls.html,/learning/features/gui/custom_gui_controls.html +/tutorials/2d/cutout_animation.html,/learning/features/animation/cutout_animation.html +/tutorials/2d/gui_skinning.html,/learning/features/gui/gui_skinning.html +/tutorials/2d/kinematic_character_2d.html,/learning/features/physics/kinematic_character_2d.html +/tutorials/2d/physics_introduction.html,/learning/features/physics/physics_introduction.html +/tutorials/2d/screen-reading_shaders.html,/learning/features/shading/screen-reading_shaders.html +/tutorials/2d/size_and_anchors.html,/learning/features/gui/size_and_anchors.html +/tutorials/2d/viewport_and_canvas_transforms.html,/learning/features/2d/2d_transforms.html +/tutorials/3d/_3d.html,/learning/features/3d/ +/tutorials/3d/_3d_physics.html,/learning/features/physics/ +/tutorials/3d/fixed_materials.html,/learning/features/3d/fixed_materials.html +/tutorials/3d/importing_3d_meshes.html,/learning/features/3d/importing_3d_meshes.html +/tutorials/3d/importing_3d_scenes.html,/learning/features/3d/importing_3d_scenes.html +/tutorials/3d/inverse_kinematics.html,/community/tutorials/3d/inverse_kinematics.html +/tutorials/3d/lighting.html,/learning/features/lighting/lighting.html +/tutorials/3d/materials.html,/learning/features/3d/materials.html +/tutorials/3d/shader_materials.html,/learning/features/shading/shader_materials.html +/tutorials/3d/shadow_mapping.html,/learning/features/lighting/shadow_mapping.html +/tutorials/3d/vertex_animation/animating_thousands_of_fish.html,/tutorials/performance/vertex_animation/animating_thousands_of_fish.html +/tutorials/3d/vertex_animation/controlling_thousands_of_fish.html,/tutorials/performance/vertex_animation/controlling_thousands_of_fish.html +/tutorials/3d/vertex_animation/index.html,/tutorials/performance/vertex_animation/index.html +/tutorials/3d/working_with_3d_skeletons.html,/community/tutorials/3d/working_with_3d_skeletons.html +/tutorials/_math.html,/learning/features/math/ +/tutorials/_misc_tutorials.html,/learning/features/misc/ +/tutorials/_networking.html,/learning/features/networking/ +/tutorials/_plugins.html,/development/plugins/ +/tutorials/_shaders.html,/learning/features/shading/ +/tutorials/asset_pipeline/_asset_pipeline.html,/learning/workflow/ +/tutorials/asset_pipeline/_export.html,/learning/workflow/export/ +/tutorials/asset_pipeline/_import.html,/learning/workflow/assets/ +/tutorials/asset_pipeline/exporting_for_android.html,/learning/workflow/export/exporting_for_android.html +/tutorials/asset_pipeline/exporting_for_ios.html,/learning/workflow/export/exporting_for_ios.html +/tutorials/asset_pipeline/exporting_for_pc.html,/learning/workflow/export/exporting_for_pc.html +/tutorials/asset_pipeline/exporting_for_uwp.html,/learning/workflow/export/exporting_for_uwp.html +/tutorials/asset_pipeline/exporting_for_web.html,/learning/workflow/export/exporting_for_web.html +/tutorials/asset_pipeline/exporting_images.html,/learning/workflow/assets/exporting_images.html +/tutorials/asset_pipeline/exporting_projects.html,/learning/workflow/export/exporting_projects.html +/tutorials/asset_pipeline/import_process.html,/learning/workflow/assets/import_process.html +/tutorials/asset_pipeline/importing_audio_samples.html,/learning/workflow/assets/importing_audio_samples.html +/tutorials/asset_pipeline/importing_fonts.html,/learning/workflow/assets/importing_fonts.html +/tutorials/asset_pipeline/importing_textures.html,/learning/workflow/assets/importing_textures.html +/tutorials/asset_pipeline/importing_translations.html,/learning/workflow/assets/importing_translations.html +/tutorials/asset_pipeline/managing_image_files.html,/learning/workflow/assets/managing_image_files.html +/tutorials/asset_pipeline/one-click_deploy.html,/learning/workflow/export/one-click_deploy.html +/tutorials/assetlib/index.html,/community/asset_library/index.html +/tutorials/assetlib/uploading_to_assetlib.html,/community/asset_library/uploading_to_assetlib.html +/tutorials/assetlib/using_assetlib.html,/community/asset_library/using_assetlib.html +/tutorials/assetlib/what_is_assetlib.html,/community/asset_library/what_is_assetlib.html +/tutorials/content/making_trees.html,/tutorials/shaders/making_trees.html +/tutorials/content/procedural_geometry/arraymesh.html,/tutorials/3d/procedural_geometry/arraymesh.html +/tutorials/content/procedural_geometry/immediategeometry.html,/tutorials/3d/procedural_geometry/immediategeometry.html +/tutorials/content/procedural_geometry/index.html,/tutorials/3d/procedural_geometry/index.html +/tutorials/content/procedural_geometry/meshdatatool.html,/tutorials/3d/procedural_geometry/meshdatatool.html +/tutorials/content/procedural_geometry/surfacetool.html,/tutorials/3d/procedural_geometry/surfacetool.html +/tutorials/debug/debugger_panel.html,/tutorials/scripting/debug/debugger_panel.html +/tutorials/debug/index.html,/tutorials/scripting/debug/index.html +/tutorials/debug/overview_of_debugging_tools.html,/tutorials/scripting/debug/overview_of_debugging_tools.html +/tutorials/engine/background_loading.html,/learning/features/misc/background_loading.html +/tutorials/engine/data_paths.html,/learning/features/misc/data_paths.html +/tutorials/engine/encrypting_save_games.html,/learning/features/misc/encrypting_save_games.html +/tutorials/engine/handling_quit_requests.html,/learning/features/misc/handling_quit_requests.html +/tutorials/engine/inputevent.html,/learning/features/inputs/inputevent.html +/tutorials/engine/internationalizing_games.html,/learning/features/misc/internationalizing_games.html +/tutorials/engine/mouse_and_input_coordinates.html,/learning/features/inputs/mouse_and_input_coordinates.html +/tutorials/engine/multiple_resolutions.html,/learning/features/viewports/multiple_resolutions.html +/tutorials/engine/pausing_games.html,/learning/features/misc/pausing_games.html +/tutorials/engine/project_organization.html,/learning/workflow/project_setup/project_organization.html +/tutorials/engine/saving_games.html,/learning/features/misc/saving_games.html +/tutorials/engine/viewports.html,/learning/features/viewports/viewports.html +/tutorials/gui/bbcode_in_richtextlabel.html,/tutorials/ui/bbcode_in_richtextlabel.html +/tutorials/gui/control_node_gallery.html,/tutorials/ui/control_node_gallery.html +/tutorials/gui/custom_gui_controls.html,/tutorials/ui/custom_gui_controls.html +/tutorials/gui/gui_containers.html,/tutorials/ui/gui_containers.html +/tutorials/gui/size_and_anchors.html,/tutorials/ui/size_and_anchors.html +/tutorials/high_level_multiplayer.html,/learning/features/networking/high_level_multiplayer.html +/tutorials/http_client_class.html,/learning/features/networking/http_client_class.html +/tutorials/legal/complying_with_licenses.html,/about/complying_with_licenses.html +/tutorials/making_plugins.html,/development/plugins/making_plugins.html +/tutorials/matrices_and_transforms.html,/learning/features/math/matrices_and_transforms.html +/tutorials/mesh_generation_with_heightmap_and_shaders.html,/community/tutorials/3d/mesh_generation_with_heightmap_and_shaders.html +/tutorials/misc/background_loading.html,/tutorials/io/background_loading.html +/tutorials/misc/binary_serialization_api.html,/tutorials/io/binary_serialization_api.html +/tutorials/misc/change_scenes_manually.html,/tutorials/scripting/change_scenes_manually.html +/tutorials/misc/data_paths.html,/tutorials/io/data_paths.html +/tutorials/misc/encrypting_save_games.html,/tutorials/io/encrypting_save_games.html +/tutorials/misc/gles2_gles3_differences.html,/tutorials/rendering/gles2_gles3_differences.html +/tutorials/misc/handling_quit_requests.html,/tutorials/inputs/handling_quit_requests.html +/tutorials/misc/instancing_with_signals.html,/tutorials/scripting/instancing_with_signals.html +/tutorials/misc/internationalizing_games.html,/tutorials/i18n/internationalizing_games.html +/tutorials/misc/jitter_stutter.html,/tutorials/rendering/jitter_stutter.html +/tutorials/misc/locales.html,/tutorials/i18n/locales.html +/tutorials/misc/pausing_games.html,/tutorials/scripting/pausing_games.html +/tutorials/misc/running_code_in_the_editor.html,/tutorials/plugins/running_code_in_the_editor.html +/tutorials/misc/saving_games.html,/tutorials/io/saving_games.html +/tutorials/optimization/batching.html,/tutorials/performance/batching.html +/tutorials/optimization/cpu_optimization.html,/tutorials/performance/cpu_optimization.html +/tutorials/optimization/general_optimization.html,/tutorials/performance/general_optimization.html +/tutorials/optimization/gpu_optimization.html,/tutorials/performance/gpu_optimization.html +/tutorials/optimization/index.html,/tutorials/performance/index.html +/tutorials/optimization/optimizing_3d_performance.html,/tutorials/performance/optimizing_3d_performance.html +/tutorials/optimization/using_multimesh.html,/tutorials/performance/using_multimesh.html +/tutorials/optimization/using_servers.html,/tutorials/performance/using_servers.html +/tutorials/platform/android_in_app_purchases.html,/tutorials/platform/android/android_in_app_purchases.html +/tutorials/plugins/android/android_plugin.html,/tutorials/platform/android/android_plugin.html +/tutorials/plugins/android/index.html,/tutorials/platform/android/index.html +/tutorials/plugins/gdnative/gdnative-c-example.html,/tutorials/scripting/gdnative/gdnative_c_example.html +/tutorials/plugins/gdnative/gdnative-cpp-example.html,/tutorials/scripting/gdnative/gdnative_cpp_example.html +/tutorials/plugins/gdnative/index.html,/tutorials/scripting/gdnative/index.html +/tutorials/ray-casting.html,/learning/features/physics/ray-casting.html +/tutorials/shading/advanced_postprocessing.html,/tutorials/shaders/advanced_postprocessing.html +/tutorials/shading/godot_shader_language_style_guide.html,/tutorials/shaders/shaders_style_guide.html +/tutorials/shading/index.html,/tutorials/shaders/index.html +/tutorials/shading/migrating_to_godot_shader_language.html,/tutorials/shaders/converting_glsl_to_godot_shaders.html +/tutorials/shading/screen-reading_shaders.html,/tutorials/shaders/screen-reading_shaders.html +/tutorials/shading/shader_materials.html,/tutorials/shaders/shader_materials.html +/tutorials/shading/shading_reference/canvas_item_shader.html,/tutorials/shaders/shader_reference/canvas_item_shader.html +/tutorials/shading/shading_reference/index.html,/tutorials/shaders/shader_reference/index.html +/tutorials/shading/shading_reference/particle_shader.html,/tutorials/shaders/shader_reference/particle_shader.html +/tutorials/shading/shading_reference/shading_language.html,/tutorials/shaders/shader_reference/shading_language.html +/tutorials/shading/shading_reference/spatial_shader.html,/tutorials/shaders/shader_reference/spatial_shader.html +/tutorials/shading/visual_shaders.html,/tutorials/shaders/visual_shaders.html +/tutorials/shading/your_first_shader/index.html,/tutorials/shaders/your_first_shader/index.html +/tutorials/shading/your_first_shader/your_second_spatial_shader.html,/tutorials/shaders/your_first_shader/your_second_3d_shader.html +/tutorials/ssl_certificates.html,/learning/features/networking/ssl_certificates.html +/tutorials/step_by_step/_step_by_step.html,/learning/step_by_step/ +/tutorials/step_by_step/animations.html,/learning/step_by_step/animations.html +/tutorials/step_by_step/filesystem.html,/learning/step_by_step/filesystem.html +/tutorials/step_by_step/gui_tutorial.html,/learning/step_by_step/gui_tutorial.html +/tutorials/step_by_step/instancing.html,/learning/step_by_step/instancing.html +/tutorials/step_by_step/instancing_continued.html,/learning/step_by_step/instancing_continued.html +/tutorials/step_by_step/resources.html,/learning/step_by_step/resources.html +/tutorials/step_by_step/scene_tree.html,/learning/step_by_step/scene_tree.html +/tutorials/step_by_step/scenes_and_nodes.html,/learning/step_by_step/scenes_and_nodes.html +/tutorials/step_by_step/scripting.html,/learning/step_by_step/scripting.html +/tutorials/step_by_step/scripting_continued.html,/learning/step_by_step/scripting_continued.html +/tutorials/step_by_step/simple_2d_game.html,/learning/step_by_step/simple_2d_game.html +/tutorials/step_by_step/singletons_autoload.html,/learning/step_by_step/singletons_autoload.html +/tutorials/step_by_step/splash_screen.html,/learning/step_by_step/splash_screen.html +/tutorials/threads/thread_safe_apis.html,/tutorials/performance/threads/thread_safe_apis.html +/tutorials/threads/using_multiple_threads.html,/tutorials/performance/threads/using_multiple_threads.html +/tutorials/vector_math.html,/learning/features/math/vector_math.html +/tutorials/viewports/custom_postprocessing.html,/tutorials/shaders/custom_postprocessing.html +/tutorials/viewports/multiple_resolutions.html,/tutorials/rendering/multiple_resolutions.html +/tutorials/viewports/using_viewport_as_texture.html,/tutorials/shaders/using_viewport_as_texture.html +/tutorials/viewports/viewports.html,/tutorials/rendering/viewports.html