重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
godot-adapt-3d-to-2d by thedivergentai/gd-agentic-skills
npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-adapt-3d-to-2d将 3D 游戏简化为 2D(或 2.5D)的专家指南。
强制要求:在实现相应模式前,请先阅读对应的脚本。
在 2D 俯视游戏中模拟 3D Z 轴高度。处理垂直速度、重力、精灵偏移和阴影缩放。
将 3D 世界位置投影到 2D 屏幕空间,用于名称标签、血条和瞄准。处理摄像机后检测和基于距离的缩放。
专家级工具,在不使用 2D 节点变换的情况下,生成 2D 笛卡尔坐标与真等轴测屏幕空间投影矩阵之间的转换。
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
专家级动态 Z 索引 Y 轴排序脚本,用于模拟 3D 排序的孤立树,匹配 CanvasItem 的 _update_sorting()。
完整的 CharacterBody2D 代码片段,在俯视游戏中将结构性的物理地面移动(X, Y)与数学模拟的跳跃高度(Z)分离开来。
伪深度摄像机,根据索引对完全不同的 CanvasLayer 应用不同的偏移算法,以模拟 3D 摄像机平移摇摄。
Area2D 派生类,在验证 2D 触发器之前,需要显式的自定义 Z 轴高度重叠(1D AABB 碰撞),以防止在 2.5D 中发生错误的“地面 vs 空中”碰撞。
Sprite2D 阴影模拟器,利用 Godot 4.x 的 Transform2D 矩阵倾斜剪切,在 2D 地面上根据模拟的 3D 太阳方向投影和倾斜阴影。
8 方向 FPS Doom 风格精灵控制器,隔离移动的 2D CharacterBody 与 Camera2D 视点之间的模拟 3D 相对角度。
俯视 2D 寻路变通方案,允许“空中”单位通过利用多个分层的 2D 导航图层来穿越墙壁,而不是使用正确的 3D 垂直性。
屏幕空间 CanvasItem 扭曲着色器,模拟 Mode 7 / 桌面透视俯仰角。通过除法收缩映射顶部屏幕坐标。
在运行时自动编程生成 CanvasTexture,结合基础反照率和烘焙的法线贴图,使精灵能像 3D 几何体一样正确响应 2D PointLIGHT。
| 原因 | 优势 |
|---|---|
| 移动端性能 | 在低端设备上快 5-10 倍 |
| 更简单的美术流程 | 精灵比 3D 模型更容易创建 |
| 更快的迭代 | 2D 关卡设计更快 |
| 可访问性 | 硬件要求更低 |
| 清晰度 | 减少解谜/策略游戏的视觉杂乱 |
# 俯视或侧视
# 示例:3D 等轴测 → 2D 俯视
# 之前 (3D):
var velocity := Vector3(input.x, 0, input.y) * speed
# 之后 (2D):
var velocity := Vector2(input.x, input.y) * speed
# 使用场景:俯视射击游戏、RTS、回合制策略游戏
# 保持视觉深度感知,但无 Z 轴游戏性
# 使用 ParallaxBackground 实现深度图层
# 场景结构:
# ParallaxBackground
# ├─ ParallaxLayer (远山,滚动慢)
# ├─ ParallaxLayer (中景建筑,滚动中等)
# └─ ParallaxLayer (近处树木,滚动快)
# player.gd
extends CharacterBody2D
func _ready() -> void:
var parallax := get_node("../ParallaxBackground")
parallax.scroll_base_scale = Vector2(0.5, 0.5) # 视差强度
# 保持等轴测/二轴测视图,但使用 2D 物理
# 使用旋转的精灵来模拟 3D 角度
const ISO_ANGLE := deg_to_rad(-30) # 等轴测倾斜角
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
)
# CharacterBody3D → CharacterBody2D
extends CharacterBody3D # 之前
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()
# ⬇️ 转换为:
extends CharacterBody2D # 之后
const SPEED = 300.0
const JUMP_VELOCITY = -400.0
const GRAVITY = 980.0 # 像素每秒平方
func _physics_process(delta: float) -> void:
velocity.y += GRAVITY * delta
var input := Input.get_vector("left", "right", "up", "down")
velocity.x = input.x * SPEED
# 注意:没有 Z 轴。对于平台游戏,使用 input.y 处理跳跃
move_and_slide()
# Camera3D → Camera2D
# 之前:第三人称 3D 摄像机
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)
# ⬇️ 转换为:
extends Camera2D # 之后
@onready var player: CharacterBody2D = $"../Player"
func _process(delta: float) -> void:
global_position = player.global_position
zoom = Vector2(2.0, 2.0) # 根据喜好调整
# 使用 Godot 从固定角度渲染 3D 模型
# sprite_renderer.gd (工具脚本)
@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 方向精灵
@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()
# Blender Python 脚本(在 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"
)
# 在编辑器中保留 3D 模型,逐帧导出
# 3D 重力 (米/秒²): 9.8
# 2D 重力 (像素/秒²): 缩放到像素单位
# 如果 1 米 = 100 像素:
const GRAVITY_2D = 9.8 * 100 # = 980 像素/秒²
# 按比例调整跳跃速度:
# 3D 跳跃: 4.5 米/秒
# 2D 跳跃: -450 像素/秒
# 3D: CapsuleShape3D (16 段,开销大)
var shape_3d := CapsuleShape3D.new()
shape_3d.radius = 0.5
shape_3d.height = 2.0
# 2D: CapsuleShape2D (简单得多)
var shape_2d := CapsuleShape2D.new()
shape_2d.radius = 16 # 像素
shape_2d.height = 64
# 3D: 完整的 3D 移动,带摄像机相对控制
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: 简单的 4 方向(或带对角线的 8 方向)
var input_2d := Input.get_vector("left", "right", "up", "down")
velocity = input_2d.normalized() * SPEED
| 指标 | 3D | 2D | 改进 |
|---|---|---|---|
| 绘制调用 | 100 | 20 | 5 倍 |
| GPU 负载 | 高 | 低 | 10 倍 |
| 电池续航(移动端) | 1 小时 | 5 小时 | 5 倍 |
| RAM 使用量 | 500MB | 100MB | 5 倍 |
# 1. 使用 TileMapLayer 代替单独的 Sprite2D 节点
var tilemap := TileMapLayer.new()
tilemap.tile_set = load("res://tileset.tres")
# 2. 批量精灵渲染
# 使用单个大精灵图集代替单独的纹理
# 3. 减少粒子数量
var godot-particles := GPUParticles2D.new()
godot-particles.amount = 50 # 从 3D 的 200 减少
# 大多数 3D 游戏已经使用 2D UI (CanvasLayer)
# 无需更改!
# 只需验证新宽高比下的 UI 缩放
get_viewport().size_changed.connect(_on_viewport_resized)
func _on_viewport_resized() -> void:
var viewport_size := get_viewport().get_visible_rect().size
# 调整 UI 锚点/边距
# 问题:重叠的精灵需要排序
# 解决方案:使用 Y 轴排序或 z_index
extends Sprite2D
func _ready() -> void:
y_sort_enabled = true # 按 Y 位置自动排序
# 或者手动设置 z_index:
z_index = int(global_position.y)
# 3D 空间音频 (AudioStreamPlayer3D) → 2D 声像 (AudioStreamPlayer2D)
var audio_2d := AudioStreamPlayer2D.new()
audio_2d.stream = load("res://sounds/footstep.ogg")
audio_2d.max_distance = 1000.0 # 2D 范围
audio_2d.attenuation = 2.0
add_child(audio_2d)
| 因素 | 保持 3D | 转为 2D |
|---|---|---|
| 目标平台 | 桌面、主机 | 移动端、网页 |
| 美术风格 | 写实、沉浸式 | 风格化、复古 |
| 游戏性 | 需要 3D 空间 | 在 2D 平面上可行 |
| 性能 | 有 GPU 预算 | 需要在低端设备上达到 60 FPS |
| 团队技能 | 3D 美术师 | 2D 美术师或像素画师 |
每周安装数
53
仓库
GitHub 星标数
59
首次出现
2026年2月10日
安全审计
安装于
opencode53
gemini-cli52
codex52
github-copilot51
kimi-cli50
amp50
Expert guidance for simplifying 3D games into 2D (or 2.5D).
MANDATORY : Read the appropriate script before implementing the corresponding pattern.
Simulates 3D Z-axis height in 2D top-down games. Handles vertical velocity, gravity, sprite offset, and shadow scaling.
Projects 3D world positions to 2D screen space for nameplates, healthbars, and targeting. Handles behind-camera detection and distance-based scaling.
Expert utility generating translating between 2D Cartesian and True Isometric screenspace projection matrices without using 2D Node transforms.
Expert dynamic Z-index Y-Sort script for fake 3D sorting isolated trees matching CanvasItem _update_sorting().
Complete CharacterBody2D snippet separating structural physical ground movement (X,Y) from a mathematically simulated jumping height (Z) in a topdown game.
Fake Depth Camera applying varying offset algorithms to completely disparate CanvasLayers based on an index to simulate 3D camera translation panning.
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.
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.
8-directional FPS Doom-style sprite controller isolating the simulated 3D relative angle between a moving 2D CharacterBody and a Camera2D viewpoint.
Topdown 2D pathfinding workaround allowing "aerial" units to cross walls by leveraging multiple tiered 2D Navigation Layers instead of proper 3D verticality.
Screen space CanvasItem warp Shader simulating a Mode 7 / tabletop perspective pitch. Maps top screen coordinates via division pinching.
Automatic programmatic generation of CanvasTexture combining base albedo and baked normal maps at runtime so Sprites correctly react to 2D PointLIGHTs like 3D geometry.
| 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 |
# 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
# 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
# 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
)
# 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()
# 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
# 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()
# 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"
)
# Keep 3D model in editor, export frame-by-frame
# 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
# 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
# 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
| 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 |
# 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
# 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
# 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)
# 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)
| 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 |
Weekly Installs
53
Repository
GitHub Stars
59
First Seen
Feb 10, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode53
gemini-cli52
codex52
github-copilot51
kimi-cli50
amp50
TanStack Query v5 完全指南:React 数据管理、乐观更新、离线支持
2,500 周安装
Cloudflare Workers 最佳实践指南:API、配置、架构与安全规则
4,900 周安装
WinMD API 搜索工具 - 快速查找 Windows API 和 WinRT 接口的完整签名与详细信息
4,900 周安装
ESP-IDF开发助手 | 官方文档集成,代码调试与最佳实践指南
52 周安装
Flutter导航与路由实现指南:命令式/声明式导航、深度链接与嵌套导航
5,200 周安装
代码安全审查清单:密钥管理、SQL注入防护、身份验证最佳实践
5,000 周安装
Tavily Research:AI驱动的深度研究工具,30秒生成带引用报告
5,200 周安装