Claude
Skills
Sign in
Back

godot-adapt-3d-to-2d

Included with Lifetime
$97 forever

Expert patterns for simplifying 3D games to 2D including dimension reduction strategies, camera flattening, physics conversion, 3D-to-sprite art pipeline, and control simplification. Use when porting 3D to 2D, creating 2D versions for mobile, or prototyping. Trigger keywords: CharacterBody3D to CharacterBody2D, Camera3D to Camera2D, Vector3 to Vector2, flatten Z-axis, orthogonal projection, 3D to sprite conversion, performance optimization.

Ads & Marketingscripts

What this skill does


# 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](scripts/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](scripts/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](scripts/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](scripts/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](scripts/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](scripts/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](scripts/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](scripts/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](scripts/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](scripts/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](scripts/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](scripts/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?

| Reason | Benefit |
|--------|---------|
| **Mobile performance** | 5-10x faster on low-end devices |
| **Simpler art pipeline** | Sprites easier to create than 3D models |
| **Faster iteration** | 2D level design is quicker |
| **Accessibility** | Lower hardware requirements |
| **Clarity** | Reduce visual clutter for puzzle/strategy games |

---

## Dimension Reduction Strategies

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

```gdscript
# 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)

```gdscript
# 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)

```gdscript
# 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

```gdscript
# 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

```gdscript
# 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)

```gdscript
# 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)

```python
# 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(

Related in Ads & Marketing