@react-three/viverse 0.2.0 → 0.2.2
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 +2 -1
- package/dist/animation.d.ts +21 -3
- package/dist/animation.js +33 -7
- package/dist/bone.d.ts +4 -0
- package/dist/bone.js +4 -0
- package/dist/gamepad.d.ts +1 -2
- package/dist/gamepad.js +23 -34
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/simple.js +17 -16
- package/dist/utils.d.ts +43 -6
- package/dist/utils.js +183 -32
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -61,9 +61,10 @@ Get started with **[building a simple game](https://pmndrs.github.io/viverse/tut
|
|
|
61
61
|
- [Accessing avatar and profile](https://pmndrs.github.io/viverse/tutorials/access-avatar-and-profile)
|
|
62
62
|
- [Equipping the character with items](https://pmndrs.github.io/viverse/tutorials/equipping-items)
|
|
63
63
|
- [Using custom animations and models](https://pmndrs.github.io/viverse/tutorials/custom-models-and-animations)
|
|
64
|
+
- [Actions](https://pmndrs.github.io/viverse/tutorials/actions)
|
|
65
|
+
- [Custom Character Controller](https://pmndrs.github.io/viverse/tutorials/custom-character-controller)
|
|
64
66
|
- [How to remove the viverse integrations](https://pmndrs.github.io/viverse/tutorials/remove-viverse-integrations)
|
|
65
67
|
- [Publish to VIVERSE](https://pmndrs.github.io/viverse/tutorials/publish-to-viverse)
|
|
66
|
-
- Building your own character controller - _Coming Soon_
|
|
67
68
|
|
|
68
69
|
## Not into react?
|
|
69
70
|
|
package/dist/animation.d.ts
CHANGED
|
@@ -1,9 +1,27 @@
|
|
|
1
1
|
import { CharacterAnimationOptions, StartAnimationOptions } from '@pmndrs/viverse';
|
|
2
2
|
import { RootState } from '@react-three/fiber';
|
|
3
3
|
import { ActionParams } from '@react-three/timeline';
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import { ReactNode } from 'react';
|
|
5
|
+
import { AnimationAction, AnimationActionLoopStyles, AnimationClip } from 'three';
|
|
6
|
+
export type AdditiveCharacterAnimationActionProps = Omit<CharacterAnimationActionProps, 'additiveToClip'> & {
|
|
7
|
+
referenceClip: CharacterAnimationOptions;
|
|
8
|
+
};
|
|
9
|
+
export declare const AdditiveCharacterAnimationAction: import("react").ForwardRefExoticComponent<Omit<CharacterAnimationActionProps, "additiveToClip"> & {
|
|
10
|
+
referenceClip: CharacterAnimationOptions;
|
|
11
|
+
} & import("react").RefAttributes<AnimationAction>>;
|
|
12
|
+
export type CharacterAnimationActionProps = {
|
|
6
13
|
dependencies?: Array<unknown>;
|
|
7
14
|
until?: () => Promise<unknown>;
|
|
8
15
|
loop?: AnimationActionLoopStyles;
|
|
9
|
-
|
|
16
|
+
additiveReferenceClip?: AnimationClip;
|
|
17
|
+
} & Omit<ActionParams<RootState>, 'until'> & CharacterAnimationOptions & StartAnimationOptions;
|
|
18
|
+
export declare function CharacterAnimationLayer({ name, children }: {
|
|
19
|
+
name: string;
|
|
20
|
+
children?: ReactNode;
|
|
21
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
export declare const CharacterAnimationAction: import("react").ForwardRefExoticComponent<{
|
|
23
|
+
dependencies?: Array<unknown>;
|
|
24
|
+
until?: () => Promise<unknown>;
|
|
25
|
+
loop?: AnimationActionLoopStyles;
|
|
26
|
+
additiveReferenceClip?: AnimationClip;
|
|
27
|
+
} & Omit<ActionParams<RootState>, "until"> & CharacterAnimationOptions & StartAnimationOptions & import("react").RefAttributes<AnimationAction>>;
|
package/dist/animation.js
CHANGED
|
@@ -1,18 +1,44 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { startAnimation } from '@pmndrs/viverse';
|
|
3
3
|
import { Action, animationFinished } from '@react-three/timeline';
|
|
4
|
-
import { useMemo } from 'react';
|
|
4
|
+
import { createContext, forwardRef, useContext, useImperativeHandle, useMemo } from 'react';
|
|
5
5
|
import { LoopRepeat } from 'three';
|
|
6
|
+
import { makeClipAdditive } from 'three/src/animation/AnimationUtils.js';
|
|
6
7
|
import { useCharacterModel } from './model.js';
|
|
7
8
|
import { useCharacterAnimationLoader } from './utils.js';
|
|
8
|
-
export
|
|
9
|
+
export const AdditiveCharacterAnimationAction = forwardRef(({ referenceClip: referenceClipOptions, ...props }, ref) => {
|
|
9
10
|
const model = useCharacterModel();
|
|
10
|
-
const
|
|
11
|
+
const referenceClip = useCharacterAnimationLoader(model, { ...props, ...referenceClipOptions });
|
|
12
|
+
return _jsx(CharacterAnimationAction, { ref: ref, additiveReferenceClip: referenceClip, ...props });
|
|
13
|
+
});
|
|
14
|
+
const CharacterAnimationLayerContext = createContext(undefined);
|
|
15
|
+
export function CharacterAnimationLayer({ name, children }) {
|
|
16
|
+
return _jsx(CharacterAnimationLayerContext.Provider, { value: name, children: children });
|
|
17
|
+
}
|
|
18
|
+
export const CharacterAnimationAction = forwardRef(({ additiveReferenceClip, until, init, dependencies, update, fadeDuration, paused, loop, sync, crossFade, layer, ...animationOptions }, ref) => {
|
|
19
|
+
const layerFromContext = useContext(CharacterAnimationLayerContext);
|
|
20
|
+
layer ??= layerFromContext;
|
|
21
|
+
const model = useCharacterModel();
|
|
22
|
+
const srcClip = useCharacterAnimationLoader(model, animationOptions);
|
|
23
|
+
const clip = useMemo(() => additiveReferenceClip == null ? srcClip : makeClipAdditive(srcClip.clone(), undefined, additiveReferenceClip), [srcClip, additiveReferenceClip]);
|
|
11
24
|
const animation = useMemo(() => model.mixer.clipAction(clip), [clip, model]);
|
|
12
25
|
animation.clampWhenFinished = true;
|
|
13
26
|
animation.loop = loop ?? LoopRepeat;
|
|
27
|
+
useImperativeHandle(ref, () => animation, [animation]);
|
|
14
28
|
return (_jsx(Action, { init: () => {
|
|
15
|
-
startAnimation(animation, model.currentAnimations, {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
29
|
+
const cleanupAnimation = startAnimation(animation, model.currentAnimations, {
|
|
30
|
+
layer,
|
|
31
|
+
fadeDuration,
|
|
32
|
+
paused,
|
|
33
|
+
sync,
|
|
34
|
+
crossFade,
|
|
35
|
+
});
|
|
36
|
+
const cleanupInit = init?.();
|
|
37
|
+
return () => {
|
|
38
|
+
cleanupInit?.();
|
|
39
|
+
cleanupAnimation?.();
|
|
40
|
+
};
|
|
41
|
+
}, until: until ?? (() => animationFinished(animation)), update: update, dependencies: dependencies != null
|
|
42
|
+
? [...dependencies, animationOptions.mask, fadeDuration, paused, sync, crossFade, animation, model, layer]
|
|
43
|
+
: undefined }));
|
|
44
|
+
});
|
package/dist/bone.d.ts
CHANGED
|
@@ -4,3 +4,7 @@ export declare function CharacterModelBone({ bone, children }: {
|
|
|
4
4
|
bone: VRMHumanBoneName;
|
|
5
5
|
children?: ReactNode;
|
|
6
6
|
}): import("react/jsx-runtime").JSX.Element | null;
|
|
7
|
+
/**
|
|
8
|
+
* @deprecated use CharacterModelBone instead
|
|
9
|
+
*/
|
|
10
|
+
export declare const VrmCharacterModelBone: typeof CharacterModelBone;
|
package/dist/bone.js
CHANGED
|
@@ -12,3 +12,7 @@ export function CharacterModelBone({ bone, children }) {
|
|
|
12
12
|
}
|
|
13
13
|
return (_jsx(Fragment, { children: createPortal(_jsx("group", { quaternion: model.boneRotationOffset, children: children }), boneObject) }, boneObject.id));
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* @deprecated use CharacterModelBone instead
|
|
17
|
+
*/
|
|
18
|
+
export const VrmCharacterModelBone = CharacterModelBone;
|
package/dist/gamepad.d.ts
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export declare function useXRControllerInput(): Input<{}>;
|
|
1
|
+
export declare function useXRControllerLocomotionActionBindings(): void;
|
package/dist/gamepad.js
CHANGED
|
@@ -1,38 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { JumpAction, MoveBackwardAction, MoveForwardAction, MoveLeftAction, MoveRightAction, } from '@pmndrs/viverse';
|
|
2
|
+
import { useFrame } from '@react-three/fiber';
|
|
2
3
|
import { useXRControllerButtonEvent, useXRInputSourceState } from '@react-three/xr';
|
|
3
|
-
import {
|
|
4
|
-
export function
|
|
4
|
+
import { useEffect, useRef } from 'react';
|
|
5
|
+
export function useXRControllerLocomotionActionBindings() {
|
|
5
6
|
const leftController = useXRInputSourceState('controller', 'left');
|
|
6
|
-
const lastAPressed = useRef(null);
|
|
7
7
|
const rightController = useXRInputSourceState('controller', 'right');
|
|
8
|
-
useXRControllerButtonEvent(rightController, 'a-button', (state) => state === 'pressed' &&
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
return field === MoveLeftField ? Math.max(0, -thumbstickXAxis) : Math.max(0, thumbstickXAxis);
|
|
29
|
-
}
|
|
30
|
-
case LastTimeJumpPressedField:
|
|
31
|
-
return lastAPressed.current;
|
|
32
|
-
case RunField:
|
|
33
|
-
return (leftController?.gamepad?.['xr-standard-trigger']?.state === 'pressed');
|
|
34
|
-
}
|
|
35
|
-
return undefined;
|
|
36
|
-
},
|
|
37
|
-
}), [leftController]);
|
|
8
|
+
useXRControllerButtonEvent(rightController, 'a-button', (state) => state === 'pressed' && JumpAction.emit(undefined));
|
|
9
|
+
const forwardWriterRef = useRef(undefined);
|
|
10
|
+
const backwardWriterRef = useRef(undefined);
|
|
11
|
+
const leftWriterRef = useRef(undefined);
|
|
12
|
+
const rightWriterRef = useRef(undefined);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const abortController = new AbortController();
|
|
15
|
+
forwardWriterRef.current = MoveForwardAction.createWriter(abortController.signal);
|
|
16
|
+
backwardWriterRef.current = MoveBackwardAction.createWriter(abortController.signal);
|
|
17
|
+
leftWriterRef.current = MoveLeftAction.createWriter(abortController.signal);
|
|
18
|
+
rightWriterRef.current = MoveRightAction.createWriter(abortController.signal);
|
|
19
|
+
return () => abortController.abort();
|
|
20
|
+
}, [leftController]);
|
|
21
|
+
useFrame(() => {
|
|
22
|
+
forwardWriterRef.current?.write(-Math.min(0, leftController?.gamepad?.['xr-standard-thumbstick']?.yAxis ?? 0));
|
|
23
|
+
backwardWriterRef.current?.write(Math.max(0, leftController?.gamepad?.['xr-standard-thumbstick']?.yAxis ?? 0));
|
|
24
|
+
leftWriterRef.current?.write(-Math.min(0, leftController?.gamepad?.['xr-standard-thumbstick']?.xAxis ?? 0));
|
|
25
|
+
rightWriterRef.current?.write(Math.max(0, leftController?.gamepad?.['xr-standard-thumbstick']?.xAxis ?? 0));
|
|
26
|
+
});
|
|
38
27
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -69,6 +69,8 @@ export declare function useViversePublicAvatarList(): Awaited<ReturnType<AvatarC
|
|
|
69
69
|
export declare function useViversePublicAvatarByID(id: string): Awaited<ReturnType<AvatarClient['getPublicAvatarByID']>> | undefined;
|
|
70
70
|
export * from './material.js';
|
|
71
71
|
export * as Vanilla from '@pmndrs/viverse';
|
|
72
|
+
export { EventAction, StateAction, BooleanOr, PointerLockRotateZoomActionBindings, KeyboardLocomotionActionBindings, updateSimpleCharacterVelocity, MoveRightAction, MoveLeftAction, MoveForwardAction, MoveBackwardAction, RunAction, JumpAction, ZoomAction, RotateYawAction, RotatePitchAction, shouldJump, lowerBody, upperBody, WalkAnimationUrl, RunAnimationUrl, IdleAnimationUrl, JumpUpAnimationUrl, JumpLoopAnimationUrl, JumpDownAnimationUrl, FirstPersonCharacterCameraBehavior, BvhCharacterPhysics, type BvhCharacterPhysicsOptions, BvhPhysicsWorld, bvhBoneMap, CharacterCameraBehavior, } from '@pmndrs/viverse';
|
|
73
|
+
export type { VRMHumanBoneName } from '@pmndrs/viverse';
|
|
72
74
|
export * from '@viverse/sdk';
|
|
73
75
|
export * from '@viverse/sdk/avatar-client';
|
|
74
76
|
export * from './gamepad.js';
|
package/dist/index.js
CHANGED
|
@@ -154,6 +154,8 @@ export function useViversePublicAvatarByID(id) {
|
|
|
154
154
|
}
|
|
155
155
|
export * from './material.js';
|
|
156
156
|
export * as Vanilla from '@pmndrs/viverse';
|
|
157
|
+
// Direct re-exports from @pmndrs/viverse used in examples and docs
|
|
158
|
+
export { EventAction, StateAction, BooleanOr, PointerLockRotateZoomActionBindings, KeyboardLocomotionActionBindings, updateSimpleCharacterVelocity, MoveRightAction, MoveLeftAction, MoveForwardAction, MoveBackwardAction, RunAction, JumpAction, ZoomAction, RotateYawAction, RotatePitchAction, shouldJump, lowerBody, upperBody, WalkAnimationUrl, RunAnimationUrl, IdleAnimationUrl, JumpUpAnimationUrl, JumpLoopAnimationUrl, JumpDownAnimationUrl, FirstPersonCharacterCameraBehavior, BvhCharacterPhysics, BvhPhysicsWorld, bvhBoneMap, CharacterCameraBehavior, } from '@pmndrs/viverse';
|
|
157
159
|
export * from '@viverse/sdk';
|
|
158
160
|
export * from '@viverse/sdk/avatar-client';
|
|
159
161
|
export * from './gamepad.js';
|
package/dist/simple.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { RunAction, shouldJump, updateSimpleCharacterVelocity, updateSimpleCharacterRotation, } from '@pmndrs/viverse';
|
|
3
|
+
import { IdleAnimationUrl, JumpDownAnimationUrl, JumpForwardAnimationUrl, JumpLoopAnimationUrl, JumpUpAnimationUrl, RunAnimationUrl, WalkAnimationUrl, } from '@pmndrs/viverse/src/animation/default.js';
|
|
3
4
|
import { useFrame } from '@react-three/fiber';
|
|
4
5
|
import { Graph, GrapthState, Parallel, RunTimeline, Switch, SwitchCase, timePassed } from '@react-three/timeline';
|
|
5
6
|
import { forwardRef, Suspense, useImperativeHandle, useRef } from 'react';
|
|
@@ -8,20 +9,20 @@ import { CharacterAnimationAction } from './animation.js';
|
|
|
8
9
|
import { useViverseActiveAvatar } from './index.js';
|
|
9
10
|
import { CharacterModelProvider } from './model.js';
|
|
10
11
|
import { useBvhCharacterPhysics } from './physics.js';
|
|
11
|
-
import { useCharacterCameraBehavior, useCharacterModelLoader,
|
|
12
|
-
export const SimpleCharacter = forwardRef(({ input, inputOptions, cameraBehavior, children, movement, physics: physicsOptions, model, useViverseAvatar, animation, ...props }, ref) => {
|
|
12
|
+
import { useCharacterCameraBehavior, useCharacterModelLoader, useSimpleCharacterActionBindings } from './utils.js';
|
|
13
|
+
export const SimpleCharacter = forwardRef(({ input, actionBindings, inputOptions, actionBindingOptions, cameraBehavior, children, movement, physics: physicsOptions, model, useViverseAvatar, animation, ...props }, ref) => {
|
|
14
|
+
useSimpleCharacterActionBindings(actionBindings ?? input, actionBindingOptions ?? inputOptions);
|
|
13
15
|
const internalRef = useRef(null);
|
|
14
|
-
|
|
15
|
-
useCharacterCameraBehavior(internalRef, inputSystem, cameraBehavior);
|
|
16
|
+
useCharacterCameraBehavior(internalRef, cameraBehavior);
|
|
16
17
|
const physics = useBvhCharacterPhysics(internalRef, physicsOptions);
|
|
17
18
|
const lastJumpTimeRef = useRef(-Infinity);
|
|
18
|
-
useFrame((state) =>
|
|
19
|
+
useFrame((state) => updateSimpleCharacterVelocity(state.camera, physics, movement));
|
|
19
20
|
useFrame(() => {
|
|
20
21
|
if (model != false || movement?.jump === false) {
|
|
21
22
|
return;
|
|
22
23
|
}
|
|
23
24
|
const bufferTime = movement?.jump === true ? undefined : movement?.jump?.bufferTime;
|
|
24
|
-
if (shouldJump(physics,
|
|
25
|
+
if (shouldJump(physics, lastJumpTimeRef.current, bufferTime)) {
|
|
25
26
|
lastJumpTimeRef.current = performance.now() / 1000;
|
|
26
27
|
physics.applyVelocity(
|
|
27
28
|
// eslint-disable-next-line @react-three/no-new-in-loop
|
|
@@ -29,9 +30,9 @@ export const SimpleCharacter = forwardRef(({ input, inputOptions, cameraBehavior
|
|
|
29
30
|
}
|
|
30
31
|
});
|
|
31
32
|
useImperativeHandle(ref, () => internalRef.current, []);
|
|
32
|
-
return (_jsx("group", { ...props, ref: internalRef, children: model == false ? (children) : (_jsx(SimpleCharacterModel, { animation: animation,
|
|
33
|
+
return (_jsx("group", { ...props, ref: internalRef, children: model == false ? (children) : (_jsx(SimpleCharacterModel, { animation: animation, physics: physics, model: model == true ? undefined : model, movement: movement, useViverseAvatar: useViverseAvatar, children: children })) }));
|
|
33
34
|
});
|
|
34
|
-
function SimpleCharacterModel({ children, model: modelOptions, movement, physics, useViverseAvatar = true,
|
|
35
|
+
function SimpleCharacterModel({ children, model: modelOptions, movement, physics, useViverseAvatar = true, animation, }) {
|
|
35
36
|
const avatar = useViverseActiveAvatar();
|
|
36
37
|
const model = useCharacterModelLoader(avatar != null && useViverseAvatar
|
|
37
38
|
? {
|
|
@@ -44,15 +45,15 @@ function SimpleCharacterModel({ children, model: modelOptions, movement, physics
|
|
|
44
45
|
useFrame((state, delta) => updateSimpleCharacterRotation(delta, physics, state.camera, model, animation));
|
|
45
46
|
return (_jsx(_Fragment, { children: _jsxs(CharacterModelProvider, { model: model, children: [_jsx(RunTimeline, { children: _jsxs(Graph, { enterState: "move", children: [_jsx(GrapthState, { name: "move", transitionTo: {
|
|
46
47
|
jumpStart: {
|
|
47
|
-
whenUpdate: () => shouldJump(physics,
|
|
48
|
+
whenUpdate: () => shouldJump(physics, lastJumpTimeRef.current),
|
|
48
49
|
},
|
|
49
50
|
jumpLoop: { whenUpdate: () => !physics.isGrounded },
|
|
50
|
-
}, children: _jsxs(Switch, { children: [_jsx(SwitchCase, { index: 0, condition: () => physics.inputVelocity.lengthSq() === 0, children: _jsx(Suspense, { fallback: null, children: _jsx(CharacterAnimationAction, { ...animation?.idle, fadeDuration: animation?.crossFadeDuration, url:
|
|
51
|
+
}, children: _jsxs(Switch, { children: [_jsx(SwitchCase, { index: 0, condition: () => physics.inputVelocity.lengthSq() === 0, children: _jsx(Suspense, { fallback: null, children: _jsx(CharacterAnimationAction, { ...animation?.idle, fadeDuration: animation?.crossFadeDuration, url: IdleAnimationUrl }) }) }), movement?.run != false && (_jsx(SwitchCase, { index: 1, condition: () => RunAction.get(), children: _jsx(Suspense, { fallback: null, children: _jsx(CharacterAnimationAction, { ...animation?.run, fadeDuration: animation?.crossFadeDuration, scaleTime: 0.8, url: RunAnimationUrl }) }) })), movement?.walk != false && (_jsx(SwitchCase, { index: 2, children: _jsx(Suspense, { fallback: null, children: _jsx(CharacterAnimationAction, { ...animation?.walk, fadeDuration: animation?.crossFadeDuration, scaleTime: 0.5, url: WalkAnimationUrl }) }) })), _jsx(SwitchCase, { index: 3, children: _jsx(Suspense, { fallback: null, children: _jsx(CharacterAnimationAction, { ...animation?.idle, fadeDuration: animation?.crossFadeDuration, url: IdleAnimationUrl }) }) })] }) }), _jsx(GrapthState, { name: "jumpStart", transitionTo: {
|
|
51
52
|
jumpDown: { whenUpdate: () => !physics.isGrounded },
|
|
52
|
-
finally: () => (
|
|
53
|
-
}, children: _jsx(Parallel, { type: "race", children: _jsxs(Suspense, { fallback: null, children: [_jsx(CharacterAnimationAction, { ...animation?.jumpUp, fadeDuration: animation?.crossFadeDuration ?? 0.1, until: () => timePassed(0.2, 'seconds'), update: () => void physics.inputVelocity.multiplyScalar(0.3), paused: true, url:
|
|
53
|
+
finally: () => (RunAction.get() ? 'jumpForward' : 'jumpUp'),
|
|
54
|
+
}, children: _jsx(Parallel, { type: "race", children: _jsxs(Suspense, { fallback: null, children: [_jsx(CharacterAnimationAction, { ...animation?.jumpUp, fadeDuration: animation?.crossFadeDuration ?? 0.1, until: () => timePassed(0.2, 'seconds'), update: () => void physics.inputVelocity.multiplyScalar(0.3), paused: true, url: JumpUpAnimationUrl }), _jsx(CharacterAnimationAction, { ...animation?.jumpForward, fadeDuration: animation?.crossFadeDuration ?? 0.1, paused: true, url: JumpForwardAnimationUrl })] }) }) }), _jsx(GrapthState, { name: "jumpLoop", transitionTo: {
|
|
54
55
|
jumpDown: { whenUpdate: () => physics.isGrounded },
|
|
55
|
-
}, children: _jsx(Suspense, { fallback: null, children: _jsx(CharacterAnimationAction, { ...animation?.jumpLoop, fadeDuration: animation?.crossFadeDuration, url:
|
|
56
|
+
}, children: _jsx(Suspense, { fallback: null, children: _jsx(CharacterAnimationAction, { ...animation?.jumpLoop, fadeDuration: animation?.crossFadeDuration, url: JumpLoopAnimationUrl }) }) }), _jsx(GrapthState, { name: "jumpUp", transitionTo: {
|
|
56
57
|
jumpDown: {
|
|
57
58
|
whenUpdate: (_, _clock, actionTime) => actionTime > 0.3 && physics.isGrounded,
|
|
58
59
|
},
|
|
@@ -60,8 +61,8 @@ function SimpleCharacterModel({ children, model: modelOptions, movement, physics
|
|
|
60
61
|
}, children: _jsx(Suspense, { fallback: null, children: _jsx(CharacterAnimationAction, { ...animation?.jumpUp, fadeDuration: animation?.crossFadeDuration, loop: LoopOnce, init: () => {
|
|
61
62
|
lastJumpTimeRef.current = performance.now() / 1000;
|
|
62
63
|
physics.applyVelocity(new Vector3(0, (typeof movement?.jump === 'object' ? movement?.jump.speed : undefined) ?? 8, 0));
|
|
63
|
-
}, url:
|
|
64
|
+
}, url: JumpUpAnimationUrl }) }) }), _jsx(GrapthState, { name: "jumpForward", transitionTo: { finally: () => (physics.isGrounded ? 'move' : 'jumpLoop') }, children: _jsx(Suspense, { fallback: null, children: _jsx(CharacterAnimationAction, { ...animation?.jumpForward, fadeDuration: animation?.crossFadeDuration, scaleTime: 0.9, init: () => {
|
|
64
65
|
lastJumpTimeRef.current = performance.now() / 1000;
|
|
65
66
|
physics.applyVelocity(new Vector3(0, 8, 0));
|
|
66
|
-
}, loop: LoopOnce, url:
|
|
67
|
+
}, loop: LoopOnce, url: JumpForwardAnimationUrl }) }) }), _jsx(GrapthState, { name: "jumpDown", transitionTo: { finally: 'move' }, children: _jsx(Suspense, { fallback: null, children: _jsx(CharacterAnimationAction, { ...animation?.jumpDown, fadeDuration: animation?.crossFadeDuration, until: () => timePassed(150, 'milliseconds'), loop: LoopOnce, url: JumpDownAnimationUrl }) }) })] }) }), _jsx("primitive", { object: model.scene }), children] }) }));
|
|
67
68
|
}
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,9 +1,46 @@
|
|
|
1
|
-
import { CharacterAnimationOptions, CharacterModelOptions,
|
|
1
|
+
import { CharacterCameraBehavior, CharacterAnimationOptions, CharacterModelOptions, CharacterCameraBehaviorOptions, CharacterModel, SimpleCharacterActionBindingOptions, WriteonlyEventAction, StateAction } from '@pmndrs/viverse';
|
|
2
2
|
import { RefObject } from 'react';
|
|
3
3
|
import { Object3D } from 'three';
|
|
4
|
-
export declare function useCharacterCameraBehavior(model: Object3D | RefObject<Object3D | null>,
|
|
5
|
-
export declare function useCharacterModelLoader(
|
|
4
|
+
export declare function useCharacterCameraBehavior(model: Object3D | RefObject<Object3D | null>, options?: CharacterCameraBehaviorOptions): RefObject<CharacterCameraBehavior | undefined>;
|
|
5
|
+
export declare function useCharacterModelLoader({ useViverseAvatar, ...modelOptions }?: CharacterModelOptions & {
|
|
6
|
+
useViverseAvatar?: boolean;
|
|
7
|
+
}): CharacterModel;
|
|
6
8
|
export declare function useCharacterAnimationLoader(model: CharacterModel, options: CharacterAnimationOptions): import("three").AnimationClip;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* @deprecated use the specific action binding hooks directly
|
|
11
|
+
*/
|
|
12
|
+
export declare function useSimpleCharacterActionBindings(actionBindingsClasses?: ReadonlyArray<{
|
|
13
|
+
new (domElement: HTMLElement, abortSignal: AbortSignal): unknown;
|
|
14
|
+
}>, options?: SimpleCharacterActionBindingOptions): void;
|
|
15
|
+
export declare function useKeyboardActionBinding(action: WriteonlyEventAction<KeyboardEvent> | StateAction<boolean>, options: {
|
|
16
|
+
keys: Array<string>;
|
|
17
|
+
requiresPointerLock?: boolean;
|
|
18
|
+
}): void;
|
|
19
|
+
export declare function usePointerButtonActionBinding(action: WriteonlyEventAction<PointerEvent> | StateAction<boolean>, options: {
|
|
20
|
+
domElement?: HTMLElement | RefObject<HTMLElement | null>;
|
|
21
|
+
buttons?: Array<number>;
|
|
22
|
+
requiresPointerLock?: boolean;
|
|
23
|
+
}): void;
|
|
24
|
+
export declare function usePointerCaptureRotateZoomActionBindings(options?: {
|
|
25
|
+
rotationSpeed?: number;
|
|
26
|
+
zoomSpeed?: number;
|
|
27
|
+
}): void;
|
|
28
|
+
export declare function usePointerLockRotateZoomActionBindings(options?: {
|
|
29
|
+
rotationSpeed?: number;
|
|
30
|
+
zoomSpeed?: number;
|
|
31
|
+
lockOnClick?: boolean;
|
|
32
|
+
}): void;
|
|
33
|
+
export declare function useKeyboardLocomotionActionBindings(options?: {
|
|
34
|
+
moveForwardKeys?: Array<string>;
|
|
35
|
+
moveBackwardKeys?: Array<string>;
|
|
36
|
+
moveLeftKeys?: Array<string>;
|
|
37
|
+
moveRightKeys?: Array<string>;
|
|
38
|
+
runKeys?: Array<string>;
|
|
39
|
+
jumpKeys?: Array<string>;
|
|
40
|
+
requiresPointerLock?: boolean;
|
|
41
|
+
}): void;
|
|
42
|
+
export declare function useScreenButton(image: string): HTMLElement;
|
|
43
|
+
export declare function useScreenJoystickLocomotionActionBindings(options?: {
|
|
44
|
+
runDistancePx?: number;
|
|
45
|
+
deadZonePx?: number;
|
|
46
|
+
}): void;
|
package/dist/utils.js
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import { VRM } from '@pixiv/three-vrm';
|
|
2
|
-
import { CharacterCameraBehavior, flattenCharacterAnimationOptions, flattenCharacterModelOptions,
|
|
2
|
+
import { CharacterCameraBehavior, flattenCharacterAnimationOptions, flattenCharacterModelOptions, loadCharacterModel, loadCharacterAnimation, ScreenJoystickLocomotionActionBindings, ScreenButtonJumpActionBindings, PointerCaptureRotateZoomActionBindings, KeyboardLocomotionActionBindings, applySimpleCharacterActionBindingOptions, KeyboardActionBinding, PointerButtonActionBinding, defaultScreenButtonStyles, PointerLockRotateZoomActionBindings, DefaultMoveBackwardKeys, DefaultMoveForwardKeys, DefaultJumpKeys, DefaultMoveLeftKeys, DefaultMoveRightKeys, DefaultRunKeys, } from '@pmndrs/viverse';
|
|
3
3
|
import { useFrame, useThree } from '@react-three/fiber';
|
|
4
|
-
import { useEffect, useMemo } from 'react';
|
|
4
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
5
5
|
import { suspend } from 'suspend-react';
|
|
6
6
|
import { Object3D } from 'three';
|
|
7
|
+
import { useViverseActiveAvatar } from './index.js';
|
|
7
8
|
import { useBvhPhysicsWorld } from './physics.js';
|
|
8
|
-
export function useCharacterCameraBehavior(model,
|
|
9
|
-
const
|
|
9
|
+
export function useCharacterCameraBehavior(model, options) {
|
|
10
|
+
const behaviorRef = useRef(undefined);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const behavior = new CharacterCameraBehavior();
|
|
13
|
+
behaviorRef.current = behavior;
|
|
14
|
+
return () => {
|
|
15
|
+
behaviorRef.current = undefined;
|
|
16
|
+
behavior.dispose();
|
|
17
|
+
};
|
|
18
|
+
}, []);
|
|
10
19
|
const world = useBvhPhysicsWorld();
|
|
11
20
|
const raycast = useMemo(() => world.raycast.bind(world), [world]);
|
|
12
21
|
useFrame((state, delta) => {
|
|
@@ -14,12 +23,23 @@ export function useCharacterCameraBehavior(model, inputSystem, options) {
|
|
|
14
23
|
if (resolvedModel == null) {
|
|
15
24
|
return;
|
|
16
25
|
}
|
|
17
|
-
|
|
18
|
-
});
|
|
26
|
+
behaviorRef.current?.update(state.camera, resolvedModel, delta, raycast, options);
|
|
27
|
+
}, -1);
|
|
28
|
+
return behaviorRef;
|
|
19
29
|
}
|
|
20
30
|
const loadCharacterModelSymbol = Symbol('loadCharacterModel');
|
|
21
|
-
export function useCharacterModelLoader(
|
|
22
|
-
const
|
|
31
|
+
export function useCharacterModelLoader({ useViverseAvatar = true, ...modelOptions } = {}) {
|
|
32
|
+
const avatar = useViverseActiveAvatar();
|
|
33
|
+
const model = suspend((_, ...params) => loadCharacterModel(...params), [
|
|
34
|
+
loadCharacterModelSymbol,
|
|
35
|
+
...flattenCharacterModelOptions(avatar != null && useViverseAvatar
|
|
36
|
+
? {
|
|
37
|
+
...modelOptions,
|
|
38
|
+
type: 'vrm',
|
|
39
|
+
url: avatar?.vrmUrl,
|
|
40
|
+
}
|
|
41
|
+
: modelOptions),
|
|
42
|
+
]);
|
|
23
43
|
useFrame((_, delta) => {
|
|
24
44
|
if (model instanceof VRM) {
|
|
25
45
|
model.update(delta);
|
|
@@ -32,36 +52,167 @@ const loadCharacterAnimationSymbol = Symbol('loadCharacterAnimation');
|
|
|
32
52
|
export function useCharacterAnimationLoader(model, options) {
|
|
33
53
|
return suspend((_, ...params) => loadCharacterAnimation(...params), [loadCharacterAnimationSymbol, model, ...flattenCharacterAnimationOptions(options)]);
|
|
34
54
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
55
|
+
/**
|
|
56
|
+
* @deprecated use the specific action binding hooks directly
|
|
57
|
+
*/
|
|
58
|
+
export function useSimpleCharacterActionBindings(actionBindingsClasses = [
|
|
59
|
+
ScreenJoystickLocomotionActionBindings,
|
|
60
|
+
ScreenButtonJumpActionBindings,
|
|
61
|
+
PointerCaptureRotateZoomActionBindings,
|
|
62
|
+
KeyboardLocomotionActionBindings,
|
|
63
|
+
], options) {
|
|
41
64
|
const dom = useThree((s) => s.gl.domElement);
|
|
42
|
-
const
|
|
43
|
-
//clearing and copying the options to optionsRef
|
|
44
|
-
for (const key in optionsRef) {
|
|
45
|
-
delete optionsRef[key];
|
|
46
|
-
}
|
|
47
|
-
Object.assign(optionsRef, options);
|
|
65
|
+
const actionBindingsList = useMemo(() => [],
|
|
48
66
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
49
|
-
|
|
67
|
+
[dom]);
|
|
50
68
|
useEffect(() => {
|
|
51
|
-
const
|
|
52
|
-
for (const
|
|
53
|
-
const
|
|
54
|
-
if (
|
|
55
|
-
|
|
69
|
+
const removeActionBindingsSet = new Set(actionBindingsList);
|
|
70
|
+
for (const actionBindingsClass of actionBindingsClasses) {
|
|
71
|
+
const existingActionBindings = actionBindingsList.find((existingActionBindings) => existingActionBindings instanceof actionBindingsClass);
|
|
72
|
+
if (existingActionBindings != null) {
|
|
73
|
+
removeActionBindingsSet.delete(existingActionBindings);
|
|
56
74
|
continue;
|
|
57
75
|
}
|
|
58
|
-
|
|
76
|
+
const abortController = new AbortController();
|
|
77
|
+
actionBindingsList.push({ actionBindings: new actionBindingsClass(dom, abortController.signal), abortController });
|
|
78
|
+
}
|
|
79
|
+
for (const entry of removeActionBindingsSet) {
|
|
80
|
+
entry.abortController.abort();
|
|
81
|
+
const index = actionBindingsList.indexOf(entry);
|
|
82
|
+
if (index != -1) {
|
|
83
|
+
actionBindingsList.splice(index, 1);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
applySimpleCharacterActionBindingOptions(actionBindingsList, options);
|
|
87
|
+
});
|
|
88
|
+
useEffect(() => () => {
|
|
89
|
+
actionBindingsList.forEach(({ abortController }) => abortController.abort());
|
|
90
|
+
actionBindingsList.length = 0;
|
|
91
|
+
}, [actionBindingsList]);
|
|
92
|
+
}
|
|
93
|
+
export function useKeyboardActionBinding(action, options) {
|
|
94
|
+
const ref = useRef(undefined);
|
|
95
|
+
const domElement = useThree((s) => s.gl.domElement);
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
const abortController = new AbortController();
|
|
98
|
+
ref.current = new KeyboardActionBinding(action, domElement, abortController.signal);
|
|
99
|
+
return () => abortController.abort();
|
|
100
|
+
}, [action, domElement]);
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
if (ref.current == null) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
ref.current.keys = options.keys;
|
|
106
|
+
ref.current.requiresPointerLock = options.requiresPointerLock;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
export function usePointerButtonActionBinding(action, options) {
|
|
110
|
+
const ref = useRef(undefined);
|
|
111
|
+
const canvasDomElement = useThree((s) => s.gl.domElement);
|
|
112
|
+
const domElement = options.domElement ?? canvasDomElement;
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
const abortController = new AbortController();
|
|
115
|
+
ref.current = new PointerButtonActionBinding(action, domElement instanceof HTMLElement ? domElement : domElement.current, abortController.signal);
|
|
116
|
+
return () => abortController.abort();
|
|
117
|
+
}, [action, domElement]);
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
if (ref.current == null) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
ref.current.buttons = options.buttons;
|
|
123
|
+
ref.current.requiresPointerLock = options.requiresPointerLock;
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
export function usePointerCaptureRotateZoomActionBindings(options) {
|
|
127
|
+
const ref = useRef(undefined);
|
|
128
|
+
const domElement = useThree((s) => s.gl.domElement);
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
const abortController = new AbortController();
|
|
131
|
+
ref.current = new PointerCaptureRotateZoomActionBindings(domElement, abortController.signal);
|
|
132
|
+
return () => abortController.abort();
|
|
133
|
+
}, [domElement]);
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
if (ref.current == null) {
|
|
136
|
+
return;
|
|
59
137
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
138
|
+
ref.current.rotationSpeed = options?.rotationSpeed;
|
|
139
|
+
ref.current.zoomSpeed = options?.zoomSpeed;
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
export function usePointerLockRotateZoomActionBindings(options) {
|
|
143
|
+
const ref = useRef(undefined);
|
|
144
|
+
const domElement = useThree((s) => s.gl.domElement);
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
const abortController = new AbortController();
|
|
147
|
+
ref.current = new PointerLockRotateZoomActionBindings(domElement, abortController.signal);
|
|
148
|
+
return () => abortController.abort();
|
|
149
|
+
}, [domElement]);
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
if (ref.current == null) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
ref.current.lockOnClick = options?.lockOnClick;
|
|
155
|
+
ref.current.rotationSpeed = options?.rotationSpeed;
|
|
156
|
+
ref.current.zoomSpeed = options?.zoomSpeed;
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
export function useKeyboardLocomotionActionBindings(options) {
|
|
160
|
+
const ref = useRef(undefined);
|
|
161
|
+
const domElement = useThree((s) => s.gl.domElement);
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
const abortController = new AbortController();
|
|
164
|
+
ref.current = new KeyboardLocomotionActionBindings(domElement, abortController.signal);
|
|
165
|
+
return () => abortController.abort();
|
|
166
|
+
}, [domElement]);
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
if (ref.current == null) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
ref.current.moveBackwardBinding.keys = options?.moveBackwardKeys ?? DefaultMoveBackwardKeys;
|
|
172
|
+
ref.current.moveBackwardBinding.requiresPointerLock = options?.requiresPointerLock;
|
|
173
|
+
ref.current.moveForwardBinding.keys = options?.moveForwardKeys ?? DefaultMoveForwardKeys;
|
|
174
|
+
ref.current.moveForwardBinding.requiresPointerLock = options?.requiresPointerLock;
|
|
175
|
+
ref.current.jumpBinding.keys = options?.jumpKeys ?? DefaultJumpKeys;
|
|
176
|
+
ref.current.jumpBinding.requiresPointerLock = options?.requiresPointerLock;
|
|
177
|
+
ref.current.moveLeftBinding.keys = options?.moveLeftKeys ?? DefaultMoveLeftKeys;
|
|
178
|
+
ref.current.moveLeftBinding.requiresPointerLock = options?.requiresPointerLock;
|
|
179
|
+
ref.current.moveRightBinding.keys = options?.moveRightKeys ?? DefaultMoveRightKeys;
|
|
180
|
+
ref.current.moveRightBinding.requiresPointerLock = options?.requiresPointerLock;
|
|
181
|
+
ref.current.runBinding.keys = options?.runKeys ?? DefaultRunKeys;
|
|
182
|
+
ref.current.runBinding.requiresPointerLock = options?.requiresPointerLock;
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
export function useScreenButton(image) {
|
|
186
|
+
const element = useMemo(() => document.createElement('div'), []);
|
|
187
|
+
element.style.backgroundImage = image;
|
|
188
|
+
const domElement = useThree((s) => s.gl.domElement);
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
domElement.className = 'viverse-button viverse-jump mobile-only';
|
|
191
|
+
const parent = domElement.parentNode ?? domElement;
|
|
192
|
+
parent.appendChild(element);
|
|
193
|
+
Object.assign(element.style, defaultScreenButtonStyles);
|
|
194
|
+
const stopPropagation = (e) => e.stopPropagation();
|
|
195
|
+
element.addEventListener('pointerdown', stopPropagation);
|
|
196
|
+
return () => {
|
|
197
|
+
element.remove();
|
|
198
|
+
element.removeEventListener('pointerdown', stopPropagation);
|
|
199
|
+
};
|
|
200
|
+
}, [element, domElement]);
|
|
201
|
+
return element;
|
|
202
|
+
}
|
|
203
|
+
export function useScreenJoystickLocomotionActionBindings(options) {
|
|
204
|
+
const ref = useRef(undefined);
|
|
205
|
+
const domElement = useThree((s) => s.gl.domElement);
|
|
206
|
+
useEffect(() => {
|
|
207
|
+
const abortController = new AbortController();
|
|
208
|
+
ref.current = new ScreenJoystickLocomotionActionBindings(domElement, abortController.signal);
|
|
209
|
+
return () => abortController.abort();
|
|
210
|
+
}, [domElement]);
|
|
211
|
+
useEffect(() => {
|
|
212
|
+
if (ref.current == null) {
|
|
213
|
+
return;
|
|
63
214
|
}
|
|
215
|
+
ref.current.deadZonePx = options?.deadZonePx;
|
|
216
|
+
ref.current.runDistancePx = options?.runDistancePx;
|
|
64
217
|
});
|
|
65
|
-
useEffect(() => () => system.inputs.forEach((input) => input.dispose?.()), [system]);
|
|
66
|
-
return system;
|
|
67
218
|
}
|
package/package.json
CHANGED
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
],
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@viverse/sdk": "^1.2.10-alpha.0",
|
|
27
|
-
"@react-three/timeline": "^0.3.
|
|
27
|
+
"@react-three/timeline": "^0.3.7",
|
|
28
28
|
"suspend-react": "^0.1.3",
|
|
29
29
|
"@pixiv/three-vrm": "^3.4.2",
|
|
30
30
|
"zustand": "^5.0.6",
|
|
31
31
|
"@react-three/xr": "^6.6.20",
|
|
32
|
-
"@pmndrs/viverse": "^0.2.
|
|
32
|
+
"@pmndrs/viverse": "^0.2.2"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"@react-three/fiber": "*"
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"@types/react": "^19.1.8",
|
|
41
41
|
"react": "^19.1.0"
|
|
42
42
|
},
|
|
43
|
-
"version": "0.2.
|
|
43
|
+
"version": "0.2.2",
|
|
44
44
|
"scripts": {
|
|
45
45
|
"build": "tsc",
|
|
46
46
|
"check:prettier": "prettier --check src",
|