Skip to content
This repository has been archived by the owner on Aug 21, 2024. It is now read-only.

Gamepad fixes #8128

Merged
merged 16 commits into from
Jun 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,12 @@ Ethereal Engine. All Rights Reserved.
*/

import { useHookstate } from '@hookstate/core'
import React from 'react'
import React, { useEffect } from 'react'
import { Joystick } from 'react-joystick-component'

import { isTouchAvailable } from '@etherealengine/engine/src/common/functions/DetectFeatures'
import { getComponent } from '@etherealengine/engine/src/ecs/functions/ComponentFunctions'
import {
getFirstNonCapturedInputSource,
InputSourceComponent
} from '@etherealengine/engine/src/input/components/InputSourceComponent'
import { InputSourceComponent } from '@etherealengine/engine/src/input/components/InputSourceComponent'
import {
AnyButton,
createInitialButtonState,
Expand All @@ -47,7 +44,7 @@ import { AppState } from '../../services/AppService'
import styles from './index.module.scss'

const triggerButton = (button: AnyButton, pressed: boolean): void => {
const nonCapturedInputSource = getFirstNonCapturedInputSource()
const nonCapturedInputSource = InputSourceComponent.nonCapturedInputSourceQuery()[0]
if (!nonCapturedInputSource) return

const inputSource = getComponent(nonCapturedInputSource, InputSourceComponent)
Expand Down Expand Up @@ -103,7 +100,22 @@ export const TouchGamepad = () => {
const availableInteractable = interactState.available.value?.[0]
const appState = useHookstate(getMutableState(AppState))

if (!isTouchAvailable || isMobileXRHeadset || !appState.showTouchPad.value) return <></>
const hasGamepad = useHookstate(false)

useEffect(() => {
const getGamepads = () => {
hasGamepad.set(!!navigator.getGamepads().filter(Boolean).length)
}
getGamepads()
window.addEventListener('gamepadconnected', getGamepads)
window.addEventListener('gamepaddisconnected', getGamepads)
return () => {
window.removeEventListener('gamepadconnected', getGamepads)
window.removeEventListener('gamepaddisconnected', getGamepads)
}
}, [])

if (!isTouchAvailable || isMobileXRHeadset || !appState.showTouchPad.value || hasGamepad.value) return <></>

const buttons = buttonsConfig.map((value, index) => {
return (
Expand Down
7 changes: 2 additions & 5 deletions packages/client-core/src/systems/AvatarUISystem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,7 @@ import { Entity } from '@etherealengine/engine/src/ecs/classes/Entity'
import { defineQuery, getComponent, hasComponent } from '@etherealengine/engine/src/ecs/functions/ComponentFunctions'
import { removeEntity } from '@etherealengine/engine/src/ecs/functions/EntityFunctions'
import { defineSystem } from '@etherealengine/engine/src/ecs/functions/SystemFunctions'
import {
getFirstNonCapturedInputSource,
InputSourceComponent
} from '@etherealengine/engine/src/input/components/InputSourceComponent'
import { InputSourceComponent } from '@etherealengine/engine/src/input/components/InputSourceComponent'
import { NetworkObjectComponent } from '@etherealengine/engine/src/networking/components/NetworkObjectComponent'
import { NetworkObjectOwnedTag } from '@etherealengine/engine/src/networking/components/NetworkObjectComponent'
import { MediaSettingsState } from '@etherealengine/engine/src/networking/MediaSettingsState'
Expand Down Expand Up @@ -151,7 +148,7 @@ const execute = () => {
const engineState = getState(EngineState)
if (!engineState.isEngineInitialized) return

const nonCapturedInputSource = getFirstNonCapturedInputSource()
const nonCapturedInputSource = InputSourceComponent.nonCapturedInputSourceQuery()[0]
if (!nonCapturedInputSource) return

const inputSource = getComponent(nonCapturedInputSource, InputSourceComponent)
Expand Down
4 changes: 2 additions & 2 deletions packages/client-core/src/systems/WarningUISystem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@ import {
} from '@etherealengine/engine/src/ecs/functions/ComponentFunctions'
import { removeEntity } from '@etherealengine/engine/src/ecs/functions/EntityFunctions'
import { defineSystem } from '@etherealengine/engine/src/ecs/functions/SystemFunctions'
import { InputComponent } from '@etherealengine/engine/src/input/components/InputComponent'
import { NameComponent } from '@etherealengine/engine/src/scene/components/NameComponent'
import { setVisibleComponent, VisibleComponent } from '@etherealengine/engine/src/scene/components/VisibleComponent'
import { ComputedTransformComponent } from '@etherealengine/engine/src/transform/components/ComputedTransformComponent'
import { XRUIComponent, XRUIInteractableComponent } from '@etherealengine/engine/src/xrui/components/XRUIComponent'
import { XRUIComponent } from '@etherealengine/engine/src/xrui/components/XRUIComponent'
import { createTransitionState } from '@etherealengine/engine/src/xrui/functions/createTransitionState'
import { createXRUI } from '@etherealengine/engine/src/xrui/functions/createXRUI'
import { ObjectFitFunctions } from '@etherealengine/engine/src/xrui/functions/ObjectFitFunctions'
Expand Down Expand Up @@ -157,7 +158,6 @@ export const WarningUISystemState = defineState({
const ui = createXRUI(WarningSystemXRUI)
removeComponent(ui.entity, VisibleComponent)
addComponent(ui.entity, NameComponent, 'Warning XRUI')
setComponent(ui.entity, XRUIInteractableComponent)

return {
ui,
Expand Down
2 changes: 0 additions & 2 deletions packages/client-core/src/systems/WidgetUISystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ import {
TransformComponent
} from '@etherealengine/engine/src/transform/components/TransformComponent'
import { isMobileXRHeadset, ReferenceSpace } from '@etherealengine/engine/src/xr/XRState'
import { XRUIInteractableComponent } from '@etherealengine/engine/src/xrui/components/XRUIComponent'
import { ObjectFitFunctions } from '@etherealengine/engine/src/xrui/functions/ObjectFitFunctions'
import {
WidgetAppActions,
Expand Down Expand Up @@ -98,7 +97,6 @@ const WidgetUISystemState = defineState({
name: 'WidgetUISystemState',
initial: () => {
const widgetMenuUI = createWidgetButtonsView()
setComponent(widgetMenuUI.entity, XRUIInteractableComponent)
removeComponent(widgetMenuUI.entity, VisibleComponent)

addComponent(widgetMenuUI.entity, NameComponent, 'widget_menu')
Expand Down
2 changes: 0 additions & 2 deletions packages/client-core/src/systems/createAnchorWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ Ethereal Engine. All Rights Reserved.
import { removeComponent, setComponent } from '@etherealengine/engine/src/ecs/functions/ComponentFunctions'
import { VisibleComponent } from '@etherealengine/engine/src/scene/components/VisibleComponent'
import { ReferenceSpace, XRAction, XRState } from '@etherealengine/engine/src/xr/XRState'
import { XRUIInteractableComponent } from '@etherealengine/engine/src/xrui/components/XRUIComponent'
import { createXRUI } from '@etherealengine/engine/src/xrui/functions/createXRUI'
import { WidgetAppActions, WidgetAppState } from '@etherealengine/engine/src/xrui/WidgetAppService'
import { Widget, Widgets } from '@etherealengine/engine/src/xrui/Widgets'
Expand All @@ -38,7 +37,6 @@ import { AnchorWidgetUI } from './ui/AnchorWidgetUI'
export function createAnchorWidget() {
const ui = createXRUI(AnchorWidgetUI)
removeComponent(ui.entity, VisibleComponent)
setComponent(ui.entity, XRUIInteractableComponent)
const xrState = getMutableState(XRState)
// const avatarInputSettings = getMutableState(AvatarInputSettingsState)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import { Engine } from '@etherealengine/engine/src/ecs/classes/Engine'
import { removeComponent, setComponent } from '@etherealengine/engine/src/ecs/functions/ComponentFunctions'
import { VisibleComponent } from '@etherealengine/engine/src/scene/components/VisibleComponent'
import { ReferenceSpace, XRState } from '@etherealengine/engine/src/xr/XRState'
import { XRUIInteractableComponent } from '@etherealengine/engine/src/xrui/components/XRUIComponent'
import { createXRUI } from '@etherealengine/engine/src/xrui/functions/createXRUI'
import { WidgetAppActions } from '@etherealengine/engine/src/xrui/WidgetAppService'
import { Widget, Widgets } from '@etherealengine/engine/src/xrui/Widgets'
Expand All @@ -37,7 +36,6 @@ import Icon from '@etherealengine/ui/src/primitives/mui/Icon'
export function createHeightAdjustmentWidget() {
const ui = createXRUI(() => null)
removeComponent(ui.entity, VisibleComponent)
setComponent(ui.entity, XRUIInteractableComponent)

const xrState = getMutableState(XRState)

Expand Down
2 changes: 0 additions & 2 deletions packages/client-core/src/systems/createMediaWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ Ethereal Engine. All Rights Reserved.

import { removeComponent, setComponent } from '@etherealengine/engine/src/ecs/functions/ComponentFunctions'
import { VisibleComponent } from '@etherealengine/engine/src/scene/components/VisibleComponent'
import { XRUIInteractableComponent } from '@etherealengine/engine/src/xrui/components/XRUIComponent'
import { createXRUI } from '@etherealengine/engine/src/xrui/functions/createXRUI'
import { Widget, Widgets } from '@etherealengine/engine/src/xrui/Widgets'

Expand All @@ -34,7 +33,6 @@ import { UserMediaWindowsWidget } from '../components/UserMediaWindows'
export function createMediaWidget() {
const ui = createXRUI(UserMediaWindowsWidget)
// removeComponent(ui.entity, VisibleComponent)
setComponent(ui.entity, XRUIInteractableComponent)

const widget: Widget = {
ui,
Expand Down
2 changes: 0 additions & 2 deletions packages/client-core/src/systems/ui/UserMenuView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import { Engine } from '@etherealengine/engine/src/ecs/classes/Engine'
import { removeComponent, setComponent } from '@etherealengine/engine/src/ecs/functions/ComponentFunctions'
import { WorldState } from '@etherealengine/engine/src/networking/interfaces/WorldState'
import { VisibleComponent } from '@etherealengine/engine/src/scene/components/VisibleComponent'
import { XRUIInteractableComponent } from '@etherealengine/engine/src/xrui/components/XRUIComponent'
import { createXRUI, XRUI } from '@etherealengine/engine/src/xrui/functions/createXRUI'
import { useXRUIState } from '@etherealengine/engine/src/xrui/functions/useXRUIState'
import { defineState, dispatchAction, getMutableState, useHookstate } from '@etherealengine/hyperflux'
Expand All @@ -52,7 +51,6 @@ export const AvatarUIContextMenuState = defineState({
initial: () => {
const ui = createXRUI(AvatarContextMenu) as XRUI<null>
removeComponent(ui.entity, VisibleComponent)
setComponent(ui.entity, XRUIInteractableComponent)
return {
ui,
id: null! as string | UserId
Expand Down
7 changes: 2 additions & 5 deletions packages/editor/src/systems/EditorControlSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,7 @@ import {
getEntityNodeArrayFromEntities
} from '@etherealengine/engine/src/ecs/functions/EntityTree'
import { defineSystem } from '@etherealengine/engine/src/ecs/functions/SystemFunctions'
import {
getFirstNonCapturedInputSource,
InputSourceComponent
} from '@etherealengine/engine/src/input/components/InputSourceComponent'
import { InputSourceComponent } from '@etherealengine/engine/src/input/components/InputSourceComponent'
import InfiniteGridHelper from '@etherealengine/engine/src/scene/classes/InfiniteGridHelper'
import { addObjectToGroup, GroupComponent } from '@etherealengine/engine/src/scene/components/GroupComponent'
import { NameComponent } from '@etherealengine/engine/src/scene/components/NameComponent'
Expand Down Expand Up @@ -440,7 +437,7 @@ const execute = () => {
}
const cursorPosition = Engine.instance.pointerState.position

const nonCapturedInputSource = getFirstNonCapturedInputSource()
const nonCapturedInputSource = InputSourceComponent.nonCapturedInputSourceQuery()[0]
if (!nonCapturedInputSource) return

const inputSource = getComponent(nonCapturedInputSource, InputSourceComponent)
Expand Down
7 changes: 2 additions & 5 deletions packages/editor/src/systems/EditorFlyControlSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,7 @@ import {
setComponent
} from '@etherealengine/engine/src/ecs/functions/ComponentFunctions'
import { defineSystem } from '@etherealengine/engine/src/ecs/functions/SystemFunctions'
import {
getFirstNonCapturedInputSource,
InputSourceComponent
} from '@etherealengine/engine/src/input/components/InputSourceComponent'
import { InputSourceComponent } from '@etherealengine/engine/src/input/components/InputSourceComponent'
import { dispatchAction } from '@etherealengine/hyperflux'

import { editorCameraCenter } from '../classes/EditorCameraState'
Expand Down Expand Up @@ -72,7 +69,7 @@ const onSecondaryReleased = () => {
}

const execute = () => {
const nonCapturedInputSource = getFirstNonCapturedInputSource()
const nonCapturedInputSource = InputSourceComponent.nonCapturedInputSourceQuery()[0]
if (!nonCapturedInputSource) return

const inputSource = getComponent(nonCapturedInputSource, InputSourceComponent)
Expand Down
80 changes: 54 additions & 26 deletions packages/engine/src/avatar/AvatarInputSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import {
import { defineSystem } from '../ecs/functions/SystemFunctions'
import { InputComponent } from '../input/components/InputComponent'
import { InputSourceComponent } from '../input/components/InputSourceComponent'
import { XRStandardGamepadButton } from '../input/state/ButtonState'
import { StandardGamepadButton, XRStandardGamepadButton } from '../input/state/ButtonState'
import { InteractState } from '../interaction/systems/InteractiveSystem'
import { WorldNetworkAction } from '../networking/functions/WorldNetworkAction'
import { RigidBodyFixedTagComponent } from '../physics/components/RigidBodyComponent'
Expand All @@ -60,10 +60,16 @@ const _quat = new Quaternion()

/**
* On 'xr-standard' mapping, get thumbstick input [2,3], fallback to thumbpad input [0,1]
* On 'standard' mapping, get thumbstick input [0,1]
*/
export function getThumbstickOrThumbpadAxes(inputSource: XRInputSource, deadZone: number = 0.05) {
const axes = inputSource.gamepad!.axes
const axesIndex = inputSource.gamepad?.mapping === 'xr-standard' ? 2 : 0
export function getThumbstickOrThumbpadAxes(
inputSource: XRInputSource,
thumstick: XRHandedness,
deadZone: number = 0.05
) {
const gamepad = inputSource.gamepad
const axes = gamepad!.axes
const axesIndex = inputSource.gamepad?.mapping === 'xr-standard' || thumstick === 'right' ? 2 : 0
const xAxis = Math.abs(axes[axesIndex]) > deadZone ? axes[axesIndex] : 0
const zAxis = Math.abs(axes[axesIndex + 1]) > deadZone ? axes[axesIndex + 1] : 0
return [xAxis, zAxis] as [number, number]
Expand All @@ -74,16 +80,21 @@ export const InputSourceAxesDidReset = new WeakMap<XRInputSource, boolean>()
export const AvatarAxesControlSchemeBehavior = {
[AvatarAxesControlScheme.Move]: (
inputSource: XRInputSource,
controller: ComponentType<typeof AvatarControllerComponent>
controller: ComponentType<typeof AvatarControllerComponent>,
handdedness: XRHandedness
) => {
const [x, z] = getThumbstickOrThumbpadAxes(inputSource, 0.05)
const [x, z] = getThumbstickOrThumbpadAxes(inputSource, handdedness)
controller.gamepadLocalInput.x += x
controller.gamepadLocalInput.z += z
},

[AvatarAxesControlScheme.Teleport]: (inputSource: XRInputSource) => {
[AvatarAxesControlScheme.Teleport]: (
inputSource: XRInputSource,
controller: ComponentType<typeof AvatarControllerComponent>,
handdedness: XRHandedness
) => {
const localClientEntity = Engine.instance.localClientEntity
const [x, z] = getThumbstickOrThumbpadAxes(inputSource, 0.05)
const [x, z] = getThumbstickOrThumbpadAxes(inputSource, handdedness)

if (x === 0 && z === 0) {
InputSourceAxesDidReset.set(inputSource, true)
Expand Down Expand Up @@ -112,16 +123,7 @@ const onShiftLeft = () => {
controller.isWalking.set(!controller.isWalking.value)
}

const onKeyE = () => {
dispatchAction(
EngineActions.interactedWithObject({
targetEntity: getState(InteractState).available[0],
handedness: 'none'
})
)
}

const onTrigger = (handedness: XRHandedness) => {
const onInteract = (handedness: XRHandedness = 'none') => {
dispatchAction(
EngineActions.interactedWithObject({
targetEntity: getState(InteractState).available[0],
Expand All @@ -142,10 +144,9 @@ const onKeyP = () => {
getMutableState(RendererState).debugEnable.set(!getMutableState(RendererState).debugEnable.value)
}

const walkableQuery = defineQuery([RigidBodyFixedTagComponent, InputComponent])
const inputSourceQuery = defineQuery([InputSourceComponent])

const filterUncapturedInputSources = (eid: Entity) => !getComponent(eid, InputSourceComponent)?.captured
const walkableQuery = defineQuery([RigidBodyFixedTagComponent, InputComponent])

let mouseMovedDuringPrimaryClick = false
const execute = () => {
Expand All @@ -155,7 +156,7 @@ const execute = () => {
const avatarInputSettings = getState(AvatarInputSettingsState)

const controller = getComponent(localClientEntity, AvatarControllerComponent)
const nonCapturedInputSourceEntities = inputSourceQuery().filter(filterUncapturedInputSources)
const nonCapturedInputSourceEntities = InputSourceComponent.nonCapturedInputSourceQuery()

const firstWalkableEntityWithInput = walkableQuery().find(
(entity) => getComponent(entity, InputComponent)?.inputSources.length
Expand All @@ -180,14 +181,37 @@ const execute = () => {
}
}

/** @todo until we have something more sophisticated, allow interaction input even when interactables are captured */
for (const inputSourceEntity of inputSourceQuery()) {
const inputSource = getComponent(inputSourceEntity, InputSourceComponent)

const buttons = inputSource.buttons

const standardGamepad =
inputSource.source.gamepad?.mapping === 'standard' || inputSource.source.gamepad?.mapping === ''

if (buttons.KeyE?.down) onInteract()

if (standardGamepad && buttons[StandardGamepadButton.ButtonY]?.down) {
onInteract()
}
}

for (const inputSourceEntity of nonCapturedInputSourceEntities) {
const inputSource = getComponent(inputSourceEntity, InputSourceComponent)

const buttons = inputSource.buttons

const standardGamepad =
inputSource.source.gamepad?.mapping === 'standard' || inputSource.source.gamepad?.mapping === ''
const xrStandardGamepad = inputSource.source.gamepad?.mapping === 'xr-standard'

if (buttons.ShiftLeft?.down) onShiftLeft()
if (buttons.KeyE?.down) onKeyE()
if (buttons[XRStandardGamepadButton.Trigger]?.down) onTrigger(inputSource.source.handedness)
if (xrStandardGamepad) {
if (buttons[XRStandardGamepadButton.Trigger]?.down) onInteract(inputSource.source.handedness)
}

const gamepadJump = standardGamepad && buttons[StandardGamepadButton.ButtonA]?.down

if (isDev) {
if (buttons.KeyO?.down) onKeyO()
Expand All @@ -204,17 +228,21 @@ const execute = () => {
(buttons.ArrowUp?.pressed ? -1 : 0) +
(buttons.ArrowDown?.pressed ? 1 : 0)

controller.gamepadLocalInput.set(keyDeltaX, 0, keyDeltaZ)
controller.gamepadLocalInput.set(keyDeltaX, 0, keyDeltaZ).normalize()

controller.gamepadJumpActive = !!buttons.Space?.pressed
controller.gamepadJumpActive = !!buttons.Space?.pressed || gamepadJump

const controlScheme =
inputSource.source.handedness === 'none'
? AvatarAxesControlScheme.Move
: inputSource.source.handedness === avatarInputSettings.preferredHand
? avatarInputSettings.rightAxesControlScheme
: avatarInputSettings.leftAxesControlScheme
AvatarAxesControlSchemeBehavior[controlScheme](inputSource.source, controller)
AvatarAxesControlSchemeBehavior[controlScheme](
inputSource.source,
controller,
avatarInputSettings.preferredHand === 'left' ? 'right' : 'left'
)
}
}

Expand Down
6 changes: 1 addition & 5 deletions packages/engine/src/avatar/AvatarTeleportSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,10 +178,6 @@ let fadeBackInAccumulator = -1

let visibleSegments = 2

const inputSourceQuery = defineQuery([InputSourceComponent])

const filterUncapturedInputSources = (eid: Entity) => !getComponent(eid, InputSourceComponent)?.captured

const execute = () => {
if (getCameraMode() !== 'attached') return

Expand Down Expand Up @@ -213,7 +209,7 @@ const execute = () => {
}
const guidelineTransform = getComponent(guidelineEntity, TransformComponent)

const nonCapturedInputSources = inputSourceQuery().filter(filterUncapturedInputSources)
const nonCapturedInputSources = InputSourceComponent.nonCapturedInputSourceQuery()

for (const entity of avatarTeleportQuery()) {
const side = getComponent(Engine.instance.localClientEntity, AvatarTeleportComponent).side
Expand Down
Loading