@almadar/ui 5.98.0 → 5.100.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.
|
@@ -1872,6 +1872,7 @@ function SideScene({
|
|
|
1872
1872
|
var UNIT_BASE_MODEL_SCALE = 0.5;
|
|
1873
1873
|
var UNIT_BASE_BILLBOARD_HEIGHT = 1.2;
|
|
1874
1874
|
var UNIT_BASE_PRIMITIVE_RADIUS = 0.3;
|
|
1875
|
+
var TILE_TOP_OFFSET = 0.1;
|
|
1875
1876
|
var EVENT_COLORS = {
|
|
1876
1877
|
hit: "#ff5544",
|
|
1877
1878
|
damage: "#ff5544",
|
|
@@ -1998,12 +1999,14 @@ function DefaultUnit({
|
|
|
1998
1999
|
}
|
|
1999
2000
|
) })
|
|
2000
2001
|
) : model?.url ? (
|
|
2001
|
-
/* GLB unit model — LOLO's `animation` field drives the named clip
|
|
2002
|
+
/* GLB unit model — LOLO's `animation` field drives the named clip;
|
|
2003
|
+
`heading` (radians) faces the model along its travel direction (driving). */
|
|
2002
2004
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2003
2005
|
ModelLoader,
|
|
2004
2006
|
{
|
|
2005
2007
|
url: model.url,
|
|
2006
2008
|
scale: modelScale,
|
|
2009
|
+
rotation: [0, unit.heading ?? 0, 0],
|
|
2007
2010
|
animation: unit.animation ?? "idle",
|
|
2008
2011
|
fallbackGeometry: "box",
|
|
2009
2012
|
castShadow: true
|
|
@@ -2358,7 +2361,7 @@ var GameCanvas3D = React8.forwardRef(
|
|
|
2358
2361
|
};
|
|
2359
2362
|
case "top-down":
|
|
2360
2363
|
return {
|
|
2361
|
-
position: [cx, d * 2, cz],
|
|
2364
|
+
position: [cx, d * 2, cz + d * 0.35],
|
|
2362
2365
|
fov: 45
|
|
2363
2366
|
};
|
|
2364
2367
|
case "follow":
|
|
@@ -2394,6 +2397,16 @@ var GameCanvas3D = React8.forwardRef(
|
|
|
2394
2397
|
}
|
|
2395
2398
|
return cameraTarget;
|
|
2396
2399
|
}, [player, pixelsPerUnit, worldHeight, units, selectedUnitId, gridBounds, gridConfig, cameraTarget]);
|
|
2400
|
+
const followOffset = React8.useMemo(() => {
|
|
2401
|
+
if (cameraMode === "chase") {
|
|
2402
|
+
const selected = units.find((u) => u.id === selectedUnitId);
|
|
2403
|
+
const h = selected?.heading ?? 0;
|
|
2404
|
+
const dist = 7;
|
|
2405
|
+
const height = 4;
|
|
2406
|
+
return [-Math.sin(h) * dist, height, -Math.cos(h) * dist];
|
|
2407
|
+
}
|
|
2408
|
+
return player ? [0, 2, 9] : [5, 7, 5];
|
|
2409
|
+
}, [cameraMode, units, selectedUnitId, player]);
|
|
2397
2410
|
if (externalLoading || assetsLoading && preloadAssets.length > 0) {
|
|
2398
2411
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2399
2412
|
Canvas3DLoadingState,
|
|
@@ -2446,11 +2459,11 @@ var GameCanvas3D = React8.forwardRef(
|
|
|
2446
2459
|
},
|
|
2447
2460
|
children: [
|
|
2448
2461
|
/* @__PURE__ */ jsxRuntime.jsx(CameraController, { onCameraChange: eventHandlers.handleCameraChange }),
|
|
2449
|
-
cameraMode === "follow" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2462
|
+
(cameraMode === "follow" || cameraMode === "chase") && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2450
2463
|
FollowCamera,
|
|
2451
2464
|
{
|
|
2452
2465
|
target: followTarget,
|
|
2453
|
-
offset:
|
|
2466
|
+
offset: followOffset
|
|
2454
2467
|
}
|
|
2455
2468
|
),
|
|
2456
2469
|
/* @__PURE__ */ jsxRuntime.jsx("ambientLight", { intensity: 0.6 }),
|
|
@@ -2536,7 +2549,7 @@ var GameCanvas3D = React8.forwardRef(
|
|
|
2536
2549
|
const position = gridToWorld2(
|
|
2537
2550
|
feature.x,
|
|
2538
2551
|
feature.z ?? feature.y ?? 0,
|
|
2539
|
-
(feature.elevation ?? 0) +
|
|
2552
|
+
(feature.elevation ?? 0) + TILE_TOP_OFFSET
|
|
2540
2553
|
);
|
|
2541
2554
|
const key = feature.id ?? `feature-${index}`;
|
|
2542
2555
|
if (CustomFeatureRenderer) {
|
|
@@ -2557,7 +2570,7 @@ var GameCanvas3D = React8.forwardRef(
|
|
|
2557
2570
|
const position = gridToWorld2(
|
|
2558
2571
|
unit.x ?? unit.position?.x ?? 0,
|
|
2559
2572
|
unit.z ?? unit.y ?? unit.position?.y ?? 0,
|
|
2560
|
-
(unit.elevation ?? 0) +
|
|
2573
|
+
(unit.elevation ?? 0) + TILE_TOP_OFFSET
|
|
2561
2574
|
);
|
|
2562
2575
|
return /* @__PURE__ */ jsxRuntime.jsx(LerpedGroup, { target: position, enabled: interpolateUnits, children: CustomUnitRenderer ? /* @__PURE__ */ jsxRuntime.jsx(CustomUnitRenderer, { unit, position: [0, 0, 0] }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2563
2576
|
DefaultUnit,
|
|
@@ -2590,7 +2603,7 @@ var GameCanvas3D = React8.forwardRef(
|
|
|
2590
2603
|
drei.OrbitControls,
|
|
2591
2604
|
{
|
|
2592
2605
|
ref: controlsRef,
|
|
2593
|
-
enabled: cameraMode !== "follow",
|
|
2606
|
+
enabled: cameraMode !== "follow" && cameraMode !== "chase",
|
|
2594
2607
|
target: cameraTarget,
|
|
2595
2608
|
enableDamping: true,
|
|
2596
2609
|
dampingFactor: 0.05,
|
|
@@ -1851,6 +1851,7 @@ function SideScene({
|
|
|
1851
1851
|
var UNIT_BASE_MODEL_SCALE = 0.5;
|
|
1852
1852
|
var UNIT_BASE_BILLBOARD_HEIGHT = 1.2;
|
|
1853
1853
|
var UNIT_BASE_PRIMITIVE_RADIUS = 0.3;
|
|
1854
|
+
var TILE_TOP_OFFSET = 0.1;
|
|
1854
1855
|
var EVENT_COLORS = {
|
|
1855
1856
|
hit: "#ff5544",
|
|
1856
1857
|
damage: "#ff5544",
|
|
@@ -1977,12 +1978,14 @@ function DefaultUnit({
|
|
|
1977
1978
|
}
|
|
1978
1979
|
) })
|
|
1979
1980
|
) : model?.url ? (
|
|
1980
|
-
/* GLB unit model — LOLO's `animation` field drives the named clip
|
|
1981
|
+
/* GLB unit model — LOLO's `animation` field drives the named clip;
|
|
1982
|
+
`heading` (radians) faces the model along its travel direction (driving). */
|
|
1981
1983
|
/* @__PURE__ */ jsx(
|
|
1982
1984
|
ModelLoader,
|
|
1983
1985
|
{
|
|
1984
1986
|
url: model.url,
|
|
1985
1987
|
scale: modelScale,
|
|
1988
|
+
rotation: [0, unit.heading ?? 0, 0],
|
|
1986
1989
|
animation: unit.animation ?? "idle",
|
|
1987
1990
|
fallbackGeometry: "box",
|
|
1988
1991
|
castShadow: true
|
|
@@ -2337,7 +2340,7 @@ var GameCanvas3D = forwardRef(
|
|
|
2337
2340
|
};
|
|
2338
2341
|
case "top-down":
|
|
2339
2342
|
return {
|
|
2340
|
-
position: [cx, d * 2, cz],
|
|
2343
|
+
position: [cx, d * 2, cz + d * 0.35],
|
|
2341
2344
|
fov: 45
|
|
2342
2345
|
};
|
|
2343
2346
|
case "follow":
|
|
@@ -2373,6 +2376,16 @@ var GameCanvas3D = forwardRef(
|
|
|
2373
2376
|
}
|
|
2374
2377
|
return cameraTarget;
|
|
2375
2378
|
}, [player, pixelsPerUnit, worldHeight, units, selectedUnitId, gridBounds, gridConfig, cameraTarget]);
|
|
2379
|
+
const followOffset = useMemo(() => {
|
|
2380
|
+
if (cameraMode === "chase") {
|
|
2381
|
+
const selected = units.find((u) => u.id === selectedUnitId);
|
|
2382
|
+
const h = selected?.heading ?? 0;
|
|
2383
|
+
const dist = 7;
|
|
2384
|
+
const height = 4;
|
|
2385
|
+
return [-Math.sin(h) * dist, height, -Math.cos(h) * dist];
|
|
2386
|
+
}
|
|
2387
|
+
return player ? [0, 2, 9] : [5, 7, 5];
|
|
2388
|
+
}, [cameraMode, units, selectedUnitId, player]);
|
|
2376
2389
|
if (externalLoading || assetsLoading && preloadAssets.length > 0) {
|
|
2377
2390
|
return /* @__PURE__ */ jsx(
|
|
2378
2391
|
Canvas3DLoadingState,
|
|
@@ -2425,11 +2438,11 @@ var GameCanvas3D = forwardRef(
|
|
|
2425
2438
|
},
|
|
2426
2439
|
children: [
|
|
2427
2440
|
/* @__PURE__ */ jsx(CameraController, { onCameraChange: eventHandlers.handleCameraChange }),
|
|
2428
|
-
cameraMode === "follow" && /* @__PURE__ */ jsx(
|
|
2441
|
+
(cameraMode === "follow" || cameraMode === "chase") && /* @__PURE__ */ jsx(
|
|
2429
2442
|
FollowCamera,
|
|
2430
2443
|
{
|
|
2431
2444
|
target: followTarget,
|
|
2432
|
-
offset:
|
|
2445
|
+
offset: followOffset
|
|
2433
2446
|
}
|
|
2434
2447
|
),
|
|
2435
2448
|
/* @__PURE__ */ jsx("ambientLight", { intensity: 0.6 }),
|
|
@@ -2515,7 +2528,7 @@ var GameCanvas3D = forwardRef(
|
|
|
2515
2528
|
const position = gridToWorld2(
|
|
2516
2529
|
feature.x,
|
|
2517
2530
|
feature.z ?? feature.y ?? 0,
|
|
2518
|
-
(feature.elevation ?? 0) +
|
|
2531
|
+
(feature.elevation ?? 0) + TILE_TOP_OFFSET
|
|
2519
2532
|
);
|
|
2520
2533
|
const key = feature.id ?? `feature-${index}`;
|
|
2521
2534
|
if (CustomFeatureRenderer) {
|
|
@@ -2536,7 +2549,7 @@ var GameCanvas3D = forwardRef(
|
|
|
2536
2549
|
const position = gridToWorld2(
|
|
2537
2550
|
unit.x ?? unit.position?.x ?? 0,
|
|
2538
2551
|
unit.z ?? unit.y ?? unit.position?.y ?? 0,
|
|
2539
|
-
(unit.elevation ?? 0) +
|
|
2552
|
+
(unit.elevation ?? 0) + TILE_TOP_OFFSET
|
|
2540
2553
|
);
|
|
2541
2554
|
return /* @__PURE__ */ jsx(LerpedGroup, { target: position, enabled: interpolateUnits, children: CustomUnitRenderer ? /* @__PURE__ */ jsx(CustomUnitRenderer, { unit, position: [0, 0, 0] }) : /* @__PURE__ */ jsx(
|
|
2542
2555
|
DefaultUnit,
|
|
@@ -2569,7 +2582,7 @@ var GameCanvas3D = forwardRef(
|
|
|
2569
2582
|
OrbitControls,
|
|
2570
2583
|
{
|
|
2571
2584
|
ref: controlsRef,
|
|
2572
|
-
enabled: cameraMode !== "follow",
|
|
2585
|
+
enabled: cameraMode !== "follow" && cameraMode !== "chase",
|
|
2573
2586
|
target: cameraTarget,
|
|
2574
2587
|
enableDamping: true,
|
|
2575
2588
|
dampingFactor: 0.05,
|
|
@@ -31,8 +31,10 @@ export interface GameEvent {
|
|
|
31
31
|
y?: number;
|
|
32
32
|
message?: string;
|
|
33
33
|
}
|
|
34
|
-
/** Camera mode for 3D view.
|
|
35
|
-
|
|
34
|
+
/** Camera mode for 3D view.
|
|
35
|
+
* - `follow` tracks the side-view player (or the selected unit) from a fixed offset.
|
|
36
|
+
* - `chase` sits behind + above the followed unit relative to its `heading` (driving). */
|
|
37
|
+
export type CameraMode = 'isometric' | 'perspective' | 'top-down' | 'follow' | 'chase';
|
|
36
38
|
/** Per-role model manifest — the 3D analogue of Canvas2D's assetManifest (values are GLB Assets). */
|
|
37
39
|
export interface GameCanvas3DAssetManifest {
|
|
38
40
|
terrains?: Record<string, Asset>;
|
|
@@ -99,6 +99,9 @@ export type IsometricUnit = {
|
|
|
99
99
|
animation?: AnimationName;
|
|
100
100
|
/** Frame index within the animation — driven by the LOLO state machine; renderer defaults to 0 */
|
|
101
101
|
frame?: number;
|
|
102
|
+
/** Y-axis heading in radians (3D) — travel/facing direction. Orients the unit model and
|
|
103
|
+
* drives the `chase` camera (behind + above, looking along heading). Driving boards set this. */
|
|
104
|
+
heading?: number;
|
|
102
105
|
};
|
|
103
106
|
/** A map feature (resource, building, portal, etc.) */
|
|
104
107
|
export type IsometricFeature = {
|
|
@@ -120,6 +123,8 @@ export type IsometricFeature = {
|
|
|
120
123
|
color?: string;
|
|
121
124
|
/** Elevation offset for 3D rendering */
|
|
122
125
|
elevation?: number;
|
|
126
|
+
/** Y-axis rotation in radians (3D) — orients directional features (arches, signs, docks). */
|
|
127
|
+
rotation?: number;
|
|
123
128
|
};
|
|
124
129
|
/** Camera state for pan/zoom */
|
|
125
130
|
export interface CameraState {
|