godot-adapt-3d-to-2d

安装量: 43
排名: #17075

安装

npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-adapt-3d-to-2d
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. 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)

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 Metric 3D 2D Improvement Draw calls 100 20 5x GPU load High Low 10x Battery life (mobile) 1 hour 5 hours 5x RAM usage 500MB 100MB 5x 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 Factor Keep 3D Go 2D Target platform Desktop, console Mobile, web Art style Realistic, immersive Stylized, retro Gameplay Requires 3D space Works in 2D plane Performance Have GPU budget Need 60 FPS on low-end Team skills 3D artists 2D artists or pixel art Reference Master Skill: godot-master

返回排行榜