@easytwin/devkit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +72 -0
- package/dist/bin.js +586 -0
- package/dist/bin.js.map +1 -0
- package/dist/index.d.ts +181 -0
- package/dist/index.js +551 -0
- package/dist/index.js.map +1 -0
- package/package.json +45 -0
- package/skills/easytwin-bootstrap/SKILL.md +26 -0
- package/skills/easytwin-core/SKILL.md +25 -0
- package/skills/easytwin-core/references/base-classes.md +32 -0
- package/skills/easytwin-core/references/camera.md +33 -0
- package/skills/easytwin-core/references/engine.md +88 -0
- package/skills/easytwin-core/references/physics.md +30 -0
- package/skills/easytwin-core/references/virtual-components.md +169 -0
- package/skills/easytwin-develop/SKILL.md +25 -0
- package/skills/easytwin-render/SKILL.md +42 -0
- package/skills/easytwin-render/references/intro.md +81 -0
- package/skills/easytwin-render/references/lifecycle-events.md +79 -0
- package/skills/easytwin-render/references/rendering-apis.md +116 -0
- package/skills/easytwin-render/references/scene-and-assets.md +94 -0
- package/skills/easytwin-scene/SKILL.md +26 -0
- package/skills/easytwin-upload/SKILL.md +21 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# 组件生命周期与事件
|
|
2
|
+
|
|
3
|
+
自定义组件继承 `Component`(或 `VirtualRootComponent` / `VirtualChildComponent`,见文末)。本节按源码 `Component.ts` 与 `docs/组件声明周期.md` 整理。
|
|
4
|
+
|
|
5
|
+
## 生命周期总序
|
|
6
|
+
|
|
7
|
+
1. **反序列化** `deserialize(json, ignoreChildren?)`:校验 `json.type === this.type` 否则抛错;恢复 id/componentId/name/active/lock/collapsed/states/data → `await afterDeserialize()` → 递归反序列化 children → `isInited = true`。
|
|
8
|
+
2. **初始化** `_processInitialize()`(一次性):`initialize()` = 先 `renderState(curState)`(可 await)→ `renderData(data)` → `await onInitialize()` → 递归 children。
|
|
9
|
+
3. **激活** `_processActive()`:置 `_activeInScene = true` → 首次 `awake()`(`onAwake()` 仅一次)→ `enable()`(注册到 ComponentManager 更新队列 + `onEnable()`;仅当覆写了 `onUpdate`/`onLateUpdate`/`onFixedUpdate` 才入对应队列;未 start 时入 onStart 队列)→ 递归 children(子 active 才激活)。
|
|
10
|
+
4. **onStart**:由 ComponentManager 单独调度,`callScriptOnStart()` 一次性,每组件仅一次。
|
|
11
|
+
5. **每帧**:`onFixedUpdate()` → `onUpdate(dt)` → `onLateUpdate(dt)`(三个队列都要求已 `_isStarted`)。
|
|
12
|
+
6. **销毁** `destroy()`:正在编辑先 `endEdit()`;若 `_activeInScene` 先 `disable()`(移除队列 + `onDisable()`);**递归子 destroy → 移除 easyv 监听 → `onDestroy()` → children 清空**。
|
|
13
|
+
|
|
14
|
+
## 钩子访问级别与触发
|
|
15
|
+
|
|
16
|
+
| 钩子 | 级别 | 触发 |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| `onInitialize()` | protected | 帧循环前,无论 active,`renderState`/`renderData` 之后,可 `void \| Promise<void>` |
|
|
19
|
+
| `onAwake()` | protected | 首次激活时,仅一次 |
|
|
20
|
+
| `onEnable()` / `onDisable()` | protected | `_activeInScene` 置 true/false 时 |
|
|
21
|
+
| `onDestroy()` | protected | 销毁时 |
|
|
22
|
+
| `onStart()` | public | 帧循环最开始,仅一次 |
|
|
23
|
+
| `onUpdate(dt)` / `onLateUpdate(dt)` | public | 每帧;late 在 update 后 |
|
|
24
|
+
| `onFixedUpdate()` | public | 每固定间隔 |
|
|
25
|
+
|
|
26
|
+
**覆写判据(易踩坑)**:`enable()` 用 `this.onUpdate !== Component.prototype.onUpdate` 与原型比较——**不覆写就不会进入更新队列**,写组件时必须覆写 `onUpdate`/`onLateUpdate`/`onFixedUpdate` 才会被调用。
|
|
27
|
+
|
|
28
|
+
## 抽象成员(必须实现)
|
|
29
|
+
|
|
30
|
+
`isRoot`(getter)、`readonly type` / `readonly version`、`getInitState()`、`onRenderState(state)`、`onRenderData(data)`、`onTransformControlUpdate(control, offsetMatrix)`、`onTransformControlDragEnd(control)`、`afterDeserialize()`。
|
|
31
|
+
|
|
32
|
+
## 状态 API
|
|
33
|
+
|
|
34
|
+
- 成员:`states`(`StateJson<SC>[]`)、`curState`(getter:有 `selectedStateId` 取之;否则取 `using` 的,再否则 `rank` 最小的)、`selectedStateId`。
|
|
35
|
+
- `async addState(newState?, index?)`:新建时用 `getInitState()` 并自动编号;同步后端。
|
|
36
|
+
- `async removeState(id)`;`updateState(id, config, sync, skipRender = false)`(sync 同步后端);`selectState(id)`(渲染);`renameState(id, name)`。
|
|
37
|
+
- 编辑器专用:`async sortState_setRank(stateId, rank)`(直接 EntityAPI)、`setDefaultState(id)`。
|
|
38
|
+
- `renderState(state)` → `onRenderState`。
|
|
39
|
+
|
|
40
|
+
## 数据 API
|
|
41
|
+
|
|
42
|
+
- `data`(getter `_data`)、`updateData(data, sync, skipRender? = false)`(sync 同步后端)、`renderData(data)` → `onRenderData`。
|
|
43
|
+
- `VirtualRootComponent.renderData` 会先 `syncChildren(data)` 再 `onRenderData`(见文末)。
|
|
44
|
+
|
|
45
|
+
## 其他成员
|
|
46
|
+
|
|
47
|
+
- `active` / `lock` / `collapsed` 与 `rename(name)` / `setActive(v)` / `setLock(v)` / `setCollapsed(v)`(带 SaveManager 后端同步)。
|
|
48
|
+
- `children` / `parent`(设 parent 自动 addChild/removeChild 与激活处理)、`addChild` / `removeChild`。
|
|
49
|
+
- 查找:`findBy(cond)`、`findById`、`findByName`、`findByType`、`findByPath(path)`(斜杠分隔)、`findRuntimeComponentById`(含 VirtualChild)、`traverse(cb)`。
|
|
50
|
+
- `serialize(ignoreChildren = false)` / `deserialize`;`destroy()`。
|
|
51
|
+
- 编辑/预览:`async startPreview()/endPreview()`、`handlePreview(value)`、protected `_startPreview()/_endPreview()`;`startEdit(id?)/endEdit()`、protected `_startEdit(id?)/_endEdit()`;`editMode: "polygon" | "create" | "none"`、`isPreviewing`、`isEditing`。
|
|
52
|
+
- 版本升级:`onUpgradeVersion(oldComponent)`(返回 `{ stateJson?, data? }` 可选)。
|
|
53
|
+
|
|
54
|
+
## 指针与物理回调
|
|
55
|
+
|
|
56
|
+
- 指针(`Pointer`):`onPointerUp/Down/Enter/Exit/Click/Drag(pointer)`。注意 README 注明**碰撞与射线检测(暂未支持)**,旧 `userData._target` 方案已在 README 划线;示例组件仍手动设置 `sceneObject.userData._target = this`——**不建议依赖**。
|
|
57
|
+
- 物理:`onCollisionEnter/Stay/Exit(other: ColliderObj, self: ColliderObj)`、`onTriggerEnter/Stay/Exit(other, self)`;绑定碰撞体才能收到。
|
|
58
|
+
|
|
59
|
+
## 蓝图 / easyv 事件
|
|
60
|
+
|
|
61
|
+
- `addEasyvEventListener(name, func)`:非 editor 模式才注册;`destroy` 时自动移除。
|
|
62
|
+
- `emitEasyvFunc(eventName, data)`:调 `this.engine.emitFunc?.(eventName, data, this.id)`;需先 `engine.registerEngineEmitFunc(func)`,`EngineEmitFunc = (type, data, sourceId) => void`。
|
|
63
|
+
- `easyvSwitchState(param)`:`param = { id, stateId, type, toChild?, animation? }`,有 `stateId` 时 `selectState`。
|
|
64
|
+
- `VirtualRootComponent` 构造器还会经 `getDefaultEasyvEventListener` 注册默认动作:`setState` / `show` / `hide` / `show/hide` / `updateCustomAttributes`。
|
|
65
|
+
|
|
66
|
+
## 信号 / 事件 / 工具包
|
|
67
|
+
|
|
68
|
+
- `Signal<T = []>`:`on / off / once / emit / removeAllListeners`。
|
|
69
|
+
- `EventEmitter<E, ARGS>`(SceneObject 基类继承;`engine.emit(name, ...)` 可发 `saveError` / `component-editing` / `component-previewing` 等)。
|
|
70
|
+
- 现成信号:`Component.signals.componentUpdated`、`SceneManager.signals.sceneUpdated`、`ComponentManager.signals.componentsUpdated`。
|
|
71
|
+
- `Blackboard`:`setValue / getValue / removeValue / clear`。
|
|
72
|
+
- `Fsm<T>(owner, stateList)`:`isRunning / isDestroyed / curStateRunningTime / fsmStatesCount`;`getData / setData / removeData / hasState / getState / start(stateType) / update(dt) / switchState(stateType) / destroy`;`FsmState<T>` 钩子 `onInit / onEnter / onUpdate / onLeave / onDestroy`,含 `blackboard` getter。
|
|
73
|
+
|
|
74
|
+
## VirtualRootComponent / VirtualChildComponent
|
|
75
|
+
|
|
76
|
+
完整 API、序列化结构、数据同步与开发示例见 `easytwin-core` 的 `references/virtual-components.md`。这里只保留最关键的差异点:
|
|
77
|
+
|
|
78
|
+
- `VirtualRootComponent`(数字要素模板、模型):`virtualChildren`;`_processActive`/`_processInActive` 同时递归子 `virtualChildren`;`shownChildren = [...virtualChildren, ...children]`;`renderData` 先 `syncChildren(data)` 再 `onRenderData(data)`。
|
|
79
|
+
- `VirtualChildComponent extends SceneObject`(非 `Component`):无 `onAwake`,激活只调 `onEnable`/`onDisable`;`config` setter 会 `updateData(serialize(), true)`;抽象 `render()` / `destroy()`;`virtualChildren` 支持嵌套。
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# 渲染 API 各域
|
|
2
|
+
|
|
3
|
+
场景对象 `scene` 即 `RuntimeScene`(加载后从 `SceneManager.mainScene` 或 `engine.mainScene` 取得)。
|
|
4
|
+
|
|
5
|
+
## 相机与场景视图
|
|
6
|
+
|
|
7
|
+
- `scene.camera.main`(当前主相机);`scene.camera.switchTo(camera)` 切到自定义相机(标记 `_isControlled`);`scene.camera.switchToScene()` 切回场景相机。
|
|
8
|
+
- `SceneCamera`:`sceneCameraMode: "perspective" | "orthographic"`(setter 同步透视/正交参数并触发 `onCameraModeChanged`);`perspectiveCamera` / `orthographicCamera` / `nativeCamera`。
|
|
9
|
+
- `scene.control`(`CameraControls`);`scene.focus(object, { distance?, duration?, focusCenter?: [number, number] })`,object 接受 `Object3D | Object3D[] | RuntimeComponent[]`(RuntimeComponent 走 `getSelectObject()`)。
|
|
10
|
+
- `scene.getCameraInfo()` → `{ position, rotation, target }`(`CameraInfo`,IVector3)。
|
|
11
|
+
- `scene.shotCanvas(): Promise<Blob | null>`(先 `update(0.1)` 再 `toBlob`)。
|
|
12
|
+
- 坐标转换(直接传 clientX/clientY 即可):
|
|
13
|
+
|
|
14
|
+
| 方法 | 说明 |
|
|
15
|
+
| --- | --- |
|
|
16
|
+
| `ndcToScreenPos(ndcPos, out)` | NDC → 屏幕像素 |
|
|
17
|
+
| `screenToNdcPos(screenPos, out)` | 屏幕 → NDC |
|
|
18
|
+
| `worldToScreenPos(worldPos, out)` | 世界 → 屏幕像素 |
|
|
19
|
+
| `screenToWorldPos(screenPos, out)` | 屏幕 → 世界 |
|
|
20
|
+
| `getMouseCoords(x, y)` | clientX/clientY → NDC `Vector2` |
|
|
21
|
+
|
|
22
|
+
- 2D CSS:`EngineCSSRenderObject extends THREE.Object3D { element: HTMLDivElement; autoResize = true; faceToCamera = true }`;`scene.cssRenderer`(CSSRenderer)每帧把含 element 的 3D 对象同步到 CSS 位置/缩放。
|
|
23
|
+
- 深度:`scene.getDepthTexture()`;渲染回调:`scene.registerRenderCallback(cb)`(返回取消函数)。
|
|
24
|
+
|
|
25
|
+
## 动画
|
|
26
|
+
|
|
27
|
+
- `EasyTwinTween`:`createNumberAnimate(option)`、`createTweenHandler<T>(option)`、`removeTweenHandler(tween)`。
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
type TweenOption<T> = {
|
|
31
|
+
source: T; target: T;
|
|
32
|
+
duration?: number; // 默认 1000
|
|
33
|
+
delay?: number; // 默认 0
|
|
34
|
+
mode?: AnimationModeType; // 默认 "Quadratic.InOut"
|
|
35
|
+
repeat?: number; // 默认 0
|
|
36
|
+
execute?(source, elapsed);
|
|
37
|
+
complete?(source, duration);
|
|
38
|
+
};
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- `AnimationModeType` 完整枚举:族 `Linear`(仅 None)、`Quadratic`、`Cubic`、`Quartic`、`Quintic`、`Sinusoidal`、`Exponential`、`Circular`、`Elastic`、`Back`、`Bounce`;每族含 `In/Out/InOut`(如 `"Quadratic.InOut"`),映射 `@tweenjs/tween.js` Easing。
|
|
42
|
+
- `TwinAnimationMixer extends EventEmitter`(`@easytwin/runtime` 导出,示例组件作成员字段使用):
|
|
43
|
+
- 属性:`actions`、`isPlaying` / `isPause`、`timeScale`、`unit: "percent" | "time"`。
|
|
44
|
+
- 方法:`play(name?)`、`pause()`、`stop(reset = true)`、`reset(name?)`、`replay()`、`preview(time, name?, unit = "time")`、`setTimeUnit(type)`、`getDuration()`、`setTime` / `setEndTime` / `getTime`、`addAction(name, force?)`、`getActionByName`、`removeByName` / `removeAll`、`setRepeat`、`tick()`。
|
|
45
|
+
- 事件:`start` / `stop` / `pause` / `preview(time, unit)` / `reset` / `update(time)`。
|
|
46
|
+
- 注意:源码**没有 `resume` 方法**,不要写。
|
|
47
|
+
|
|
48
|
+
## 物理
|
|
49
|
+
|
|
50
|
+
- 组件:`DynamicRigidbodyComponent`、`StaticRigidbodyComponent`、`CharacterControllerComponent`(均相关于 `RigidbodyComponent`);碰撞体 `ColliderObj` 与 `BoxColliderObj` / `CapsuleColliderObj` / `SphereColliderObj` / `CharacterColliderObj`;`PhysicsMaterial`(摩擦/弹性/混合规则;Rapier 版无原生对象,destroy 空操作)。
|
|
51
|
+
- `scene.physicsScene.gravity` / `scene.physicsScene.fixedDeltaTime`(getter/setter)。
|
|
52
|
+
- 物理开关:`scene.physicsEnabled`(第一人称相机预览用)与 `scene.physicsEnabled2`(全局开关);实际生效条件 = 两者**或**(源码 `_internalPhysicsEnabled` 注释)。
|
|
53
|
+
- 回调(需绑定碰撞体):`onCollisionEnter/Stay/Exit(other, self)`、`onTriggerEnter/Stay/Exit(other, self)`,参数为 `ColliderObj`。
|
|
54
|
+
|
|
55
|
+
## 地理
|
|
56
|
+
|
|
57
|
+
- `scene.geoTransform`(`GeographicModule`);`gisMode: "referencePoints" | "tileSet" | "tileMap" | "tileSetAndTileMap" | "none"`。
|
|
58
|
+
- `geoToScene(geoLocation): IVector3` / `sceneToGeo(position): GeoLocation`。
|
|
59
|
+
- `GeoTransformForReferencePoints(options)`:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
options = {
|
|
63
|
+
point1: { position, geoLocation, wgs84GeoLocation },
|
|
64
|
+
point2: { position, geoLocation, wgs84GeoLocation },
|
|
65
|
+
geographicCRS?: string, // 默认 "WGS84"
|
|
66
|
+
projectedCRS?: string, // 默认 "EPSG:3857"
|
|
67
|
+
northDir: "-Z" | "+Z" | "+X" | "-X",
|
|
68
|
+
};
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- 另有 `geoToSceneLinear`、`updateReferencePoints`、`getReferencePoints`。
|
|
72
|
+
- `GeoLocation = { latitude, longitude, altitude }`。
|
|
73
|
+
|
|
74
|
+
## 命名空间与直接导出(根导出)
|
|
75
|
+
|
|
76
|
+
- 命名空间:`THREE`、`Mobx`(mobx)、`TweenJs`(@tweenjs/tween.js)、`Semver`(semver)。
|
|
77
|
+
- `export *` 十大命名空间:`manager` / `core` / `loader` / `utils` / `packages` / `server` / `type` / `three-extension` / `constants` / `module`。
|
|
78
|
+
- 直接导出:`OrbitControls`、`CSM`、`ConvexHull`、`VertexList`、`UnrealBloomPass`、`GTAOPass`、`OutlinePass`、`DragControls`、`RectAreaLightHelper`。
|
|
79
|
+
|
|
80
|
+
## 工具函数与类型
|
|
81
|
+
|
|
82
|
+
- `SRGB2Linear(r, g, b)` 返回 `[number, number, number]`(0~1 sRGB → linear);`Linear2SRGB` 反向。示例组件:`new THREE.Color(...SRGB2Linear(...state.config.color.rgb))`。
|
|
83
|
+
- `genUUID(prefix = "")`、`deepClone(obj)`、`getTypeVersionKey(type, version)`(返回 `type@version`)、`getTypeVersionClassNameKey(type, version, className)`。
|
|
84
|
+
- 常量:`GROUP_COMPONENT_TYPE = "EngineGroup"`、`MODEL_COMPONENT_TYPE = "Model"`、`SCENE_COMPONENT_TYPE = "Scene"`。
|
|
85
|
+
- 类型:`Color = { rgb: [r,g,b]; alpha? }`(sRGB 空间);`IVector3 = { x, y, z }`(含命名空间方法 `scale/scaleVector/clamp/clampVector3/copy`);`IVector2 = { x, y }`;`EngineEmitFunc = (type: string, data: any, sourceId: string) => void`。
|
|
86
|
+
|
|
87
|
+
## 自定义组件模块(CustomComponentManager)
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
type CustomComponentModule = {
|
|
91
|
+
Settings: ComponentSettings;
|
|
92
|
+
ComponentClass: ComponentCtor;
|
|
93
|
+
Init: (engine: RuntimeEngine) => void;
|
|
94
|
+
GetInitState: (scene: RuntimeScene) => InitStateDto | InitStateDto[];
|
|
95
|
+
GetInitData?: (scene: RuntimeScene) => any | Promise<any>;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
type ComponentSettings = {
|
|
99
|
+
type: string;
|
|
100
|
+
category: "CustomComponent" | "FunctionalComponent" | "EffectComposer" | "Scene";
|
|
101
|
+
version: string;
|
|
102
|
+
name: string;
|
|
103
|
+
};
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
- `ComponentUI` 四种:`ComponentUI_Scene`(type `"Scene"`,editor)、`ComponentUI_Custom`(`"CustomComponent"`,editors `{ Entity/Data/Material/FunctionalComponent/EffectComposer? }`)、`ComponentUI_Function`(`FunctionalComponent`,editor)、`ComponentUI_Effect`(`EffectComposer`,editors);均可带 `icon/contextMenu/previewMode/editMode`。
|
|
107
|
+
- 方法:`getOrLoad(type, version?)`(加载远程 script 并 `module.Init(engine)`)、`registerUI(type, version, className, ui)`、`getUI(...)`、`getComponentLastVersion(type)`、`getComponentVersionList(type, isLatest?)`、`isComponentStale(comp)`、`externalDeps`。
|
|
108
|
+
- 脚本加载地址见 scene-and-assets.md 的 `loadCustomComponentScript`。
|
|
109
|
+
|
|
110
|
+
### 真实示例结构(`packages/easytwin-components-example`)
|
|
111
|
+
|
|
112
|
+
- `settings.ts` 导出 `Settings`(`ComponentSettings`);`index.ts` 导出 `customModule: CustomComponentModule`(default);`init.tsx` 注册 UI。
|
|
113
|
+
- `ExampleComponent extends VirtualRootComponent<SC, ChildCfg, Data>`:覆写 `onAwake/onEnable/onUpdate/onStart/onDisable`、指针事件、`onTransformControlDragEnd` 内 `updateState(this._selectedStateId, newState, true)`;`onRenderState` 用 `SRGB2Linear`;`onRenderData` 遍历 virtualChildren 调 `render()`;`syncChildren` 按 id 复用/销毁。
|
|
114
|
+
- `ExampleChildComponent extends VirtualChildComponent<ChildCfg>`:构造器 `super(scene, attachComponent)` 后建 cube 挂 `this.sceneObject` 与 `attachComponent.sceneObject`;`render()` 应用 `config.position`;`destroy()` 移除。
|
|
115
|
+
|
|
116
|
+
> TODO(外部输入): 渲染 API 官方文档与 d.ts 到位后,如需与本节逐条核对,以官方为准。
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# 场景加载与资产
|
|
2
|
+
|
|
3
|
+
> 说明:本节讲的是 **runtime 内部**的场景 JSON(组件树序列化结构,`SceneJson`)与资产加载;`easytwin scene pull` 拉的是**服务端场景 JSON**,两者出处不同。可用 `SceneManager.importScene` 或 `loadScene` 把本地 JSON 喂给 runtime。
|
|
4
|
+
|
|
5
|
+
## 场景管理(SceneManager)
|
|
6
|
+
|
|
7
|
+
`engine.getManager(SceneManager)` 取得管理器。
|
|
8
|
+
|
|
9
|
+
- `LoadSceneMode = { Single, Additive }`:`Single` 加载前先销毁并清空旧场景、设为 `mainScene`;`Additive` 直接追加。
|
|
10
|
+
- `mainScene`(getter/setter,setter 会 `signals.sceneUpdated.emit()`)。便捷入口:`engine.mainScene`。
|
|
11
|
+
- `createScene(name, mode, preserveDrawingBuffer = false)`:new `RuntimeScene` → 生成 id → push → `scene.init()`。
|
|
12
|
+
- `createSceneById(sceneId, name, mode, loadSceneMode)`:内部 `EntityAPI.getEntities(sceneId)` 拉组件 JSON,拼成 `SceneJson` 后 `loadScene`。
|
|
13
|
+
- `loadScene(sceneJson, mode, loadSceneMode, preserveDrawingBuffer = false)`:new RuntimeScene → init → `scene.deserialize(sceneJson)`;`Single` 时逐个 `destroy()` 旧场景、clear、设 `mainScene`、push。
|
|
14
|
+
- `exportScene(scene)` = `scene.serialize()`;`importScene(file, mode, loadSceneMode)` = `file.arrayBuffer()` → 字符串 → `JSON.parse` → `loadScene`。
|
|
15
|
+
- `deleteScene(name)`:**主场景不可删**,否则抛 `"can not unload main scene"`。
|
|
16
|
+
- `signals.sceneUpdated`(`Signal`)。
|
|
17
|
+
|
|
18
|
+
`mode` 取值见 intro.md 的 `RuntimeSceneMode`。
|
|
19
|
+
|
|
20
|
+
## 序列化与组件管理(RuntimeScene)
|
|
21
|
+
|
|
22
|
+
- `serialize(): SceneJson` = `{ id, name, sceneComponent: rootComponent.serialize() }`;root 无效抛错。
|
|
23
|
+
- `deserialize(data: SceneJson)`:要求 `data.sceneComponent.type === "Scene"` 否则抛错;`deserializeComponent` 出根组件 → 加入 `sceneObject` → 设 `rootComponent` → 激活/初始化。
|
|
24
|
+
- `deserializeComponent(data: ComponentJson)`:按 `type/version` `getOrLoad` 组件模块 → `new module.ComponentClass(scene)` → `comp.deserialize(data)`。
|
|
25
|
+
- `createComponent(params, sync?)`:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
type CreateComponentParams = {
|
|
29
|
+
type: string;
|
|
30
|
+
version?: string;
|
|
31
|
+
name?: string; // 缺省用 Settings.name
|
|
32
|
+
parent?: Component;
|
|
33
|
+
data?: any;
|
|
34
|
+
states?: StateJson<any>[];
|
|
35
|
+
hierarchyConfig?: HierarchyConfig;
|
|
36
|
+
};
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- `sync=true`:走 SaveManager 创建(同步后端);`sync=false`:本地用 `GetInitState`/`GetInitData` 初始化。
|
|
40
|
+
- `createModelComponent(name, url, sync?, onProgress?)`:type 为 `"Model"`,data 为 `{ url$R, animation, sceneGraph, materialSlots, modification: { objects, materials }, physicsConfig }`;`onProgress(cur: 0~1, state: LoadState)`。
|
|
41
|
+
- `create3dgsComponent(name, url, sync?, onProgress?)`:type 为 `"Splat3d"`,data 为 `{ url$R }`。
|
|
42
|
+
- `removeComponent(comps: Component[], sync)`;`findComponentById/ByName/ByType`(走 `rootComponent.findById` 等);`findRuntimeComponentById`(含 VirtualChild)。
|
|
43
|
+
- `reflowScene(compJson?)`:场景重排(版本升级);`updateComponentVersion(updateInfos)`。
|
|
44
|
+
|
|
45
|
+
## 序列化结构(`src/core/interface.ts`,逐字)
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
type StateJson<T = any> = { id: string; name: string; config: T; using: boolean; rank: number };
|
|
49
|
+
|
|
50
|
+
type ComponentJson<S = any, D = any> = {
|
|
51
|
+
id: string; // 实体 id,仅用于层级关系
|
|
52
|
+
active: boolean; // 世界大纲 config 修改
|
|
53
|
+
lock: boolean; // 仅编辑器生效(是否被选中)
|
|
54
|
+
collapsed: boolean;
|
|
55
|
+
componentId: string; // 组件 id,兼容后端绑定关系
|
|
56
|
+
name: string;
|
|
57
|
+
type: string;
|
|
58
|
+
version: string;
|
|
59
|
+
states?: StateJson<S>[];
|
|
60
|
+
data?: D;
|
|
61
|
+
children: ComponentJson[];
|
|
62
|
+
parentId: string | null; // 引擎不直接存储 parentId
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
interface SceneJson { id: string; name: string; sceneComponent: ComponentJson; }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
注意:`Component.serialize()` 输出的 `componentId` 即 `this.id`;`states`/`data` 直接引用实例成员。
|
|
69
|
+
|
|
70
|
+
## 资源(ResourceManager)
|
|
71
|
+
|
|
72
|
+
`engine.getManager(ResourceManager)`。
|
|
73
|
+
|
|
74
|
+
- `loadModelAsync(address, onProgress?)` → `GLTFLoadResult`(带缓存;失败 fallback 默认模型)。
|
|
75
|
+
- `getModelInitData(url, onProgress?)` → `{ animation, sceneGraph, materialSlots, materialMap }`;animation 项:`autoPlay / loop("repeat"|"once"|"pingpong") / stopAtEnd / timeScale / id / name`。
|
|
76
|
+
- `loadTextureAsync(address, onProgress?)`;`loadVideoTextureAsync(address)`(自动建 video:`muted`、`loop`、`playsInline`、`crossOrigin: "anonymous"`,mp4 source);`loadHDRTextureAsync(address, onProgress?)`(`.exr` 走 EXRLoader,否则 RGBELoader,自动 `EquirectangularReflectionMapping`)。
|
|
77
|
+
- `loadCustomComponentScript(type, version, externalDeps)`:localOSSUrl 非空优先读 `${localOSSUrl}/${type}@${version}.js`,否则 `${baseOSSUrl}/easytwin/system/components/custom/${type}/${version}/script.js`。
|
|
78
|
+
- `cacheModelByFileAsync(file, address)`;`instantiateModel(model)`(同 url 共享 geometry/纹理源);`getResourceUrl(relativeUrl)`。
|
|
79
|
+
|
|
80
|
+
**资源路径约定(README)**:data 和 state 中**以 `$R` 结尾的字段**会被判定为资源路径,要求 value 是 string,参与导入导出(如 `url$R`)。
|
|
81
|
+
|
|
82
|
+
## LCC 大场景(RuntimeScene)
|
|
83
|
+
|
|
84
|
+
- `scene.loadLCC(dataPath, options?) → Promise<string | null>`:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
options = {
|
|
88
|
+
useEnv?, useIndexDB?, useLoadingEffect?, appKey?, modelMatrix?,
|
|
89
|
+
enableCollision?, onComplete?(lccObject), onProgress?(percent), onError?,
|
|
90
|
+
};
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
- 默认 `useEnv` / `useIndexDB` / `useLoadingEffect` 为 `true`,`enableCollision` 为 `false`;返回实例 id,失败 `null`。
|
|
94
|
+
- `scene.unloadLCC(id)` / `scene.unloadAllLCC()`;`scene.clearLCC()` 只清实例,保留 `window.LCCRender` 便于复用。
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: easytwin-scene
|
|
3
|
+
description: 当用户要列出场景、查看有哪些场景、或把某个场景的 JSON 拉到本地作为开发参考/预览输入时使用。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 场景管理
|
|
7
|
+
|
|
8
|
+
- `easytwin scene list`:列出凭据可见的场景。
|
|
9
|
+
- `easytwin scene pull <id> [--out <path>]`:拉取场景 JSON 到本地,缺省输出 `./<id>.scene.json`。
|
|
10
|
+
|
|
11
|
+
## 场景 JSON 格式
|
|
12
|
+
|
|
13
|
+
<!-- TODO(外部输入): 场景 JSON 具体格式尚未定稿。格式到位后在此补充字段说明,并同步更新 scene/upload 实现与场景预览。 -->
|
|
14
|
+
场景 JSON 的字段结构待定(向 EasyTwin 维护者索取)。当前拉取结果按服务端原样保存,字段以实际返回为准。
|
|
15
|
+
|
|
16
|
+
> 注意:此处是**服务端场景 JSON**;runtime 内部用于 loadScene 的 SceneJson(组件树序列化结构)见 easytwin-render 的 references/scene-and-assets.md,两者出处不同。
|
|
17
|
+
|
|
18
|
+
## 典型用法
|
|
19
|
+
|
|
20
|
+
拉取参考场景,作为渲染开发与本地预览的输入:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
easytwin scene list
|
|
24
|
+
easytwin scene pull scene-123 --out ./scenes/scene-123.json
|
|
25
|
+
```
|
|
26
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: easytwin-upload
|
|
3
|
+
description: 当用户要把本地开发产物目录上传回 EasyTwin、全量覆盖服务端内容、或询问上传是否可逆/会覆盖什么时使用。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 全量上传(覆盖不可逆)
|
|
7
|
+
|
|
8
|
+
`easytwin upload <dir>` 把 `<dir>` 下的全部文件上传,按 App ID 决定的目标空间**全量覆盖**。
|
|
9
|
+
|
|
10
|
+
> ⚠️ **覆盖不可逆**:上传会覆盖服务端同名内容,没有本地清单与回滚机制。上传前务必确认目录内容与目标空间。
|
|
11
|
+
|
|
12
|
+
## 行为
|
|
13
|
+
|
|
14
|
+
- 递归收集 `<dir>` 下所有文件(默认跳过 `.git`)。
|
|
15
|
+
- 逐文件 multipart 上传,带进度输出。
|
|
16
|
+
- 上传目标空间由凭据(App ID)决定;pull 场景与 upload 目录是两条独立能力,互不引用。
|
|
17
|
+
|
|
18
|
+
## 步骤
|
|
19
|
+
|
|
20
|
+
1. 确认目标空间(App ID)与目录内容。
|
|
21
|
+
2. `easytwin upload <dir>`,观察进度与完成摘要。
|