optimize-scene
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseOptimizing Decentraland Scenes
优化 Decentraland 场景
Scene Limits (Per Parcel Count)
场景限制(按地块数量)
All limits scale with parcel count . Triangles, entities, and bodies scale linearly. Materials, textures, and height scale logarithmically.
n| Resource | Formula | 1 parcel | 2 parcels | 3 parcels | 4 parcels | 6 parcels | 9 parcels | 16 parcels | 20 parcels |
|---|---|---|---|---|---|---|---|---|---|
| Triangles | n x 10,000 | 10,000 | 20,000 | 30,000 | 40,000 | 60,000 | 90,000 | 160,000 | 200,000 |
| Entities | n x 200 | 200 | 400 | 600 | 800 | 1,200 | 1,800 | 3,200 | 4,000 |
| Physics bodies | n x 300 | 300 | 600 | 900 | 1,200 | 1,800 | 2,700 | 4,800 | 6,000 |
| Materials | log2(n+1) x 20 | 20 | 31 | 40 | 46 | 56 | 66 | 81 | 87 |
| Textures | log2(n+1) x 10 | 10 | 15 | 20 | 23 | 28 | 33 | 40 | 43 |
| Height limit | log2(n+1) x 20m | 20m | 31m | 40m | 46m | 56m | 66m | 81m | 87m |
File limits: 15 MB per parcel, 300 MB max total, 200 files per parcel, 50 MB max per individual file.
File limits count only what is actually uploaded on deploy. Make sure (at the project root) excludes all working files — Blender/FBX sources, draft models, concept art, spreadsheets, markdown docs — since these are often the bulk of a project's size and are never needed at runtime. See the section in the deploy-scene skill.
.dclignore.dclignoreImportant: Except for the MB size limits, all other limits can be exceeded. It's generally not recommended to go over them because of performance impact, but if a user tests their scene and determines that it's good enough, it should be ok to publish.
所有限制都会随地块数量 扩展。三角形、实体和刚体呈线性增长。材质、纹理和高度呈对数增长。
n| 资源 | 公式 | 1 个地块 | 2 个地块 | 3 个地块 | 4 个地块 | 6 个地块 | 9 个地块 | 16 个地块 | 20 个地块 |
|---|---|---|---|---|---|---|---|---|---|
| 三角形 | n x 10,000 | 10,000 | 20,000 | 30,000 | 40,000 | 60,000 | 90,000 | 160,000 | 200,000 |
| 实体(Entity) | n x 200 | 200 | 400 | 600 | 800 | 1,200 | 1,800 | 3,200 | 4,000 |
| 物理刚体 | n x 300 | 300 | 600 | 900 | 1,200 | 1,800 | 2,700 | 4,800 | 6,000 |
| 材质 | log2(n+1) x 20 | 20 | 31 | 40 | 46 | 56 | 66 | 81 | 87 |
| 纹理 | log2(n+1) x 10 | 10 | 15 | 20 | 23 | 28 | 33 | 40 | 43 |
| 高度限制 | log2(n+1) x 20m | 20m | 31m | 40m | 46m | 56m | 66m | 81m | 87m |
**文件限制:**每个地块 15 MB,总计最多 300 MB,每个地块最多 200 个文件,单个文件最多 50 MB。
文件限制只统计部署时实际上传的内容。确保项目根目录中的 排除所有工作文件——Blender/FBX 源文件、草稿模型、概念图、电子表格、Markdown 文档——因为这些文件通常占项目体积的大头,且运行时永远不需要。请参阅 deploy-scene skill 中的 一节。
.dclignore.dclignore重要:除 MB 大小限制外,其他所有限制都可以超出。通常不建议超出,因为超出可能会影响性能;但如果用户测试场景后认为其运行效果已经足够好,发布也可以接受。
Entity Count Optimization
实体数量优化
Reuse Entities
复用实体
Use this pattern only for cases where the scene should be spawning and removing instances dynamically.
typescript
// BAD: Creating new entity each time
function spawnBullet() {
const bullet = engine.addEntity() // Creates entity every call
// ...
}
// GOOD: Object pooling
const bulletPool: Entity[] = []
function getBullet(): Entity {
const existing = bulletPool.find((e) => !ActiveBullet.has(e))
if (existing) return existing
const newBullet = engine.addEntity()
bulletPool.push(newBullet)
return newBullet
}仅在场景需要动态生成和移除实例的情况下使用此模式。
typescript
// BAD: Creating new entity each time
function spawnBullet() {
const bullet = engine.addEntity() // Creates entity every call
// ...
}
// GOOD: Object pooling
const bulletPool: Entity[] = []
function getBullet(): Entity {
const existing = bulletPool.find((e) => !ActiveBullet.has(e))
if (existing) return existing
const newBullet = engine.addEntity()
bulletPool.push(newBullet)
return newBullet
}Remove Unused Entities
移除未使用的实体
typescript
engine.removeEntity(entity) // Frees the entity slottypescript
engine.removeEntity(entity) // Frees the entity slotUse Parenting
使用父子层级(Parenting)
Instead of independent transform values for each child, use entity hierarchy:
typescript
const parent = engine.addEntity()
Transform.create(parent, { position: Vector3.create(8, 0, 8) })
// Children inherit parent transform
const child1 = engine.addEntity()
Transform.create(child1, { position: Vector3.create(0, 1, 0), parent })
const child2 = engine.addEntity()
Transform.create(child2, { position: Vector3.create(1, 1, 0), parent })不要为每个子实体设置独立的 transform 值,而应使用实体层级:
typescript
const parent = engine.addEntity()
Transform.create(parent, { position: Vector3.create(8, 0, 8) })
// Children inherit parent transform
const child1 = engine.addEntity()
Transform.create(child1, { position: Vector3.create(0, 1, 0), parent })
const child2 = engine.addEntity()
Transform.create(child2, { position: Vector3.create(1, 1, 0), parent })Triangle Count Optimization
三角形数量优化
Use Lower-Poly Models
使用低多边形模型
- Small props: 100-500 triangles
- Medium objects: 500-1,500 triangles
- Large buildings: 1,500-5,000 triangles
- Hero pieces: Up to 10,000 triangles
- 小型道具:100-500 个三角形
- 中型物件:500-1,500 个三角形
- 大型建筑:1,500-5,000 个三角形
- 重点物件:最多 10,000 个三角形
Use LOD (Level of Detail)
使用 LOD(Level of Detail)
Show simpler models at distance:
typescript
engine.addSystem(() => {
// Check distance to player and swap models
const playerPos = Transform.get(engine.PlayerEntity).position
const objPos = Transform.get(myEntity).position
const distance = Vector3.distance(playerPos, objPos)
const gltf = GltfContainer.getMutable(myEntity)
if (distance > 30) {
gltf.src = 'models/building_lod2.glb' // Low poly
} else if (distance > 15) {
gltf.src = 'models/building_lod1.glb' // Medium poly
} else {
gltf.src = 'models/building_lod0.glb' // High poly
}
})在较远距离显示更简单的模型:
typescript
engine.addSystem(() => {
// Check distance to player and swap models
const playerPos = Transform.get(engine.PlayerEntity).position
const objPos = Transform.get(myEntity).position
const distance = Vector3.distance(playerPos, objPos)
const gltf = GltfContainer.getMutable(myEntity)
if (distance > 30) {
gltf.src = 'models/building_lod2.glb' // Low poly
} else if (distance > 15) {
gltf.src = 'models/building_lod1.glb' // Medium poly
} else {
gltf.src = 'models/building_lod0.glb' // High poly
}
})Use Primitives Instead of Models
使用图元代替模型
For simple shapes, is lighter than loading a .glb:
MeshRenderertypescript
MeshRenderer.setBox(entity) // Very cheap
MeshRenderer.setSphere(entity) // Cheap
MeshRenderer.setPlane(entity) // Very cheap对于简单形状, 比加载 .glb 文件更轻量:
MeshRenderertypescript
MeshRenderer.setBox(entity) // Very cheap
MeshRenderer.setSphere(entity) // Cheap
MeshRenderer.setPlane(entity) // Very cheapTexture Optimization
纹理优化
- Dimensions must be power-of-two: 256, 512, 1024
- Maximum is 1024x1024. The asset-bundle-converter enforces (
DESKTOP_MAX_TEXTURE_SIZE = 1024→AssetBundleConverter.cs): anything larger (e.g. 2048) is downscaled to 1024 at conversion, so authoring above 1024 wastes source size without visual benefit.ReduceTextureSizeIfNeeded - Recommended sizes: 512x512 for most objects, 1024x1024 for hero pieces
- Use for UI/sprites with transparency
.png - Use for photos and textures without transparency
.jpg - Prefer compressed formats (WebP) over raw PNG where possible
- Use texture atlases (combine multiple textures into one image) to reduce draw calls and material count
- Share texture references across materials — do not duplicate texture files
- Reuse materials across entities:
typescript
// GOOD: Define material once, apply to many
Material.setPbrMaterial(entity1, {
texture: Material.Texture.Common({ src: 'images/wall.jpg' }),
})
Material.setPbrMaterial(entity2, {
texture: Material.Texture.Common({ src: 'images/wall.jpg' }),
})
// Same texture URL = shared in memory- 尺寸必须为 2 的幂:256、512、1024
- 最大尺寸为 1024x1024。 asset-bundle-converter 会强制执行 (
DESKTOP_MAX_TEXTURE_SIZE = 1024→AssetBundleConverter.cs):任何更大的尺寸(例如 2048)都会在转换时缩小到 1024,因此在 1024 以上制作只会浪费源文件体积,而不会带来视觉收益。ReduceTextureSizeIfNeeded - 推荐尺寸:大多数物件使用 512x512,重点物件使用 1024x1024
- 对带透明通道的 UI/精灵图使用
.png - 对照片和不带透明通道的纹理使用
.jpg - 在可能的情况下,优先使用压缩格式(WebP),而不是原始 PNG
- 使用纹理图集(将多张纹理合并为一张图片),以减少 draw call 和材质数量
- 在不同材质之间共享纹理引用——不要复制纹理文件
- 在多个实体之间复用材质:
typescript
// GOOD: Define material once, apply to many
Material.setPbrMaterial(entity1, {
texture: Material.Texture.Common({ src: 'images/wall.jpg' }),
})
Material.setPbrMaterial(entity2, {
texture: Material.Texture.Common({ src: 'images/wall.jpg' }),
})
// Same texture URL = shared in memoryTexture Size Guide by Use Case
按使用场景划分纹理尺寸
| Use Case | Recommended | Maximum |
|---|---|---|
| Scene objects (walls, floors) | 1024x1024 | 1024x1024 |
| Props and furniture | 512x512 | 1024x1024 |
| UI elements / icons | 256x256 | 512x512 |
| Skybox / environment maps | 1024x1024 | 1024x1024 |
Textures do not need to be square — 512x1024 is valid as long as both dimensions are powers of two.
| 使用场景 | 推荐尺寸 | 最大尺寸 |
|---|---|---|
| 场景物件(墙面、地面) | 1024x1024 | 1024x1024 |
| 道具和家具 | 512x512 | 1024x1024 |
| UI 元素/图标 | 256x256 | 512x512 |
| Skybox/环境贴图 | 1024x1024 | 1024x1024 |
纹理不一定是正方形——只要两个维度都是 2 的幂,512x1024 也是有效尺寸。
Back-Face Culling
背面剔除(Back-Face Culling)
Back-face culling skips rendering the inside face of any polygon the player will never see from behind. It's set in your 3D modeling tool (Blender, Maya, etc.) — not in SDK code.
Rule of thumb: Enable back-face culling on all materials by default. Only disable it when a surface must be visible from both sides (e.g., a leaf plane on a tree, a thin wall).
背面剔除会跳过玩家永远不会从背面看到的任何多边形内侧。它需要在 3D 建模工具(Blender、Maya 等)中设置——而不是在 SDK 代码中设置。
**经验法则:**默认对所有材质启用背面剔除。仅当某个表面必须从两侧都可见时(例如树上的叶片平面、薄墙),才将其禁用。
System Optimization
系统优化
Avoid Per-Frame Allocations
避免每帧分配
typescript
// BAD: Creates new Vector3 every frame
engine.addSystem(() => {
const target = Vector3.create(8, 1, 8) // Allocation!
})
// GOOD: Reuse constants
const TARGET = Vector3.create(8, 1, 8)
engine.addSystem(() => {
// Use TARGET
})typescript
// BAD: Creates new Vector3 every frame
engine.addSystem(() => {
const target = Vector3.create(8, 1, 8) // Allocation!
})
// GOOD: Reuse constants
const TARGET = Vector3.create(8, 1, 8)
engine.addSystem(() => {
// Use TARGET
})Throttle Expensive Operations
对高开销操作进行节流
typescript
let lastCheck = 0
engine.addSystem((dt) => {
lastCheck += dt
if (lastCheck < 0.5) return // Only run every 0.5 seconds
lastCheck = 0
// Expensive operation here
})typescript
let lastCheck = 0
engine.addSystem((dt) => {
lastCheck += dt
if (lastCheck < 0.5) return // Only run every 0.5 seconds
lastCheck = 0
// Expensive operation here
})Remove Systems When Not Needed
不再需要时移除系统
typescript
const systemFn = (dt: number) => {
/* ... */
}
engine.addSystem(systemFn)
// When no longer needed:
engine.removeSystem(systemFn)typescript
const systemFn = (dt: number) => {
/* ... */
}
engine.addSystem(systemFn)
// When no longer needed:
engine.removeSystem(systemFn)Asset Preloading (AssetLoad Component)
资源预加载(AssetLoad 组件)
Use to pre-load assets into memory ahead of time so they display instantly when needed. has a single field: — the list of asset paths to load. Commonly created on , but any entity works.
AssetLoadPBAssetLoadassets: string[]engine.RootEntitytypescript
import {
engine,
AssetLoad,
assetLoadLoadingStateSystem,
LoadingState,
} from '@dcl/sdk/ecs'
// Queue assets to pre-load (any entity works — commonly a dedicated cube/root).
// AssetLoad.getOrCreateMutable lets you also PUSH more paths later:
// AssetLoad.getOrCreateMutable(entity, { assets: [...] }).assets.push(morePath)
AssetLoad.create(entity, { assets: ['models/big.glb', 'sounds/win.mp3'] })
// React to loading state. The callback fires PER ASSET (once per path in the
// list), receiving { asset, currentState } — NOT one batch-level event.
assetLoadLoadingStateSystem.registerAssetLoadLoadingStateEntity(
entity,
(state: { asset: string; currentState: LoadingState }) => {
if (state.currentState === LoadingState.FINISHED) {
// state.asset finished loading and is now cached
}
},
)
// Stop listening: assetLoadLoadingStateSystem.removeAssetLoadLoadingStateEntity(entity)LoadingStateLOADINGFINISHEDFINISHED_WITH_ERRORNOT_FOUNDUNKNOWNsrcNOT_FOUNDCaveats:
- The state callback is per-asset, keyed by the path string — dispatch on
assetto update the right entity. There is no single "all finished" event; track completion yourself by counting per-assetstate.asset/error states.FINISHED - only adds assets to memory. Removing a path from the
AssetLoadlist does not free memory — there is no unload viaassets.AssetLoad - Preloading a path does not create/render anything — you still the real component (
getOrCreateMutable,GltfContainer,AudioSource,VideoPlayertexture) on an entity to use it; the preload just makes that later use instant.Material - If an asset is used immediately at scene startup, there is no need for . Only pre-load assets NOT required at startup — things that appear later or on player interaction.
AssetLoad
使用 提前将资源加载到内存中,以便在需要时立即显示。 只有一个字段:,即要加载的资源路径列表。通常在 上创建,但也可以使用任何实体。
AssetLoadPBAssetLoadassets: string[]engine.RootEntitytypescript
import {
engine,
AssetLoad,
assetLoadLoadingStateSystem,
LoadingState,
} from '@dcl/sdk/ecs'
// Queue assets to pre-load (any entity works — commonly a dedicated cube/root).
// AssetLoad.getOrCreateMutable lets you also PUSH more paths later:
// AssetLoad.getOrCreateMutable(entity, { assets: [...] }).assets.push(morePath)
AssetLoad.create(entity, { assets: ['models/big.glb', 'sounds/win.mp3'] })
// React to loading state. The callback fires PER ASSET (once per path in the
// list), receiving { asset, currentState } — NOT one batch-level event.
assetLoadLoadingStateSystem.registerAssetLoadLoadingStateEntity(
entity,
(state: { asset: string; currentState: LoadingState }) => {
if (state.currentState === LoadingState.FINISHED) {
// state.asset finished loading and is now cached
}
},
)
// Stop listening: assetLoadLoadingStateSystem.removeAssetLoadLoadingStateEntity(entity)LoadingStateLOADINGFINISHEDFINISHED_WITH_ERRORNOT_FOUNDUNKNOWNNOT_FOUND注意事项:
- 状态回调是按资源触发的,并以 路径字符串作为键——应根据
asset进行分派,以更新正确的实体。不存在单一的“全部完成”事件;需要自行统计各资源的state.asset/错误状态来跟踪完成进度。FINISHED - 只会向内存中添加资源。从
AssetLoad列表中移除路径不会释放内存——assets不提供卸载功能。AssetLoad - 预加载路径不会创建或渲染任何内容——仍需在实体上 实际组件(
getOrCreateMutable、GltfContainer、AudioSource、VideoPlayer纹理)才能使用;预加载只会让后续使用变为即时。Material - 如果资源在场景启动时立即使用,则无需 。只预加载启动时不需要的资源——例如稍后出现或由玩家交互触发的内容。
AssetLoad
Loading Time Optimization
加载时间优化
- Use CDN URLs for large shared assets when possible
- 对大型共享资源,尽量使用 CDN URL
Loading Areas for Large Scenes
大型场景的分区加载
For scenes with many 3D models (e.g. a furnished multi-room building), avoid rendering everything at once. Use trigger areas to load and unload content as the player moves through the scene:
typescript
import { engine, Transform, GltfContainer, TriggerArea, triggerAreaEventsSystem, ColliderLayer } from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'
// Keep furniture hidden initially
let furnitureLoaded = false
// When player enters the building, spawn interior furniture
const trigger = engine.addEntity()
Transform.create(trigger, {
position: Vector3.create(8, 1, 8),
scale: Vector3.create(3, 3, 3)
})
TriggerArea.setBox(trigger, ColliderLayer.CL_PLAYER)
triggerAreaEventsSystem.onTriggerEnter(trigger, () => {
if (!furnitureLoaded) loadInterior()
furnitureLoaded = true
})
triggerAreaEventsSystem.onTriggerExit(trigger, () => {
if (furnitureLoaded) unloadInterior()
furnitureLoaded = false
})This pattern keeps the initial triangle and entity counts low and loads detail only when needed.
对于包含大量 3D 模型(例如布置了多个房间的建筑)的场景,不要一次性渲染所有内容。使用触发区域(TriggerArea),在玩家移动于场景中时加载和卸载内容:
typescript
import { engine, Transform, GltfContainer, TriggerArea, triggerAreaEventsSystem, ColliderLayer } from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'
// Keep furniture hidden initially
let furnitureLoaded = false
// When player enters the building, spawn interior furniture
const trigger = engine.addEntity()
Transform.create(trigger, {
position: Vector3.create(8, 1, 8),
scale: Vector3.create(3, 3, 3)
})
TriggerArea.setBox(trigger, ColliderLayer.CL_PLAYER)
triggerAreaEventsSystem.onTriggerEnter(trigger, () => {
if (!furnitureLoaded) loadInterior()
furnitureLoaded = true
})
triggerAreaEventsSystem.onTriggerExit(trigger, () => {
if (furnitureLoaded) unloadInterior()
furnitureLoaded = false
})这种模式可以保持初始三角形和实体数量较低,并仅在需要时加载细节。
Common Performance Pitfalls
常见性能问题
| Pitfall | Symptom | Fix |
|---|---|---|
| Too many unique materials | High draw calls, low FPS | Merge into texture atlases, reuse materials |
| Non-power-of-two textures | Memory bloat, visual artifacts | Resize all textures to 256/512/1024 (1024 max) |
| Creating/destroying entities rapidly | Frame stutters | Use entity pooling |
| Heavy computation every frame | Consistent low FPS | Add timer guards, reduce frequency |
| Unused colliders on decorations | Physics body limit exceeded | Remove MeshCollider from non-interactive objects |
| Large uncompressed textures | Slow loading, file size exceeded | Use WebP, reduce resolution, use atlases |
| Working files uploaded on deploy | "Scene too large" deploy error | Add Blender/FBX sources, concept art, docs to |
| Too many transparent materials | Extra draw calls, sorting issues | Minimize transparency, use alpha cutoff instead of blend |
| Adding entities/components in a system without guards | Entity count explodes | Systems run every frame — always check before creating |
| Unbounded entity queries | CPU spike | Filter with specific components, cache results |
| All detail loaded at all distances | Triangle budget blown | Implement LOD system |
| No asset preloading | Pop-in during gameplay | Use AssetLoad to preload assets needed later (not startup assets) |
| 问题 | 症状 | 修复方式 |
|---|---|---|
| 过多独立材质 | draw call 过多、FPS 过低 | 合并为纹理图集,复用材质 |
| 非 2 的幂纹理 | 内存膨胀、视觉异常 | 将所有纹理调整为 256/512/1024(最大 1024) |
| 快速创建/销毁实体 | 帧卡顿 | 使用实体池(Entity Pooling) |
| 每帧执行高开销计算 | FPS 持续偏低 | 添加计时器保护,降低执行频率 |
| 装饰物上存在未使用的 Collider | 超过物理刚体限制 | 从非交互物件中移除 MeshCollider |
| 大型未压缩纹理 | 加载缓慢、文件大小超限 | 使用 WebP、降低分辨率、使用图集 |
| 部署时上传了工作文件 | 出现“Scene too large”部署错误 | 将 Blender/FBX 源文件、概念图和文档加入 |
| 过多透明材质 | 额外 draw call、排序问题 | 尽量减少透明度,使用 alpha cutoff 代替 blend |
| 在无保护机制的 system 中添加实体/组件 | 实体数量爆炸 | System 每帧都会运行——创建前务必检查 |
| 无限制的实体查询 | CPU 峰值 | 使用特定组件进行过滤,并缓存结果 |
| 所有距离都加载全部细节 | 超出三角形预算 | 实现 LOD 系统 |
| 未预加载资源 | 游戏过程中出现弹出效果 | 使用 AssetLoad 预加载稍后需要的资源(不需要预加载启动资源) |
Scene Statistics Monitoring
场景统计监控
In Preview Mode
在预览模式下
When running the scene locally with :
npm run start- Press P to toggle the performance panel.
- Monitor: FPS, draw calls, triangles, entities, materials, textures, memory.
- Scene limits are shown alongside current usage with green/yellow/red indicators.
使用 在本地运行场景时:
npm run start- 按 P 切换性能面板。
- 监控:FPS、draw call、三角形、实体、材质、纹理、内存。
- 场景限制会与当前用量一起显示,并带有绿色/黄色/红色指示器。
What to Watch
需要关注的内容
- FPS below 30: Something is too expensive. Check draw calls and system execution time.
- Triangle count approaching limit: Enable LOD, reduce model detail, remove hidden faces.
- Entity count climbing: Likely a leak — entities being created but never destroyed. Implement pooling.
- Draw calls above 300 (1 parcel): Too many materials. Merge, atlas, and reduce transparency.
- FPS 低于 30:某项开销过高。检查 draw call 和 system 执行时间。
- 三角形数量接近限制:启用 LOD,减少模型细节,移除隐藏面。
- 实体数量持续增长:可能存在泄漏——实体被创建却从未销毁。实现对象池。
- 单地块 draw call 超过 300:材质过多。进行合并、使用图集并减少透明材质。
Recommended Optimization Tools
推荐的优化工具
| Tool | Purpose |
|---|---|
| Blender Decimate modifier | Reduce triangle count on imported models |
| Blender Limited Dissolve | Remove unnecessary vertices from flat surfaces |
| Squoosh (squoosh.app) | Convert images to WebP, resize to power-of-two |
| TexturePacker | Create texture atlases from multiple images |
| gltf-transform CLI | Compress GLB files with Draco, strip unused data |
| glTF Validator | Check for export errors before importing into DCL |
| Creator Hub Scene Inspector | Visual tool for entity counts, triangle counts, placement |
| Preview Debug Panel (P key) | Live performance metrics during |
bash
undefined| 工具 | 用途 |
|---|---|
| Blender Decimate 修改器 | 减少导入模型的三角形数量 |
| Blender Limited Dissolve | 从平面表面移除不必要的顶点 |
| Squoosh(squoosh.app) | 将图像转换为 WebP,并调整为 2 的幂 |
| TexturePacker | 从多张图片创建纹理图集 |
| gltf-transform CLI | 使用 Draco 压缩 GLB 文件,并移除未使用的数据 |
| glTF Validator | 导入 DCL 前检查导出错误 |
| Creator Hub Scene Inspector | 用于查看实体数量、三角形数量和布局的可视化工具 |
| Preview Debug Panel(P 键) | 在 |
bash
undefinedOptimize a GLB with Draco compression
Optimize a GLB with Draco compression
npx @gltf-transform/cli optimize input.glb output.glb --compress draco
undefinednpx @gltf-transform/cli optimize input.glb output.glb --compress draco
undefinedExample scenes
示例场景
Engine-team stress-test scenes (treat as ground truth for API shape):
- https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/0,2-cube-wave-32x32 — ~961 primitive cubes in ONE parcel (far over the 200-entity soft limit), all mutated every frame via a single
Transform.position.yquery. Demonstrates that soft limits can be exceeded and that a per-frame query over hundreds of entities is the intended pattern (no pooling needed for a fixed static set — cubes are created once inengine.getEntitiesWith(MeshRenderer), not per frame).main() - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/73,-2-dbmonster — UI stress test: dozens of nested in a ReactEcs tree rebuilt every frame with
<Label>values. Demonstrates the UI render function re-runs each frame, so heavy per-frame allocation inMath.random()is a real cost..tsx - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/88,-12-asset-load — preloading with the per-asset
AssetLoadstate callback (mp3 / texture / video / glb, plus a deliberately missing path that resolves toassetLoadLoadingStateSystem).NOT_FOUND
Engine 团队的压力测试场景(视为 API 形态的基准):
- https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/0,2-cube-wave-32x32 — 单个地块中约 961 个 primitive 立方体(远超 200 实体的软限制),所有 都通过一次
Transform.position.y查询逐帧修改。这说明软限制可以被超出,而且对数百个实体进行每帧查询是预期用法(无需对象池:立方体仅在engine.getEntitiesWith(MeshRenderer)中创建一次,并非每帧创建)。main() - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/73,-2-dbmonster — UI 压力测试:ReactEcs 树中包含数十个嵌套的 ,并使用
<Label>值逐帧重建。这表明 UI 渲染函数每帧都会重新运行,因此Math.random()中高开销的每帧分配确实会带来性能成本。.tsx - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/88,-12-asset-load — 使用 预加载,并通过按资源触发的
AssetLoad状态回调进行监控(mp3/纹理/视频/glb,另包含一个故意缺失且会解析为assetLoadLoadingStateSystem的路径)。NOT_FOUND
Cross-References
交叉参考
- add-3d-models — model loading, colliders, and file organization
- game-design — performance budgets, design patterns, and MVP planning
- advanced-rendering — texture modes, material reuse, and LOD with VisibilityComponent
- add-3d-models — 模型加载、碰撞体和文件组织
- game-design — 性能预算、设计模式和 MVP 规划
- advanced-rendering — 纹理模式、材质复用以及使用 VisibilityComponent 的 LOD