Skill v1.0.1
currentAutomated scan100/10016 files
version: "1.0.1" name: godot-genre-rts description: "Expert blueprint for real-time strategy games including unit selection (drag box, shift-add), command systems (move, attack, gather), pathfinding (NavigationAgent2D with RVO avoidance), fog of war (SubViewport mask shader), resource economy (gather/build loop), and AI opponents (behavior trees, utility AI). Use for base-building RTS or tactical combat games. Trigger keywords: RTS, unit_selection, command_system, fog_of_war, pathfinding_RVO, resource_economy, command_queue."
Genre: Real-Time Strategy (RTS)
Expert blueprint for RTS games balancing strategy, micromanagement, and performance.
NEVER Do (Expert Anti-Patterns)
Unit Logic & Pathfinding
- NEVER allow pathfinding "Jitter" when moving group units; strictly stagger path queries and enable RVO Avoidance only when units are in motion to save CPU cycles.
- NEVER update RVO avoidance every frame for all units; strictly use Avoidance Threading (Project Settings) and replace static units with
NavigationObstacle. - NEVER let units get stuck in infinite path loops; strictly implement a timeout and IDLE state if a destination is unreachable.
- NEVER use
_process()on hundreds of individual units; strictly use a central UnitManager or_physics_processonly when required. - NEVER calculate unit visibility manually for Fog of War; strictly use a Shader-based mask (SubViewport + ColorRect) for GPU efficiency.
- NEVER process unit AI or pathfinding synchronously for mass groups; strictly offload to `WorkerThreadPool` and stagger path updates.
- NEVER use high-poly visual meshes as NavMesh source geometry; strictly use simplified Collision Shapes for baking.
Interaction & Commands
- NEVER forget Command Queuing (Shift-Click); strictly store an
Array[Command]and implement a "Force Move/Attack" bypass. - NEVER create excessive micromanagement; strictly automate low-level tasks like auto-aggro range and auto-return for resource gathering.
- NEVER use exact floating-point equality (==) for grid or timers; strictly use
is_equal_approx()for deterministic triggers. - NEVER rely on the visual SceneTree for selection data; strictly maintain a Typed Selection Set of
RefCountedorResourceobjects for deterministic serialization and netcode. - NEVER forget Command Queuing; strictly implement a Command Pattern using serializable
DictionaryorJSONstates for save-game and multiplayer playback. - NEVER forget to duplicate_deep() globally shared Resources; otherwise, modifying one unit's data (e.g., stats) affects all.
Performance & Simulation
- NEVER render thousands of units using separate
MeshInstance3Dnodes; strictly use `MultiMeshInstance` with `INSTANCE_CUSTOM` data to drive unique GPU-side state animations (walking/attacking/color). - NEVER calculate transforms for mass units on the main thread; strictly use `WorkerThreadPool` to push buffers to
RenderingServer.multimesh_set_buffer(). - NEVER update every unit's navigation path in the same frame; strictly use random timers to stagger updates.
- NEVER use standard Strings for high-frequency AI state identifiers; strictly use StringName (&"harvesting") for pointer-speed comparisons.
- NEVER allow simulation coordinates to exceed 8192 units without float-precision management; strictly use world-origin shifts.
- NEVER use
CSGShape3Dfor building placement ghosts; strictly use optimized staticArrayMeshgeometry.
🛠 Expert Components (scripts/)
Original Expert Patterns
- selection_manager_marquee_2d.gd - Professional-grade unit selection system with drag-box, unit filtering, and shift-add support.
Modular Components
- rts_army_manager.gd - Multithreaded AI update system for managing mass units on background cores.
- selection_manager_raycast_3d.gd - Optimized 3D selection using direct PhysicsServer raycasting.
- rts_path_query_pool.gd - Pooled Navigation query system to prevent memory allocations.
- navigation_mask_helper.gd - Bitmask utilities for dynamic navigation layers and avoidance.
- rts_targeting_logic.gd - Distance-squared performance optimization for mass enemy filtering.
- rts_group_commander.gd - SceneTree group broadcasting pattern for decoupled mass units.
- rts_unit_stat_duplicator.gd - Pattern for deep duplicating unit data for isolation.
- rts_unit.gd - Comprehensive unit controller with state management and navigation integration.
- building_grid_astar.gd - High-speed grid-based pathfinding for building placement.
- fog_of_war_tile_mask.gd - Efficient Fog of War clearing using the TileMapLayer API and Vector2i.
- rendering_ghost_spawner.gd - Optimized placement ghosts using RenderingServer RIDs.
Core Loop
- Gather: Units collect resources (Gold, Wood, etc.).
- Build: Construct base buildings to unlock tech/units.
- Train: Produce an army of diverse units.
- Command: Micromanage units in real-time battles.
- Expand: Secure map control and resources.
Skill Chain
| Phase | Skills | Purpose | |
|---|---|---|---|
| 1. Controls | godot-input-handling, camera-rts | Selection box, camera panning/zoom | |
| 2. Units | navigation-server, state-machines | Pathfinding, avoidance, states (Idle/Move/Attack) | |
| 3. Systems | fog-of-war, building-system | Map visibility, grid placement | |
| 4. AI | behavior-trees, utility-ai | Enemy commander logic | |
| 5. Polish | ui-minimap, godot-particles | Strategic overview, battle feedback |
Architecture Overview
1. Selection Manager (Singleton or Commander Node)
Handles mouse input for selecting units.
# selection_manager.gdextends Node2Dvar selected_units: Array[Unit] = []var drag_start: Vector2var is_dragging: bool = false@onready var selection_box: Panel = $SelectionBoxfunc _unhandled_input(event):if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:if event.pressed:start_selection(event.position)else:end_selection(event.position)elif event is InputEventMouseMotion and is_dragging:update_selection_box(event.position)func end_selection(end_pos: Vector2):is_dragging = falseselection_box.visible = falsevar rect = Rect2(drag_start, end_pos - drag_start).abs()if Input.is_key_pressed(KEY_SHIFT):# Add to selectionpasselse:deselect_all()# Query physics server for units in rectvar query = PhysicsShapeQueryParameters2D.new()var shape = RectangleShape2D.new()shape.size = rect.sizequery.shape = shapequery.transform = Transform2D(0, rect.get_center())# ... execute query and add units to selected_unitsfor unit in selected_units:unit.set_selected(true)func issue_command(target_position: Vector2):for unit in selected_units:unit.move_to(target_position)
2. Unit Controller (State Machine)
Units need robust state management to handle commands and auto-attacks.
# unit.gdextends CharacterBody2Dclass_name Unitenum State { IDLE, MOVE, ATTACK, HOLD }var state: State = State.IDLEvar command_queue: Array[Command] = []@onready var nav_agent: NavigationAgent2D = $NavigationAgent2Dfunc move_to(target: Vector2):nav_agent.target_position = targetstate = State.MOVEfunc _physics_process(delta):if state == State.MOVE:if nav_agent.is_navigation_finished():state = State.IDLEreturnvar next_pos = nav_agent.get_next_path_position()var direction = global_position.direction_to(next_pos)velocity = direction * speedmove_and_slide()### 3. Group Movement & FlockingInstead of moving all units directly to a single point (clumping), use **Relative Offsets**:- Calculate the **Center of Mass** for the selected group.- On click, calculate each unit's **Relative Offset** from the center.- Issue `target_position + unit_offset` to each unit to maintain formation.
3. Fog of War
A system to hide unvisited areas. Usually implemented with a texture and a shader.
- Grid Approach: 2D array of "visibility" values.
- Viewport Texture: A
SubViewportdrawing white circles for units on a black background. This texture is then used as a mask in a shader on a full-screenColorRectoverlay.
shader_type canvas_item;uniform sampler2D visibility_texture;uniform vec4 fog_color : source_color;void fragment() {float visibility = texture(visibility_texture, UV).r;COLOR = mix(fog_color, vec4(0,0,0,0), visibility);}
Key Mechanics Implementation
Command Queue
Allow players to chain commands (Shift-Click).
- Implementation: Store commands in an
Array. When one finishes, pop the next. - Visuals: Draw lines showing the queued path.
Resource Gathering
- Nodes:
ResourceNode(Tree/GoldMine) andDropoffPoint(TownCenter). - Logic:
- Move to Resource.
- Work (Timer).
- Move to Dropoff.
- Deposit (Global Economy update).
- Repeat.
Common Pitfalls
- Pathfinding Jitter: Units pushing each other endlessly. Fix: Use RVO (Reciprocal Velocity Obstacles) built into Godot's
NavigationAgent2D(propertiesavoidance_enabled,radius). - Too Much Micro: Automate mundane tasks (auto-attack nearby, auto-gather behavior).
- Performance: Too many nodes. Fix: Use
MultiMeshInstance2Dfor rendering thousands of units if needed, and run logic on aServernode rather than individual scripts for mass units.
Godot-Specific Tips
- Avoidance:
NavigationAgent2Dhas built-in RVO avoidance. Make sure to callset_velocity()and use thevelocity_computedsignal for the actual movement! - Server Architecture: For 100+ units, don't use
_processon every unit. Have a centralUnitManageriterate through active units to save function call overhead. - Groups: Use Groups heavily (
Units,Buildings,Resources) for easy selection filters.
🚀 Elite Technical Implementations (Batch 09)
1. Center-of-Mass Formation Movement Pattern
To prevent CPU bottlenecks when moving hundreds of units, avoid querying individual paths. Instead, calculate the "Center of Mass" of the selection and perform a single NavigationServer3D (or 2D) path query.
class_name RTSFormationManager extends Node## Moves a group of units in formation to a target destination using a single path query.static func move_group_to_target(units: Array[CharacterBody3D], target_position: Vector3, map_rid: RID) -> void:if units.is_empty():return# 1. Calculate the Center of Mass (Average Position)var center_of_mass := Vector3.ZEROfor unit in units:center_of_mass += unit.global_positioncenter_of_mass /= units.size()# 2. Query NavigationServer for the optimized central pathvar central_path: PackedVector3Array = NavigationServer3D.map_get_path(map_rid,center_of_mass,target_position,true)if central_path.is_empty():returnvar final_center_destination: Vector3 = central_path[central_path.size() - 1]# 3. Distribute commands with relative offsets to maintain formationfor unit in units:var offset: Vector3 = unit.global_position - center_of_massvar unit_destination: Vector3 = final_center_destination + offsetif unit.has_method("set_movement_target"):unit.set_movement_target(unit_destination)
2. MultiMeshInstance Rendering for Massive Armies
Standard nodes fail when unit counts reach thousands. Use MultiMeshInstance3D to draw millions of objects in a single draw call via the GPU.
- Architectural Tip: Pre-allocate the maximum expected units and toggle visibility via
visible_instance_count. - Performance: Note that individual frustum culling is disabled for MultiMesh instances; the entire group is either drawn or not.
class_name RTSMassiveUnitRenderer extends MultiMeshInstance3D@export var max_units: int = 10000@export var unit_mesh: Meshfunc _ready() -> void:multimesh = MultiMesh.new()multimesh.transform_format = MultiMesh.TRANSFORM_3Dmultimesh.use_colors = truemultimesh.instance_count = max_unitsmultimesh.mesh = unit_meshmultimesh.visible_instance_count = 0## Sync logical unit transforms to GPU instancesfunc synchronize_rendering(active_units: Array[Transform3D]) -> void:var count: int = min(active_units.size(), max_units)multimesh.visible_instance_count = countfor i in range(count):multimesh.set_instance_transform(i, active_units[i])
3. SubViewport Fog-of-War System
Avoid complex geometry. Use a SubViewport as a dynamic render target to generate a vision mask (White = Vision, Black = Fog).
Mask Generator Logic:
class_name FogOfWarManager extends SubViewport@export var terrain_material: ShaderMaterial@export var world_bounds: Vector2 = Vector2(1024, 1024)func _ready() -> void:disable_3d = truerender_target_update_mode = SubViewport.UPDATE_ALWAYSawait RenderingServer.frame_post_drawvar fow_texture: ViewportTexture = get_texture()terrain_material.set_shader_parameter("fow_mask", fow_texture)terrain_material.set_shader_parameter("world_bounds", world_bounds)
Projection Shader (Spatial):
shader_type spatial;uniform sampler2D fow_mask : hint_default_black, filter_linear;uniform vec2 world_bounds;uniform vec3 fog_color : source_color = vec3(0.1, 0.1, 0.15);void fragment() {// Map World X/Z to 2D UV Coordinatesvec2 fow_uv = (NODE_POSITION_WORLD.xz / world_bounds) + vec2(0.5);float visibility = texture(fow_mask, fow_uv).r;ALBEDO = mix(fog_color, ALBEDO, visibility);}
- Master Skill: godot-master