Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
thedivergentai avatar

Godot Adapt 3d To 2d

  • 154 installs
  • 454 repo stars
  • Updated July 28, 2026
  • thedivergentai/gd-agentic-skills

Use godot-adapt-3d-to-2d for development tasks

About

godot-adapt-3d-to-2d: A skill for development. This provides functionality for development workflows.

  • godot-adapt-3d-to-2d

Godot Adapt 3d To 2d by the numbers

  • 154 all-time installs (skills.sh)
  • +16 installs in the week ending Aug 5, 2026 (Skillselion tracking)
  • Ranked #2,466 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-adapt-3d-to-2d

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs154
repo stars454
Last updatedJuly 28, 2026
Repositorythedivergentai/gd-agentic-skills

What it does

Use godot-adapt-3d-to-2d for development tasks

Files

SKILL.mdMarkdownGitHub ↗

Adapt: 3D to 2D

Expert guidance for simplifying 3D games into 2D (or 2.5D).

NEVER Do

  • NEVER remove Z-axis without gameplay compensation — Blindly flattening 3D to 2D removes spatial strategy. Add other depth mechanics (layers, jump height variations).
  • NEVER keep 3D collision shapes — Use simpler 2D shapes (CapsuleShape2D, RectangleShape2D). 3D shapes don't convert automatically.
  • NEVER use orthographic Camera3D as "2D mode" — Use actual Camera2D for proper 2D rendering pipeline and performance.
  • NEVER assume automatic performance gain — Poorly optimized 2D (too many draw calls, large sprite sheets) can be slower than optimized 3D.
  • NEVER forget to adjust gravity — 3D gravity is Vector3(0, -9.8, 0). 2D gravity is float (980 pixels/s²). Scale appropriately.

---

Available Scripts

MANDATORY: Read the appropriate script before implementing the corresponding pattern.

ortho_simulation.gd

Simulates 3D Z-axis height in 2D top-down games. Handles vertical velocity, gravity, sprite offset, and shadow scaling.

projection_utils.gd

Projects 3D world positions to 2D screen space for nameplates, healthbars, and targeting. Handles behind-camera detection and distance-based scaling.

isometric_math_core.gd

Expert utility generating translating between 2D Cartesian and True Isometric screenspace projection matrices without using 2D Node transforms.

depth_sorting_y_sort.gd

Expert dynamic Z-index Y-Sort script for fake 3D sorting isolated trees matching CanvasItem _update_sorting().

jump_z_axis_sim.gd

Complete CharacterBody2D snippet separating structural physical ground movement (X,Y) from a mathematically simulated jumping height (Z) in a topdown game.

parallax_depth_camera.gd

Fake Depth Camera applying varying offset algorithms to completely disparate CanvasLayers based on an index to simulate 3D camera translation panning.

hitbox_depth_manager.gd

Area2D derived class that requires explicit custom Z-height overlap (1D AABB collision) prior to validating 2D triggers to stop incorrect "ground vs air" collision in 2.5D.

fake_3d_shadows.gd

Sprite2D shadow simulator exploiting Godot 4.x Transform2D matrix skew shear to project and angle shadows away from a simulated 3D sun direction on a 2D floor.

billboard_sprite_manager.gd

8-directional FPS Doom-style sprite controller isolating the simulated 3D relative angle between a moving 2D CharacterBody and a Camera2D viewpoint.

nav_region_flattening.gd

Topdown 2D pathfinding workaround allowing "aerial" units to cross walls by leveraging multiple tiered 2D Navigation Layers instead of proper 3D verticality.

ortho_to_perspective_fx.gd

Screen space CanvasItem warp Shader simulating a Mode 7 / tabletop perspective pitch. Maps top screen coordinates via division pinching.

2d_lighting_normals.gd

Automatic programmatic generation of CanvasTexture combining base albedo and baked normal maps at runtime so Sprites correctly react to 2D PointLIGHTs like 3D geometry.

---

Why Go from 3D to 2D?

ReasonBenefit
Mobile performance5-10x faster on low-end devices
Simpler art pipelineSprites easier to create than 3D models
Faster iteration2D level design is quicker
AccessibilityLower hardware requirements
ClarityReduce visual clutter for puzzle/strategy games

---

Dimension Reduction Strategies

Strategy 1: True 2D (Remove Z-axis)

# Top-down or side-view
# Example: 3D isometric → 2D top-down

# Before (3D):
var velocity := Vector3(input.x, 0, input.y) * speed

# After (2D):
var velocity := Vector2(input.x, input.y) * speed

# Use case: Top-down shooters, RTS, turn-based strategy

Strategy 2: 2.5D (Fake depth with layers)

# Keep visual depth perception without Z-axis gameplay
# Use ParallaxBackground for depth layers

# Scene structure:
# ParallaxBackground
#   ├─ ParallaxLayer (far mountains, scroll slow)
#   ├─ ParallaxLayer (mid buildings, scroll medium)
#   └─ ParallaxLayer (near trees, scroll fast)

# player.gd
extends CharacterBody2D

func _ready() -> void:
    var parallax := get_node("../ParallaxBackground")
    parallax.scroll_base_scale = Vector2(0.5, 0.5)  # Parallax strength

Strategy 3: Fixed Perspective (Isometric Stay)

# Keep isometric/dimetric view but use 2D physics
# Use rotated sprites to simulate 3D angles

const ISO_ANGLE := deg_to_rad(-30)  # Isometric tilt

func world_to_iso(pos: Vector2) -> Vector2:
    return Vector2(
        pos.x - pos.y,
        (pos.x + pos.y) * 0.5
    )

func iso_to_world(iso_pos: Vector2) -> Vector2:
    return Vector2(
        (iso_pos.x + iso_pos.y * 2) * 0.5,
        (iso_pos.y * 2 - iso_pos.x) * 0.5
    )

---

Node Conversion

Physics Bodies

# CharacterBody3D → CharacterBody2D
extends CharacterBody3D  # Before

const SPEED = 5.0
const JUMP_VELOCITY = 4.5
const GRAVITY = 9.8

func _physics_process(delta: float) -> void:
    velocity.y -= GRAVITY * delta
    var input := Input.get_vector("left", "right", "forward", "back")
    velocity.x = input.x * SPEED
    velocity.z = input.y * SPEED
    move_and_slide()

# ⬇️ Convert to:

extends CharacterBody2D  # After

const SPEED = 300.0
const JUMP_VELOCITY = -400.0
const GRAVITY = 980.0  # Pixels per second squared

func _physics_process(delta: float) -> void:
    velocity.y += GRAVITY * delta
    var input := Input.get_vector("left", "right", "up", "down")
    velocity.x = input.x * SPEED
    # Note: No Z-axis. For platformer, use input.y for jump
    move_and_slide()

Camera Conversion

# Camera3D → Camera2D
# Before: Third-person 3D camera
extends SpringArm3D

@onready var camera: Camera3D = $Camera3D

func _process(delta: float) -> void:
    spring_length = 10.0
    rotate_y(Input.get_axis("cam_left", "cam_right") * delta)

# ⬇️ Convert to:

extends Camera2D  # After

@onready var player: CharacterBody2D = $"../Player"

func _process(delta: float) -> void:
    global_position = player.global_position
    zoom = Vector2(2.0, 2.0)  # Adjust to taste

---

Art Pipeline: 3D Models → Sprites

Option 1: Render Sprites from 3D (Automation)

# Use Godot to render 3D model from fixed angles
# sprite_renderer.gd (tool script)
@tool
extends Node3D

@export var model_path: String = "res://models/character.glb"
@export var output_dir: String = "res://sprites/"
@export var angles: int = 8  # 8-directional sprites
@export var render: bool = false:
    set(value):
        if value:
            render_sprites()

func render_sprites() -> void:
    var model := load(model_path).instantiate()
    add_child(model)
    
    var camera := Camera3D.new()
    camera.position = Vector3(0, 2, 5)
    camera.look_at(Vector3.ZERO)
    add_child(camera)
    
    var viewport := SubViewport.new()
    viewport.size = Vector2i(256, 256)
    viewport.transparent_bg = true
    viewport.add_child(camera)
    add_child(viewport)
    
    for i in range(angles):
        model.rotation.y = (TAU / angles) * i
        
        await RenderingServer.frame_post_draw
        var img := viewport.get_texture().get_image()
        img.save_png("%s/sprite_%d.png" % [output_dir, i])
    
    model.queue_free()
    camera.queue_free()
    viewport.queue_free()

Option 2: Manual Export (Blender)

# Blender Python script (run in Blender)
import bpy
import math

angles = 8
output_dir = "/path/to/sprites/"
model = bpy.data.objects["Character"]

for i in range(angles):
    model.rotation_euler.z = (2 * math.pi / angles) * i
    bpy.ops.render.render(write_still=True)
    bpy.data.images['Render Result'].save_render(
        filepath=f"{output_dir}/sprite_{i}.png"
    )

Option 3: Use Sprite3D as Reference

# Keep 3D model in editor, export  frame-by-frame

---

Physics Adjustments

Gravity Scaling

# 3D gravity (m/s²): 9.8
# 2D gravity (pixels/s²): Scale to pixel units

# If 1 meter = 100 pixels:
const GRAVITY_2D = 9.8 * 100  # = 980 pixels/s²

# Adjust jump velocity proportionally:
# 3D jump: 4.5 m/s
# 2D jump: -450 pixels/s

Collision Simplification

# 3D: CapsuleShape3D (16 segments, expensive)
var shape_3d := CapsuleShape3D.new()
shape_3d.radius = 0.5
shape_3d.height = 2.0

# 2D: CapsuleShape2D (much simpler)
var shape_2d := CapsuleShape2D.new()
shape_2d.radius = 16  # pixels
shape_2d.height = 64

---

Control Simplification

3D Free Movement → 2D Restricted

# 3D: Full 3D movement with camera-relative controls
var input_3d := Input.get_vector("left", "right", "forward", "back")
var camera_basis := camera.global_transform.basis
var direction := (camera_basis * Vector3(input_3d.x, 0, input_3d.y)).normalized()

# 2D: Simple 4-direction (or 8-direction with diagonals)
var input_2d := Input.get_vector("left", "right", "up", "down")
velocity = input_2d.normalized() * SPEED

---

Performance Gains

Expected Improvements

Metric3D2DImprovement
Draw calls100205x
GPU loadHighLow10x
Battery life (mobile)1 hour5 hours5x
RAM usage500MB100MB5x

Optimization Techniques

# 1. Use TileMapLayer instead of individual Sprite2D nodes
var tilemap := TileMapLayer.new()
tilemap.tile_set = load("res://tileset.tres")

# 2. Batch sprite rendering
# Use single large sprite sheet instead of individual textures

# 3. Reduce particle count
var godot-particles := GPUParticles2D.new()
godot-particles.amount = 50  # Down from 200 in 3D

---

UI Adaptation

# Most 3D games already use 2D UI (CanvasLayer)
# No changes needed!

# Just verify UI scaling for new aspect ratios
get_viewport().size_changed.connect(_on_viewport_resized)

func _on_viewport_resized() -> void:
    var viewport_size := get_viewport().get_visible_rect().size
    # Adjust UI anchors/margins

---

Edge Cases

Depth Sorting

# Problem: Overlapping sprites need sorting
# Solution: Use Y-sort or z_index

extends Sprite2D

func _ready() -> void:
    y_sort_enabled = true  # Auto-sort by Y position
    # Or set z_index manually:
    z_index = int(global_position.y)

Lost Spatial Audio

# 3D spatial audio (AudioStreamPlayer3D) → 2D panning (AudioStreamPlayer2D)

var audio_2d := AudioStreamPlayer2D.new()
audio_2d.stream = load("res://sounds/footstep.ogg")
audio_2d.max_distance = 1000.0  # 2D range
audio_2d.attenuation = 2.0
add_child(audio_2d)

---

Decision Tree: When to Simplify to 2D

FactorKeep 3DGo 2D
Target platformDesktop, consoleMobile, web
Art styleRealistic, immersiveStylized, retro
GameplayRequires 3D spaceWorks in 2D plane
PerformanceHave GPU budgetNeed 60 FPS on low-end
Team skills3D artists2D artists or pixel art

Expert Techniques & Optimizations

1. Flattened Navigation (3D Navmesh to 2D Grid)

While Godot treats 2D and 3D navigation as separate systems, you can project 3D pathfinding logic onto a 2D grid using AStarGrid2D. This is highly optimized for 2D grid-based movement and avoids the overhead of a full 3D navmesh.

class_name GridNavBridge extends Node

var astar_grid: AStarGrid2D

func _ready() -> void:
    astar_grid = AStarGrid2D.new()
    astar_grid.region = Rect2i(0, 0, 100, 100)
    astar_grid.cell_size = Vector2(16, 16)
    astar_grid.update()

## Converts a 3D target position to a 2D grid path.
func get_grid_path_from_3d(start_3d: Vector3, end_3d: Vector3) -> PackedVector2Array:
    var start_map := Vector2i(start_3d.x / 16, start_3d.z / 16)
    var end_map := Vector2i(end_3d.x / 16, end_3d.z / 16)
    
    return astar_grid.get_point_path(start_map, end_map)

2. Auto-LOD for 2D (Performance Optimization)

Automatic LOD is natively a 3D feature, but you can simulate it in 2D using VisibleOnScreenEnabler2D. This node automatically toggles the process_mode of target nodes (like high-res sprites or complex AI) when they leave the screen, preserving CPU cycles and GPU fill rate.

# Attach to a complex 2D entity
func setup_2d_lod(target_node: Node2D) -> void:
    var enabler := VisibleOnScreenEnabler2D.new()
    # Define the 'high-detail' rect
    enabler.rect = Rect2(-64, -64, 128, 128) 
    enabler.enable_node_path = target_node.get_path()
    add_child(enabler)

3. Dimensional Patcher (CharacterBody3D to 2D Regex)

To automate the down-porting of 3D controllers, use a RegEx script to map Vector3 to Vector2 and replace 3D-specific properties. This is essential for massive porting tasks where manual conversion of movement logic is prone to error.

@tool
extends EditorScript

func _run() -> void:
    var regex = RegEx.new()
    # Pattern to find Vector3 constructors and replace with Vector2
    regex.compile("Vector3\\(([^,]+),\\s*([^,]+),\\s*([^)]+)\\)")
    
    var script_content = "velocity = Vector3(input.x, 0.0, input.y) * speed"
    var result = regex.sub(script_content, "Vector2($1, $3)", true)
    
    # Output: "velocity = Vector2(input.x, input.y) * speed"
    print(result)

Reference

  • Master Skill: godot-master

Related skills

Backend & APIsbackendintegrations

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.