react-three-game 0.0.68 → 0.0.70
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/dist/helpers/SoundManager.d.ts +2 -0
- package/dist/helpers/SoundManager.js +6 -0
- package/dist/index.d.ts +10 -7
- package/dist/index.js +8 -4
- package/dist/shared/GameCanvas.js +0 -2
- package/dist/tools/assetviewer/page.d.ts +5 -0
- package/dist/tools/assetviewer/page.js +3 -0
- package/dist/tools/dragdrop/DragDropLoader.d.ts +3 -2
- package/dist/tools/dragdrop/DragDropLoader.js +18 -3
- package/dist/tools/dragdrop/index.d.ts +2 -2
- package/dist/tools/dragdrop/index.js +1 -1
- package/dist/tools/dragdrop/modelLoader.d.ts +10 -0
- package/dist/tools/dragdrop/modelLoader.js +60 -0
- package/dist/tools/prefabeditor/EditorTree.js +6 -30
- package/dist/tools/prefabeditor/EditorTreeMenus.js +3 -3
- package/dist/tools/prefabeditor/EditorUI.js +6 -4
- package/dist/tools/prefabeditor/InstanceProvider.d.ts +2 -0
- package/dist/tools/prefabeditor/InstanceProvider.js +54 -52
- package/dist/tools/prefabeditor/PrefabEditor.d.ts +22 -0
- package/dist/tools/prefabeditor/PrefabEditor.js +68 -27
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +5 -1
- package/dist/tools/prefabeditor/PrefabRoot.js +148 -145
- package/dist/tools/prefabeditor/components/ClickComponent.js +10 -7
- package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +10 -4
- package/dist/tools/prefabeditor/components/ComponentRegistry.js +6 -6
- package/dist/tools/prefabeditor/components/GeometryComponent.js +1 -1
- package/dist/tools/prefabeditor/components/Input.d.ts +16 -0
- package/dist/tools/prefabeditor/components/Input.js +33 -0
- package/dist/tools/prefabeditor/components/MaterialComponent.js +10 -2
- package/dist/tools/prefabeditor/components/ModelComponent.js +35 -43
- package/dist/tools/prefabeditor/components/PhysicsComponent.d.ts +10 -1
- package/dist/tools/prefabeditor/components/PhysicsComponent.js +122 -28
- package/dist/tools/prefabeditor/components/SoundComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/SoundComponent.js +240 -0
- package/dist/tools/prefabeditor/components/SpotLightComponent.js +6 -1
- package/dist/tools/prefabeditor/components/TransformComponent.js +2 -2
- package/dist/tools/prefabeditor/components/index.js +2 -0
- package/dist/tools/prefabeditor/prefabStore.d.ts +1 -0
- package/dist/tools/prefabeditor/prefabStore.js +11 -13
- package/dist/tools/prefabeditor/sceneApi.d.ts +15 -1
- package/dist/tools/prefabeditor/sceneApi.js +77 -32
- package/dist/tools/prefabeditor/styles.d.ts +1 -0
- package/dist/tools/prefabeditor/styles.js +9 -0
- package/dist/tools/prefabeditor/types.d.ts +13 -0
- package/dist/tools/prefabeditor/types.js +28 -1
- package/dist/tools/prefabeditor/useClickValid.d.ts +13 -0
- package/dist/tools/prefabeditor/useClickValid.js +21 -0
- package/dist/tools/prefabeditor/utils.d.ts +2 -0
- package/dist/tools/prefabeditor/utils.js +34 -35
- package/package.json +1 -1
- package/dist/tools/prefabeditor/EditorContext.d.ts +0 -16
- package/dist/tools/prefabeditor/EditorContext.js +0 -9
|
@@ -198,7 +198,7 @@ const MaterialComponent = {
|
|
|
198
198
|
name: 'Material',
|
|
199
199
|
Editor: MaterialComponentEditor,
|
|
200
200
|
View: MaterialComponentView,
|
|
201
|
-
|
|
201
|
+
isWrapper: true,
|
|
202
202
|
defaultProperties: {
|
|
203
203
|
materialType: 'standard',
|
|
204
204
|
color: '#ffffff',
|
|
@@ -208,6 +208,14 @@ const MaterialComponent = {
|
|
|
208
208
|
opacity: 1,
|
|
209
209
|
metalness: 0,
|
|
210
210
|
roughness: 1
|
|
211
|
-
}
|
|
211
|
+
},
|
|
212
|
+
getAssetRefs: (properties) => {
|
|
213
|
+
const refs = [];
|
|
214
|
+
if (properties.texture)
|
|
215
|
+
refs.push({ type: 'texture', path: properties.texture });
|
|
216
|
+
if (properties.normalMapTexture)
|
|
217
|
+
refs.push({ type: 'texture', path: properties.normalMapTexture });
|
|
218
|
+
return refs;
|
|
219
|
+
},
|
|
212
220
|
};
|
|
213
221
|
export default MaterialComponent;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { ModelPicker } from '../../assetviewer/page';
|
|
3
3
|
import { useContext, useMemo } from 'react';
|
|
4
|
-
import { BooleanField, FieldGroup, Label, NumberInput, SelectInput } from './Input';
|
|
5
|
-
import { EditorContext } from '../
|
|
4
|
+
import { BooleanField, FieldGroup, Label, ListEditor, NumberInput, SelectInput } from './Input';
|
|
5
|
+
import { EditorContext } from '../PrefabEditor';
|
|
6
6
|
import { DEFAULT_REPEAT_AXES, getRepeatAxesFromModelProperties, normalizeRepeatAxes } from '../InstanceProvider';
|
|
7
7
|
import { colors } from '../styles';
|
|
8
8
|
const AXIS_OPTIONS = [
|
|
@@ -18,12 +18,10 @@ function quantize(value, step) {
|
|
|
18
18
|
return Math.round(value / step) * step;
|
|
19
19
|
}
|
|
20
20
|
function RepeatAxisEditor({ axes, onChange, positionSnap, }) {
|
|
21
|
-
const addAxis = () => {
|
|
22
|
-
|
|
23
|
-
const nextAxis = AXIS_OPTIONS.find(option => !used.has(option.value));
|
|
24
|
-
if (!nextAxis)
|
|
21
|
+
const addAxis = (axisValue) => {
|
|
22
|
+
if (!axisValue)
|
|
25
23
|
return;
|
|
26
|
-
onChange([...axes, { axis:
|
|
24
|
+
onChange([...axes, { axis: axisValue, count: 1, offset: 1 }]);
|
|
27
25
|
};
|
|
28
26
|
const updateAxis = (index, patch) => {
|
|
29
27
|
const nextAxes = axes.map((axis, axisIndex) => axisIndex === index ? Object.assign(Object.assign({}, axis), patch) : axis);
|
|
@@ -32,41 +30,30 @@ function RepeatAxisEditor({ axes, onChange, positionSnap, }) {
|
|
|
32
30
|
const removeAxis = (index) => {
|
|
33
31
|
onChange(axes.filter((_, axisIndex) => axisIndex !== index));
|
|
34
32
|
};
|
|
35
|
-
const
|
|
36
|
-
return (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
height: 24,
|
|
60
|
-
width: 28,
|
|
61
|
-
borderRadius: 3,
|
|
62
|
-
border: `1px solid ${colors.border}`,
|
|
63
|
-
background: colors.bgInput,
|
|
64
|
-
color: colors.text,
|
|
65
|
-
cursor: 'pointer',
|
|
66
|
-
padding: 0,
|
|
67
|
-
flexShrink: 0,
|
|
68
|
-
}, title: "Remove repeat axis", children: "\u00D7" })) : null] }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 6 }, children: [_jsxs("div", { children: [_jsx(Label, { children: "Count" }), _jsx(NumberInput, { value: axisConfig.count, onChange: (count) => updateAxis(index, { count: Math.max(1, Math.floor(count)) }), step: 1, min: 1, style: { width: '100%', minWidth: 0, boxSizing: 'border-box' } })] }), _jsxs("div", { children: [_jsx(Label, { children: "Offset" }), _jsx(NumberInput, { value: axisConfig.offset, onChange: (offset) => updateAxis(index, { offset: quantize(offset, positionSnap) }), step: positionSnap > 0 ? positionSnap : 0.1, style: { width: '100%', minWidth: 0, boxSizing: 'border-box' } })] })] })] }, `${axisConfig.axis}-${index}`));
|
|
69
|
-
})] }));
|
|
33
|
+
const availableAxisOptions = AXIS_OPTIONS.filter(option => !axes.some(axis => axis.axis === option.value));
|
|
34
|
+
return (_jsx(ListEditor, { label: "Repeat Axes", items: axes, onAdd: addAxis, addOptions: availableAxisOptions, canAdd: availableAxisOptions.length > 0, emptyMessage: "No repeat axes added.", addButtonTitle: "Add repeat axis", addDisabledTitle: "All axes already in use", renderItem: (axisConfig, index) => {
|
|
35
|
+
const usedByOthers = new Set(axes.filter((_, axisIndex) => axisIndex !== index).map(axis => axis.axis));
|
|
36
|
+
const axisOptions = AXIS_OPTIONS.filter(option => option.value === axisConfig.axis || !usedByOthers.has(option.value));
|
|
37
|
+
return (_jsxs("div", { style: {
|
|
38
|
+
display: 'flex',
|
|
39
|
+
flexDirection: 'column',
|
|
40
|
+
gap: 6,
|
|
41
|
+
padding: 8,
|
|
42
|
+
border: `1px solid ${colors.border}`,
|
|
43
|
+
borderRadius: 4,
|
|
44
|
+
background: colors.bgSurface,
|
|
45
|
+
}, children: [_jsxs("div", { style: { display: 'flex', gap: 6, alignItems: 'end' }, children: [_jsx("div", { style: { flex: 1, minWidth: 0 }, children: _jsx(SelectInput, { label: "Axis", value: axisConfig.axis, onChange: (axis) => updateAxis(index, { axis: axis }), options: axisOptions }) }), _jsx("button", { type: "button", onClick: () => removeAxis(index), style: {
|
|
46
|
+
height: 24,
|
|
47
|
+
width: 28,
|
|
48
|
+
borderRadius: 3,
|
|
49
|
+
border: `1px solid ${colors.border}`,
|
|
50
|
+
background: colors.bgInput,
|
|
51
|
+
color: colors.text,
|
|
52
|
+
cursor: 'pointer',
|
|
53
|
+
padding: 0,
|
|
54
|
+
flexShrink: 0,
|
|
55
|
+
}, title: "Remove repeat axis", children: "\u00D7" })] }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 6 }, children: [_jsxs("div", { children: [_jsx(Label, { children: "Count" }), _jsx(NumberInput, { value: axisConfig.count, onChange: (count) => updateAxis(index, { count: Math.max(1, Math.floor(count)) }), step: 1, min: 1, style: { width: '100%', minWidth: 0, boxSizing: 'border-box' } })] }), _jsxs("div", { children: [_jsx(Label, { children: "Offset" }), _jsx(NumberInput, { value: axisConfig.offset, onChange: (offset) => updateAxis(index, { offset: quantize(offset, positionSnap) }), step: positionSnap > 0 ? positionSnap : 0.1, style: { width: '100%', minWidth: 0, boxSizing: 'border-box' } })] })] })] }, `${axisConfig.axis}-${index}`));
|
|
56
|
+
} }));
|
|
70
57
|
}
|
|
71
58
|
function ModelComponentEditor({ component, node, onUpdate, basePath = "" }) {
|
|
72
59
|
var _a;
|
|
@@ -107,6 +94,11 @@ const ModelComponent = {
|
|
|
107
94
|
instanced: false,
|
|
108
95
|
repeat: false,
|
|
109
96
|
repeatAxes: DEFAULT_REPEAT_AXES
|
|
110
|
-
}
|
|
97
|
+
},
|
|
98
|
+
getAssetRefs: (properties) => {
|
|
99
|
+
if (properties.filename)
|
|
100
|
+
return [{ type: 'model', path: properties.filename }];
|
|
101
|
+
return [];
|
|
102
|
+
},
|
|
111
103
|
};
|
|
112
104
|
export default ModelComponent;
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import type { RigidBodyOptions } from "@react-three/rapier";
|
|
2
2
|
import { Component } from "./ComponentRegistry";
|
|
3
|
-
|
|
3
|
+
type PhysicsColliderType = NonNullable<RigidBodyOptions['colliders']> | 'capsule';
|
|
4
|
+
export type PhysicsProps = Omit<RigidBodyOptions, 'colliders'> & {
|
|
5
|
+
colliders?: PhysicsColliderType;
|
|
4
6
|
activeCollisionTypes?: 'all' | undefined;
|
|
5
7
|
linearVelocity?: [number, number, number];
|
|
6
8
|
angularVelocity?: [number, number, number];
|
|
9
|
+
capsuleRadius?: number;
|
|
10
|
+
capsuleHalfHeight?: number;
|
|
11
|
+
sensorEnterEventName?: string;
|
|
12
|
+
sensorExitEventName?: string;
|
|
13
|
+
collisionEnterEventName?: string;
|
|
14
|
+
collisionExitEventName?: string;
|
|
7
15
|
};
|
|
16
|
+
export declare function isPhysicsProps(v: any): v is PhysicsProps;
|
|
8
17
|
declare const PhysicsComponent: Component;
|
|
9
18
|
export default PhysicsComponent;
|
|
@@ -9,22 +9,111 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
9
9
|
}
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
|
-
import { RigidBody, useRapier } from "@react-three/rapier";
|
|
12
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
13
|
+
import { CapsuleCollider, RigidBody, useRapier } from "@react-three/rapier";
|
|
14
14
|
import { useRef, useEffect, useCallback } from 'react';
|
|
15
|
-
import { BooleanField, FieldGroup, NumberField, SelectField, Vector3Field } from "./Input";
|
|
15
|
+
import { BooleanField, FieldGroup, ListEditor, NumberField, SelectField, SelectInput, StringInput, Vector3Field } from "./Input";
|
|
16
16
|
import { gameEvents, getEntityIdFromRigidBody } from "../GameEvents";
|
|
17
17
|
import { colors } from "../styles";
|
|
18
|
+
export function isPhysicsProps(v) {
|
|
19
|
+
return (v === null || v === void 0 ? void 0 : v.type) === "fixed" || (v === null || v === void 0 ? void 0 : v.type) === "dynamic" || (v === null || v === void 0 ? void 0 : v.type) === "kinematicPosition" || (v === null || v === void 0 ? void 0 : v.type) === "kinematicVelocity";
|
|
20
|
+
}
|
|
18
21
|
const enabledAxesFallback = [true, true, true];
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
const capsuleRadiusFallback = 0.35;
|
|
23
|
+
const capsuleHalfHeightFallback = 0.45;
|
|
24
|
+
const PHYSICS_EVENT_OPTIONS = [
|
|
25
|
+
{
|
|
26
|
+
key: 'sensorEnterEventName',
|
|
27
|
+
label: 'Sensor Enter',
|
|
28
|
+
defaultName: 'sensor:enter',
|
|
29
|
+
requiresSensor: true,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
key: 'sensorExitEventName',
|
|
33
|
+
label: 'Sensor Exit',
|
|
34
|
+
defaultName: 'sensor:exit',
|
|
35
|
+
requiresSensor: true,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
key: 'collisionEnterEventName',
|
|
39
|
+
label: 'Collision Enter',
|
|
40
|
+
defaultName: 'collision:enter',
|
|
41
|
+
requiresSensor: false,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
key: 'collisionExitEventName',
|
|
45
|
+
label: 'Collision Exit',
|
|
46
|
+
defaultName: 'collision:exit',
|
|
47
|
+
requiresSensor: false,
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
function getPhysicsEventOption(key) {
|
|
51
|
+
return PHYSICS_EVENT_OPTIONS.find(option => option.key === key);
|
|
52
|
+
}
|
|
53
|
+
function getConfiguredPhysicsEvents(values) {
|
|
54
|
+
return PHYSICS_EVENT_OPTIONS.filter(option => typeof values[option.key] === 'string' && values[option.key].trim().length > 0);
|
|
55
|
+
}
|
|
56
|
+
function getAvailablePhysicsEvents(values, currentKey) {
|
|
57
|
+
const configuredKeys = new Set(getConfiguredPhysicsEvents(values).map(option => option.key));
|
|
58
|
+
return PHYSICS_EVENT_OPTIONS
|
|
59
|
+
.filter(option => option.key === currentKey || !configuredKeys.has(option.key))
|
|
60
|
+
.map(option => ({ value: option.key, label: option.label }));
|
|
61
|
+
}
|
|
62
|
+
function PhysicsEventBindingsEditor({ values, onChange, }) {
|
|
63
|
+
const configuredEvents = getConfiguredPhysicsEvents(values);
|
|
64
|
+
const nextEventOptions = getAvailablePhysicsEvents(values);
|
|
65
|
+
const addEvent = (eventKey) => {
|
|
66
|
+
if (!eventKey)
|
|
67
|
+
return;
|
|
68
|
+
const option = getPhysicsEventOption(eventKey);
|
|
69
|
+
if (!option)
|
|
70
|
+
return;
|
|
71
|
+
onChange(Object.assign({ [option.key]: option.defaultName }, (option.requiresSensor ? { sensor: true } : null)));
|
|
72
|
+
};
|
|
73
|
+
const updateEventKey = (currentKey, nextKey) => {
|
|
74
|
+
const nextOption = getPhysicsEventOption(nextKey);
|
|
75
|
+
if (!nextOption)
|
|
76
|
+
return;
|
|
77
|
+
onChange(Object.assign({ [currentKey]: undefined, [nextOption.key]: values[currentKey] || nextOption.defaultName }, (nextOption.requiresSensor ? { sensor: true } : null)));
|
|
78
|
+
};
|
|
79
|
+
const updateEventName = (key, eventName) => {
|
|
80
|
+
onChange({ [key]: eventName });
|
|
81
|
+
};
|
|
82
|
+
const removeEvent = (key) => {
|
|
83
|
+
onChange({ [key]: undefined });
|
|
84
|
+
};
|
|
85
|
+
return (_jsx(ListEditor, { label: "Events", items: configuredEvents, onAdd: addEvent, addOptions: nextEventOptions, canAdd: nextEventOptions.length > 0, emptyMessage: "No physics events configured.", addButtonTitle: "Add physics event", addDisabledTitle: "All physics events already added", renderItem: (option) => {
|
|
86
|
+
var _a;
|
|
87
|
+
return (_jsxs("div", { style: {
|
|
88
|
+
display: 'flex',
|
|
89
|
+
flexDirection: 'column',
|
|
90
|
+
gap: 6,
|
|
91
|
+
padding: 8,
|
|
92
|
+
border: `1px solid ${colors.border}`,
|
|
93
|
+
borderRadius: 4,
|
|
94
|
+
background: colors.bgSurface,
|
|
95
|
+
}, children: [_jsxs("div", { style: { display: 'flex', gap: 6, alignItems: 'end' }, children: [_jsx("div", { style: { flex: 1, minWidth: 0 }, children: _jsx(SelectInput, { label: "Type", value: option.key, onChange: (nextKey) => updateEventKey(option.key, nextKey), options: getAvailablePhysicsEvents(values, option.key) }) }), _jsx("button", { type: "button", onClick: () => removeEvent(option.key), style: {
|
|
96
|
+
height: 24,
|
|
97
|
+
width: 28,
|
|
98
|
+
borderRadius: 3,
|
|
99
|
+
border: `1px solid ${colors.border}`,
|
|
100
|
+
background: colors.bgInput,
|
|
101
|
+
color: colors.text,
|
|
102
|
+
cursor: 'pointer',
|
|
103
|
+
padding: 0,
|
|
104
|
+
flexShrink: 0,
|
|
105
|
+
}, title: "Remove physics event", children: "\u00D7" })] }), _jsx(StringInput, { label: "Event Name", value: (_a = values[option.key]) !== null && _a !== void 0 ? _a : option.defaultName, onChange: (eventName) => updateEventName(option.key, eventName), placeholder: option.defaultName })] }, option.key));
|
|
106
|
+
} }));
|
|
107
|
+
}
|
|
108
|
+
function LockedAxisField({ label, name, values, onChange, }) {
|
|
109
|
+
const enabledAxes = Array.isArray(values[name])
|
|
110
|
+
? values[name]
|
|
22
111
|
: enabledAxesFallback;
|
|
23
112
|
const axisLabels = ['X', 'Y', 'Z'];
|
|
24
113
|
const toggleAxisLock = (index) => {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
onChange({
|
|
114
|
+
const nextEnabledAxes = [...enabledAxes];
|
|
115
|
+
nextEnabledAxes[index] = !nextEnabledAxes[index];
|
|
116
|
+
onChange({ [name]: nextEnabledAxes });
|
|
28
117
|
};
|
|
29
118
|
return (_jsxs("div", { children: [_jsxs("div", { style: {
|
|
30
119
|
display: 'flex',
|
|
@@ -42,7 +131,7 @@ function LockedAxisField({ label, values, onChange, }) {
|
|
|
42
131
|
fontSize: '10px',
|
|
43
132
|
color: colors.textDim,
|
|
44
133
|
}, children: "Active means locked" })] }), _jsx("div", { style: { display: 'flex', gap: 4 }, children: axisLabels.map((axisLabel, index) => {
|
|
45
|
-
const isLocked = !
|
|
134
|
+
const isLocked = !enabledAxes[index];
|
|
46
135
|
return (_jsx("button", { type: "button", onClick: () => toggleAxisLock(index), style: {
|
|
47
136
|
flex: 1,
|
|
48
137
|
backgroundColor: isLocked ? colors.dangerBg : colors.bgInput,
|
|
@@ -67,20 +156,22 @@ function PhysicsComponentEditor({ component, onUpdate }) {
|
|
|
67
156
|
{ value: 'trimesh', label: 'Trimesh (exact)' },
|
|
68
157
|
{ value: 'cuboid', label: 'Cuboid (box)' },
|
|
69
158
|
{ value: 'ball', label: 'Ball (sphere)' },
|
|
70
|
-
|
|
159
|
+
{ value: 'capsule', label: 'Capsule' },
|
|
160
|
+
] }), component.properties.colliders === 'capsule' ? (_jsxs(_Fragment, { children: [_jsx(NumberField, { name: "capsuleRadius", label: "Capsule Radius", values: component.properties, onChange: onUpdate, fallback: capsuleRadiusFallback, min: 0.01, step: 0.01 }), _jsx(NumberField, { name: "capsuleHalfHeight", label: "Capsule Half Height", values: component.properties, onChange: onUpdate, fallback: capsuleHalfHeightFallback, min: 0.01, step: 0.01 })] })) : null, _jsx(NumberField, { name: "mass", label: "Mass", values: component.properties, onChange: onUpdate, fallback: 1, step: 0.1, min: 0 }), _jsx(NumberField, { name: "restitution", label: "Restitution (Bounciness)", values: component.properties, onChange: onUpdate, fallback: 0, min: 0, max: 1, step: 0.1 }), _jsx(NumberField, { name: "friction", label: "Friction", values: component.properties, onChange: onUpdate, fallback: 0.5, min: 0, step: 0.1 }), _jsx(NumberField, { name: "linearDamping", label: "Linear Damping", values: component.properties, onChange: onUpdate, fallback: 0, min: 0, step: 0.1 }), _jsx(NumberField, { name: "angularDamping", label: "Angular Damping", values: component.properties, onChange: onUpdate, fallback: 0, min: 0, step: 0.1 }), _jsx(NumberField, { name: "gravityScale", label: "Gravity Scale", values: component.properties, onChange: onUpdate, fallback: 1, step: 0.1 }), _jsx(Vector3Field, { name: "linearVelocity", label: "Linear Velocity", values: component.properties, onChange: onUpdate, fallback: [0, 0, 0] }), _jsx(Vector3Field, { name: "angularVelocity", label: "Angular Velocity", values: component.properties, onChange: onUpdate, fallback: [0, 0, 0] }), _jsx(LockedAxisField, { label: "Lock Movement", name: "enabledTranslations", values: component.properties, onChange: onUpdate }), _jsx(LockedAxisField, { label: "Lock Rotations", name: "enabledRotations", values: component.properties, onChange: onUpdate }), _jsx(BooleanField, { name: "sensor", label: "Sensor (Trigger Only)", values: component.properties, onChange: onUpdate, fallback: false }), _jsx(PhysicsEventBindingsEditor, { values: component.properties, onChange: onUpdate }), _jsx(SelectField, { name: "activeCollisionTypes", label: "Collision Detection", values: component.properties, onChange: onUpdate, options: [
|
|
71
161
|
{ value: '', label: 'Default (Dynamic only)' },
|
|
72
162
|
{ value: 'all', label: 'All (includes kinematic & fixed)' },
|
|
73
163
|
] })] }));
|
|
74
164
|
}
|
|
75
165
|
function PhysicsComponentView({ properties, children, position, rotation, scale, editMode, nodeId, registerRigidBodyRef }) {
|
|
76
|
-
const { type, colliders, sensor, activeCollisionTypes, linearVelocity = [0, 0, 0], angularVelocity = [0, 0, 0], enabledTranslations = enabledAxesFallback } = properties, otherProps = __rest(properties, ["type", "colliders", "sensor", "activeCollisionTypes", "linearVelocity", "angularVelocity", "enabledTranslations"]);
|
|
166
|
+
const { type, colliders, sensor, activeCollisionTypes, linearVelocity = [0, 0, 0], angularVelocity = [0, 0, 0], capsuleRadius = capsuleRadiusFallback, capsuleHalfHeight = capsuleHalfHeightFallback, sensorEnterEventName, sensorExitEventName, collisionEnterEventName, collisionExitEventName, enabledTranslations = enabledAxesFallback, enabledRotations = enabledAxesFallback } = properties, otherProps = __rest(properties, ["type", "colliders", "sensor", "activeCollisionTypes", "linearVelocity", "angularVelocity", "capsuleRadius", "capsuleHalfHeight", "sensorEnterEventName", "sensorExitEventName", "collisionEnterEventName", "collisionExitEventName", "enabledTranslations", "enabledRotations"]);
|
|
77
167
|
const colliderType = colliders || (type === 'fixed' ? 'trimesh' : 'hull');
|
|
168
|
+
const usesManualCapsuleCollider = colliderType === 'capsule';
|
|
78
169
|
const rigidBodyRef = useRef(null);
|
|
79
170
|
const linearVelocityKey = linearVelocity.join(',');
|
|
80
171
|
const angularVelocityKey = angularVelocity.join(',');
|
|
81
172
|
const rbKey = editMode
|
|
82
|
-
? `${type || 'dynamic'}_${colliderType}_${position === null || position === void 0 ? void 0 : position.join(',')}_${rotation === null || rotation === void 0 ? void 0 : rotation.join(',')}`
|
|
83
|
-
: `${type || 'dynamic'}_${colliderType}`;
|
|
173
|
+
? `${type || 'dynamic'}_${colliderType}_${capsuleRadius}_${capsuleHalfHeight}_${position === null || position === void 0 ? void 0 : position.join(',')}_${rotation === null || rotation === void 0 ? void 0 : rotation.join(',')}`
|
|
174
|
+
: `${type || 'dynamic'}_${colliderType}_${capsuleRadius}_${capsuleHalfHeight}`;
|
|
84
175
|
// Try to get rapier context - will be null if not inside <Physics>
|
|
85
176
|
let rapier = null;
|
|
86
177
|
try {
|
|
@@ -135,54 +226,57 @@ function PhysicsComponentView({ properties, children, position, rotation, scale,
|
|
|
135
226
|
}, [rbKey, angularVelocityKey]);
|
|
136
227
|
// Event handlers for physics interactions
|
|
137
228
|
const handleIntersectionEnter = useCallback((payload) => {
|
|
138
|
-
if (!nodeId)
|
|
229
|
+
if (!nodeId || !sensorEnterEventName)
|
|
139
230
|
return;
|
|
140
|
-
gameEvents.emit(
|
|
231
|
+
gameEvents.emit(sensorEnterEventName, {
|
|
141
232
|
sourceEntityId: nodeId,
|
|
142
233
|
targetEntityId: getEntityIdFromRigidBody(payload.other.rigidBody),
|
|
143
234
|
targetRigidBody: payload.other.rigidBody,
|
|
144
235
|
});
|
|
145
|
-
}, [nodeId]);
|
|
236
|
+
}, [nodeId, sensorEnterEventName]);
|
|
146
237
|
const handleIntersectionExit = useCallback((payload) => {
|
|
147
|
-
if (!nodeId)
|
|
238
|
+
if (!nodeId || !sensorExitEventName)
|
|
148
239
|
return;
|
|
149
|
-
gameEvents.emit(
|
|
240
|
+
gameEvents.emit(sensorExitEventName, {
|
|
150
241
|
sourceEntityId: nodeId,
|
|
151
242
|
targetEntityId: getEntityIdFromRigidBody(payload.other.rigidBody),
|
|
152
243
|
targetRigidBody: payload.other.rigidBody,
|
|
153
244
|
});
|
|
154
|
-
}, [nodeId]);
|
|
245
|
+
}, [nodeId, sensorExitEventName]);
|
|
155
246
|
const handleCollisionEnter = useCallback((payload) => {
|
|
156
|
-
if (!nodeId)
|
|
247
|
+
if (!nodeId || !collisionEnterEventName)
|
|
157
248
|
return;
|
|
158
|
-
gameEvents.emit(
|
|
249
|
+
gameEvents.emit(collisionEnterEventName, {
|
|
159
250
|
sourceEntityId: nodeId,
|
|
160
251
|
targetEntityId: getEntityIdFromRigidBody(payload.other.rigidBody),
|
|
161
252
|
targetRigidBody: payload.other.rigidBody,
|
|
162
253
|
});
|
|
163
|
-
}, [nodeId]);
|
|
254
|
+
}, [collisionEnterEventName, nodeId]);
|
|
164
255
|
const handleCollisionExit = useCallback((payload) => {
|
|
165
|
-
if (!nodeId)
|
|
256
|
+
if (!nodeId || !collisionExitEventName)
|
|
166
257
|
return;
|
|
167
|
-
gameEvents.emit(
|
|
258
|
+
gameEvents.emit(collisionExitEventName, {
|
|
168
259
|
sourceEntityId: nodeId,
|
|
169
260
|
targetEntityId: getEntityIdFromRigidBody(payload.other.rigidBody),
|
|
170
261
|
targetRigidBody: payload.other.rigidBody,
|
|
171
262
|
});
|
|
172
|
-
}, [nodeId]);
|
|
173
|
-
return (
|
|
263
|
+
}, [collisionExitEventName, nodeId]);
|
|
264
|
+
return (_jsxs(RigidBody, Object.assign({ ref: rigidBodyRef, type: type, colliders: usesManualCapsuleCollider ? false : colliderType, position: position, rotation: rotation, scale: scale, sensor: sensor, enabledTranslations: enabledTranslations, enabledRotations: enabledRotations, userData: { entityId: nodeId }, onIntersectionEnter: handleIntersectionEnter, onIntersectionExit: handleIntersectionExit, onCollisionEnter: handleCollisionEnter, onCollisionExit: handleCollisionExit }, otherProps, { children: [usesManualCapsuleCollider ? _jsx(CapsuleCollider, { args: [capsuleHalfHeight, capsuleRadius], sensor: sensor }) : null, children] }), rbKey));
|
|
174
265
|
}
|
|
175
266
|
const PhysicsComponent = {
|
|
176
267
|
name: 'Physics',
|
|
177
268
|
Editor: PhysicsComponentEditor,
|
|
178
269
|
View: PhysicsComponentView,
|
|
179
|
-
|
|
270
|
+
isWrapper: true,
|
|
180
271
|
defaultProperties: {
|
|
181
272
|
type: 'dynamic',
|
|
182
273
|
colliders: 'hull',
|
|
274
|
+
capsuleRadius: capsuleRadiusFallback,
|
|
275
|
+
capsuleHalfHeight: capsuleHalfHeightFallback,
|
|
183
276
|
linearVelocity: [0, 0, 0],
|
|
184
277
|
angularVelocity: [0, 0, 0],
|
|
185
278
|
enabledTranslations: [true, true, true],
|
|
279
|
+
enabledRotations: [true, true, true],
|
|
186
280
|
}
|
|
187
281
|
};
|
|
188
282
|
export default PhysicsComponent;
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef } from 'react';
|
|
3
|
+
import { useThree } from '@react-three/fiber';
|
|
4
|
+
import { SoundPicker } from '../../assetviewer/page';
|
|
5
|
+
import { sound as soundManager } from '../../../helpers/SoundManager';
|
|
6
|
+
import { gameEvents } from '../GameEvents';
|
|
7
|
+
import { BooleanField, FieldGroup, FieldRenderer, ListEditor, NumberField, SelectField, StringField } from './Input';
|
|
8
|
+
import { colors } from '../styles';
|
|
9
|
+
import { AudioListener } from 'three';
|
|
10
|
+
const CLIP_MODE_OPTIONS = [
|
|
11
|
+
{ value: 'single', label: 'Single Clip' },
|
|
12
|
+
{ value: 'random', label: 'Random Clip' },
|
|
13
|
+
{ value: 'sequence', label: 'Sequence' },
|
|
14
|
+
];
|
|
15
|
+
let sharedAudioListener = null;
|
|
16
|
+
function getSharedAudioListener() {
|
|
17
|
+
if (!sharedAudioListener) {
|
|
18
|
+
sharedAudioListener = new AudioListener();
|
|
19
|
+
}
|
|
20
|
+
return sharedAudioListener;
|
|
21
|
+
}
|
|
22
|
+
function normalizeClips(clips) {
|
|
23
|
+
return (clips !== null && clips !== void 0 ? clips : []).map(clip => clip.trim()).filter(Boolean);
|
|
24
|
+
}
|
|
25
|
+
function clampRange(min, max, fallbackMin, fallbackMax) {
|
|
26
|
+
const safeMin = Number.isFinite(min) ? Number(min) : fallbackMin;
|
|
27
|
+
const safeMax = Number.isFinite(max) ? Number(max) : fallbackMax;
|
|
28
|
+
return safeMin <= safeMax ? [safeMin, safeMax] : [safeMax, safeMin];
|
|
29
|
+
}
|
|
30
|
+
function sampleRange(min, max) {
|
|
31
|
+
return min + Math.random() * (max - min);
|
|
32
|
+
}
|
|
33
|
+
function getPitchValue(properties) {
|
|
34
|
+
if (properties.randomizePitch) {
|
|
35
|
+
const [pitchFloor, pitchCeiling] = clampRange(properties.minPitch, properties.maxPitch, 0.96, 1.04);
|
|
36
|
+
return sampleRange(pitchFloor, pitchCeiling);
|
|
37
|
+
}
|
|
38
|
+
return Number.isFinite(properties.pitch) ? Number(properties.pitch) : 1;
|
|
39
|
+
}
|
|
40
|
+
function getVolumeValue(properties) {
|
|
41
|
+
if (properties.randomizeVolume) {
|
|
42
|
+
const [volumeFloor, volumeCeiling] = clampRange(properties.minVolume, properties.maxVolume, 0.9, 1);
|
|
43
|
+
return sampleRange(volumeFloor, volumeCeiling);
|
|
44
|
+
}
|
|
45
|
+
return Number.isFinite(properties.volume) ? Number(properties.volume) : 1;
|
|
46
|
+
}
|
|
47
|
+
function resolveClipPaths({ path, clips, clipMode }) {
|
|
48
|
+
const normalizedClips = normalizeClips(clips);
|
|
49
|
+
if (normalizedClips.length > 0) {
|
|
50
|
+
return { paths: normalizedClips, mode: clipMode !== null && clipMode !== void 0 ? clipMode : 'random' };
|
|
51
|
+
}
|
|
52
|
+
return path ? { paths: [path], mode: 'single' } : { paths: [], mode: 'single' };
|
|
53
|
+
}
|
|
54
|
+
function pickClip(paths, mode, sequenceIndexRef) {
|
|
55
|
+
if (paths.length <= 1 || mode === 'single') {
|
|
56
|
+
return paths[0];
|
|
57
|
+
}
|
|
58
|
+
if (mode === 'sequence') {
|
|
59
|
+
const clip = paths[sequenceIndexRef.current % paths.length];
|
|
60
|
+
sequenceIndexRef.current += 1;
|
|
61
|
+
return clip;
|
|
62
|
+
}
|
|
63
|
+
return paths[Math.floor(Math.random() * paths.length)];
|
|
64
|
+
}
|
|
65
|
+
function SoundComponentEditor({ component, onUpdate, basePath = '' }) {
|
|
66
|
+
const clips = normalizeClips(component.properties.clips);
|
|
67
|
+
const randomizePitch = Boolean(component.properties.randomizePitch);
|
|
68
|
+
const randomizeVolume = Boolean(component.properties.randomizeVolume);
|
|
69
|
+
const positional = Boolean(component.properties.positional);
|
|
70
|
+
const addClip = () => {
|
|
71
|
+
onUpdate({ clips: [...clips, ''] });
|
|
72
|
+
};
|
|
73
|
+
const updateClip = (index, nextPath) => {
|
|
74
|
+
onUpdate({
|
|
75
|
+
clips: clips.map((clip, clipIndex) => clipIndex === index ? nextPath : clip),
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
const removeClip = (index) => {
|
|
79
|
+
onUpdate({ clips: clips.filter((_, clipIndex) => clipIndex !== index) });
|
|
80
|
+
};
|
|
81
|
+
return (_jsxs(FieldGroup, { children: [_jsx(SoundPicker, { value: component.properties.path, onChange: (path) => onUpdate({ path: path !== null && path !== void 0 ? path : '' }), basePath: basePath }), _jsx(StringField, { name: "eventName", label: "Listen Event", values: component.properties, onChange: onUpdate, placeholder: "click" }), _jsx(FieldRenderer, { fields: [
|
|
82
|
+
{
|
|
83
|
+
name: 'clipMode',
|
|
84
|
+
label: 'Clip Mode',
|
|
85
|
+
type: 'select',
|
|
86
|
+
options: CLIP_MODE_OPTIONS.map(option => ({ value: option.value, label: option.label })),
|
|
87
|
+
},
|
|
88
|
+
], values: component.properties, onChange: onUpdate }), _jsx(ListEditor, { label: "Clips", items: clips, onAdd: addClip, emptyMessage: "No clips added.", addButtonTitle: "Add clip", addDisabledTitle: "Add clip", renderItem: (clip, index) => (_jsxs("div", { style: {
|
|
89
|
+
display: 'flex',
|
|
90
|
+
gap: 6,
|
|
91
|
+
alignItems: 'end',
|
|
92
|
+
padding: 8,
|
|
93
|
+
border: `1px solid ${colors.border}`,
|
|
94
|
+
borderRadius: 4,
|
|
95
|
+
background: colors.bgSurface,
|
|
96
|
+
}, children: [_jsx("div", { style: { flex: 1, minWidth: 0 }, children: _jsx(SoundPicker, { value: clip || undefined, onChange: (nextPath) => updateClip(index, nextPath !== null && nextPath !== void 0 ? nextPath : ''), basePath: basePath }) }), _jsx("button", { type: "button", onClick: () => removeClip(index), style: {
|
|
97
|
+
height: 24,
|
|
98
|
+
width: 28,
|
|
99
|
+
borderRadius: 3,
|
|
100
|
+
border: `1px solid ${colors.border}`,
|
|
101
|
+
background: colors.bgInput,
|
|
102
|
+
color: colors.text,
|
|
103
|
+
cursor: 'pointer',
|
|
104
|
+
padding: 0,
|
|
105
|
+
flexShrink: 0,
|
|
106
|
+
}, title: "Remove clip", children: "\u00D7" })] }, `${clip}-${index}`)) }), _jsx(BooleanField, { name: "positional", label: "Positional", values: component.properties, onChange: onUpdate, fallback: false }), positional ? (_jsxs(_Fragment, { children: [_jsx(NumberField, { name: "refDistance", label: "Ref Distance", values: component.properties, onChange: onUpdate, fallback: 1, min: 0.01, step: 0.1 }), _jsx(NumberField, { name: "maxDistance", label: "Max Distance", values: component.properties, onChange: onUpdate, fallback: 24, min: 0.01, step: 0.1 }), _jsx(NumberField, { name: "rolloffFactor", label: "Rolloff", values: component.properties, onChange: onUpdate, fallback: 1, min: 0, step: 0.1 }), _jsx(SelectField, { name: "distanceModel", label: "Distance Model", values: component.properties, onChange: onUpdate, fallback: "inverse", options: [
|
|
107
|
+
{ value: 'inverse', label: 'Inverse' },
|
|
108
|
+
{ value: 'linear', label: 'Linear' },
|
|
109
|
+
{ value: 'exponential', label: 'Exponential' },
|
|
110
|
+
] })] })) : null, _jsx(BooleanField, { name: "randomizePitch", label: "Random Pitch", values: component.properties, onChange: onUpdate, fallback: false }), randomizePitch ? (_jsxs(_Fragment, { children: [_jsx(NumberField, { name: "minPitch", label: "Min Pitch", values: component.properties, onChange: onUpdate, fallback: 0.96, step: 0.01, min: 0.1 }), _jsx(NumberField, { name: "maxPitch", label: "Max Pitch", values: component.properties, onChange: onUpdate, fallback: 1.04, step: 0.01, min: 0.1 })] })) : (_jsx(NumberField, { name: "pitch", label: "Pitch", values: component.properties, onChange: onUpdate, fallback: 1, step: 0.01, min: 0.1 })), _jsx(BooleanField, { name: "randomizeVolume", label: "Random Volume", values: component.properties, onChange: onUpdate, fallback: false }), randomizeVolume ? (_jsxs(_Fragment, { children: [_jsx(NumberField, { name: "minVolume", label: "Min Volume", values: component.properties, onChange: onUpdate, fallback: 0.9, step: 0.01, min: 0 }), _jsx(NumberField, { name: "maxVolume", label: "Max Volume", values: component.properties, onChange: onUpdate, fallback: 1, step: 0.01, min: 0 })] })) : (_jsx(NumberField, { name: "volume", label: "Volume", values: component.properties, onChange: onUpdate, fallback: 1, step: 0.01, min: 0 }))] }));
|
|
111
|
+
}
|
|
112
|
+
function payloadMatchesNode(nodeId, payload) {
|
|
113
|
+
if (!nodeId || !payload || typeof payload !== 'object')
|
|
114
|
+
return true;
|
|
115
|
+
const eventPayload = payload;
|
|
116
|
+
const ids = [eventPayload.sourceEntityId, eventPayload.targetEntityId, eventPayload.instanceEntityId];
|
|
117
|
+
const hasEntityIds = ids.some(id => typeof id === 'string');
|
|
118
|
+
return hasEntityIds ? ids.includes(nodeId) : true;
|
|
119
|
+
}
|
|
120
|
+
function SoundComponentView({ properties, editMode, nodeId, children, loadedSounds }) {
|
|
121
|
+
const { camera } = useThree();
|
|
122
|
+
const { eventName, positional = false, refDistance = 1, maxDistance = 24, rolloffFactor = 1, distanceModel = 'inverse' } = properties;
|
|
123
|
+
const sequenceIndexRef = useRef(0);
|
|
124
|
+
const listenerRef = useRef(null);
|
|
125
|
+
const positionalAudioRef = useRef(null);
|
|
126
|
+
const { paths, mode } = resolveClipPaths(properties);
|
|
127
|
+
if (!listenerRef.current) {
|
|
128
|
+
listenerRef.current = getSharedAudioListener();
|
|
129
|
+
}
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
var _a;
|
|
132
|
+
if (!positional) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const listener = listenerRef.current;
|
|
136
|
+
if (!listener) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (listener.parent !== camera) {
|
|
140
|
+
(_a = listener.parent) === null || _a === void 0 ? void 0 : _a.remove(listener);
|
|
141
|
+
camera.add(listener);
|
|
142
|
+
}
|
|
143
|
+
return () => {
|
|
144
|
+
if (listener.parent === camera) {
|
|
145
|
+
camera.remove(listener);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}, [camera, positional]);
|
|
149
|
+
useEffect(() => {
|
|
150
|
+
const audio = positionalAudioRef.current;
|
|
151
|
+
if (!audio) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
audio.setRefDistance(refDistance);
|
|
155
|
+
audio.setMaxDistance(maxDistance);
|
|
156
|
+
audio.setRolloffFactor(rolloffFactor);
|
|
157
|
+
audio.setDistanceModel(distanceModel);
|
|
158
|
+
}, [distanceModel, maxDistance, refDistance, rolloffFactor]);
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
if (editMode || paths.length === 0 || !eventName) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
return gameEvents.on(eventName, (payload) => {
|
|
164
|
+
if (!payloadMatchesNode(nodeId, payload)) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const clip = pickClip(paths, mode, sequenceIndexRef);
|
|
168
|
+
if (!clip)
|
|
169
|
+
return;
|
|
170
|
+
const pitch = getPitchValue(properties);
|
|
171
|
+
const volume = getVolumeValue(properties);
|
|
172
|
+
if (!positional) {
|
|
173
|
+
const loadedBuffer = loadedSounds === null || loadedSounds === void 0 ? void 0 : loadedSounds[clip];
|
|
174
|
+
if (loadedBuffer && !soundManager.hasBuffer(clip)) {
|
|
175
|
+
soundManager.setBuffer(clip, loadedBuffer);
|
|
176
|
+
}
|
|
177
|
+
if (soundManager.hasBuffer(clip)) {
|
|
178
|
+
soundManager.playSync(clip, { pitch, volume });
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
void soundManager.play(clip, { pitch, volume });
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const audio = positionalAudioRef.current;
|
|
185
|
+
const listener = listenerRef.current;
|
|
186
|
+
const buffer = loadedSounds === null || loadedSounds === void 0 ? void 0 : loadedSounds[clip];
|
|
187
|
+
if (!audio || !listener || !buffer) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
void listener.context.resume();
|
|
191
|
+
if (audio.isPlaying) {
|
|
192
|
+
audio.stop();
|
|
193
|
+
}
|
|
194
|
+
audio.setBuffer(buffer);
|
|
195
|
+
audio.setLoop(false);
|
|
196
|
+
audio.setPlaybackRate(pitch);
|
|
197
|
+
audio.setVolume(volume);
|
|
198
|
+
audio.play();
|
|
199
|
+
});
|
|
200
|
+
}, [editMode, eventName, loadedSounds, mode, nodeId, paths, positional, properties]);
|
|
201
|
+
return (_jsxs(_Fragment, { children: [positional && listenerRef.current ? _jsx("positionalAudio", { ref: positionalAudioRef, args: [listenerRef.current] }) : null, children] }));
|
|
202
|
+
}
|
|
203
|
+
const SoundComponent = {
|
|
204
|
+
name: 'Sound',
|
|
205
|
+
Editor: SoundComponentEditor,
|
|
206
|
+
View: SoundComponentView,
|
|
207
|
+
defaultProperties: {
|
|
208
|
+
path: '',
|
|
209
|
+
eventName: '',
|
|
210
|
+
clips: [],
|
|
211
|
+
clipMode: 'single',
|
|
212
|
+
positional: false,
|
|
213
|
+
refDistance: 1,
|
|
214
|
+
maxDistance: 24,
|
|
215
|
+
rolloffFactor: 1,
|
|
216
|
+
distanceModel: 'inverse',
|
|
217
|
+
pitch: 1,
|
|
218
|
+
randomizePitch: false,
|
|
219
|
+
minPitch: 0.96,
|
|
220
|
+
maxPitch: 1.04,
|
|
221
|
+
volume: 1,
|
|
222
|
+
randomizeVolume: false,
|
|
223
|
+
minVolume: 0.9,
|
|
224
|
+
maxVolume: 1,
|
|
225
|
+
},
|
|
226
|
+
getAssetRefs: (properties) => {
|
|
227
|
+
const refs = [];
|
|
228
|
+
if (properties.path)
|
|
229
|
+
refs.push({ type: 'sound', path: properties.path });
|
|
230
|
+
if (Array.isArray(properties.clips)) {
|
|
231
|
+
properties.clips.forEach((clip) => {
|
|
232
|
+
if (typeof clip === 'string' && clip.trim().length > 0) {
|
|
233
|
+
refs.push({ type: 'sound', path: clip });
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
return refs;
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
export default SoundComponent;
|