@react-three/viverse 0.2.1 → 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 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/gamepad.d.ts CHANGED
@@ -1 +1 @@
1
- export declare function useXRControllerInput(): void;
1
+ export declare function useXRControllerLocomotionActionBindings(): void;
package/dist/gamepad.js CHANGED
@@ -2,10 +2,10 @@ import { JumpAction, MoveBackwardAction, MoveForwardAction, MoveLeftAction, Move
2
2
  import { useFrame } from '@react-three/fiber';
3
3
  import { useXRControllerButtonEvent, useXRInputSourceState } from '@react-three/xr';
4
4
  import { useEffect, useRef } from 'react';
5
- export function useXRControllerInput() {
5
+ export function useXRControllerLocomotionActionBindings() {
6
6
  const leftController = useXRInputSourceState('controller', 'left');
7
7
  const rightController = useXRInputSourceState('controller', 'right');
8
- useXRControllerButtonEvent(rightController, 'a-button', (state) => state === 'pressed' && JumpAction.emit());
8
+ useXRControllerButtonEvent(rightController, 'a-button', (state) => state === 'pressed' && JumpAction.emit(undefined));
9
9
  const forwardWriterRef = useRef(undefined);
10
10
  const backwardWriterRef = useRef(undefined);
11
11
  const leftWriterRef = useRef(undefined);
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,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { RunAction, shouldJump, updateSimpleCharacterInputVelocity, updateSimpleCharacterRotation, } from '@pmndrs/viverse';
2
+ import { RunAction, shouldJump, updateSimpleCharacterVelocity, updateSimpleCharacterRotation, } from '@pmndrs/viverse';
3
3
  import { IdleAnimationUrl, JumpDownAnimationUrl, JumpForwardAnimationUrl, JumpLoopAnimationUrl, JumpUpAnimationUrl, RunAnimationUrl, WalkAnimationUrl, } from '@pmndrs/viverse/src/animation/default.js';
4
4
  import { useFrame } from '@react-three/fiber';
5
5
  import { Graph, GrapthState, Parallel, RunTimeline, Switch, SwitchCase, timePassed } from '@react-three/timeline';
@@ -9,14 +9,14 @@ import { CharacterAnimationAction } from './animation.js';
9
9
  import { useViverseActiveAvatar } from './index.js';
10
10
  import { CharacterModelProvider } from './model.js';
11
11
  import { useBvhCharacterPhysics } from './physics.js';
12
- import { useCharacterCameraBehavior, useCharacterModelLoader, useSimpleCharacterInputs } from './utils.js';
13
- export const SimpleCharacter = forwardRef(({ input, inputOptions, cameraBehavior, children, movement, physics: physicsOptions, model, useViverseAvatar, animation, ...props }, ref) => {
14
- useSimpleCharacterInputs(input, inputOptions);
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);
15
15
  const internalRef = useRef(null);
16
16
  useCharacterCameraBehavior(internalRef, cameraBehavior);
17
17
  const physics = useBvhCharacterPhysics(internalRef, physicsOptions);
18
18
  const lastJumpTimeRef = useRef(-Infinity);
19
- useFrame((state) => updateSimpleCharacterInputVelocity(state.camera, physics, movement));
19
+ useFrame((state) => updateSimpleCharacterVelocity(state.camera, physics, movement));
20
20
  useFrame(() => {
21
21
  if (model != false || movement?.jump === false) {
22
22
  return;
package/dist/utils.d.ts CHANGED
@@ -1,14 +1,46 @@
1
- import { CharacterCameraBehavior, CharacterAnimationOptions, CharacterModelOptions, SimpleCharacterCameraBehaviorOptions, CharacterModel, SimpleCharacterInputOptions } from '@pmndrs/viverse';
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>, options?: SimpleCharacterCameraBehaviorOptions): RefObject<CharacterCameraBehavior | undefined>;
5
- export declare function useCharacterModelLoader(options?: CharacterModelOptions): CharacterModel;
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
9
  /**
8
- * @deprecated use inputs directly
10
+ * @deprecated use the specific action binding hooks directly
9
11
  */
10
- export declare function useSimpleCharacterInputs(inputsClasses?: ReadonlyArray<{
11
- new (domElement: HTMLElement): {
12
- dispose(): void;
13
- };
14
- }>, options?: SimpleCharacterInputOptions): void;
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,9 +1,10 @@
1
1
  import { VRM } from '@pixiv/three-vrm';
2
- import { CharacterCameraBehavior, flattenCharacterAnimationOptions, flattenCharacterModelOptions, loadCharacterModel, loadCharacterAnimation, ScreenJoystickInput, ScreenJumpButtonInput, PointerCaptureInput, LocomotionKeyboardInput, applySimpleCharacterInputOptions, } from '@pmndrs/viverse';
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
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
9
  export function useCharacterCameraBehavior(model, options) {
9
10
  const behaviorRef = useRef(undefined);
@@ -23,12 +24,22 @@ export function useCharacterCameraBehavior(model, options) {
23
24
  return;
24
25
  }
25
26
  behaviorRef.current?.update(state.camera, resolvedModel, delta, raycast, options);
26
- });
27
+ }, -1);
27
28
  return behaviorRef;
28
29
  }
29
30
  const loadCharacterModelSymbol = Symbol('loadCharacterModel');
30
- export function useCharacterModelLoader(options) {
31
- const model = suspend((_, ...params) => loadCharacterModel(...params), [loadCharacterModelSymbol, ...flattenCharacterModelOptions(options)]);
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
+ ]);
32
43
  useFrame((_, delta) => {
33
44
  if (model instanceof VRM) {
34
45
  model.update(delta);
@@ -42,38 +53,166 @@ export function useCharacterAnimationLoader(model, options) {
42
53
  return suspend((_, ...params) => loadCharacterAnimation(...params), [loadCharacterAnimationSymbol, model, ...flattenCharacterAnimationOptions(options)]);
43
54
  }
44
55
  /**
45
- * @deprecated use inputs directly
56
+ * @deprecated use the specific action binding hooks directly
46
57
  */
47
- export function useSimpleCharacterInputs(inputsClasses = [
48
- ScreenJoystickInput,
49
- ScreenJumpButtonInput,
50
- PointerCaptureInput,
51
- LocomotionKeyboardInput,
58
+ export function useSimpleCharacterActionBindings(actionBindingsClasses = [
59
+ ScreenJoystickLocomotionActionBindings,
60
+ ScreenButtonJumpActionBindings,
61
+ PointerCaptureRotateZoomActionBindings,
62
+ KeyboardLocomotionActionBindings,
52
63
  ], options) {
53
64
  const dom = useThree((s) => s.gl.domElement);
65
+ const actionBindingsList = useMemo(() => [],
54
66
  // eslint-disable-next-line react-hooks/exhaustive-deps
55
- const inputs = useMemo(() => [], [dom]);
67
+ [dom]);
56
68
  useEffect(() => {
57
- const removedInputs = new Set(inputs);
58
- for (const inputClass of inputsClasses) {
59
- const existingInput = inputs.find((existingInput) => existingInput instanceof inputClass);
60
- if (existingInput != null) {
61
- removedInputs.delete(existingInput);
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);
62
74
  continue;
63
75
  }
64
- inputs.push(new inputClass(dom));
76
+ const abortController = new AbortController();
77
+ actionBindingsList.push({ actionBindings: new actionBindingsClass(dom, abortController.signal), abortController });
65
78
  }
66
- for (const removedInput of removedInputs) {
67
- removedInput.dispose();
68
- const index = inputs.indexOf(removedInput);
79
+ for (const entry of removeActionBindingsSet) {
80
+ entry.abortController.abort();
81
+ const index = actionBindingsList.indexOf(entry);
69
82
  if (index != -1) {
70
- inputs.splice(index, 1);
83
+ actionBindingsList.splice(index, 1);
71
84
  }
72
85
  }
73
- applySimpleCharacterInputOptions(inputs, options);
86
+ applySimpleCharacterActionBindingOptions(actionBindingsList, options);
74
87
  });
75
88
  useEffect(() => () => {
76
- inputs.forEach((input) => input.dispose());
77
- inputs.length = 0;
78
- }, [inputs]);
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;
137
+ }
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;
214
+ }
215
+ ref.current.deadZonePx = options?.deadZonePx;
216
+ ref.current.runDistancePx = options?.runDistancePx;
217
+ });
79
218
  }
package/package.json CHANGED
@@ -29,7 +29,7 @@
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.1"
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.1",
43
+ "version": "0.2.2",
44
44
  "scripts": {
45
45
  "build": "tsc",
46
46
  "check:prettier": "prettier --check src",