godot-adapt-2d-to-3d

安装量: 42
排名: #17330

安装

npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-adapt-2d-to-3d
Adapt: 2D to 3D
Expert guidance for migrating 2D games into the third dimension.
NEVER Do
NEVER directly replace Vector2 with Vector3(x, y, 0)
— This creates a "flat 3D" game with no depth gameplay. Add Z-axis movement or camera rotation to justify 3D.
NEVER keep 2D collision layers
— 2D and 3D physics use separate layer systems. You must reconfigure collision_layer/collision_mask for 3D nodes.
NEVER forget to add lighting
— 3D without lights is pitch black (unless using unlit materials). Add at least one DirectionalLight3D.
NEVER use Camera2D follow logic in 3D
— Camera3D needs spring arm or look-at logic. Direct position copying causes clipping and disorientation.
NEVER assume same performance
— 3D is 5-10x more demanding. Budget for lower draw calls, smaller viewport resolution on mobile.
Available Scripts
MANDATORY
Read the appropriate script before implementing the corresponding pattern. sprite_plane.gd Sprite3D billboard configuration and world-to-screen projection for placing 2D UI over 3D objects. Handles behind-camera detection. vector_mapping.gd Static utility for 2D→3D vector translation. The Y-to-Z rule: 2D Y (down) maps to 3D Z (forward). Essential for movement code. Node Conversion Matrix 2D Node 3D Equivalent Notes CharacterBody2D CharacterBody3D Add Z-axis movement, rotate with mouse RigidBody2D RigidBody3D Gravity now Vector3(0, -9.8, 0) StaticBody2D StaticBody3D Collision shapes use Shape3D Area2D Area3D Triggers work the same way Sprite2D MeshInstance3D + QuadMesh Or use Sprite3D (billboarded) AnimatedSprite2D AnimatedSprite3D Billboard mode available TileMapLayer GridMap Requires MeshLibrary creation Camera2D Camera3D Requires repositioning logic CollisionShape2D CollisionShape3D BoxShape2D → BoxShape3D, etc. RayCast2D RayCast3D target_position is now Vector3 Migration Steps Step 1: Physics Layer Reconfiguration

2D collision layers are SEPARATE from 3D

You must reconfigure in Project Settings → Layer Names → 3D Physics

Before (2D):

Layer 1: Player

Layer 2: Enemies

Layer 3: World

After (3D) - same names, but different system

In code, update all collision layer references:

2D version:

collision_layer = 0b0001

3D version (same logic, different node):

var character_3d := CharacterBody3D . new ( ) character_3d . collision_layer = 0b0001

Layer 1: Player

character_3d . collision_mask = 0b0110

Detect Enemies + World

Step 2: Camera Conversion

❌ BAD: Direct 2D follow logic

extends Camera3D @ onready var player : Node3D = $ "../Player" func _process ( delta : float ) -> void : global_position = player . global_position

Clipping, disorienting!

✅ GOOD: Third-person camera with SpringArm3D

Scene structure:

Player (CharacterBody3D)

└─ SpringArm3D

└─ Camera3D

player.gd

extends CharacterBody3D @ onready var spring_arm : SpringArm3D = $SpringArm3D @ onready var camera : Camera3D = $SpringArm3D / Camera3D func _ready ( ) -> void : spring_arm . spring_length = 10.0

Distance from player

spring_arm . position = Vector3 ( 0 , 2 , 0 )

Above player

func _unhandled_input ( event : InputEvent ) -> void : if event is InputEventMouseMotion : spring_arm . rotate_y ( - event . relative . x * 0.005 )

Horizontal rotation

spring_arm . rotate_object_local ( Vector3 . RIGHT , - event . relative . y * 0.005 )

Vertical

Clamp vertical rotation

spring_arm . rotation . x = clamp ( spring_arm . rotation . x , - PI / 3 , PI / 6 ) Step 3: Movement Conversion

2D platformer movement

extends CharacterBody2D const SPEED = 300.0 const JUMP_VELOCITY = - 400.0 func _physics_process ( delta : float ) -> void : if not is_on_floor ( ) : velocity . y += gravity * delta if Input . is_action_just_pressed ( "jump" ) and is_on_floor ( ) : velocity . y = JUMP_VELOCITY var direction := Input . get_axis ( "left" , "right" ) velocity . x = direction * SPEED move_and_slide ( )

✅ 3D equivalent (third-person platformer)

extends CharacterBody3D const SPEED = 5.0 const JUMP_VELOCITY = 4.5 const GRAVITY = 9.8 @ onready var spring_arm : SpringArm3D = $SpringArm3D func _physics_process ( delta : float ) -> void : if not is_on_floor ( ) : velocity . y -= GRAVITY * delta if Input . is_action_just_pressed ( "jump" ) and is_on_floor ( ) : velocity . y = JUMP_VELOCITY

Movement relative to camera direction

var input_dir := Input . get_vector ( "left" , "right" , "forward" , "back" ) var camera_basis := spring_arm . global_transform . basis var direction := ( camera_basis * Vector3 ( input_dir . x , 0 , input_dir . y ) ) . normalized ( ) if direction : velocity . x = direction . x * SPEED velocity . z = direction . z * SPEED

Rotate player to face movement direction

rotation . y = lerp_angle ( rotation . y , atan2 ( - direction . x , - direction . z ) , 0.1 ) else : velocity . x = move_toward ( velocity . x , 0 , SPEED ) velocity . z = move_toward ( velocity . z , 0 , SPEED ) move_and_slide ( ) Art Pipeline: Sprites → 3D Models Option 1: Billboard Sprites (2.5D)

Use Sprite3D for quick conversion

extends Sprite3D func _ready ( ) -> void : texture = load ( "res://sprites/character.png" ) billboard = BaseMaterial3D . BILLBOARD_ENABLED

Always face camera

pixel_size

0.01

Scale sprite in 3D space

Option 2: Quad Meshes (Floating Sprites)

Create textured quads

var mesh_instance := MeshInstance3D . new ( ) var quad := QuadMesh . new ( ) quad . size = Vector2 ( 1 , 1 ) mesh_instance . mesh = quad var material := StandardMaterial3D . new ( ) material . albedo_texture = load ( "res://sprites/character.png" ) material . transparency = BaseMaterial3D . TRANSPARENCY_ALPHA material . cull_mode = BaseMaterial3D . CULL_DISABLED

Show both sides

mesh_instance . material_override = material Option 3: Full 3D Models (Blender/Asset Library)

Import .glb, .fbx models

var character := load ( "res://models/character.glb" ) . instantiate ( ) add_child ( character )

Access animations

var anim_player := character . get_node ( "AnimationPlayer" ) anim_player . play ( "idle" ) Lighting Considerations Minimum Lighting Setup

Add to main scene

var sun := DirectionalLight3D . new ( ) sun . rotation_degrees = Vector3 ( - 45 , 30 , 0 ) sun . light_energy = 1.0 sun . shadow_enabled = true add_child ( sun )

Ambient light

var env := WorldEnvironment . new ( ) var environment := Environment . new ( ) environment . ambient_light_source = Environment . AMBIENT_SOURCE_COLOR environment . ambient_light_color = Color ( 0.3 , 0.3 , 0.4 )

Subtle blue

environment . ambient_light_energy = 0.5 env . environment = environment add_child ( env ) UI Adaptation

✅ GOOD: Keep 2D UI overlay

Scene structure:

Main (Node3D)

├─ WorldEnvironment

├─ DirectionalLight3D

├─ Player (CharacterBody3D)

└─ CanvasLayer # 2D UI on top of 3D world

└─ Control (HUD)

UI remains 2D (Control nodes, Sprite2D for HUD elements)

Performance Budgeting 2D vs 3D Performance Metric 2D Budget 3D Budget Notes Draw calls 100-200 50-100 Use fewer meshes Vertices Unlimited 100K-500K LOD important Lights N/A 3-5 shadowed Expensive Transparent objects Many <10 Sorting overhead Particle systems Many 2-3 max GPU godot-particles only Optimization Checklist

1. Use LOD for distant objects

var mesh_instance := MeshInstance3D . new ( ) mesh_instance . lod_bias = 1.0

Lower detail sooner

2. Occlusion culling

Use OccluderInstance3D for large walls/buildings

3. Reduce shadow distance

var sun := DirectionalLight3D . new ( ) sun . directional_shadow_max_distance = 50.0

Don't render far shadows

4. Use unlit materials for distant objects

var material := StandardMaterial3D . new ( ) material . shading_mode = BaseMaterial3D . SHADING_MODE_UNSHADED Input Scheme Changes 2D → 3D Input Mapping

2D: left/right for horizontal movement

Input . get_axis ( "left" , "right" )

3D: Add forward/back, use get_vector()

var input := Input . get_vector ( "left" , "right" , "forward" , "back" )

Returns Vector2(horizontal, vertical) for 3D movement

Configure in Project Settings → Input Map:

forward: W, Up Arrow

back: S, Down Arrow

left: A, Left Arrow

right: D, Right Arrow

Mouse look (lock cursor)

func _ready ( ) -> void : Input . mouse_mode = Input . MOUSE_MODE_CAPTURED func _input ( event : InputEvent ) -> void : if event is InputEventMouseMotion and Input . mouse_mode == Input . MOUSE_MODE_CAPTURED : rotate_camera ( event . relative ) Edge Cases Physics Not Working

Problem: Forgot to set collision layers for 3D

Solution: Reconfigure layers

var body := CharacterBody3D . new ( ) body . collision_layer = 0b0001

What AM I?

body . collision_mask = 0b0110

What do I DETECT?

Camera Clipping Through Walls

SpringArm3D automatically pulls camera forward when obstructed

spring_arm . spring_length = 10.0 spring_arm . collision_mask = 0b0100

Layer 3: World

Player Falling Through Floor

Problem: StaticBody3D floor has no CollisionShape3D

Solution: Add collision

var floor_collision := CollisionShape3D . new ( ) var box_shape := BoxShape3D . new ( ) box_shape . size = Vector3 ( 100 , 1 , 100 ) floor_collision . shape = box_shape floor . add_child ( floor_collision ) Decision Tree: When to Go 3D Factor Stay 2D Go 3D Gameplay Platformer, top-down, no depth needed Exploration, first-person, 3D space combat Art budget Pixel art, limited resources 3D models available or necessary Performance target Mobile, web, low-end Desktop, console, high-end mobile Development time Limited Have time for 3D learning curve Team skills 2D artists only 3D artists or asset library Reference Master Skill: godot-master

返回排行榜