@jgengine/shell 0.7.0 → 0.9.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/CHANGELOG.md +63 -0
- package/README.md +37 -1
- package/dist/GameHost.d.ts +12 -0
- package/dist/GameHost.js +33 -0
- package/dist/GamePlayer.d.ts +11 -0
- package/dist/GamePlayer.js +24 -0
- package/dist/GamePlayerShell.d.ts +36 -1
- package/dist/GamePlayerShell.js +751 -80
- package/dist/GameUiPreview.js +2 -1
- package/dist/audio/AudioComponents.js +25 -8
- package/dist/audio/audioEngine.d.ts +1 -0
- package/dist/audio/audioEngine.js +4 -0
- package/dist/behaviour.d.ts +12 -0
- package/dist/behaviour.js +27 -0
- package/dist/behaviourAttach.d.ts +9 -0
- package/dist/behaviourAttach.js +32 -0
- package/dist/behaviourDriver.d.ts +7 -0
- package/dist/behaviourDriver.js +21 -0
- package/dist/camera/GameCameraRig.d.ts +8 -3
- package/dist/camera/GameCameraRig.js +28 -14
- package/dist/camera/GameFirstPersonCamera.js +16 -0
- package/dist/camera/GameInspectionCamera.d.ts +6 -0
- package/dist/camera/GameInspectionCamera.js +24 -0
- package/dist/camera/GameOrbitCamera.js +10 -1
- package/dist/camera/PlayerFov.d.ts +18 -0
- package/dist/camera/PlayerFov.js +48 -0
- package/dist/camera/cameraBlendMath.d.ts +13 -0
- package/dist/camera/cameraBlendMath.js +36 -0
- package/dist/camera/cameraRigs.d.ts +8 -1
- package/dist/camera/cameraRigs.js +78 -38
- package/dist/camera/fovPreference.d.ts +25 -0
- package/dist/camera/fovPreference.js +73 -0
- package/dist/camera/index.d.ts +7 -2
- package/dist/camera/index.js +7 -2
- package/dist/camera/inspectionCameraMath.d.ts +25 -0
- package/dist/camera/inspectionCameraMath.js +44 -0
- package/dist/camera/rigMath.d.ts +50 -1
- package/dist/camera/rigMath.js +91 -1
- package/dist/camera/rigResolve.d.ts +15 -0
- package/dist/camera/rigResolve.js +52 -0
- package/dist/camera/shakeChannel.d.ts +3 -17
- package/dist/camera/shakeChannel.js +2 -21
- package/dist/camera/shakeChannelMath.d.ts +8 -0
- package/dist/camera/shakeChannelMath.js +21 -0
- package/dist/cartridge.d.ts +145 -0
- package/dist/cartridge.js +245 -0
- package/dist/defineGame.d.ts +16 -0
- package/dist/defineGame.js +52 -0
- package/dist/devtools/CollisionDebugWorld.d.ts +5 -0
- package/dist/devtools/CollisionDebugWorld.js +180 -0
- package/dist/devtools/DevtoolsOverlay.d.ts +78 -0
- package/dist/devtools/DevtoolsOverlay.js +676 -0
- package/dist/devtools/collisionDebug.d.ts +57 -0
- package/dist/devtools/collisionDebug.js +127 -0
- package/dist/devtools/collisionDebugMath.d.ts +102 -0
- package/dist/devtools/collisionDebugMath.js +128 -0
- package/dist/environment/Daylight.d.ts +56 -0
- package/dist/environment/Daylight.js +101 -0
- package/dist/environment/EnvironmentScene.js +87 -48
- package/dist/environment/GroundPad.d.ts +7 -0
- package/dist/environment/GroundPad.js +12 -0
- package/dist/environment/daylightCycle.d.ts +26 -0
- package/dist/environment/daylightCycle.js +116 -0
- package/dist/environment/groundPadMath.d.ts +13 -0
- package/dist/environment/groundPadMath.js +10 -0
- package/dist/environment/index.d.ts +3 -0
- package/dist/environment/index.js +3 -0
- package/dist/environment/skyLightingPolicy.d.ts +10 -0
- package/dist/environment/skyLightingPolicy.js +6 -0
- package/dist/input/mouseLook.d.ts +27 -0
- package/dist/input/mouseLook.js +35 -0
- package/dist/materialOverride.d.ts +6 -0
- package/dist/materialOverride.js +28 -0
- package/dist/multiplayer.d.ts +16 -9
- package/dist/multiplayer.js +62 -12
- package/dist/pointer/PointerProbe.js +20 -3
- package/dist/pointer/pointerService.d.ts +5 -0
- package/dist/pointer/pointerService.js +51 -24
- package/dist/registry.d.ts +4 -1
- package/dist/registry.js +3 -1
- package/dist/render/modelRender.d.ts +23 -0
- package/dist/render/modelRender.js +106 -0
- package/dist/render/resolveModel.d.ts +14 -0
- package/dist/render/resolveModel.js +24 -0
- package/dist/settings/QuickControls.d.ts +4 -0
- package/dist/settings/QuickControls.js +42 -0
- package/dist/settings/SettingsChrome.d.ts +1 -0
- package/dist/settings/SettingsChrome.js +10 -0
- package/dist/settings/SettingsMenu.d.ts +6 -0
- package/dist/settings/SettingsMenu.js +148 -0
- package/dist/settings/SettingsRuntime.d.ts +11 -0
- package/dist/settings/SettingsRuntime.js +19 -0
- package/dist/settings/appliedSettings.d.ts +14 -0
- package/dist/settings/appliedSettings.js +27 -0
- package/dist/settings/settingsController.d.ts +20 -0
- package/dist/settings/settingsController.js +165 -0
- package/dist/structures/GeneratedBuilding.d.ts +10 -0
- package/dist/structures/GeneratedBuilding.js +96 -8
- package/dist/structures/index.d.ts +1 -1
- package/dist/structures/index.js +1 -1
- package/dist/terrain/CarvedTerrain.d.ts +2 -1
- package/dist/terrain/CarvedTerrain.js +3 -3
- package/dist/terrain/GrassField.d.ts +3 -1
- package/dist/terrain/GrassField.js +4 -2
- package/dist/terrain/grassBudget.d.ts +3 -0
- package/dist/terrain/grassBudget.js +6 -0
- package/dist/terrain/grassGeometry.js +1 -1
- package/dist/terrain/index.d.ts +1 -1
- package/dist/terrain/index.js +1 -1
- package/dist/terrain/terrainMath.d.ts +9 -0
- package/dist/terrain/terrainMath.js +17 -2
- package/dist/touch/OrientationHint.d.ts +3 -0
- package/dist/touch/OrientationHint.js +13 -0
- package/dist/touch/TouchControlsOverlay.d.ts +34 -0
- package/dist/touch/TouchControlsOverlay.js +279 -0
- package/dist/visibility/CullingProvider.d.ts +21 -0
- package/dist/visibility/CullingProvider.js +134 -0
- package/dist/vision/FrustumSensorHud.d.ts +1 -6
- package/dist/vision/FrustumSensorHud.js +25 -27
- package/dist/vision/frustumSampleEqual.d.ts +2 -0
- package/dist/vision/frustumSampleEqual.js +10 -0
- package/dist/water/Ocean.js +1 -1
- package/dist/water/OceanConfig.d.ts +13 -0
- package/dist/water/OceanConfig.js +25 -17
- package/dist/water/OceanShader.d.ts +1 -1
- package/dist/water/OceanShader.js +3 -3
- package/dist/water/index.d.ts +1 -1
- package/dist/water/index.js +1 -1
- package/dist/weather/FireSpreadLayer.js +7 -2
- package/dist/weather/RainField.d.ts +3 -1
- package/dist/weather/RainField.js +4 -4
- package/dist/weather/SnowField.d.ts +3 -1
- package/dist/weather/SnowField.js +4 -4
- package/dist/weather/fireSpreadPose.d.ts +2 -0
- package/dist/weather/fireSpreadPose.js +4 -0
- package/dist/weather/weatherMath.d.ts +5 -1
- package/dist/weather/weatherMath.js +7 -2
- package/dist/world/DataObjects.d.ts +44 -0
- package/dist/world/DataObjects.js +77 -0
- package/dist/world/GridWorldScene.d.ts +10 -0
- package/dist/world/GridWorldScene.js +54 -0
- package/dist/world/SpriteBatch.d.ts +44 -0
- package/dist/world/SpriteBatch.js +112 -0
- package/dist/world/WorldHud.d.ts +8 -1
- package/dist/world/WorldHud.js +90 -39
- package/dist/world/WorldItems.js +21 -2
- package/dist/world/entityPose.d.ts +14 -0
- package/dist/world/entityPose.js +10 -0
- package/dist/world/telegraphPulse.d.ts +1 -0
- package/dist/world/telegraphPulse.js +4 -0
- package/dist/world/worldBarSamples.d.ts +30 -0
- package/dist/world/worldBarSamples.js +51 -0
- package/llms.txt +2643 -0
- package/package.json +6 -5
- package/dist/demo/builderDemo.d.ts +0 -2
- package/dist/demo/builderDemo.js +0 -189
- package/dist/demo/demoGame.d.ts +0 -2
- package/dist/demo/demoGame.js +0 -208
- package/dist/demo/environmentShowcase.d.ts +0 -2
- package/dist/demo/environmentShowcase.js +0 -15
- package/dist/demo/mapDemo.d.ts +0 -2
- package/dist/demo/mapDemo.js +0 -206
- package/dist/demo/pointerDemo.d.ts +0 -2
- package/dist/demo/pointerDemo.js +0 -151
- package/dist/demo/sensorShowcase.d.ts +0 -2
- package/dist/demo/sensorShowcase.js +0 -131
- package/dist/demo/survivalDemo.d.ts +0 -2
- package/dist/demo/survivalDemo.js +0 -291
package/dist/GameUiPreview.js
CHANGED
|
@@ -2,6 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
3
|
import { createGameContext } from "@jgengine/core/runtime/gameContext";
|
|
4
4
|
import { GameProvider } from "@jgengine/react/provider";
|
|
5
|
+
import { HudViewportProvider } from "@jgengine/react/hudViewport";
|
|
5
6
|
const PREVIEW_USER_ID = "ui-preview";
|
|
6
7
|
const TICK_STEP = 1 / 60;
|
|
7
8
|
function runTicks(ctx, playable, seconds) {
|
|
@@ -59,5 +60,5 @@ export function GameUiPreview({ playable, scenario = defaultUiScenario, }) {
|
|
|
59
60
|
if (ctx === null)
|
|
60
61
|
return _jsx("div", { className: "h-full w-full bg-neutral-900" });
|
|
61
62
|
const GameUI = playable.GameUI;
|
|
62
|
-
return (_jsx("div", { "data-ui-preview-ready": true, className: "relative h-full w-full overflow-hidden", style: { background: "linear-gradient(180deg, #2a3d33 0%, #1a2320 55%, #141b18 100%)" }, children: _jsx(GameProvider, { context: ctx, children: _jsx(GameUI, {}) }) }));
|
|
63
|
+
return (_jsx("div", { "data-ui-preview-ready": true, className: "relative h-full w-full overflow-hidden", style: { background: "linear-gradient(180deg, #2a3d33 0%, #1a2320 55%, #141b18 100%)" }, children: _jsx(GameProvider, { context: ctx, children: _jsx(HudViewportProvider, { platforms: playable.platforms, config: playable.hudFit, children: _jsx(GameUI, {}) }) }) }));
|
|
63
64
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useFrame, useThree } from "@react-three/fiber";
|
|
2
2
|
import { useEffect, useRef } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import { useGameContext } from "@jgengine/react/provider";
|
|
4
4
|
export function AudioListener({ engine }) {
|
|
5
5
|
const camera = useThree((state) => state.camera);
|
|
6
6
|
useFrame(() => {
|
|
@@ -8,18 +8,20 @@ export function AudioListener({ engine }) {
|
|
|
8
8
|
});
|
|
9
9
|
return null;
|
|
10
10
|
}
|
|
11
|
-
function useTrackedEmitters(engine, soundByKey,
|
|
11
|
+
function useTrackedEmitters(engine, soundByKey, readEntries) {
|
|
12
12
|
const handles = useRef(new Map());
|
|
13
|
+
const lastPose = useRef(new Map());
|
|
13
14
|
useEffect(() => () => {
|
|
14
15
|
for (const handle of handles.current.values())
|
|
15
16
|
handle.stop();
|
|
16
17
|
handles.current.clear();
|
|
18
|
+
lastPose.current.clear();
|
|
17
19
|
}, [engine]);
|
|
18
20
|
useFrame(() => {
|
|
19
21
|
if (soundByKey === undefined)
|
|
20
22
|
return;
|
|
21
23
|
const live = new Set();
|
|
22
|
-
for (const entry of
|
|
24
|
+
for (const entry of readEntries()) {
|
|
23
25
|
const soundId = soundByKey[entry.key];
|
|
24
26
|
if (soundId === undefined)
|
|
25
27
|
continue;
|
|
@@ -27,27 +29,42 @@ function useTrackedEmitters(engine, soundByKey, entries) {
|
|
|
27
29
|
let handle = handles.current.get(entry.id);
|
|
28
30
|
if (handle === undefined) {
|
|
29
31
|
handle = engine.playLoop(soundId, { x: entry.position[0], y: entry.position[1], z: entry.position[2] }) ?? undefined;
|
|
30
|
-
if (handle !== undefined)
|
|
32
|
+
if (handle !== undefined) {
|
|
31
33
|
handles.current.set(entry.id, handle);
|
|
34
|
+
lastPose.current.set(entry.id, entry.position);
|
|
35
|
+
}
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const prev = lastPose.current.get(entry.id);
|
|
39
|
+
if (prev !== undefined &&
|
|
40
|
+
prev[0] === entry.position[0] &&
|
|
41
|
+
prev[1] === entry.position[1] &&
|
|
42
|
+
prev[2] === entry.position[2]) {
|
|
32
43
|
continue;
|
|
33
44
|
}
|
|
34
45
|
handle.setPosition({ x: entry.position[0], y: entry.position[1], z: entry.position[2] });
|
|
46
|
+
lastPose.current.set(entry.id, entry.position);
|
|
35
47
|
}
|
|
36
48
|
for (const [id, handle] of handles.current) {
|
|
37
49
|
if (!live.has(id)) {
|
|
38
50
|
handle.stop();
|
|
39
51
|
handles.current.delete(id);
|
|
52
|
+
lastPose.current.delete(id);
|
|
40
53
|
}
|
|
41
54
|
}
|
|
42
55
|
});
|
|
43
56
|
}
|
|
44
57
|
export function EntityAudioEmitters({ engine, entitySounds, }) {
|
|
45
|
-
const
|
|
46
|
-
useTrackedEmitters(engine, entitySounds,
|
|
58
|
+
const ctx = useGameContext();
|
|
59
|
+
useTrackedEmitters(engine, entitySounds, () => ctx.scene.entity.list().map((entity) => ({ id: entity.id, key: entity.name, position: entity.position })));
|
|
47
60
|
return null;
|
|
48
61
|
}
|
|
49
62
|
export function ObjectAudioEmitters({ engine, objectSounds, }) {
|
|
50
|
-
const
|
|
51
|
-
useTrackedEmitters(engine, objectSounds,
|
|
63
|
+
const ctx = useGameContext();
|
|
64
|
+
useTrackedEmitters(engine, objectSounds, () => ctx.scene.object.list().map((object) => ({
|
|
65
|
+
id: object.instanceId,
|
|
66
|
+
key: object.catalogId,
|
|
67
|
+
position: object.position,
|
|
68
|
+
})));
|
|
52
69
|
return null;
|
|
53
70
|
}
|
|
@@ -17,6 +17,7 @@ export interface AudioEngine {
|
|
|
17
17
|
playOneShot(soundId: string, position?: Vec3): void;
|
|
18
18
|
playLoop(soundId: string, position?: Vec3): AudioEmitterHandle | null;
|
|
19
19
|
setBusGain(busId: string, gain: number): void;
|
|
20
|
+
setMasterGain(gain: number): void;
|
|
20
21
|
resume(): void;
|
|
21
22
|
dispose(): void;
|
|
22
23
|
}
|
|
@@ -5,6 +5,7 @@ function createNoopEngine() {
|
|
|
5
5
|
playOneShot: () => undefined,
|
|
6
6
|
playLoop: () => null,
|
|
7
7
|
setBusGain: () => undefined,
|
|
8
|
+
setMasterGain: () => undefined,
|
|
8
9
|
resume: () => undefined,
|
|
9
10
|
dispose: () => undefined,
|
|
10
11
|
};
|
|
@@ -100,6 +101,9 @@ export function createAudioEngine(config = {}) {
|
|
|
100
101
|
setBusGain(busId, gain) {
|
|
101
102
|
busGainNode(busId).gain.value = gain;
|
|
102
103
|
},
|
|
104
|
+
setMasterGain(gain) {
|
|
105
|
+
masterGain.gain.value = gain;
|
|
106
|
+
},
|
|
103
107
|
resume() {
|
|
104
108
|
void context.resume().catch(() => undefined);
|
|
105
109
|
},
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { BehaviourWorld } from "@jgengine/core/behaviour/behaviour";
|
|
2
|
+
export { createBehaviourWorldDriver } from "./behaviourDriver.js";
|
|
3
|
+
export { attachObject3D, Object3DBehaviour } from "./behaviourAttach.js";
|
|
4
|
+
/**
|
|
5
|
+
* Bootstraps `world` on mount and dispatches `world.update` every frame at
|
|
6
|
+
* simulation priority. Stops driving updates on unmount so remount does not
|
|
7
|
+
* double-step. `scaleDt` maps the raw frame delta to the dt behaviours
|
|
8
|
+
* receive — pass the game clock's scaling to keep behaviours on game time, or
|
|
9
|
+
* omit for real-time seconds. Games driving `world.update` themselves from
|
|
10
|
+
* `loop.onTick` should not also render this.
|
|
11
|
+
*/
|
|
12
|
+
export declare function useBehaviourWorld(world: BehaviourWorld, scaleDt?: (rawDt: number) => number): void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useFrame } from "@react-three/fiber";
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
|
+
import { createBehaviourWorldDriver } from "./behaviourDriver.js";
|
|
4
|
+
export { createBehaviourWorldDriver } from "./behaviourDriver.js";
|
|
5
|
+
export { attachObject3D, Object3DBehaviour } from "./behaviourAttach.js";
|
|
6
|
+
/**
|
|
7
|
+
* Bootstraps `world` on mount and dispatches `world.update` every frame at
|
|
8
|
+
* simulation priority. Stops driving updates on unmount so remount does not
|
|
9
|
+
* double-step. `scaleDt` maps the raw frame delta to the dt behaviours
|
|
10
|
+
* receive — pass the game clock's scaling to keep behaviours on game time, or
|
|
11
|
+
* omit for real-time seconds. Games driving `world.update` themselves from
|
|
12
|
+
* `loop.onTick` should not also render this.
|
|
13
|
+
*/
|
|
14
|
+
export function useBehaviourWorld(world, scaleDt) {
|
|
15
|
+
const driverRef = useRef(createBehaviourWorldDriver(world));
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const driver = createBehaviourWorldDriver(world);
|
|
18
|
+
driverRef.current = driver;
|
|
19
|
+
driver.start();
|
|
20
|
+
return () => {
|
|
21
|
+
driver.stop();
|
|
22
|
+
};
|
|
23
|
+
}, [world]);
|
|
24
|
+
useFrame((_state, rawDt) => {
|
|
25
|
+
driverRef.current.step(scaleDt === undefined ? rawDt : scaleDt(rawDt));
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Object3D } from "three";
|
|
2
|
+
import { Behaviour, type BehaviourWorld } from "@jgengine/core/behaviour/behaviour";
|
|
3
|
+
export declare class Object3DBehaviour extends Behaviour {
|
|
4
|
+
/** @internal */ _object: Object3D | null;
|
|
5
|
+
get object(): Object3D;
|
|
6
|
+
onBeforeRender(): void;
|
|
7
|
+
onAfterRender(): void;
|
|
8
|
+
}
|
|
9
|
+
export declare function attachObject3D<T extends Object3DBehaviour>(world: BehaviourWorld, object: Object3D, behaviour: T, nodeId?: string): T;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Behaviour } from "@jgengine/core/behaviour/behaviour";
|
|
2
|
+
export class Object3DBehaviour extends Behaviour {
|
|
3
|
+
/** @internal */ _object = null;
|
|
4
|
+
get object() {
|
|
5
|
+
if (this._object === null)
|
|
6
|
+
throw new Error("behaviour is not bound to an Object3D");
|
|
7
|
+
return this._object;
|
|
8
|
+
}
|
|
9
|
+
onBeforeRender() { }
|
|
10
|
+
onAfterRender() { }
|
|
11
|
+
}
|
|
12
|
+
export function attachObject3D(world, object, behaviour, nodeId = object.uuid) {
|
|
13
|
+
behaviour._object = object;
|
|
14
|
+
world.attach(nodeId, behaviour);
|
|
15
|
+
if (behaviour.onBeforeRender !== Object3DBehaviour.prototype.onBeforeRender) {
|
|
16
|
+
const previous = object.onBeforeRender;
|
|
17
|
+
object.onBeforeRender = function chainedBeforeRender(...args) {
|
|
18
|
+
previous.apply(this, args);
|
|
19
|
+
if (behaviour.isActive)
|
|
20
|
+
behaviour.onBeforeRender();
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
if (behaviour.onAfterRender !== Object3DBehaviour.prototype.onAfterRender) {
|
|
24
|
+
const previous = object.onAfterRender;
|
|
25
|
+
object.onAfterRender = function chainedAfterRender(...args) {
|
|
26
|
+
previous.apply(this, args);
|
|
27
|
+
if (behaviour.isActive)
|
|
28
|
+
behaviour.onAfterRender();
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return behaviour;
|
|
32
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function createBehaviourWorldDriver(world) {
|
|
2
|
+
let running = false;
|
|
3
|
+
return {
|
|
4
|
+
start() {
|
|
5
|
+
if (!world.started())
|
|
6
|
+
world.start();
|
|
7
|
+
running = true;
|
|
8
|
+
},
|
|
9
|
+
stop() {
|
|
10
|
+
running = false;
|
|
11
|
+
},
|
|
12
|
+
isRunning() {
|
|
13
|
+
return running;
|
|
14
|
+
},
|
|
15
|
+
step(dt) {
|
|
16
|
+
if (!running)
|
|
17
|
+
return;
|
|
18
|
+
world.update(dt);
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { type MutableRefObject } from "react";
|
|
2
|
-
import type {
|
|
2
|
+
import type { GameCameraConfig } from "@jgengine/core/game/playableGame";
|
|
3
|
+
import type { CameraDirector } from "@jgengine/core/runtime/cameraDirector";
|
|
4
|
+
export { resolveRigKind } from "./rigResolve.js";
|
|
3
5
|
export interface GameCameraRigProps {
|
|
4
6
|
yawRef: MutableRefObject<number>;
|
|
5
7
|
pitchRef: MutableRefObject<number>;
|
|
6
8
|
config?: GameCameraConfig;
|
|
7
9
|
onDragChange?: (dragging: boolean) => void;
|
|
8
10
|
pointerControls?: boolean;
|
|
11
|
+
/** False when the game's own input map already claims WASD, so the RTS rig should stop panning on those keys. */
|
|
12
|
+
panKeysEnabled?: boolean;
|
|
13
|
+
/** Runtime follow/cinematic override (#196.2). While present it wins over the static `config` for any field it reports non-`undefined`/non-`null`; absent (or an all-`undefined` snapshot) is a no-op. */
|
|
14
|
+
director?: CameraDirector;
|
|
9
15
|
}
|
|
10
|
-
export declare function
|
|
11
|
-
export declare function GameCameraRig({ yawRef, pitchRef, config, onDragChange, pointerControls }: GameCameraRigProps): import("react").JSX.Element;
|
|
16
|
+
export declare function GameCameraRig({ yawRef, pitchRef, config, onDragChange, pointerControls, panKeysEnabled, director, }: GameCameraRigProps): import("react").JSX.Element;
|
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useMemo } from "react";
|
|
3
|
-
import { ChaseRig, CinematicRig, LockOnRig, ObserverRig, RtsRig, ShoulderRig, TopDownRig, } from "./cameraRigs.js";
|
|
2
|
+
import { useEffect, useMemo, useReducer } from "react";
|
|
3
|
+
import { ChaseRig, CinematicRig, LockOnRig, ObserverRig, RtsRig, ShoulderRig, SideScrollRig, TopDownRig, } from "./cameraRigs.js";
|
|
4
4
|
import { GameFirstPersonCamera } from "./GameFirstPersonCamera.js";
|
|
5
|
+
import { GameInspectionCamera } from "./GameInspectionCamera.js";
|
|
5
6
|
import { GameOrbitCamera } from "./GameOrbitCamera.js";
|
|
7
|
+
import { resolveDirectedCamera } from "./rigMath.js";
|
|
8
|
+
import { resolveRigKind, turntableAsObserver } from "./rigResolve.js";
|
|
6
9
|
import { CameraShakeContext, createCameraShakeChannel } from "./shakeChannel.js";
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
return config.rig;
|
|
10
|
-
if (config?.perspective === "first")
|
|
11
|
-
return "first";
|
|
12
|
-
return "orbit";
|
|
13
|
-
}
|
|
14
|
-
export function GameCameraRig({ yawRef, pitchRef, config, onDragChange, pointerControls }) {
|
|
10
|
+
export { resolveRigKind } from "./rigResolve.js";
|
|
11
|
+
export function GameCameraRig({ yawRef, pitchRef, config, onDragChange, pointerControls, panKeysEnabled, director, }) {
|
|
15
12
|
const channel = useMemo(() => createCameraShakeChannel(config?.shake?.decayPerSecond), [config?.shake?.decayPerSecond]);
|
|
16
|
-
const
|
|
13
|
+
const [, notifyDirectorChange] = useReducer((count) => count + 1, 0);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (director === undefined)
|
|
16
|
+
return undefined;
|
|
17
|
+
return director.subscribe(notifyDirectorChange);
|
|
18
|
+
}, [director]);
|
|
19
|
+
const directed = resolveDirectedCamera(director === undefined
|
|
20
|
+
? undefined
|
|
21
|
+
: { followEntityId: director.followedEntityId(), cinematic: director.cinematic() }, { followEntityId: config?.followEntityId, cinematic: config?.cinematic });
|
|
22
|
+
const followEntityId = directed.followEntityId;
|
|
17
23
|
const rig = (() => {
|
|
18
|
-
if (
|
|
19
|
-
return _jsx(CinematicRig, { yawRef: yawRef, pitchRef: pitchRef, config: config, followEntityId: followEntityId });
|
|
24
|
+
if (directed.cinematic !== undefined) {
|
|
25
|
+
return (_jsx(CinematicRig, { yawRef: yawRef, pitchRef: pitchRef, config: { ...config, cinematic: directed.cinematic }, followEntityId: followEntityId, absoluteFov: true }));
|
|
20
26
|
}
|
|
21
27
|
const kind = resolveRigKind(config);
|
|
22
28
|
switch (kind) {
|
|
@@ -25,7 +31,7 @@ export function GameCameraRig({ yawRef, pitchRef, config, onDragChange, pointerC
|
|
|
25
31
|
case "topDown":
|
|
26
32
|
return _jsx(TopDownRig, { yawRef: yawRef, pitchRef: pitchRef, config: config, followEntityId: followEntityId });
|
|
27
33
|
case "rts":
|
|
28
|
-
return _jsx(RtsRig, { yawRef: yawRef, pitchRef: pitchRef, config: config, followEntityId: followEntityId });
|
|
34
|
+
return (_jsx(RtsRig, { yawRef: yawRef, pitchRef: pitchRef, config: config, followEntityId: followEntityId, panKeysEnabled: panKeysEnabled }));
|
|
29
35
|
case "shoulder":
|
|
30
36
|
return _jsx(ShoulderRig, { yawRef: yawRef, pitchRef: pitchRef, config: config, followEntityId: followEntityId });
|
|
31
37
|
case "lockOn":
|
|
@@ -34,6 +40,14 @@ export function GameCameraRig({ yawRef, pitchRef, config, onDragChange, pointerC
|
|
|
34
40
|
return _jsx(ChaseRig, { yawRef: yawRef, pitchRef: pitchRef, config: config, followEntityId: followEntityId });
|
|
35
41
|
case "observer":
|
|
36
42
|
return _jsx(ObserverRig, { yawRef: yawRef, pitchRef: pitchRef, config: config, followEntityId: followEntityId });
|
|
43
|
+
case "turntable":
|
|
44
|
+
return (_jsx(ObserverRig, { yawRef: yawRef, pitchRef: pitchRef, config: turntableAsObserver(config), followEntityId: followEntityId }));
|
|
45
|
+
case "sideScroll":
|
|
46
|
+
return _jsx(SideScrollRig, { yawRef: yawRef, pitchRef: pitchRef, config: config, followEntityId: followEntityId });
|
|
47
|
+
case "inspection":
|
|
48
|
+
return _jsx(GameInspectionCamera, { config: config?.inspection });
|
|
49
|
+
case "none":
|
|
50
|
+
return null;
|
|
37
51
|
default:
|
|
38
52
|
return (_jsx(GameOrbitCamera, { yawRef: yawRef, pitchRef: pitchRef, config: config, followEntityId: followEntityId ?? undefined, onCameraFollow: config?.onCameraFollow, onDragChange: onDragChange, pointerControls: pointerControls }));
|
|
39
53
|
}
|
|
@@ -4,6 +4,7 @@ import { useEffect, useRef } from "react";
|
|
|
4
4
|
import * as THREE from "three";
|
|
5
5
|
import { useGameContext } from "@jgengine/react/provider";
|
|
6
6
|
import { usePlayer } from "@jgengine/react/hooks";
|
|
7
|
+
import { usePlayerFov } from "./PlayerFov.js";
|
|
7
8
|
import { GAME_SIM_FRAME_PRIORITY, ORBIT_CAMERA_FRAME_PRIORITY } from "./orbitCameraMath.js";
|
|
8
9
|
const DEFAULT_EYE_HEIGHT = 1.6;
|
|
9
10
|
const DEFAULT_SENSITIVITY = 0.0025;
|
|
@@ -14,11 +15,15 @@ export function GameFirstPersonCamera({ yawRef, pitchRef, config, followEntityId
|
|
|
14
15
|
const maxPitch = config?.maxPitch ?? DEFAULT_MAX_PITCH;
|
|
15
16
|
const { userId } = usePlayer();
|
|
16
17
|
const ctx = useGameContext();
|
|
18
|
+
const playerFov = usePlayerFov();
|
|
17
19
|
const camera = useThree((state) => state.camera);
|
|
18
20
|
const domElement = useThree((state) => state.gl.domElement);
|
|
19
21
|
const followId = followEntityId ?? userId;
|
|
22
|
+
const seededRef = useRef(false);
|
|
20
23
|
useEffect(() => {
|
|
21
24
|
const requestLock = () => {
|
|
25
|
+
if (window.matchMedia?.("(pointer: coarse)").matches)
|
|
26
|
+
return;
|
|
22
27
|
if (document.pointerLockElement !== domElement)
|
|
23
28
|
void domElement.requestPointerLock?.();
|
|
24
29
|
};
|
|
@@ -39,9 +44,20 @@ export function GameFirstPersonCamera({ yawRef, pitchRef, config, followEntityId
|
|
|
39
44
|
const entity = ctx.scene.entity.get(followId);
|
|
40
45
|
if (entity === null)
|
|
41
46
|
return;
|
|
47
|
+
if (!seededRef.current) {
|
|
48
|
+
seededRef.current = true;
|
|
49
|
+
yawRef.current = entity.rotationY;
|
|
50
|
+
}
|
|
42
51
|
const cosPitch = Math.cos(pitchRef.current);
|
|
43
52
|
camera.position.set(entity.position[0], entity.position[1] + eyeHeight, entity.position[2]);
|
|
44
53
|
camera.lookAt(camera.position.x + Math.sin(yawRef.current) * cosPitch, camera.position.y + Math.sin(pitchRef.current), camera.position.z + Math.cos(yawRef.current) * cosPitch);
|
|
54
|
+
if (camera.isPerspectiveCamera === true) {
|
|
55
|
+
const perspective = camera;
|
|
56
|
+
if (Math.abs(perspective.fov - playerFov.fov) > 0.001) {
|
|
57
|
+
perspective.fov = playerFov.fov;
|
|
58
|
+
perspective.updateProjectionMatrix();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
45
61
|
}, ORBIT_CAMERA_FRAME_PRIORITY);
|
|
46
62
|
if (config?.viewmodel === false)
|
|
47
63
|
return null;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type InspectionCameraConfig } from "./inspectionCameraMath.js";
|
|
2
|
+
export interface GameInspectionCameraProps {
|
|
3
|
+
config?: InspectionCameraConfig;
|
|
4
|
+
}
|
|
5
|
+
/** Model-viewer style rig (#207.7): left-drag orbit, middle/right-drag pan, scroll zoom toward a configurable anchor. Orbits a fixed `target`; never reads player/entity state. */
|
|
6
|
+
export declare function GameInspectionCamera({ config: configPatch }: GameInspectionCameraProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { OrbitControls } from "@react-three/drei";
|
|
3
|
+
import { useThree } from "@react-three/fiber";
|
|
4
|
+
import { useEffect, useRef } from "react";
|
|
5
|
+
import { MOUSE } from "three";
|
|
6
|
+
import { resolveInspectionCameraConfig, resolveInspectionZoomToCursor, seedInspectionCamera, } from "./inspectionCameraMath.js";
|
|
7
|
+
/** Model-viewer style rig (#207.7): left-drag orbit, middle/right-drag pan, scroll zoom toward a configurable anchor. Orbits a fixed `target`; never reads player/entity state. */
|
|
8
|
+
export function GameInspectionCamera({ config: configPatch }) {
|
|
9
|
+
const config = resolveInspectionCameraConfig(configPatch);
|
|
10
|
+
const controlsRef = useRef(null);
|
|
11
|
+
const camera = useThree((state) => state.camera);
|
|
12
|
+
const seededRef = useRef(false);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (seededRef.current)
|
|
15
|
+
return;
|
|
16
|
+
const seeded = seedInspectionCamera(config);
|
|
17
|
+
camera.position.set(seeded.camera.x, seeded.camera.y, seeded.camera.z);
|
|
18
|
+
camera.lookAt(seeded.target.x, seeded.target.y, seeded.target.z);
|
|
19
|
+
controlsRef.current?.target.set(seeded.target.x, seeded.target.y, seeded.target.z);
|
|
20
|
+
controlsRef.current?.update();
|
|
21
|
+
seededRef.current = true;
|
|
22
|
+
}, [camera, config]);
|
|
23
|
+
return (_jsx(OrbitControls, { ref: controlsRef, enableDamping: true, dampingFactor: config.dampingFactor, rotateSpeed: config.rotateSpeed, zoomSpeed: config.zoomSpeed, zoomToCursor: resolveInspectionZoomToCursor(config.anchor), enablePan: config.pan, enableRotate: true, enableZoom: true, minDistance: config.minDistance, maxDistance: config.maxDistance, minPolarAngle: config.minPolarAngle, maxPolarAngle: config.maxPolarAngle, screenSpacePanning: true, mouseButtons: { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.PAN, RIGHT: MOUSE.PAN } }));
|
|
24
|
+
}
|
|
@@ -2,10 +2,11 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { OrbitControls } from "@react-three/drei";
|
|
3
3
|
import { useFrame, useThree } from "@react-three/fiber";
|
|
4
4
|
import { useEffect, useRef } from "react";
|
|
5
|
-
import { MOUSE, Vector3 } from "three";
|
|
5
|
+
import { MOUSE, PerspectiveCamera, Vector3 } from "three";
|
|
6
6
|
import { useGameContext } from "@jgengine/react/provider";
|
|
7
7
|
import { usePlayer } from "@jgengine/react/hooks";
|
|
8
8
|
import { cameraLookPitch, ORBIT_CAMERA_FRAME_PRIORITY, orbitFollowStep, orbitYawFromCamera, resolveFollowTargetFromPosition, resolveOrbitCameraConfig, seedOrbitFollowState, } from "./orbitCameraMath.js";
|
|
9
|
+
import { usePlayerFov } from "./PlayerFov.js";
|
|
9
10
|
export function GameOrbitCamera({ yawRef, pitchRef, config: configPatch, followEntityId, resolveFollowTarget, onDragChange, onCameraFollow, pointerControls = false, }) {
|
|
10
11
|
const config = resolveOrbitCameraConfig(configPatch);
|
|
11
12
|
const controlsRef = useRef(null);
|
|
@@ -13,6 +14,7 @@ export function GameOrbitCamera({ yawRef, pitchRef, config: configPatch, followE
|
|
|
13
14
|
const draggingRef = useRef(false);
|
|
14
15
|
const { userId } = usePlayer();
|
|
15
16
|
const ctx = useGameContext();
|
|
17
|
+
const playerFov = usePlayerFov();
|
|
16
18
|
const camera = useThree((state) => state.camera);
|
|
17
19
|
const followId = followEntityId ?? userId;
|
|
18
20
|
useEffect(() => {
|
|
@@ -64,6 +66,13 @@ export function GameOrbitCamera({ yawRef, pitchRef, config: configPatch, followE
|
|
|
64
66
|
if (pitchRef !== undefined) {
|
|
65
67
|
pitchRef.current = cameraLookPitch({ x: camera.position.x, y: camera.position.y, z: camera.position.z }, stepped.target);
|
|
66
68
|
}
|
|
69
|
+
if (camera.isPerspectiveCamera === true) {
|
|
70
|
+
const perspective = camera;
|
|
71
|
+
if (Math.abs(perspective.fov - playerFov.fov) > 0.001) {
|
|
72
|
+
perspective.fov = playerFov.fov;
|
|
73
|
+
perspective.updateProjectionMatrix();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
67
76
|
onCameraFollow?.({
|
|
68
77
|
entityId: followId,
|
|
69
78
|
target: stepped.target,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { GameCameraConfig } from "@jgengine/core/game/playableGame";
|
|
3
|
+
import { type PlayerFovBounds } from "./fovPreference.js";
|
|
4
|
+
export interface PlayerFovState {
|
|
5
|
+
fov: number;
|
|
6
|
+
bounds: PlayerFovBounds;
|
|
7
|
+
enabled: boolean;
|
|
8
|
+
persist: boolean;
|
|
9
|
+
setFov: (value: number) => void;
|
|
10
|
+
compose: (poseFov: number, mode?: "relative" | "absolute") => number;
|
|
11
|
+
}
|
|
12
|
+
export declare function PlayerFovProvider({ config, orthographic, children, }: {
|
|
13
|
+
config?: GameCameraConfig;
|
|
14
|
+
orthographic: boolean;
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
}): import("react").JSX.Element;
|
|
17
|
+
export declare function usePlayerFov(): PlayerFovState;
|
|
18
|
+
export declare function PlayerFovSlider(): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useCallback, useContext, useMemo, useState, } from "react";
|
|
3
|
+
import { composePlayerFov, loadPlayerFov, resolvePlayerFovBounds, savePlayerFov, } from "./fovPreference.js";
|
|
4
|
+
const PlayerFovContext = createContext(null);
|
|
5
|
+
export function PlayerFovProvider({ config, orthographic, children, }) {
|
|
6
|
+
const bounds = useMemo(() => resolvePlayerFovBounds({
|
|
7
|
+
min: config?.playerFov?.min,
|
|
8
|
+
max: config?.playerFov?.max,
|
|
9
|
+
default: config?.playerFov?.default ?? config?.frustum?.fov,
|
|
10
|
+
}), [config?.playerFov?.min, config?.playerFov?.max, config?.playerFov?.default, config?.frustum?.fov]);
|
|
11
|
+
const persist = config?.playerFov?.persist !== false;
|
|
12
|
+
const showControl = !orthographic && config?.playerFov?.control !== false;
|
|
13
|
+
const [fov, setFovState] = useState(() => persist ? loadPlayerFov(bounds) : bounds.defaultFov);
|
|
14
|
+
const setFov = useCallback((value) => {
|
|
15
|
+
setFovState(persist ? savePlayerFov(value, bounds) : savePlayerFov(value, bounds, null));
|
|
16
|
+
}, [bounds, persist]);
|
|
17
|
+
const compose = useCallback((poseFov, mode = "relative") => orthographic ? poseFov : composePlayerFov(fov, poseFov, mode, bounds), [bounds, fov, orthographic]);
|
|
18
|
+
const value = useMemo(() => ({
|
|
19
|
+
fov,
|
|
20
|
+
bounds,
|
|
21
|
+
enabled: showControl,
|
|
22
|
+
persist,
|
|
23
|
+
setFov,
|
|
24
|
+
compose,
|
|
25
|
+
}), [bounds, compose, fov, persist, setFov, showControl]);
|
|
26
|
+
return _jsx(PlayerFovContext.Provider, { value: value, children: children });
|
|
27
|
+
}
|
|
28
|
+
export function usePlayerFov() {
|
|
29
|
+
const value = useContext(PlayerFovContext);
|
|
30
|
+
if (value === null) {
|
|
31
|
+
const bounds = resolvePlayerFovBounds();
|
|
32
|
+
return {
|
|
33
|
+
fov: bounds.defaultFov,
|
|
34
|
+
bounds,
|
|
35
|
+
enabled: false,
|
|
36
|
+
persist: false,
|
|
37
|
+
setFov: () => undefined,
|
|
38
|
+
compose: (poseFov) => poseFov,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
export function PlayerFovSlider() {
|
|
44
|
+
const { fov, setFov, bounds, enabled } = usePlayerFov();
|
|
45
|
+
if (!enabled)
|
|
46
|
+
return null;
|
|
47
|
+
return (_jsxs("label", { className: "pointer-events-auto absolute bottom-3 right-3 z-20 flex items-center gap-2 rounded-md bg-neutral-950/75 px-2.5 py-1.5 text-[11px] font-medium tracking-wide text-neutral-200 shadow-lg ring-1 ring-white/10 backdrop-blur-sm", children: [_jsx("span", { className: "text-neutral-400", children: "FOV" }), _jsx("input", { type: "range", min: bounds.min, max: bounds.max, step: 1, value: fov, "aria-label": "Field of view", className: "h-1 w-24 cursor-pointer accent-emerald-400", onChange: (event) => setFov(Number(event.target.value)) }), _jsx("span", { className: "w-7 tabular-nums text-neutral-100", children: Math.round(fov) })] }));
|
|
48
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Camera, Quaternion, Vector3 } from "three";
|
|
2
|
+
export interface CameraBlendScratch {
|
|
3
|
+
fromPos: Vector3;
|
|
4
|
+
fromQuat: Quaternion;
|
|
5
|
+
toPos: Vector3;
|
|
6
|
+
toQuat: Quaternion;
|
|
7
|
+
fov: number;
|
|
8
|
+
elapsed: number;
|
|
9
|
+
duration: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function createCameraBlendScratch(Vector3Ctor: new () => Vector3, QuaternionCtor: new () => Quaternion): CameraBlendScratch;
|
|
12
|
+
export declare function captureCameraBlendFrom(scratch: CameraBlendScratch, position: Vector3, quaternion: Quaternion, fov: number, duration: number): void;
|
|
13
|
+
export declare function applyCameraBlendStep(scratch: CameraBlendScratch, camera: Camera, targetFov: number, dt: number): boolean;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { smoothstep } from "./rigMath.js";
|
|
2
|
+
export function createCameraBlendScratch(Vector3Ctor, QuaternionCtor) {
|
|
3
|
+
return {
|
|
4
|
+
fromPos: new Vector3Ctor(),
|
|
5
|
+
fromQuat: new QuaternionCtor(),
|
|
6
|
+
toPos: new Vector3Ctor(),
|
|
7
|
+
toQuat: new QuaternionCtor(),
|
|
8
|
+
fov: 55,
|
|
9
|
+
elapsed: 0,
|
|
10
|
+
duration: 0,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function captureCameraBlendFrom(scratch, position, quaternion, fov, duration) {
|
|
14
|
+
scratch.fromPos.copy(position);
|
|
15
|
+
scratch.fromQuat.copy(quaternion);
|
|
16
|
+
scratch.fov = fov;
|
|
17
|
+
scratch.elapsed = 0;
|
|
18
|
+
scratch.duration = duration;
|
|
19
|
+
}
|
|
20
|
+
function isPerspective(camera) {
|
|
21
|
+
return camera.isPerspectiveCamera === true;
|
|
22
|
+
}
|
|
23
|
+
export function applyCameraBlendStep(scratch, camera, targetFov, dt) {
|
|
24
|
+
scratch.elapsed += dt;
|
|
25
|
+
const t = scratch.duration <= 0 ? 1 : Math.min(scratch.elapsed / scratch.duration, 1);
|
|
26
|
+
const eased = smoothstep(t);
|
|
27
|
+
scratch.toPos.copy(camera.position);
|
|
28
|
+
scratch.toQuat.copy(camera.quaternion);
|
|
29
|
+
camera.position.lerpVectors(scratch.fromPos, scratch.toPos, eased);
|
|
30
|
+
camera.quaternion.slerpQuaternions(scratch.fromQuat, scratch.toQuat, eased);
|
|
31
|
+
if (isPerspective(camera)) {
|
|
32
|
+
camera.fov = scratch.fov + (targetFov - scratch.fov) * eased;
|
|
33
|
+
camera.updateProjectionMatrix();
|
|
34
|
+
}
|
|
35
|
+
return t >= 1;
|
|
36
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type MutableRefObject } from "react";
|
|
2
2
|
import type { GameCameraConfig } from "@jgengine/core/game/playableGame";
|
|
3
|
+
export { applyCameraBlendStep, captureCameraBlendFrom, createCameraBlendScratch, type CameraBlendScratch, } from "./cameraBlendMath.js";
|
|
3
4
|
export declare const CAMERA_RIG_FRAME_PRIORITY = -1;
|
|
4
5
|
export declare const CAMERA_POST_FRAME_PRIORITY: number;
|
|
5
6
|
export interface RigProps {
|
|
@@ -7,9 +8,15 @@ export interface RigProps {
|
|
|
7
8
|
pitchRef: MutableRefObject<number>;
|
|
8
9
|
config?: GameCameraConfig;
|
|
9
10
|
followEntityId?: string | null;
|
|
11
|
+
/** When true, pose FOV is treated as absolute (cinematic keyframes). */
|
|
12
|
+
absoluteFov?: boolean;
|
|
10
13
|
}
|
|
11
14
|
export declare function TopDownRig(props: RigProps): null;
|
|
12
|
-
|
|
15
|
+
/** Fixed side-on 2.5D follow rig: watches the followed entity from the perpendicular axis, never reading WASD/mouse-look. */
|
|
16
|
+
export declare function SideScrollRig(props: RigProps): null;
|
|
17
|
+
export declare function RtsRig(props: RigProps & {
|
|
18
|
+
panKeysEnabled?: boolean;
|
|
19
|
+
}): null;
|
|
13
20
|
export declare function ShoulderRig(props: RigProps): null;
|
|
14
21
|
export declare function LockOnRig(props: RigProps): null;
|
|
15
22
|
export declare function ChaseRig(props: RigProps): null;
|