Kaynağa Gözat

Merge pull request #9833 from ShawnHardern/update-csharp-docs

Add missing C# code examples to documentation
A Thousand Ships 11 ay önce
ebeveyn
işleme
d459bb9129

+ 9 - 1
about/introduction.rst

@@ -5,11 +5,19 @@
 Introduction
 ============
 
-::
+.. tabs::
+ .. code-tab:: gdscript
 
     func _ready():
         print("Hello world!")
 
+ .. code-tab:: csharp
+
+    public override void _Ready()
+    {
+        GD.Print("Hello world!");
+    }
+
 Welcome to the official documentation of **Godot Engine**, the free and open source
 community-driven 2D and 3D game engine! Behind this mouthful, you will find a
 powerful yet user-friendly tool that you can use to develop any kind of game,

+ 33 - 5
tutorials/i18n/internationalizing_games.rst

@@ -75,11 +75,17 @@ Translate** in the inspector.
 In code, the :ref:`Object.tr() <class_Object_method_tr>` function can be used.
 This will just look up the text in the translations and convert it if found:
 
-::
+.. tabs::
+ .. code-tab:: gdscript
 
     level.text = tr("LEVEL_5_NAME")
     status.text = tr("GAME_STATUS_%d" % status_index)
 
+ .. code-tab:: csharp
+
+    level.Text = Tr("LEVEL_5_NAME");
+    status.Text = Tr($"GAME_STATUS_{statusIndex}");
+
 .. note::
 
     If no text is displayed after changing the language, try to use a different
@@ -105,7 +111,8 @@ allows translations to sound more natural. Named placeholders with the
 ``String.format()`` function should be used whenever possible, as they also
 allow translators to choose the *order* in which placeholders appear:
 
-::
+.. tabs::
+ .. code-tab:: gdscript
 
     # The placeholder's locations can be changed, but not their order.
     # This will probably not suffice for some target languages.
@@ -125,7 +132,8 @@ optionally specify a *translation context* to resolve this ambiguity and allow
 target languages to use different strings, even though the source string is
 identical:
 
-::
+.. tabs::
+ .. code-tab:: gdscript
 
     # "Close", as in an action (to close something).
     button.set_text(tr("Close", "Actions"))
@@ -133,6 +141,14 @@ identical:
     # "Close", as in a distance (opposite of "far").
     distance_label.set_text(tr("Close", "Distance"))
 
+ .. code-tab:: csharp
+
+    // "Close", as in an action (to close something).
+    GetNode<Button>("Button").Text = Tr("Close", "Actions");
+
+    // "Close", as in a distance (opposite of "far").
+    GetNode<Label>("Distance").Text = Tr("Close", "Distance");
+
 Pluralization
 ^^^^^^^^^^^^^
 
@@ -148,18 +164,30 @@ Pluralization is meant to be used with positive (or zero) integer numbers only.
 Negative and floating-point values usually represent physical entities for which
 singular and plural don't clearly apply.
 
-::
+.. tabs::
+ .. code-tab:: gdscript
 
     var num_apples = 5
     label.text = tr_n("There is %d apple", "There are %d apples", num_apples) % num_apples
 
+ .. code-tab:: csharp
+
+    int numApples = 5;
+    GetNode<Label>("Label").Text = string.Format(TrN("There is {0} apple", "There are {0} apples", numApples), numApples);
+
 This can be combined with a context if needed:
 
-::
+.. tabs::
+ .. code-tab:: gdscript
 
     var num_jobs = 1
     label.text = tr_n("%d job", "%d jobs", num_jobs, "Task Manager") % num_jobs
 
+ .. code-tab:: csharp
+
+    int numJobs = 1;
+    GetNode<Label>("Label").Text = string.Format(TrN("{0} job", "{0} jobs", numJobs, "Task Manager"), numJobs);
+
 .. note::
 
     Providing pluralized translations is only supported with

+ 154 - 8
tutorials/io/runtime_file_loading_and_saving.rst

@@ -45,9 +45,10 @@ Plain text and binary files
 Godot's :ref:`class_FileAccess` class provides methods to access files on the
 filesystem for reading and writing:
 
-::
+.. tabs::
+ .. code-tab:: gdscript
 
-     func save_file(content):
+    func save_file(content):
         var file = FileAccess.open("/path/to/file.txt", FileAccess.WRITE)
         file.store_string(content)
 
@@ -56,6 +57,21 @@ filesystem for reading and writing:
         var content = file.get_as_text()
         return content
 
+ .. code-tab:: csharp
+
+    private void SaveFile(string content)
+    {
+        using var file = FileAccess.Open("/Path/To/File.txt", FileAccess.ModeFlags.Write);
+        file.StoreString(content);
+    }
+
+    private string LoadFile()
+    {
+        using var file = FileAccess.Open("/Path/To/File.txt", FileAccess.ModeFlags.Read);
+        string content = file.GetAsText();
+        return content;
+    }
+
 To handle custom binary formats (such as loading file formats not supported by
 Godot), :ref:`class_FileAccess` provides several methods to read/write integers,
 floats, strings and more. These FileAccess methods have names that start with
@@ -116,7 +132,8 @@ increase performance by reducing I/O utilization.
 Example of loading an image and displaying it in a :ref:`class_TextureRect` node
 (which requires conversion to :ref:`class_ImageTexture`):
 
-::
+.. tabs::
+ .. code-tab:: gdscript
 
     # Load an image of any format supported by Godot from the filesystem.
     var image = Image.load_from_file(path)
@@ -131,6 +148,21 @@ Example of loading an image and displaying it in a :ref:`class_TextureRect` node
     # Save the converted ImageTexture to a PNG image.
     $TextureRect.texture.get_image().save_png("/path/to/file.png")
 
+ .. code-tab:: csharp
+
+    // Load an image of any format supported by Godot from the filesystem.
+    var image = Image.LoadFromFile(path);
+    // Optionally, generate mipmaps if displaying the texture on a 3D surface
+    // so that the texture doesn't look grainy when viewed at a distance.
+    // image.GenerateMipmaps();
+    GetNode<TextureRect>("TextureRect").Texture = ImageTexture.CreateFromImage(image);
+
+    // Save the loaded Image to a PNG image.
+    image.SavePng("/Path/To/File.png");
+
+    // Save the converted ImageTexture to a PNG image.
+    GetNode<TextureRect>("TextureRect").Texture.GetImage().SavePng("/Path/To/File.png");
+
 .. _doc_runtime_file_loading_and_saving_audio_video_files:
 
 Audio/video files
@@ -143,13 +175,19 @@ load correctly as audio files in Godot.
 
 Example of loading an Ogg Vorbis audio file in an :ref:`class_AudioStreamPlayer` node:
 
-::
+.. tabs::
+ .. code-tab:: gdscript
 
     $AudioStreamPlayer.stream = AudioStreamOggVorbis.load_from_file(path)
 
+ .. code-tab:: csharp
+
+    GetNode<AudioStreamPlayer>("AudioStreamPlayer").Stream = AudioStreamOggVorbis.LoadFromFile(path);
+
 Example of loading an Ogg Theora video file in a :ref:`class_VideoStreamPlayer` node:
 
-::
+.. tabs::
+ .. code-tab:: gdscript
 
     var video_stream_theora = VideoStreamTheora.new()
     # File extension is ignored, so it is possible to load Ogg Theora videos
@@ -161,6 +199,18 @@ Example of loading an Ogg Theora video file in a :ref:`class_VideoStreamPlayer`
     # before this property is set, so call `play()` after setting `stream`.
     $VideoStreamPlayer.play()
 
+ .. code-tab:: csharp
+
+    var videoStreamTheora = new VideoStreamTheora();
+    // File extension is ignored, so it is possible to load Ogg Theora videos
+    // that have an `.ogg` extension this way.
+    videoStreamTheora.File = "/Path/To/File.ogv";
+    GetNode<VideoStreamPlayer>("VideoStreamPlayer").Stream = videoStreamTheora;
+
+    // VideoStreamPlayer's Autoplay property won't work if the stream is empty
+    // before this property is set, so call `Play()` after setting `Stream`.
+    GetNode<VideoStreamPlayer>("VideoStreamPlayer").Play();
+
 .. note::
 
     Godot doesn't support runtime loading of MP3 or WAV files yet. Until this is
@@ -185,7 +235,8 @@ as it's faster to write and smaller, but the text format is easier to debug.
 
 Example of loading a glTF scene and appending its root node to the scene:
 
-::
+.. tabs::
+ .. code-tab:: gdscript
 
     # Load an existing glTF scene.
     # GLTFState is used by GLTFDocument to store the loaded scene's state.
@@ -209,6 +260,34 @@ Example of loading a glTF scene and appending its root node to the scene:
     # `GLTFDocument.generate_buffer()` is also available for saving to memory.
     gltf_document_save.write_to_filesystem(gltf_state_save, path)
 
+ .. code-tab:: csharp
+
+    // Load an existing glTF scene.
+    // GLTFState is used by GLTFDocument to store the loaded scene's state.
+    // GLTFDocument is the class that handles actually loading glTF data into a Godot node tree,
+    // which means it supports glTF features such as lights and cameras.
+    var gltfDocumentLoad = new GltfDocument();
+    var gltfStateLoad = new GltfState();
+    var error = gltfDocumentLoad.AppendFromFile("/Path/To/File.gltf", gltfStateLoad);
+    if (error == Error.Ok)
+    {
+        var gltfSceneRootNode = gltfDocumentLoad.GenerateScene(gltfStateLoad);
+        AddChild(gltfSceneRootNode);
+    }
+    else
+    {
+        GD.PrintErr($"Couldn't load glTF scene (error code: {error}).");
+    }
+
+    // Save a new glTF scene.
+    var gltfDocumentSave = new GltfDocument();
+    var gltfStateSave = new GltfState();
+    gltfDocumentSave.AppendFromScene(gltfSceneRootNode, gltfStateSave);
+    // The file extension in the output `path` (`.gltf` or `.glb`) determines
+    // whether the output uses text or binary format.
+    // `GltfDocument.GenerateBuffer()` is also available for saving to memory.
+    gltfDocumentSave.WriteToFilesystem(gltfStateSave, path);
+
 .. note::
 
     When loading a glTF scene, a *base path* must be set so that external
@@ -240,7 +319,8 @@ Godot's support for :ref:`doc_using_fonts_system_fonts`.
 Example of loading a font file automatically according to its file extension,
 then adding it as a theme override to a :ref:`class_Label` node:
 
-::
+.. tabs::
+ .. code-tab:: gdscript
 
     var path = "/path/to/font.ttf"
     var path_lower = path.to_lower()
@@ -263,6 +343,37 @@ then adding it as a theme override to a :ref:`class_Label` node:
         # If font was loaded successfully, add it as a theme override.
         $Label.add_theme_font_override("font", font_file)
 
+ .. code-tab:: csharp
+
+    string path = "/Path/To/Font.ttf";
+    var fontFile = new FontFile();
+
+    if (
+        path.EndsWith(".ttf", StringComparison.OrdinalIgnoreCase)
+        || path.EndsWith(".otf", StringComparison.OrdinalIgnoreCase)
+        || path.EndsWith(".woff", StringComparison.OrdinalIgnoreCase)
+        || path.EndsWith(".woff2", StringComparison.OrdinalIgnoreCase)
+        || path.EndsWith(".pfb", StringComparison.OrdinalIgnoreCase)
+        || path.EndsWith(".pfm", StringComparison.OrdinalIgnoreCase)
+    )
+    {
+        fontFile.LoadDynamicFont(path);
+    }
+    else if (path.EndsWith(".fnt", StringComparison.OrdinalIgnoreCase) || path.EndsWith(".font", StringComparison.OrdinalIgnoreCase))
+    {
+        fontFile.LoadBitmapFont(path);
+    }
+    else
+    {
+        GD.PrintErr("Invalid font file format.");
+    }
+
+    if (!fontFile.Data.IsEmpty())
+    {
+        // If font was loaded successfully, add it as a theme override.
+        GetNode<Label>("Label").AddThemeFontOverride("font", fontFile);
+    }
+
 ZIP archives
 ------------
 
@@ -285,7 +396,8 @@ through the Godot editor to generate PCK/ZIP files.
 Example that lists files in a ZIP archive in an :ref:`class_ItemList` node,
 then writes contents read from it to a new ZIP archive (essentially duplicating the archive):
 
-::
+.. tabs::
+ .. code-tab:: gdscript
 
     # Load an existing ZIP archive.
     var zip_reader = ZIPReader.new()
@@ -312,3 +424,37 @@ then writes contents read from it to a new ZIP archive (essentially duplicating
         zip_packer.close_file()
 
     zip_packer.close()
+
+ .. code-tab:: csharp
+
+    // Load an existing ZIP archive.
+    var zipReader = new ZipReader();
+    zipReader.Open(path);
+    string[] files = zipReader.GetFiles();
+    // The list of files isn't sorted by default. Sort it for more consistent processing.
+    Array.Sort(files);
+    foreach (string file in files)
+    {
+        GetNode<ItemList>("ItemList").AddItem(file);
+        // Make folders disabled in the list.
+        GetNode<ItemList>("ItemList").SetItemDisabled(-1, file.EndsWith('/'));
+    }
+
+    // Save a new ZIP archive.
+    var zipPacker = new ZipPacker();
+    var error = zipPacker.Open(path);
+    if (error != Error.Ok)
+    {
+        GD.PrintErr($"Couldn't open path for saving ZIP archive (error code: {error}).");
+        return;
+    }
+
+    // Reuse the above ZIPReader instance to read files from an existing ZIP archive.
+    foreach (string file in zipReader.GetFiles())
+    {
+        zipPacker.StartFile(file);
+        zipPacker.WriteFile(zipReader.ReadFile(file));
+        zipPacker.CloseFile();
+    }
+
+    zipPacker.Close();

+ 28 - 0
tutorials/navigation/navigation_connecting_navmesh.rst

@@ -53,6 +53,20 @@ The edge connection margin value of any navigation map can also be changed at ru
         var default_map_rid: RID = get_world_2d().get_navigation_map()
         NavigationServer2D.map_set_edge_connection_margin(default_map_rid, 50.0)
 
+ .. code-tab:: csharp 2D C#
+
+    using Godot;
+
+    public partial class MyNode2D : Node2D
+    {
+        public override void _Ready()
+        {
+            // 2D margins are designed to work with 2D "pixel" values.
+            Rid defaultMapRid = GetWorld2D().NavigationMap;
+            NavigationServer2D.MapSetEdgeConnectionMargin(defaultMapRid, 50.0f);
+        }
+    }
+
  .. code-tab:: gdscript 3D GDScript
 
     extends Node3D
@@ -62,6 +76,20 @@ The edge connection margin value of any navigation map can also be changed at ru
         var default_map_rid: RID = get_world_3d().get_navigation_map()
         NavigationServer3D.map_set_edge_connection_margin(default_map_rid, 0.5)
 
+ .. code-tab:: csharp 3D C#
+
+    using Godot;
+
+    public partial class MyNode3D : Node3D
+    {
+        public override void _Ready()
+        {
+            // 3D margins are designed to work with 3D world unit values.
+            Rid defaultMapRid = GetWorld3D().NavigationMap;
+            NavigationServer3D.MapSetEdgeConnectionMargin(defaultMapRid, 0.5f);
+        }
+    }
+
 .. note::
 
     Changing the edge connection margin will trigger a full update of all navigation mesh connections on the NavigationServer.

+ 6 - 1
tutorials/navigation/navigation_debug_tools.rst

@@ -24,7 +24,12 @@ In Godot debug builds the navigation debug can also be toggled through the Navig
     NavigationServer2D.set_debug_enabled(false)
     NavigationServer3D.set_debug_enabled(true)
 
-Debug visualizations are currently based on Nodes in the SceneTree. If the :ref:`NavigationServer2D<class_NavigationServer2D>` or :ref:`NavigationServer3D<class_NavigationServer3D>` 
+ .. code-tab:: csharp
+
+    NavigationServer2D.SetDebugEnabled(false);
+    NavigationServer3D.SetDebugEnabled(true);
+
+Debug visualizations are currently based on Nodes in the SceneTree. If the :ref:`NavigationServer2D<class_NavigationServer2D>` or :ref:`NavigationServer3D<class_NavigationServer3D>`
 APIs are used exclusively then changes will not be reflected by the debug navigation tools.
 
 Navigation debug settings

+ 41 - 7
tutorials/navigation/navigation_different_actor_locomotion.rst

@@ -5,13 +5,13 @@ Support different actor locomotion
 
 .. image:: img/nav_actor_locomotion.png
 
-To support different actor locomotion like crouching and crawling, a similar 
+To support different actor locomotion like crouching and crawling, a similar
 map setup as supporting :ref:`doc_navigation_different_actor_types` is required.
 
-Bake different navigation meshes with an appropriate height for crouched 
+Bake different navigation meshes with an appropriate height for crouched
 or crawling actors so they can find paths through those narrow sections in your game world.
 
-When an actor changes locomotion state, e.g. stands up, starts 
+When an actor changes locomotion state, e.g. stands up, starts
 crouching or crawling, query the appropriate map for a path.
 
 If the avoidance behavior should also change with the locomotion e.g. only avoid while standing or only avoid
@@ -19,18 +19,18 @@ other agents in the same locomotion state, switch the actor's avoidance agent to
 
 .. tabs::
  .. code-tab:: gdscript GDScript
-    
+
     func update_path():
-        
+
         if actor_standing:
             path = NavigationServer3D.map_get_path(standing_navigation_map_rid, start_position, target_position, true)
         elif actor_crouching:
             path = NavigationServer3D.map_get_path(crouched_navigation_map_rid, start_position, target_position, true)
         elif actor_crawling:
             path = NavigationServer3D.map_get_path(crawling_navigation_map_rid, start_position, target_position, true)
-    
+
     func change_agent_avoidance_state():
-        
+
         if actor_standing:
             NavigationServer3D.agent_set_map(avoidance_agent_rid, standing_navigation_map_rid)
         elif actor_crouching:
@@ -38,6 +38,40 @@ other agents in the same locomotion state, switch the actor's avoidance agent to
         elif actor_crawling:
             NavigationServer3D.agent_set_map(avoidance_agent_rid, crawling_navigation_map_rid)
 
+ .. code-tab:: csharp
+
+    private void UpdatePath()
+    {
+        if (_actorStanding)
+        {
+            _path = NavigationServer3D.MapGetPath(_standingNavigationMapRid, _startPosition, _targetPosition, true);
+        }
+        else if (_actorCrouching)
+        {
+            _path = NavigationServer3D.MapGetPath(_crouchedNavigationMapRid, _startPosition, _targetPosition, true);
+        }
+        else if (_actorCrawling)
+        {
+            _path = NavigationServer3D.MapGetPath(_crawlingNavigationMapRid, _startPosition, _targetPosition, true);
+        }
+    }
+
+    private void ChangeAgentAvoidanceState()
+    {
+        if (_actorStanding)
+        {
+            NavigationServer3D.AgentSetMap(_avoidanceAgentRid, _standingNavigationMapRid);
+        }
+        else if (_actorCrouching)
+        {
+            NavigationServer3D.AgentSetMap(_avoidanceAgentRid, _crouchedNavigationMapRid);
+        }
+        else if (_actorCrawling)
+        {
+            NavigationServer3D.AgentSetMap(_avoidanceAgentRid, _crawlingNavigationMapRid);
+        }
+    }
+
 .. note::
 
     While a path query can be execute immediately for multiple maps, the avoidance agent map switch will only take effect after the next server synchronization.

+ 66 - 0
tutorials/navigation/navigation_different_actor_types.rst

@@ -79,3 +79,69 @@ The same approach can be used to distinguish between e.g. landwalking, swimming
     var path_standard_agent = NavigationServer3D.map_get_path(navigation_map_standard, start_pos, end_pos, use_corridorfunnel)
     var path_small_agent = NavigationServer3D.map_get_path(navigation_map_small, start_pos, end_pos, use_corridorfunnel)
     var path_huge_agent = NavigationServer3D.map_get_path(navigation_map_huge, start_pos, end_pos, use_corridorfunnel)
+
+ .. code-tab:: csharp
+
+    // Create a navigation mesh resource for each actor size.
+    NavigationMesh navigationMeshStandardSize = new NavigationMesh();
+    NavigationMesh navigationMeshSmallSize = new NavigationMesh();
+    NavigationMesh navigationMeshHugeSize = new NavigationMesh();
+
+    // Set appropriated agent parameters.
+    navigationMeshStandardSize.AgentRadius = 0.5f;
+    navigationMeshStandardSize.AgentHeight = 1.8f;
+    navigationMeshSmallSize.AgentRadius = 0.25f;
+    navigationMeshSmallSize.AgentHeight = 0.7f;
+    navigationMeshHugeSize.AgentRadius = 1.5f;
+    navigationMeshHugeSize.AgentHeight = 2.5f;
+
+    // Get the root node to parse geometry for the baking.
+    Node3D rootNode = GetNode<Node3D>("NavigationMeshBakingRootNode");
+
+    // Create the source geometry resource that will hold the parsed geometry data.
+    NavigationMeshSourceGeometryData3D sourceGeometryData = new NavigationMeshSourceGeometryData3D();
+
+    // Parse the source geometry from the scene tree on the main thread.
+    // The navigation mesh is only required for the parse settings so any of the three will do.
+    NavigationServer3D.ParseSourceGeometryData(navigationMeshStandardSize, sourceGeometryData, rootNode);
+
+    // Bake the navigation geometry for each agent size from the same source geometry.
+    // If required for performance this baking step could also be done on background threads.
+    NavigationServer3D.BakeFromSourceGeometryData(navigationMeshStandardSize, sourceGeometryData);
+    NavigationServer3D.BakeFromSourceGeometryData(navigationMeshSmallSize, sourceGeometryData);
+    NavigationServer3D.BakeFromSourceGeometryData(navigationMeshHugeSize, sourceGeometryData);
+
+    // Create different navigation maps on the NavigationServer.
+    Rid navigationMapStandard = NavigationServer3D.MapCreate();
+    Rid navigationMapSmall = NavigationServer3D.MapCreate();
+    Rid navigationMapHuge = NavigationServer3D.MapCreate();
+
+    // Set the new navigation maps as active.
+    NavigationServer3D.MapSetActive(navigationMapStandard, true);
+    NavigationServer3D.MapSetActive(navigationMapSmall, true);
+    NavigationServer3D.MapSetActive(navigationMapHuge, true);
+
+    // Create a region for each map.
+    Rid navigationRegionStandard = NavigationServer3D.RegionCreate();
+    Rid navigationRegionSmall = NavigationServer3D.RegionCreate();
+    Rid navigationRegionHuge = NavigationServer3D.RegionCreate();
+
+    // Add the regions to the maps.
+    NavigationServer3D.RegionSetMap(navigationRegionStandard, navigationMapStandard);
+    NavigationServer3D.RegionSetMap(navigationRegionSmall, navigationMapSmall);
+    NavigationServer3D.RegionSetMap(navigationRegionHuge, navigationMapHuge);
+
+    // Set navigation mesh for each region.
+    NavigationServer3D.RegionSetNavigationMesh(navigationRegionStandard, navigationMeshStandardSize);
+    NavigationServer3D.RegionSetNavigationMesh(navigationRegionSmall, navigationMeshSmallSize);
+    NavigationServer3D.RegionSetNavigationMesh(navigationRegionHuge, navigationMeshHugeSize);
+
+    // Create start and end position for the navigation path query.
+    Vector3 startPos = new Vector3(0.0f, 0.0f, 0.0f);
+    Vector3 endPos = new Vector3(2.0f, 0.0f, 0.0f);
+    bool useCorridorFunnel = true;
+
+    // Query paths for each agent size.
+    var pathStandardAgent = NavigationServer3D.MapGetPath(navigationMapStandard, startPos, endPos, useCorridorFunnel);
+    var pathSmallAgent = NavigationServer3D.MapGetPath(navigationMapSmall, startPos, endPos, useCorridorFunnel);
+    var pathHugeAgent = NavigationServer3D.MapGetPath(navigationMapHuge, startPos, endPos, useCorridorFunnel);

+ 375 - 0
tutorials/navigation/navigation_using_navigationagents.rst

@@ -158,6 +158,29 @@ toggle avoidance on agents, create or delete avoidance callbacks or switch avoid
         # Delete avoidance callback
         NavigationServer2D.agent_set_avoidance_callback(agent, Callable())
 
+ .. code-tab:: csharp 2D C#
+
+    using Godot;
+
+    public partial class MyNavigationAgent2D : NavigationAgent2D
+    {
+        public override void _Ready()
+        {
+            Rid agent = GetRid();
+            // Enable avoidance
+            NavigationServer2D.AgentSetAvoidanceEnabled(agent, true);
+            // Create avoidance callback
+            NavigationServer2D.AgentSetAvoidanceCallback(agent, Callable.From(AvoidanceDone));
+
+            // Disable avoidance
+            NavigationServer2D.AgentSetAvoidanceEnabled(agent, false);
+            //Delete avoidance callback
+            NavigationServer2D.AgentSetAvoidanceCallback(agent, default);
+        }
+
+        private void AvoidanceDone() { }
+    }
+
  .. code-tab:: gdscript 3D GDScript
 
     extends NavigationAgent3D
@@ -178,6 +201,32 @@ toggle avoidance on agents, create or delete avoidance callbacks or switch avoid
         # Switch to 2D avoidance
         NavigationServer3D.agent_set_use_3d_avoidance(agent, false)
 
+ .. code-tab:: csharp 3D C#
+
+    using Godot;
+
+    public partial class MyNavigationAgent3D : NavigationAgent3D
+    {
+        public override void _Ready()
+        {
+            Rid agent = GetRid();
+            // Enable avoidance
+            NavigationServer3D.AgentSetAvoidanceEnabled(agent, true);
+            // Create avoidance callback
+            NavigationServer3D.AgentSetAvoidanceCallback(agent, Callable.From(AvoidanceDone));
+            // Switch to 3D avoidance
+            NavigationServer3D.AgentSetUse3DAvoidance(agent, true);
+
+            // Disable avoidance
+            NavigationServer3D.AgentSetAvoidanceEnabled(agent, false);
+            //Delete avoidance callback
+            NavigationServer3D.AgentSetAvoidanceCallback(agent, default);
+            // Switch to 2D avoidance
+            NavigationServer3D.AgentSetUse3DAvoidance(agent, false);
+        }
+
+        private void AvoidanceDone() { }
+    }
 
 NavigationAgent Script Templates
 --------------------------------
@@ -283,6 +332,169 @@ The following sections provides script templates for nodes commonly used with Na
             func _on_velocity_computed(safe_velocity: Vector2):
                 linear_velocity = safe_velocity
 
+   .. tab:: 2D C#
+
+      .. tabs::
+
+         .. code-tab:: csharp Node2D
+
+            using Godot;
+
+            public partial class MyNode2D : Node2D
+            {
+                [Export]
+                public float MovementSpeed { get; set; } = 4.0f;
+                NavigationAgent2D _navigationAgent;
+                private float _movementDelta;
+
+                public override void _Ready()
+                {
+                    _navigationAgent = GetNode<NavigationAgent2D>("NavigationAgent2D");
+                    _navigationAgent.VelocityComputed += OnVelocityComputed;
+                }
+
+                private void SetMovementTarget(Vector2 movementTarget)
+                {
+                    _navigationAgent.TargetPosition = movementTarget;
+                }
+
+                public override void _PhysicsProcess(double delta)
+                {
+                    // Do not query when the map has never synchronized and is empty.
+                    if (NavigationServer2D.MapGetIterationId(_navigationAgent.GetNavigationMap()) == 0)
+                    {
+                        return;
+                    }
+
+                    if (_navigationAgent.IsNavigationFinished())
+                    {
+                        return;
+                    }
+
+                    _movementDelta = MovementSpeed * (float)delta;
+                    Vector2 nextPathPosition = _navigationAgent.GetNextPathPosition();
+                    Vector2 newVelocity = GlobalPosition.DirectionTo(nextPathPosition) * _movementDelta;
+                    if (_navigationAgent.AvoidanceEnabled)
+                    {
+                        _navigationAgent.Velocity = newVelocity;
+                    }
+                    else
+                    {
+                        OnVelocityComputed(newVelocity);
+                    }
+                }
+
+                private void OnVelocityComputed(Vector2 safeVelocity)
+                {
+                    GlobalPosition = GlobalPosition.MoveToward(GlobalPosition + safeVelocity, _movementDelta);
+                }
+            }
+
+         .. code-tab:: csharp CharacterBody2D
+
+            using Godot;
+
+            public partial class MyCharacterBody2D : CharacterBody2D
+            {
+                [Export]
+                public float MovementSpeed { get; set; } = 4.0f;
+                NavigationAgent2D _navigationAgent;
+
+                public override void _Ready()
+                {
+                    _navigationAgent = GetNode<NavigationAgent2D>("NavigationAgent2D");
+                    _navigationAgent.VelocityComputed += OnVelocityComputed;
+                }
+
+                private void SetMovementTarget(Vector2 movementTarget)
+                {
+                    _navigationAgent.TargetPosition = movementTarget;
+                }
+
+                public override void _PhysicsProcess(double delta)
+                {
+                    // Do not query when the map has never synchronized and is empty.
+                    if (NavigationServer2D.MapGetIterationId(_navigationAgent.GetNavigationMap()) == 0)
+                    {
+                        return;
+                    }
+
+                    if (_navigationAgent.IsNavigationFinished())
+                    {
+                        return;
+                    }
+
+                    Vector2 nextPathPosition = _navigationAgent.GetNextPathPosition();
+                    Vector2 newVelocity = GlobalPosition.DirectionTo(nextPathPosition) * MovementSpeed;
+                    if (_navigationAgent.AvoidanceEnabled)
+                    {
+                        _navigationAgent.Velocity = newVelocity;
+                    }
+                    else
+                    {
+                        OnVelocityComputed(newVelocity);
+                    }
+                }
+
+                private void OnVelocityComputed(Vector2 safeVelocity)
+                {
+                    Velocity = safeVelocity;
+                    MoveAndSlide();
+                }
+            }
+
+         .. code-tab:: csharp RigidBody2D
+
+            using Godot;
+
+            public partial class MyRigidBody2D : RigidBody2D
+            {
+                [Export]
+                public float MovementSpeed { get; set; } = 4.0f;
+                NavigationAgent2D _navigationAgent;
+
+                public override void _Ready()
+                {
+                    _navigationAgent = GetNode<NavigationAgent2D>("NavigationAgent2D");
+                    _navigationAgent.VelocityComputed += OnVelocityComputed;
+                }
+
+                private void SetMovementTarget(Vector2 movementTarget)
+                {
+                    _navigationAgent.TargetPosition = movementTarget;
+                }
+
+                public override void _PhysicsProcess(double delta)
+                {
+                    // Do not query when the map has never synchronized and is empty.
+                    if (NavigationServer2D.MapGetIterationId(_navigationAgent.GetNavigationMap()) == 0)
+                    {
+                        return;
+                    }
+
+                    if (_navigationAgent.IsNavigationFinished())
+                    {
+                        return;
+                    }
+
+                    Vector2 nextPathPosition = _navigationAgent.GetNextPathPosition();
+                    Vector2 newVelocity = GlobalPosition.DirectionTo(nextPathPosition) * MovementSpeed;
+                    if (_navigationAgent.AvoidanceEnabled)
+                    {
+                        _navigationAgent.Velocity = newVelocity;
+                    }
+                    else
+                    {
+                        OnVelocityComputed(newVelocity);
+                    }
+                }
+
+                private void OnVelocityComputed(Vector2 safeVelocity)
+                {
+                    LinearVelocity = safeVelocity;
+                }
+            }
+
    .. tab:: 3D GDScript
 
       .. tabs::
@@ -379,3 +591,166 @@ The following sections provides script templates for nodes commonly used with Na
 
             func _on_velocity_computed(safe_velocity: Vector3):
                 linear_velocity = safe_velocity
+
+   .. tab:: 3D C#
+
+      .. tabs::
+
+         .. code-tab:: csharp Node3D
+
+            using Godot;
+
+            public partial class MyNode3D : Node3D
+            {
+                [Export]
+                public float MovementSpeed { get; set; } = 4.0f;
+                NavigationAgent3D _navigationAgent;
+                private float _movementDelta;
+
+                public override void _Ready()
+                {
+                    _navigationAgent = GetNode<NavigationAgent3D>("NavigationAgent3D");
+                    _navigationAgent.VelocityComputed += OnVelocityComputed;
+                }
+
+                private void SetMovementTarget(Vector3 movementTarget)
+                {
+                    _navigationAgent.TargetPosition = movementTarget;
+                }
+
+                public override void _PhysicsProcess(double delta)
+                {
+                    // Do not query when the map has never synchronized and is empty.
+                    if (NavigationServer3D.MapGetIterationId(_navigationAgent.GetNavigationMap()) == 0)
+                    {
+                        return;
+                    }
+
+                    if (_navigationAgent.IsNavigationFinished())
+                    {
+                        return;
+                    }
+
+                    _movementDelta = MovementSpeed * (float)delta;
+                    Vector3 nextPathPosition = _navigationAgent.GetNextPathPosition();
+                    Vector3 newVelocity = GlobalPosition.DirectionTo(nextPathPosition) * _movementDelta;
+                    if (_navigationAgent.AvoidanceEnabled)
+                    {
+                        _navigationAgent.Velocity = newVelocity;
+                    }
+                    else
+                    {
+                        OnVelocityComputed(newVelocity);
+                    }
+                }
+
+                private void OnVelocityComputed(Vector3 safeVelocity)
+                {
+                    GlobalPosition = GlobalPosition.MoveToward(GlobalPosition + safeVelocity, _movementDelta);
+                }
+            }
+
+         .. code-tab:: csharp CharacterBody3D
+
+            using Godot;
+
+            public partial class MyCharacterBody3D : CharacterBody3D
+            {
+                [Export]
+                public float MovementSpeed { get; set; } = 4.0f;
+                NavigationAgent3D _navigationAgent;
+
+                public override void _Ready()
+                {
+                    _navigationAgent = GetNode<NavigationAgent3D>("NavigationAgent3D");
+                    _navigationAgent.VelocityComputed += OnVelocityComputed;
+                }
+
+                private void SetMovementTarget(Vector3 movementTarget)
+                {
+                    _navigationAgent.TargetPosition = movementTarget;
+                }
+
+                public override void _PhysicsProcess(double delta)
+                {
+                    // Do not query when the map has never synchronized and is empty.
+                    if (NavigationServer3D.MapGetIterationId(_navigationAgent.GetNavigationMap()) == 0)
+                    {
+                        return;
+                    }
+
+                    if (_navigationAgent.IsNavigationFinished())
+                    {
+                        return;
+                    }
+
+                    Vector3 nextPathPosition = _navigationAgent.GetNextPathPosition();
+                    Vector3 newVelocity = GlobalPosition.DirectionTo(nextPathPosition) * MovementSpeed;
+                    if (_navigationAgent.AvoidanceEnabled)
+                    {
+                        _navigationAgent.Velocity = newVelocity;
+                    }
+                    else
+                    {
+                        OnVelocityComputed(newVelocity);
+                    }
+                }
+
+                private void OnVelocityComputed(Vector3 safeVelocity)
+                {
+                    Velocity = safeVelocity;
+                    MoveAndSlide();
+                }
+            }
+
+         .. code-tab:: csharp RigidBody3D
+
+            using Godot;
+
+            public partial class MyRigidBody3D : RigidBody3D
+            {
+                [Export]
+                public float MovementSpeed { get; set; } = 4.0f;
+                NavigationAgent3D _navigationAgent;
+
+                public override void _Ready()
+                {
+                    _navigationAgent = GetNode<NavigationAgent3D>("NavigationAgent3D");
+                    _navigationAgent.VelocityComputed += OnVelocityComputed;
+                }
+
+                private void SetMovementTarget(Vector3 movementTarget)
+                {
+                    _navigationAgent.TargetPosition = movementTarget;
+                }
+
+                public override void _PhysicsProcess(double delta)
+                {
+                    // Do not query when the map has never synchronized and is empty.
+                    if (NavigationServer3D.MapGetIterationId(_navigationAgent.GetNavigationMap()) == 0)
+                    {
+                        return;
+                    }
+
+                    if (_navigationAgent.IsNavigationFinished())
+                    {
+                        return;
+                    }
+
+                    Vector3 nextPathPosition = _navigationAgent.GetNextPathPosition();
+                    Vector3 newVelocity = GlobalPosition.DirectionTo(nextPathPosition) * MovementSpeed;
+                    if (_navigationAgent.AvoidanceEnabled)
+                    {
+                        _navigationAgent.Velocity = newVelocity;
+                    }
+                    else
+                    {
+                        OnVelocityComputed(newVelocity);
+                    }
+                }
+
+                private void OnVelocityComputed(Vector3 safeVelocity)
+                {
+                    LinearVelocity = safeVelocity;
+                }
+            }

+ 100 - 0
tutorials/navigation/navigation_using_navigationlayers.rst

@@ -54,6 +54,56 @@ In scripts the following helper functions can be used to work with the ``navigat
     static func disable_bitmask_inx(_bitmask: int, _index: int) -> int:
         return _bitmask & ~(1 << _index)
 
+ .. code-tab:: csharp 2D C#
+
+    using Godot;
+
+    public partial class MyNode2D : Node2D
+    {
+        private Rid _map;
+        private Vector2 _startPosition;
+        private Vector2 _targetPosition;
+
+        private void ChangeLayers()
+        {
+            NavigationRegion2D region = GetNode<NavigationRegion2D>("NavigationRegion2D");
+            // Enables the 4th layer for this region.
+            region.NavigationLayers = EnableBitmaskInx(region.NavigationLayers, 4);
+            // Disables the 1st layer for this region.
+            region.NavigationLayers = DisableBitmaskInx(region.NavigationLayers, 1);
+
+            NavigationAgent2D agent = GetNode<NavigationAgent2D>("NavigationAgent2D");
+            // Make future path queries of this agent ignore regions with the 4th layer.
+            agent.NavigationLayers = DisableBitmaskInx(agent.NavigationLayers, 4);
+
+            uint pathQueryNavigationLayers = 0;
+            pathQueryNavigationLayers = EnableBitmaskInx(pathQueryNavigationLayers, 2);
+            // Get a path that only considers 2nd layer regions.
+            Vector2[] path = NavigationServer2D.MapGetPath(
+                _map,
+                _startPosition,
+                _targetPosition,
+                true,
+                pathQueryNavigationLayers
+            );
+        }
+
+        private static bool IsBitmaskInxEnabled(uint bitmask, int index)
+        {
+            return (bitmask & (1 << index)) != 0;
+        }
+
+        private static uint EnableBitmaskInx(uint bitmask, int index)
+        {
+            return bitmask | (1u << index);
+        }
+
+        private static uint DisableBitmaskInx(uint bitmask, int index)
+        {
+            return bitmask & ~(1u << index);
+        }
+    }
+
  .. code-tab:: gdscript 3D GDScript
 
     func change_layers():
@@ -87,6 +137,56 @@ In scripts the following helper functions can be used to work with the ``navigat
     static func disable_bitmask_inx(_bitmask: int, _index: int) -> int:
         return _bitmask & ~(1 << _index)
 
+ .. code-tab:: csharp 3D C#
+
+    using Godot;
+
+    public partial class MyNode3D : Node3D
+    {
+        private Rid _map;
+        private Vector3 _startPosition;
+        private Vector3 _targetPosition;
+
+        private void ChangeLayers()
+        {
+            NavigationRegion3D region = GetNode<NavigationRegion3D>("NavigationRegion3D");
+            // Enables the 4th layer for this region.
+            region.NavigationLayers = EnableBitmaskInx(region.NavigationLayers, 4);
+            // Disables the 1st layer for this region.
+            region.NavigationLayers = DisableBitmaskInx(region.NavigationLayers, 1);
+
+            NavigationAgent3D agent = GetNode<NavigationAgent3D>("NavigationAgent2D");
+            // Make future path queries of this agent ignore regions with the 4th layer.
+            agent.NavigationLayers = DisableBitmaskInx(agent.NavigationLayers, 4);
+
+            uint pathQueryNavigationLayers = 0;
+            pathQueryNavigationLayers = EnableBitmaskInx(pathQueryNavigationLayers, 2);
+            // Get a path that only considers 2nd layer regions.
+            Vector3[] path = NavigationServer3D.MapGetPath(
+                _map,
+                _startPosition,
+                _targetPosition,
+                true,
+                pathQueryNavigationLayers
+            );
+        }
+
+        private static bool IsBitmaskInxEnabled(uint bitmask, int index)
+        {
+            return (bitmask & (1 << index)) != 0;
+        }
+
+        private static uint EnableBitmaskInx(uint bitmask, int index)
+        {
+            return bitmask | (1u << index);
+        }
+
+        private static uint DisableBitmaskInx(uint bitmask, int index)
+        {
+            return bitmask & ~(1u << index);
+        }
+    }
+
 Changing navigation layers for path queries is a performance friendly alternative to
 enabling / disabling entire navigation regions. Compared to region changes a
 navigation path query with different navigation layers does not

+ 72 - 0
tutorials/navigation/navigation_using_navigationlinks.rst

@@ -88,6 +88,42 @@ The following script uses the NavigationServer to create a new navigation link.
         NavigationServer2D.link_set_start_position(link_rid, link_start_position)
         NavigationServer2D.link_set_end_position(link_rid, link_end_position)
 
+ .. code-tab:: csharp 2D C#
+
+    using Godot;
+
+    public partial class MyNode2D : Node2D
+    {
+        private Rid _linkRid;
+        private Vector2 _linkStartPosition;
+        private Vector2 _linkEndPosition;
+
+        public override void _Ready()
+        {
+            _linkRid = NavigationServer2D.LinkCreate();
+
+            ulong linkOwnerId = GetInstanceId();
+            float linkEnterCost = 1.0f;
+            float linkTravelCost = 1.0f;
+            uint linkNavigationLayers = 1;
+            bool linkBidirectional = true;
+
+            NavigationServer2D.LinkSetOwnerId(_linkRid, linkOwnerId);
+            NavigationServer2D.LinkSetEnterCost(_linkRid, linkEnterCost);
+            NavigationServer2D.LinkSetTravelCost(_linkRid, linkTravelCost);
+            NavigationServer2D.LinkSetNavigationLayers(_linkRid, linkNavigationLayers);
+            NavigationServer2D.LinkSetBidirectional(_linkRid, linkBidirectional);
+
+            // Enable the link and set it to the default navigation map.
+            NavigationServer2D.LinkSetEnabled(_linkRid, true);
+            NavigationServer2D.LinkSetMap(_linkRid, GetWorld2D().NavigationMap);
+
+            // Move the 2 link positions to their intended global positions.
+            NavigationServer2D.LinkSetStartPosition(_linkRid, _linkStartPosition);
+            NavigationServer2D.LinkSetEndPosition(_linkRid, _linkEndPosition);
+        }
+    }
+
  .. code-tab:: gdscript 3D GDScript
 
     extends Node3D
@@ -118,3 +154,39 @@ The following script uses the NavigationServer to create a new navigation link.
         # Move the 2 link positions to their intended global positions.
         NavigationServer3D.link_set_start_position(link_rid, link_start_position)
         NavigationServer3D.link_set_end_position(link_rid, link_end_position)
+
+ .. code-tab:: csharp 3D C#
+
+    using Godot;
+
+    public partial class MyNode3D : Node3D
+    {
+        private Rid _linkRid;
+        private Vector3 _linkStartPosition;
+        private Vector3 _linkEndPosition;
+
+        public override void _Ready()
+        {
+            _linkRid = NavigationServer3D.LinkCreate();
+
+            ulong linkOwnerId = GetInstanceId();
+            float linkEnterCost = 1.0f;
+            float linkTravelCost = 1.0f;
+            uint linkNavigationLayers = 1;
+            bool linkBidirectional = true;
+
+            NavigationServer3D.LinkSetOwnerId(_linkRid, linkOwnerId);
+            NavigationServer3D.LinkSetEnterCost(_linkRid, linkEnterCost);
+            NavigationServer3D.LinkSetTravelCost(_linkRid, linkTravelCost);
+            NavigationServer3D.LinkSetNavigationLayers(_linkRid, linkNavigationLayers);
+            NavigationServer3D.LinkSetBidirectional(_linkRid, linkBidirectional);
+
+            // Enable the link and set it to the default navigation map.
+            NavigationServer3D.LinkSetEnabled(_linkRid, true);
+            NavigationServer3D.LinkSetMap(_linkRid, GetWorld3D().NavigationMap);
+
+            // Move the 2 link positions to their intended global positions.
+            NavigationServer3D.LinkSetStartPosition(_linkRid, _linkStartPosition);
+            NavigationServer3D.LinkSetEndPosition(_linkRid, _linkEndPosition);
+        }
+    }

+ 210 - 2
tutorials/navigation/navigation_using_navigationmeshes.rst

@@ -30,7 +30,7 @@ By itself the navigation system will never know "this is a tree / rock / wall co
 
 .. _doc_navigation_navmesh_baking:
 
-Navigation mesh baking can be done either by using a :ref:`NavigationRegion2D<class_NavigationRegion2D>` or :ref:`NavigationRegion3D<class_NavigationRegion3D>`, or by using the 
+Navigation mesh baking can be done either by using a :ref:`NavigationRegion2D<class_NavigationRegion2D>` or :ref:`NavigationRegion3D<class_NavigationRegion3D>`, or by using the
 :ref:`NavigationServer2D<class_NavigationServer2D>` and :ref:`NavigationServer3D<class_NavigationServer3D>` API directly.
 
 .. _doc_navigation_using_navigationmeshes_baking_navigation_mesh_with_navigationregion:
@@ -84,6 +84,11 @@ The nodes are available in 2D and 3D as :ref:`NavigationRegion2D<class_Navigatio
             var on_thread: bool = true
             bake_navigation_polygon(on_thread)
 
+         .. code-tab:: csharp
+
+            bool onThread = true;
+            BakeNavigationPolygon(onThread);
+
         To quickly test the 2D baking with default settings:
 
         - Add a :ref:`NavigationRegion2D<class_NavigationRegion2D>`.
@@ -137,6 +142,11 @@ The nodes are available in 2D and 3D as :ref:`NavigationRegion2D<class_Navigatio
             var on_thread: bool = true
             bake_navigation_mesh(on_thread)
 
+         .. code-tab:: csharp
+
+            bool onThread = true;
+            BakeNavigationMesh(onThread);
+
         To quickly test the 3D baking with default settings:
 
         - Add a :ref:`NavigationRegion3D<class_NavigationRegion3D>`.
@@ -370,6 +380,76 @@ The following script uses the NavigationServer to parse source geometry from the
         # Update the region with the updated navigation mesh.
         NavigationServer2D.region_set_navigation_polygon(region_rid, navigation_mesh)
 
+ .. code-tab:: csharp 2D C#
+
+    using Godot;
+
+    public partial class MyNode2D : Node2D
+    {
+        private NavigationPolygon _navigationMesh;
+        private NavigationMeshSourceGeometryData2D _sourceGeometry;
+        private Callable _callbackParsing;
+        private Callable _callbackBaking;
+        private Rid _regionRid;
+
+        public override void _Ready()
+        {
+            _navigationMesh = new NavigationPolygon();
+            _navigationMesh.AgentRadius = 10.0f;
+            _sourceGeometry = new NavigationMeshSourceGeometryData2D();
+            _callbackParsing = Callable.From(OnParsingDone);
+            _callbackBaking = Callable.From(OnBakingDone);
+            _regionRid = NavigationServer2D.RegionCreate();
+
+            // Enable the region and set it to the default navigation map.
+            NavigationServer2D.RegionSetEnabled(_regionRid, true);
+            NavigationServer2D.RegionSetMap(_regionRid, GetWorld2D().NavigationMap);
+
+            // Some mega-nodes like TileMap are often not ready on the first frame.
+            // Also the parsing needs to happen on the main-thread.
+            // So do a deferred call to avoid common parsing issues.
+            CallDeferred(MethodName.ParseSourceGeometry);
+        }
+
+        private void ParseSourceGeometry()
+        {
+            _sourceGeometry.Clear();
+            Node2D rootNode = this;
+
+            // Parse the obstruction outlines from all child nodes of the root node by default.
+            NavigationServer2D.ParseSourceGeometryData(
+                _navigationMesh,
+                _sourceGeometry,
+                rootNode,
+                _callbackParsing
+            );
+        }
+
+        private void OnParsingDone()
+        {
+            // If we did not parse a TileMap with navigation mesh cells we may now only
+            // have obstruction outlines so add at least one traversable outline
+            // so the obstructions outlines have something to "cut" into.
+            _sourceGeometry.AddTraversableOutline(new Vector2[]
+            {
+                new Vector2(0.0f, 0.0f),
+                new Vector2(500.0f, 0.0f),
+                new Vector2(500.0f, 500.0f),
+                new Vector2(0.0f, 500.0f),
+            });
+
+            // Bake the navigation mesh on a thread with the source geometry data.
+            NavigationServer2D.BakeFromSourceGeometryDataAsync(_navigationMesh, _sourceGeometry, _callbackBaking);
+        }
+
+        private void OnBakingDone()
+        {
+            // Update the region with the updated navigation mesh.
+            NavigationServer2D.RegionSetNavigationPolygon(_regionRid, _navigationMesh);
+        }
+    }
+
+
  .. code-tab:: gdscript 3D GDScript
 
     extends Node3D
@@ -421,6 +501,64 @@ The following script uses the NavigationServer to parse source geometry from the
         # Update the region with the updated navigation mesh.
         NavigationServer3D.region_set_navigation_mesh(region_rid, navigation_mesh)
 
+ .. code-tab:: csharp 3D C#
+
+    using Godot;
+
+    public partial class MyNode3D : Node3D
+    {
+        private NavigationMesh _navigationMesh;
+        private NavigationMeshSourceGeometryData3D _sourceGeometry;
+        private Callable _callbackParsing;
+        private Callable _callbackBaking;
+        private Rid _regionRid;
+
+        public override void _Ready()
+        {
+            _navigationMesh = new NavigationMesh();
+            _navigationMesh.AgentRadius = 0.5f;
+            _sourceGeometry = new NavigationMeshSourceGeometryData3D();
+            _callbackParsing = Callable.From(OnParsingDone);
+            _callbackBaking = Callable.From(OnBakingDone);
+            _regionRid = NavigationServer3D.RegionCreate();
+
+            // Enable the region and set it to the default navigation map.
+            NavigationServer3D.RegionSetEnabled(_regionRid, true);
+            NavigationServer3D.RegionSetMap(_regionRid, GetWorld3D().NavigationMap);
+
+            // Some mega-nodes like GridMap are often not ready on the first frame.
+            // Also the parsing needs to happen on the main-thread.
+            // So do a deferred call to avoid common parsing issues.
+            CallDeferred(MethodName.ParseSourceGeometry);
+        }
+
+        private void ParseSourceGeometry ()
+        {
+            _sourceGeometry.Clear();
+            Node3D rootNode = this;
+
+            // Parse the geometry from all mesh child nodes of the root node by default.
+            NavigationServer3D.ParseSourceGeometryData(
+                _navigationMesh,
+                _sourceGeometry,
+                rootNode,
+                _callbackParsing
+            );
+        }
+
+        private void OnParsingDone()
+        {
+            // Bake the navigation mesh on a thread with the source geometry data.
+            NavigationServer3D.BakeFromSourceGeometryDataAsync(_navigationMesh, _sourceGeometry, _callbackBaking);
+        }
+
+        private void OnBakingDone()
+        {
+            // Update the region with the updated navigation mesh.
+            NavigationServer3D.RegionSetNavigationMesh(_regionRid, _navigationMesh);
+        }
+    }
+
 The following script uses the NavigationServer to update a navigation region with procedurally generated navigation mesh data.
 
 .. tabs::
@@ -444,7 +582,7 @@ The following script uses the NavigationServer to update a navigation region wit
             Vector2(0.0, 0.0),
             Vector2(100.0, 0.0),
             Vector2(100.0, 100.0),
-            Vector2(0.0, 100.0)
+            Vector2(0.0, 100.0),
         ])
 
         # Add indices for the polygon.
@@ -454,6 +592,41 @@ The following script uses the NavigationServer to update a navigation region wit
 
         NavigationServer2D.region_set_navigation_polygon(region_rid, navigation_mesh)
 
+ .. code-tab:: csharp 2D C#
+
+    using Godot;
+
+    public partial class MyNode2D : Node2D
+    {
+        private NavigationPolygon _navigationMesh;
+        private Rid _regionRid;
+
+        public override void _Ready()
+        {
+            _navigationMesh = new NavigationPolygon();
+            _regionRid = NavigationServer2D.RegionCreate();
+
+            // Enable the region and set it to the default navigation map.
+            NavigationServer2D.RegionSetEnabled(_regionRid, true);
+            NavigationServer2D.RegionSetMap(_regionRid, GetWorld2D().NavigationMap);
+
+            // Add vertices for a convex polygon.
+            _navigationMesh.Vertices = new Vector2[]
+            {
+                new Vector2(0, 0),
+                new Vector2(100.0f, 0),
+                new Vector2(100.0f, 100.0f),
+                new Vector2(0, 100.0f),
+            };
+
+            // Add indices for the polygon.
+            _navigationMesh.AddPolygon(new int[] { 0, 1, 2, 3 });
+
+            NavigationServer2D.RegionSetNavigationPolygon(_regionRid, _navigationMesh);
+        }
+    }
+
+
  .. code-tab:: gdscript 3D GDScript
 
     extends Node3D
@@ -483,3 +656,38 @@ The following script uses the NavigationServer to update a navigation region wit
         )
 
         NavigationServer3D.region_set_navigation_mesh(region_rid, navigation_mesh)
+
+ .. code-tab:: csharp 3D C#
+
+    using Godot;
+
+    public partial class MyNode3D : Node3D
+    {
+        private NavigationMesh _navigationMesh;
+        private Rid _regionRid;
+
+        public override void _Ready()
+        {
+            _navigationMesh = new NavigationMesh();
+            _regionRid = NavigationServer3D.RegionCreate();
+
+            // Enable the region and set it to the default navigation map.
+            NavigationServer3D.RegionSetEnabled(_regionRid, true);
+            NavigationServer3D.RegionSetMap(_regionRid, GetWorld3D().NavigationMap);
+
+            // Add vertices for a convex polygon.
+            _navigationMesh.Vertices = new Vector3[]
+            {
+                new Vector3(-1.0f, 0.0f, 1.0f),
+                new Vector3(1.0f, 0.0f, 1.0f),
+                new Vector3(1.0f, 0.0f, -1.0f),
+                new Vector3(-1.0f, 0.0f, -1.0f),
+            };
+
+            // Add indices for the polygon.
+            _navigationMesh.AddPolygon(new int[] { 0, 1, 2, 3 });
+
+            NavigationServer3D.RegionSetNavigationMesh(_regionRid, _navigationMesh);
+        }
+    }
+

+ 94 - 0
tutorials/navigation/navigation_using_navigationobstacles.rst

@@ -79,6 +79,26 @@ Obstacles are not involved in the source geometry parsing so adding them just be
 
     NavigationServer2D.bake_from_source_geometry_data(navigation_mesh, source_geometry)
 
+ .. code-tab:: csharp 2D C#
+
+    Vector2[] obstacleOutline = new Vector2[]
+    {
+        new Vector2(-50, -50),
+        new Vector2(50, -50),
+        new Vector2(50, 50),
+        new Vector2(-50, 50),
+    };
+
+    var navigationMesh = new NavigationPolygon();
+    var sourceGeometry = new NavigationMeshSourceGeometryData2D();
+
+    NavigationServer2D.ParseSourceGeometryData(navigationMesh, sourceGeometry, GetNode<Node2D>("MyTestRootNode"));
+
+    bool obstacleCarve = true;
+
+    sourceGeometry.AddProjectedObstruction(obstacleOutline, obstacleCarve);
+    NavigationServer2D.BakeFromSourceGeometryData(navigationMesh, sourceGeometry);
+
  .. code-tab:: gdscript 3D GDScript
 
     var obstacle_outline = PackedVector3Array([
@@ -101,6 +121,28 @@ Obstacles are not involved in the source geometry parsing so adding them just be
 
     NavigationServer3D.bake_from_source_geometry_data(navigation_mesh, source_geometry)
 
+ .. code-tab:: csharp 3D C#
+
+    Vector3[] obstacleOutline = new Vector3[]
+    {
+        new Vector3(-5, 0, -5),
+        new Vector3(5, 0, -5),
+        new Vector3(5, 0, 5),
+        new Vector3(-5, 0, 5),
+    };
+
+    var navigationMesh = new NavigationMesh();
+    var sourceGeometry = new NavigationMeshSourceGeometryData3D();
+
+    NavigationServer3D.ParseSourceGeometryData(navigationMesh, sourceGeometry, GetNode<Node3D>("MyTestRootNode"));
+
+    float obstacleElevation = GetNode<Node3D>("MyTestObstacleNode").GlobalPosition.Y;
+    float obstacleHeight = 50.0f;
+    bool obstacleCarve = true;
+
+    sourceGeometry.AddProjectedObstruction(obstacleOutline, obstacleElevation, obstacleHeight, obstacleCarve);
+    NavigationServer3D.BakeFromSourceGeometryData(navigationMesh, sourceGeometry);
+
 Obstacles and agent avoidance
 -----------------------------
 
@@ -179,6 +221,31 @@ For static use an array of ``vertices`` is required.
     # Enable the obstacle.
     NavigationServer2D.obstacle_set_avoidance_enabled(new_obstacle_rid, true)
 
+ .. code-tab:: csharp 2D C#
+
+    // Create a new "obstacle" and place it on the default navigation map.
+    Rid newObstacleRid = NavigationServer2D.ObstacleCreate();
+    Rid defaultMapRid = GetWorld2D().NavigationMap;
+
+    NavigationServer2D.ObstacleSetMap(newObstacleRid, defaultMapRid);
+    NavigationServer2D.ObstacleSetPosition(newObstacleRid, GlobalPosition);
+
+    // Use obstacle dynamic by increasing radius above zero.
+    NavigationServer2D.ObstacleSetRadius(newObstacleRid, 5.0f);
+
+    // Use obstacle static by adding a square that pushes agents out.
+    Vector2[] outline = new Vector2[]
+    {
+        new Vector2(-100, -100),
+        new Vector2(100, -100),
+        new Vector2(100, 100),
+        new Vector2(-100, 100),
+    };
+    NavigationServer2D.ObstacleSetVertices(newObstacleRid, outline);
+
+    // Enable the obstacle.
+    NavigationServer2D.ObstacleSetAvoidanceEnabled(newObstacleRid, true);
+
  .. code-tab:: gdscript 3D GDScript
 
     # Create a new "obstacle" and place it on the default navigation map.
@@ -199,3 +266,30 @@ For static use an array of ``vertices`` is required.
 
     # Enable the obstacle.
     NavigationServer3D.obstacle_set_avoidance_enabled(new_obstacle_rid, true)
+
+ .. code-tab:: csharp 3D C#
+
+    // Create a new "obstacle" and place it on the default navigation map.
+    Rid newObstacleRid = NavigationServer3D.ObstacleCreate();
+    Rid defaultMapRid = GetWorld3D().NavigationMap;
+
+    NavigationServer3D.ObstacleSetMap(newObstacleRid, defaultMapRid);
+    NavigationServer3D.ObstacleSetPosition(newObstacleRid, GlobalPosition);
+
+    // Use obstacle dynamic by increasing radius above zero.
+    NavigationServer3D.ObstacleSetRadius(newObstacleRid, 5.0f);
+
+    // Use obstacle static by adding a square that pushes agents out.
+    Vector3[] outline = new Vector3[]
+    {
+        new Vector3(-5, 0, -5),
+        new Vector3(5, 0, -5),
+        new Vector3(5, 0, 5),
+        new Vector3(-5, 0, 5),
+    };
+    NavigationServer3D.ObstacleSetVertices(newObstacleRid, outline);
+    // Set the obstacle height on the y-axis.
+    NavigationServer3D.ObstacleSetHeight(newObstacleRid, 1.0f);
+
+    // Enable the obstacle.
+    NavigationServer3D.ObstacleSetAvoidanceEnabled(newObstacleRid, true);

+ 118 - 0
tutorials/navigation/navigation_using_navigationpaths.rst

@@ -52,6 +52,33 @@ Outside of grids due to polygons often covering large open areas with a single,
         )
         return path
 
+ .. code-tab:: csharp 2D C#
+
+    using Godot;
+    using System;
+
+    public partial class MyNode2D : Node2D
+    {
+        // Basic query for a navigation path using the default navigation map.
+
+        private Vector2[] GetNavigationPath(Vector2 startPosition, Vector2 targetPosition)
+        {
+            if (!IsInsideTree())
+            {
+                return Array.Empty<Vector2>();
+            }
+
+            Rid defaultMapRid = GetWorld2D().NavigationMap;
+            Vector2[] path = NavigationServer2D.MapGetPath(
+                defaultMapRid,
+                startPosition,
+                targetPosition,
+                true
+            );
+            return path;
+        }
+    }
+
  .. code-tab:: gdscript 3D GDScript
 
     extends Node3D
@@ -71,6 +98,33 @@ Outside of grids due to polygons often covering large open areas with a single,
         )
         return path
 
+ .. code-tab:: csharp 3D C#
+
+    using Godot;
+    using System;
+
+    public partial class MyNode3D : Node3D
+    {
+        // Basic query for a navigation path using the default navigation map.
+
+        private Vector3[] GetNavigationPath(Vector3 startPosition, Vector3 targetPosition)
+        {
+            if (!IsInsideTree())
+            {
+                return Array.Empty<Vector3>();
+            }
+
+            Rid defaultMapRid = GetWorld3D().NavigationMap;
+            Vector3[] path = NavigationServer3D.MapGetPath(
+                defaultMapRid,
+                startPosition,
+                targetPosition,
+                true
+            );
+            return path;
+        }
+    }
+
 A returned ``path`` by the NavigationServer will be a ``PackedVector2Array`` for 2D or a ``PackedVector3Array`` for 3D.
 These are just a memory-optimized ``Array`` of vector positions.
 All position vectors inside the array are guaranteed to be inside a NavigationPolygon or NavigationMesh.
@@ -134,3 +188,67 @@ the default navigation map by setting the target position with ``set_movement_ta
         var new_velocity: Vector3 = global_transform.origin.direction_to(current_path_point) * movement_delta
 
         global_transform.origin = global_transform.origin.move_toward(global_transform.origin + new_velocity, movement_delta)
+
+ .. code-tab:: csharp
+
+    using Godot;
+
+    public partial class MyNode3D : Node3D
+    {
+        private Rid _default3DMapRid;
+
+        private float _movementSpeed = 4.0f;
+        private float _movementDelta;
+        private float _pathPointMargin = 0.5f;
+
+        private int _currentPathIndex = 0;
+        private Vector3 _currentPathPoint;
+        private Vector3[] _currentPath;
+
+        public override void _Ready()
+        {
+            _default3DMapRid = GetWorld3D().NavigationMap;
+        }
+
+        private void SetMovementTarget(Vector3 targetPosition)
+        {
+            Vector3 startPosition = GlobalTransform.Origin;
+
+            _currentPath = NavigationServer3D.MapGetPath(_default3DMapRid, startPosition, targetPosition, true);
+
+            if (!_currentPath.IsEmpty())
+            {
+                _currentPathIndex = 0;
+                _currentPathPoint = _currentPath[0];
+            }
+        }
+
+        public override void _PhysicsProcess(double delta)
+        {
+            if (_currentPath.IsEmpty())
+            {
+                return;
+            }
+
+            _movementDelta = _movementSpeed * (float)delta;
+
+            if (GlobalTransform.Origin.DistanceTo(_currentPathPoint) <= _pathPointMargin)
+            {
+                _currentPathIndex += 1;
+                if (_currentPathIndex >= _currentPath.Length)
+                {
+                    _currentPath = Array.Empty<Vector3>();
+                    _currentPathIndex = 0;
+                    _currentPathPoint = GlobalTransform.Origin;
+                    return;
+                }
+            }
+
+            _currentPathPoint = _currentPath[_currentPathIndex];
+
+            Vector3 newVelocity = GlobalTransform.Origin.DirectionTo(_currentPathPoint) * _movementDelta;
+            var globalTransform = GlobalTransform;
+            globalTransform.Origin = globalTransform.Origin.MoveToward(globalTransform.Origin + newVelocity, _movementDelta);
+            GlobalTransform = globalTransform;
+        }
+    }

+ 19 - 3
tutorials/performance/thread_safe_apis.rst

@@ -25,22 +25,38 @@ Scene tree
 
 Interacting with the active scene tree is **NOT** thread-safe. Make sure to use mutexes when sending data between threads. If you want to call functions from a thread, the *call_deferred* function may be used:
 
-::
+.. tabs::
+ .. code-tab:: gdscript
 
     # Unsafe:
     node.add_child(child_node)
     # Safe:
     node.add_child.call_deferred(child_node)
 
+ .. code-tab:: csharp
+
+    // Unsafe:
+    node.AddChild(childNode);
+    // Safe:
+    node.CallDeferred(Node.MethodName.AddChild, childNode);
+
 However, creating scene chunks (nodes in tree arrangement) outside the active tree is fine. This way, parts of a scene can be built or instantiated in a thread, then added in the main thread:
 
-::
+.. tabs::
+ .. code-tab:: gdscript
 
     var enemy_scene = load("res://enemy_scene.scn")
     var enemy = enemy_scene.instantiate()
     enemy.add_child(weapon) # Set a weapon.
     world.add_child.call_deferred(enemy)
 
+ .. code-tab:: csharp
+
+    PackedScene enemyScene = GD.Load<PackedScene>("res://EnemyScene.scn");
+    Node enemy = enemyScene.Instantiate<Node>();
+    enemy.AddChild(weapon);
+    world.CallDeferred(Node.MethodName.AddChild, enemy);
+
 Still, this is only really useful if you have **one** thread loading data.
 Attempting to load or create scene chunks from multiple threads may work, but you risk
 resources (which are only loaded once in Godot) tweaked by the multiple
@@ -61,7 +77,7 @@ Note that the Multi-Threaded thread model has several known bugs, so it may not
 in all scenarios.
 
 You should avoid calling functions involving direct interaction with the GPU on other threads, such as creating new textures
-or modifying and retrieving image data, these operations can lead to performance stalls because they require synchronization 
+or modifying and retrieving image data, these operations can lead to performance stalls because they require synchronization
 with the :ref:`RenderingServer<class_RenderingServer>`, as data needs to be transmitted to or updated on the GPU.
 
 GDScript arrays, dictionaries

+ 35 - 0
tutorials/performance/using_servers.rst

@@ -252,6 +252,41 @@ and moves a :ref:`CanvasItem <class_CanvasItem>` when the body moves.
         # if you have many bodies and a single callback.
         Physics2DServer.body_set_force_integration_callback(body, self, "_body_moved", 0)
 
+ .. code-tab:: csharp
+
+    using Godot;
+
+    public partial class MyNode2D : Node2D
+    {
+        private Rid _canvasItem;
+
+        private void BodyMoved(PhysicsDirectBodyState2D state, int index)
+        {
+            RenderingServer.CanvasItemSetTransform(_canvasItem, state.Transform);
+        }
+
+        public override void _Ready()
+        {
+            // Create the body.
+            var body = PhysicsServer2D.BodyCreate();
+            PhysicsServer2D.BodySetMode(body, PhysicsServer2D.BodyMode.Rigid);
+            // Add a shape.
+            var shape = PhysicsServer2D.RectangleShapeCreate();
+            // Set rectangle extents.
+            PhysicsServer2D.ShapeSetData(shape, new Vector2(10, 10));
+            // Make sure to keep the shape reference!
+            PhysicsServer2D.BodyAddShape(body, shape);
+            // Set space, so it collides in the same space as current scene.
+            PhysicsServer2D.BodySetSpace(body, GetWorld2D().Space);
+            // Move initial position.
+            PhysicsServer2D.BodySetState(body, PhysicsServer2D.BodyState.Transform, new Transform2D(0, new Vector2(10, 20)));
+            // Add the transform callback, when body moves
+            // The last parameter is optional, can be used as index
+            // if you have many bodies and a single callback.
+            PhysicsServer2D.BodySetForceIntegrationCallback(body, new Callable(this, MethodName.BodyMoved), 0);
+        }
+    }
+
 The 3D version should be very similar, as 2D and 3D physics servers are identical (using
 :ref:`RigidBody3D <class_RigidBody3D>` and :ref:`PhysicsServer3D <class_PhysicsServer3D>` respectively).
 

+ 7 - 0
tutorials/physics/ragdoll_system.rst

@@ -68,6 +68,13 @@ The ragdoll is now ready to use. To start the simulation and play the ragdoll an
     func _ready():
         physical_bones_start_simulation()
 
+ .. code-tab:: csharp
+
+    public override void _Ready()
+    {
+        PhysicalBonesStartSimulation();
+    }
+
 To stop the simulation, call the ``physical_bones_stop_simulation()`` method.
 
 .. image:: img/ragdoll_sim_stop.gif

+ 18 - 0
tutorials/physics/ray-casting.rst

@@ -171,6 +171,24 @@ with Area3D, the boolean parameter ``collide_with_areas`` must be set to ``true`
 
             var result = space_state.intersect_ray(query)
 
+ .. code-tab:: csharp
+
+    private const int RayLength = 1000;
+
+    public override void _PhysicsProcess(double delta)
+    {
+        var spaceState = GetWorld3D().DirectSpaceState;
+        var cam = GetNode<Camera3D>("Camera3D");
+        var mousePos = GetViewport().GetMousePosition();
+
+        var origin = cam.ProjectRayOrigin(mousePos);
+        var end = origin + cam.ProjectRayNormal(mousePos) * RayLength;
+        var query = PhysicsRayQueryParameters3D.Create(origin, end);
+        query.CollideWithAreas = true;
+
+        var result = spaceState.IntersectRay(query);
+    }
+
 Collision exceptions
 --------------------