@ringozz/react-godot 4.7.2-583 → 4.7.2-592
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/README.md +16 -14
- package/package.json +2 -2
- package/src/react-hooks.ts +44 -31
- package/src/react-types.ts +14 -16
package/README.md
CHANGED
|
@@ -123,9 +123,9 @@ Note for physics bodies: `attach="shape"` goes on the shape **resource** element
|
|
|
123
123
|
|
|
124
124
|
Props map directly to Godot node setters and properties. Helper arrays are automatically cast to Godot types:
|
|
125
125
|
|
|
126
|
-
- **Vectors**: `position={[0, 1, 0]}`, `
|
|
127
|
-
- **Colors**: `albedoColor={[1, 0.2, 0.2]}`
|
|
128
|
-
- **Packed arrays**: `Packed*Array`-typed props (e.g. `Gradient.colors`/`offsets`) accept plain JS arrays — elements convert with the array's element type, so `colors={[[0.5, 0, 0, 1], [1, 1, 1, 1]]}` works; `new PackedVector2Array([[1, 2], [3, 4]])` works too.
|
|
126
|
+
- **Vectors / value types**: value types are **named tuples** — flat (`Vector3`, `Color`, `Rect2`, …) are scalar-labeled tuples (`Vector3 = [x, y, z]`, `Color = [r, g, b, a?]`); nested (`AABB`/`Transform2D`/`Basis`/`Transform3D`/`Projection`) are row-labeled tuples of the flat element alias (`Transform3D = [x, y, z, origin]`, each row a `Vector3`), so `position={[0, 1, 0]}`, `transform={[[1, 0, 0], [0, 1, 0], [0, 0, 1], [10, 20, 30]]}`. Getters return the same labeled shape (`node.position` is `[x, y, z]`); direct setters accept them too (`node.position = [1, 2, 3]`). **Wrong-arity arrays fail typecheck** (`position={[1, 2]}` errors) — except `Color`'s trailing `a`, which is optional (`[r, g, b, a?]`, alpha defaults 1; it's the only value type with a partial scalar ctor). The math API lives as named functions per type module — `import * as v3 from '@ringozz/godot/Vector3'`, then `v3.normalized(v)`, `v3.dot(a, b)`; construction uses `fromXxx` factories (`basis.fromAxisAngle(axis, angle)`, `transform3d.fromBasisOrigin(basis, origin)`).
|
|
127
|
+
- **Colors**: `albedoColor={[1, 0.2, 0.2]}` (a `Color` is just `[r, g, b, a?]`).
|
|
128
|
+
- **Packed arrays**: `Packed*Array`-typed props (e.g. `Gradient.colors`/`offsets`) accept plain JS arrays — elements convert with the array's element type, so `colors={[[0.5, 0, 0, 1], [1, 1, 1, 1]]}` works; `new PackedVector2Array([[1, 2], [3, 4]])` works too. The same element-aware conversion applies to **direct setters and method args**: `gradient.colors = [[1, 0, 0, 1], [0, 1, 0, 1]]`, `addPoint(0.5, [1, 0, 0, 1])`, and `PhysicsRayQueryParameters3D.create([1, 2, 3], [4, 5, 6])` all accept tuples.
|
|
129
129
|
- **Array-typed props** (`GodotArray`, e.g. `CodeEdit.lineLengthGuidelines`, `Font.fallbacks`) accept plain JS arrays directly — they become a Godot `Array`.
|
|
130
130
|
- **Sub-properties / Dash props**: Set individual vector/color components directly using dash syntax:
|
|
131
131
|
```tsx
|
|
@@ -176,7 +176,8 @@ Signal handlers are connected **before** value props are applied within a commit
|
|
|
176
176
|
Below is a complete interactive 3D physics scene with custom camera controls and physics bodies:
|
|
177
177
|
|
|
178
178
|
```tsx
|
|
179
|
-
import {
|
|
179
|
+
import { Engine, Key, MouseButton } from '@ringozz/godot';
|
|
180
|
+
import type { Color } from '@ringozz/godot';
|
|
180
181
|
import { BoxMesh } from '@ringozz/godot/BoxMesh';
|
|
181
182
|
import { BoxShape3D } from '@ringozz/godot/BoxShape3D';
|
|
182
183
|
import { Camera3D } from '@ringozz/godot/Camera3D';
|
|
@@ -201,7 +202,7 @@ const tree = Engine.getMainLoop() as SceneTree;
|
|
|
201
202
|
const root = tree.root;
|
|
202
203
|
|
|
203
204
|
function PhysBody({ color, shape, children, ...rest }: {
|
|
204
|
-
color: Color
|
|
205
|
+
color: Color;
|
|
205
206
|
shape: ReactElement;
|
|
206
207
|
} & ComponentProps<typeof RigidBody3D>) {
|
|
207
208
|
return (
|
|
@@ -224,8 +225,8 @@ export function App() {
|
|
|
224
225
|
|
|
225
226
|
useSignal(root.windowInput, (event) => {
|
|
226
227
|
if (event instanceof InputEventMouseMotion && isDragging.current) {
|
|
227
|
-
yaw.current -= event.relative
|
|
228
|
-
pitch.current = Math.max(-1.4, Math.min(1.4, pitch.current + event.relative
|
|
228
|
+
yaw.current -= event.relative[0] * 0.005;
|
|
229
|
+
pitch.current = Math.max(-1.4, Math.min(1.4, pitch.current + event.relative[1] * 0.005));
|
|
229
230
|
} else if (event instanceof InputEventMouseButton) {
|
|
230
231
|
if (event.buttonIndex === MouseButton.MOUSE_BUTTON_LEFT) {
|
|
231
232
|
isDragging.current = event.pressed;
|
|
@@ -241,8 +242,8 @@ export function App() {
|
|
|
241
242
|
const cx = zoom.current * Math.cos(pitch.current) * Math.sin(yaw.current);
|
|
242
243
|
const cy = zoom.current * Math.sin(pitch.current) + 1;
|
|
243
244
|
const cz = zoom.current * Math.cos(pitch.current) * Math.cos(yaw.current);
|
|
244
|
-
camera.position =
|
|
245
|
-
camera.lookAt(
|
|
245
|
+
camera.position = [cx, cy, cz];
|
|
246
|
+
camera.lookAt([0, 1, 0], [0, 1, 0]);
|
|
246
247
|
});
|
|
247
248
|
|
|
248
249
|
return (
|
|
@@ -283,7 +284,7 @@ export function App() {
|
|
|
283
284
|
| `createPortal(children, container)` | Function | Renders children into a target Godot `Node` (e.g. `CanvasLayer` or `SubViewport`). |
|
|
284
285
|
| `useSignal(signal, callback)` | Hook | Connects a callback to a Godot object signal (e.g. `tree.processFrame`, `node.treeEntered`). Disconnects on unmount. |
|
|
285
286
|
| `useMutableCallback(fn)` | Hook | Returns a `RefObject` whose `.current` always points to the latest callback implementation. |
|
|
286
|
-
| `useTween(create, deps)` | Hook | Reactive tween-as-prop-value. Returns `[spring, tweenRef]` — spread `spring` onto a node; `tweenRef.current` is the live native `Tween`. |
|
|
287
|
+
| `useTween<NodeT>(create, deps)` | Hook | Reactive tween-as-prop-value. Returns `[spring, tweenRef]` — spread `spring` onto a node; `tweenRef.current` is the live native `Tween`. The optional node type (`useTween<Node3D>`) types `to`/`from` array goals as that node's value-type tuples. |
|
|
287
288
|
| `ComponentProps<T>` | Type | Utility type to infer valid React JSX props for any Godot node class `T`. |
|
|
288
289
|
|
|
289
290
|
## Animating props with `useTween`
|
|
@@ -291,9 +292,9 @@ export function App() {
|
|
|
291
292
|
`useTween` animates a node's properties declaratively. You describe the target values once per `to` key; spreading the returned `spring` onto a node binds the tween to it. Re-rendering with the same `deps` keeps the same tween (nothing restarts); changing `deps` creates a new `Tween` and animates from the current values.
|
|
292
293
|
|
|
293
294
|
```tsx
|
|
294
|
-
const [spring, tweenRef] = useTween(() => ({
|
|
295
|
-
from: { position: [0, 0, 0] },
|
|
296
|
-
to: { position: [0, 2, 0], globalRotation: [0, 90, 0] },
|
|
295
|
+
const [spring, tweenRef] = useTween<Node3D>(() => ({
|
|
296
|
+
from: { position: [0, 0, 0] },
|
|
297
|
+
to: { position: [0, 2, 0], globalRotation: [0, 90, 0] },
|
|
297
298
|
config: { duration: 0.6, transition: TransitionType.TRANS_QUAD, ease: EaseType.EASE_IN_OUT },
|
|
298
299
|
delay: 0, loops: 1, immediate: false,
|
|
299
300
|
onFinished: () => {},
|
|
@@ -302,7 +303,8 @@ const [spring, tweenRef] = useTween(() => ({
|
|
|
302
303
|
return <Node3D {...spring} />;
|
|
303
304
|
```
|
|
304
305
|
|
|
305
|
-
- **`spring`** holds one function per `to` key (spread them all onto the node; each contributes its own prop's tweener to the shared `Tween`). Array values are converted to the property's value type.
|
|
306
|
+
- **`spring`** holds one function per `to` key (spread them all onto the node; each contributes its own prop's tweener to the shared `Tween`). Array values are converted to the property's value type.
|
|
307
|
+
- **Node-typed goals**: call `useTween<Node3D>(...)` so `to`/`from` are typed against that node's props — `position: [0, 2, 0]` and nested `basis: [[…]]` infer as `Vector3`/`Basis`, with no `as Vector3` casts. A bare `useTween(...)` keeps array goals loose (`number[]`).
|
|
306
308
|
- **`tweenRef.current`** is the **live native Godot `Tween`**, created when the node mounts (null before that): `tweenRef.current?.kill()`, `?.pause()`, `?.play()`, `?.setSpeedScale(n)`, `?.isRunning()`, `?.finished`…
|
|
307
309
|
- **`config`** maps to the Godot Tween API: `duration` (seconds), `transition`/`ease` (`TransitionType`/`EaseType` — the tween's defaults), `easing` (a `[0,1]→[0,1]` function → `setCustomInterpolator`), `speedScale`.
|
|
308
310
|
- **`onStart`** fires when the tween is created/bound (on mount or when `deps` change), before it starts stepping.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ringozz/react-godot",
|
|
3
3
|
"author": "Vladimir Davidovich",
|
|
4
|
-
"version": "4.7.2-
|
|
4
|
+
"version": "4.7.2-592",
|
|
5
5
|
"description": "A React renderer for Godot Engine via @ringozz/godot",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"*.md"
|
|
24
24
|
],
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@ringozz/godot": "^4.7.2-
|
|
26
|
+
"@ringozz/godot": "^4.7.2-592",
|
|
27
27
|
"@types/react-reconciler": "^0.33.0",
|
|
28
28
|
"react-reconciler": "^0.33.0"
|
|
29
29
|
},
|
package/src/react-hooks.ts
CHANGED
|
@@ -6,10 +6,10 @@ import type { Signal } from '@ringozz/godot';
|
|
|
6
6
|
import type { Node } from '@ringozz/godot/Node';
|
|
7
7
|
import type { Object as Instance } from '@ringozz/godot/Object';
|
|
8
8
|
import { PropertyTweener } from '@ringozz/godot/PropertyTweener';
|
|
9
|
-
import type { GodotVar } from '@ringozz/godot/runtime';
|
|
10
9
|
import { Tween, type EaseType, type TransitionType } from '@ringozz/godot/Tween';
|
|
11
10
|
import type React from 'react';
|
|
12
11
|
import { useEffect, useMemo, useRef } from 'react';
|
|
12
|
+
import { toValueType, type GodotVar } from '@ringozz/godot/runtime';
|
|
13
13
|
|
|
14
14
|
// Tween/PropertyTweener are only type-used here; value-reference them so their
|
|
15
15
|
// `_R` class registration isn't tree-shaken (otherwise createTween's wrapper
|
|
@@ -47,7 +47,21 @@ export function useSignal<T extends (...args: any[]) => any>(signal: Signal<T>,
|
|
|
47
47
|
// identity diff drops them, so nothing restarts); changing deps creates a new
|
|
48
48
|
// tween (the previous one is killed).
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
// Value-type goals are named tuples on the JS side — flat ones are `number[]`,
|
|
51
|
+
// nested ones (Transform2D/Basis/Transform3D/Projection/AABB) are `number[][]`.
|
|
52
|
+
//
|
|
53
|
+
// When the caller passes a node instance type (e.g. `useTween<Node3D>`), `to`
|
|
54
|
+
// /`from` are contextually typed against that node's settable props, so each
|
|
55
|
+
// array goal infers as the property's value-type tuple (Vector3, Basis, …). A
|
|
56
|
+
// bare `useTween(...)` call keeps the loose behavior below (`number[]` goals).
|
|
57
|
+
export type TweenValue = number | number[] | number[][] | GodotVar;
|
|
58
|
+
|
|
59
|
+
// Settable props whose value is tween-compatible (numeric, value-type tuples,
|
|
60
|
+
// GodotVar); excludes bool/string/signal/container/method props. Mapping over
|
|
61
|
+
// `keyof N` lets `to` be contextually typed with the node's value-type tuples.
|
|
62
|
+
type TweenableProps<T> = {
|
|
63
|
+
[K in keyof T as T[K] extends TweenValue ? K : never]: T[K];
|
|
64
|
+
};
|
|
51
65
|
|
|
52
66
|
export interface TweenConfig {
|
|
53
67
|
/** Seconds (default 1). */
|
|
@@ -62,11 +76,11 @@ export interface TweenConfig {
|
|
|
62
76
|
speedScale?: number;
|
|
63
77
|
}
|
|
64
78
|
|
|
65
|
-
export interface TweenProps<
|
|
66
|
-
/** Start values, applied on the first build (mount) before animating to `to`. */
|
|
67
|
-
from?: Partial<
|
|
68
|
-
/** Target values — one assigner is returned per key. */
|
|
69
|
-
to:
|
|
79
|
+
export interface TweenProps<P extends Record<string, unknown> = Record<string, TweenValue>> {
|
|
80
|
+
/** Start values, applied on the first build (mount) before animating to `to`. */
|
|
81
|
+
from?: Partial<P>;
|
|
82
|
+
/** Target values — one assigner is returned per key. */
|
|
83
|
+
to: P;
|
|
70
84
|
config?: TweenConfig;
|
|
71
85
|
/** Seconds; per-tweener PropertyTweener.setDelay. */
|
|
72
86
|
delay?: number;
|
|
@@ -79,23 +93,19 @@ export interface TweenProps<T extends Record<string, TweenValue> = Record<string
|
|
|
79
93
|
onFinished?: () => void;
|
|
80
94
|
}
|
|
81
95
|
|
|
82
|
-
export type Spring<
|
|
96
|
+
export type Spring<P extends Record<string, unknown>> = { [K in keyof P]: P[K] };
|
|
83
97
|
|
|
84
|
-
// tweenProperty/set reject JS arrays, so array goals are reconstructed
|
|
85
|
-
// property's value type (
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const Ctor = (node.get(native) as any)?.constructor;
|
|
89
|
-
if (typeof Ctor === 'function') return new Ctor(...value);
|
|
90
|
-
return value;
|
|
91
|
-
}
|
|
98
|
+
// tweenProperty/set reject generic JS arrays, so array goals are reconstructed
|
|
99
|
+
// as the property's value type by `toValueType` (resolves the node's
|
|
100
|
+
// property type and returns a GodotVar carrier the `_C` tween path unwraps;
|
|
101
|
+
// non-array goals pass through unchanged).
|
|
92
102
|
|
|
93
|
-
function makeSpring<
|
|
94
|
-
tweenRef: { current: Tween | null },
|
|
95
|
-
props: TweenProps<
|
|
96
|
-
): Spring<
|
|
97
|
-
const config = props.config ?? {};
|
|
98
|
-
const spring = {} as Spring<
|
|
103
|
+
function makeSpring<P extends Record<string, unknown>>(
|
|
104
|
+
tweenRef: { current: Tween | null },
|
|
105
|
+
props: TweenProps<P>,
|
|
106
|
+
): Spring<P> {
|
|
107
|
+
const config = props.config ?? {};
|
|
108
|
+
const spring = {} as Spring<P>;
|
|
99
109
|
let started = false;
|
|
100
110
|
let applyFrom = false;
|
|
101
111
|
|
|
@@ -128,15 +138,18 @@ function makeSpring<T extends Record<string, TweenValue>>(
|
|
|
128
138
|
if (props.delay !== undefined) tw.setDelay(props.delay);
|
|
129
139
|
if (config.easing) tw.setCustomInterpolator(config.easing);
|
|
130
140
|
};
|
|
131
|
-
spring[key as keyof
|
|
132
|
-
}
|
|
133
|
-
return spring;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export function useTween<
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
141
|
+
spring[key as keyof P] = assigner as unknown as P[keyof P];
|
|
142
|
+
}
|
|
143
|
+
return spring;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function useTween<
|
|
147
|
+
T extends Instance = any,
|
|
148
|
+
P extends Partial<TweenableProps<T>> = Partial<TweenableProps<T>>,
|
|
149
|
+
>(
|
|
150
|
+
create: () => TweenProps<P>,
|
|
151
|
+
deps: unknown[] = [],
|
|
152
|
+
): [Spring<P>, React.RefObject<Tween | null>] {
|
|
140
153
|
const tweenRef = useRef<Tween | null>(null);
|
|
141
154
|
// 'create' is intentionally re-evaluated via `deps`, not listed here.
|
|
142
155
|
/* oxlint-disable-next-line react-hooks/exhaustive-deps */
|
package/src/react-types.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import type React from 'react';
|
|
6
6
|
import type { Object } from '@ringozz/godot/Object';
|
|
7
|
-
import type { Signal,
|
|
7
|
+
import type { Signal, GodotDictionary } from '@ringozz/godot';
|
|
8
8
|
import type { PackedScene } from '@ringozz/godot/PackedScene';
|
|
9
9
|
|
|
10
10
|
// Identity check sensitive to readonly (deferred-conditional trick; the
|
|
@@ -39,29 +39,27 @@ type SettableKey<T, K extends keyof T> =
|
|
|
39
39
|
|
|
40
40
|
// Container props accept plain JS forms: JSX values flow through
|
|
41
41
|
// `Instance.assign` (`napi_to_variant_typed`), which builds packed arrays,
|
|
42
|
-
// Godot `Array`s, and `Dictionary`s from JS arrays/objects.
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
// -> `(E | number[])[]` (instances or value-type tuples);
|
|
48
|
-
// `(E | number[])[]`; `GodotDictionary<K,V>` ->
|
|
42
|
+
// Godot `Array`s, and `Dictionary`s from JS arrays/objects. Every gen heap
|
|
43
|
+
// container implements `Iterable` and has `get(index: number)`; `get` recovers
|
|
44
|
+
// the concrete element type (packed class getters return it even though the
|
|
45
|
+
// class generic defaults to `any`), and `GodotDictionary` routes to
|
|
46
|
+
// `Record<string, V>` first. Scalar-packed -> `number[]`/`string[]`;
|
|
47
|
+
// element-packed -> `(E | number[])[]` (instances or value-type tuples);
|
|
48
|
+
// `GodotArray<E>` -> `(E | number[])[]`; `GodotDictionary<K,V>` ->
|
|
49
|
+
// `Record<string, V>`.
|
|
49
50
|
type ContainerForm<T> =
|
|
50
51
|
T extends GodotDictionary<any, infer V> ? Record<string, V>
|
|
51
52
|
: T extends { get(index: number): infer E } ? (E extends number | string ? E[] : (E | number[])[])
|
|
52
|
-
: T extends Iterable<infer E> ? E[]
|
|
53
53
|
: never;
|
|
54
54
|
|
|
55
55
|
// JSX props are settable instance members, typed as the reconciler's
|
|
56
|
-
// `Instance.assign` accepts them:
|
|
57
|
-
// (getter `Signal<CB>`, setter `CB | null`)
|
|
58
|
-
// containers accept plain JS arrays/objects.
|
|
59
|
-
// (homomorphic) so optional/readonly modifiers survive.
|
|
56
|
+
// `Instance.assign` accepts them: value types are plain tuples (the getter
|
|
57
|
+
// type IS the array form), signals (getter `Signal<CB>`, setter `CB | null`)
|
|
58
|
+
// take the callback directly, and containers accept plain JS arrays/objects.
|
|
59
|
+
// Maps over `keyof T` (homomorphic) so optional/readonly modifiers survive.
|
|
60
60
|
type Properties<T> = {
|
|
61
61
|
[K in keyof T as SettableKey<T, K>]:
|
|
62
|
-
T[K] extends
|
|
63
|
-
: T[K] extends Signal<infer CB> ? CB | null
|
|
64
|
-
: T[K] extends GodotDictionary<any, any> ? T[K] | ContainerForm<T[K]>
|
|
62
|
+
T[K] extends Signal<infer CB> ? CB | null
|
|
65
63
|
: T[K] extends Iterable<unknown> ? T[K] | ContainerForm<T[K]>
|
|
66
64
|
: T[K];
|
|
67
65
|
};
|