optimize-scene

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Optimizing Decentraland Scenes

优化 Decentraland 场景

Scene Limits (Per Parcel Count)

场景限制(按地块数量)

All limits scale with parcel count
n
. Triangles, entities, and bodies scale linearly. Materials, textures, and height scale logarithmically.
ResourceFormula1 parcel2 parcels3 parcels4 parcels6 parcels9 parcels16 parcels20 parcels
Trianglesn x 10,00010,00020,00030,00040,00060,00090,000160,000200,000
Entitiesn x 2002004006008001,2001,8003,2004,000
Physics bodiesn x 3003006009001,2001,8002,7004,8006,000
Materialslog2(n+1) x 202031404656668187
Textureslog2(n+1) x 101015202328334043
Height limitlog2(n+1) x 20m20m31m40m46m56m66m81m87m
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
.dclignore
(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
.dclignore
section in the deploy-scene skill.
Important: 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,00010,00020,00030,00040,00060,00090,000160,000200,000
实体(Entity)n x 2002004006008001,2001,8003,2004,000
物理刚体n x 3003006009001,2001,8002,7004,8006,000
材质log2(n+1) x 202031404656668187
纹理log2(n+1) x 101015202328334043
高度限制log2(n+1) x 20m20m31m40m46m56m66m81m87m
**文件限制:**每个地块 15 MB,总计最多 300 MB,每个地块最多 200 个文件,单个文件最多 50 MB。
文件限制只统计部署时实际上传的内容。确保项目根目录中的
.dclignore
排除所有工作文件——Blender/FBX 源文件、草稿模型、概念图、电子表格、Markdown 文档——因为这些文件通常占项目体积的大头,且运行时永远不需要。请参阅 deploy-scene skill 中的
.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 slot
typescript
engine.removeEntity(entity) // Frees the entity slot

Use 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,
MeshRenderer
is lighter than loading a .glb:
typescript
MeshRenderer.setBox(entity) // Very cheap
MeshRenderer.setSphere(entity) // Cheap
MeshRenderer.setPlane(entity) // Very cheap
对于简单形状,
MeshRenderer
比加载 .glb 文件更轻量:
typescript
MeshRenderer.setBox(entity) // Very cheap
MeshRenderer.setSphere(entity) // Cheap
MeshRenderer.setPlane(entity) // Very cheap

Texture 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
    →
    ReduceTextureSizeIfNeeded
    ): anything larger (e.g. 2048) is downscaled to 1024 at conversion, so authoring above 1024 wastes source size without visual benefit.
  • Recommended sizes: 512x512 for most objects, 1024x1024 for hero pieces
  • Use
    .png
    for UI/sprites with transparency
  • Use
    .jpg
    for photos and textures without transparency
  • 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
    →
    ReduceTextureSizeIfNeeded
    ):任何更大的尺寸(例如 2048)都会在转换时缩小到 1024,因此在 1024 以上制作只会浪费源文件体积,而不会带来视觉收益。
  • 推荐尺寸:大多数物件使用 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 memory

Texture Size Guide by Use Case

按使用场景划分纹理尺寸

Use CaseRecommendedMaximum
Scene objects (walls, floors)1024x10241024x1024
Props and furniture512x5121024x1024
UI elements / icons256x256512x512
Skybox / environment maps1024x10241024x1024
Textures do not need to be square — 512x1024 is valid as long as both dimensions are powers of two.
使用场景推荐尺寸最大尺寸
场景物件(墙面、地面)1024x10241024x1024
道具和家具512x5121024x1024
UI 元素/图标256x256512x512
Skybox/环境贴图1024x10241024x1024
纹理不一定是正方形——只要两个维度都是 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
AssetLoad
to pre-load assets into memory ahead of time so they display instantly when needed.
PBAssetLoad
has a single field:
assets: string[]
— the list of asset paths to load. Commonly created on
engine.RootEntity
, but any entity works.
typescript
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)
LoadingState
enum members:
LOADING
,
FINISHED
,
FINISHED_WITH_ERROR
(asset found but failed to load),
NOT_FOUND
(path does not exist),
UNKNOWN
(initial/default state). A missing/typo'd
src
resolves to
NOT_FOUND
, not a thrown error.
Caveats:
  • The state callback is per-asset, keyed by the
    asset
    path string — dispatch on
    state.asset
    to update the right entity. There is no single "all finished" event; track completion yourself by counting per-asset
    FINISHED
    /error states.
  • AssetLoad
    only adds assets to memory. Removing a path from the
    assets
    list does not free memory — there is no unload via
    AssetLoad
    .
  • Preloading a path does not create/render anything — you still
    getOrCreateMutable
    the real component (
    GltfContainer
    ,
    AudioSource
    ,
    VideoPlayer
    ,
    Material
    texture) on an entity to use it; the preload just makes that later use instant.
  • If an asset is used immediately at scene startup, there is no need for
    AssetLoad
    . Only pre-load assets NOT required at startup — things that appear later or on player interaction.
使用
AssetLoad
提前将资源加载到内存中,以便在需要时立即显示。
PBAssetLoad
只有一个字段:
assets: string[]
,即要加载的资源路径列表。通常在
engine.RootEntity
上创建,但也可以使用任何实体。
typescript
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)
LoadingState
枚举成员包括:
LOADING
、
FINISHED
、
FINISHED_WITH_ERROR
(找到资源但加载失败)、
NOT_FOUND
(路径不存在)、
UNKNOWN
(初始/默认状态)。路径缺失或拼写错误会解析为
NOT_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

常见性能问题

PitfallSymptomFix
Too many unique materialsHigh draw calls, low FPSMerge into texture atlases, reuse materials
Non-power-of-two texturesMemory bloat, visual artifactsResize all textures to 256/512/1024 (1024 max)
Creating/destroying entities rapidlyFrame stuttersUse entity pooling
Heavy computation every frameConsistent low FPSAdd timer guards, reduce frequency
Unused colliders on decorationsPhysics body limit exceededRemove MeshCollider from non-interactive objects
Large uncompressed texturesSlow loading, file size exceededUse WebP, reduce resolution, use atlases
Working files uploaded on deploy"Scene too large" deploy errorAdd Blender/FBX sources, concept art, docs to
.dclignore
(see deploy-scene)
Too many transparent materialsExtra draw calls, sorting issuesMinimize transparency, use alpha cutoff instead of blend
Adding entities/components in a system without guardsEntity count explodesSystems run every frame — always check before creating
Unbounded entity queriesCPU spikeFilter with specific components, cache results
All detail loaded at all distancesTriangle budget blownImplement LOD system
No asset preloadingPop-in during gameplayUse 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 源文件、概念图和文档加入
.dclignore
(参阅 deploy-scene)
过多透明材质额外 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

推荐的优化工具

ToolPurpose
Blender Decimate modifierReduce triangle count on imported models
Blender Limited DissolveRemove unnecessary vertices from flat surfaces
Squoosh (squoosh.app)Convert images to WebP, resize to power-of-two
TexturePackerCreate texture atlases from multiple images
gltf-transform CLICompress GLB files with Draco, strip unused data
glTF ValidatorCheck for export errors before importing into DCL
Creator Hub Scene InspectorVisual tool for entity counts, triangle counts, placement
Preview Debug Panel (P key)Live performance metrics during
npm run start
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 键)在
npm run start
期间显示实时性能指标
bash
undefined

Optimize a GLB with Draco compression

Optimize a GLB with Draco compression

npx @gltf-transform/cli optimize input.glb output.glb --compress draco
undefined
npx @gltf-transform/cli optimize input.glb output.glb --compress draco
undefined

Example scenes

示例场景

Engine-team stress-test scenes (treat as ground truth for API shape):
Engine 团队的压力测试场景(视为 API 形态的基准):

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