Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Rework mobile controls! #260

Merged
merged 7 commits into from
Jan 31, 2025
Merged

refactor: Rework mobile controls! #260

merged 7 commits into from
Jan 31, 2025

Conversation

zardoy
Copy link
Owner

@zardoy zardoy commented Jan 31, 2025

User description

  • Separate movement and interaction touch controls
  • Add new options for touch control types (modern/classic)
  • Refactor touch interaction handling and event management
  • Create a new GameInteractionOverlay component for touch interactions for less bugs in future
  • Update touch controls UI and positioning logic

PR Type

Enhancement, Bug fix


Description

  • Refactored mobile controls, separating movement and interaction.

  • Introduced new touch control options: modern and classic.

  • Added GameInteractionOverlay component for improved touch interaction handling.

  • Updated UI logic and positioning for touch controls.

  • Fixed various issues related to pointer lock and touch interaction.


Changes walkthrough 📝

Relevant files
Enhancement
10 files
cameraRotationControls.ts
Added camera rotation controls and event handling.             
+77/-0   
globalState.ts
Updated modal stack subscription for UI adjustments.         
+4/-0     
index.ts
Refactored and modularized camera movement and pointer lock logic.
+6/-183 
optionsGuiScheme.tsx
Updated GUI options for new touch control types.                 
+15/-10 
optionsStorage.ts
Added default options for new touch control types.             
+5/-1     
GameInteractionOverlay.tsx
Introduced `GameInteractionOverlay` for touch interaction handling.
+187/-0 
TouchAreasControls.tsx
Updated touch area controls for modern and classic modes.
+67/-54 
TouchAreasControlsProvider.tsx
Adjusted touch controls provider for new control types.   
+1/-4     
TouchControls.tsx
Refactored touch controls to support new movement types. 
+2/-2     
reactUi.tsx
Integrated `GameInteractionOverlay` into in-game UI.         
+2/-0     
Bug fix
2 files
IndicatorEffects.css
Adjusted effects screen container positioning.                     
+1/-1     
MobileTopButtons.module.css
Fixed z-index for mobile top buttons with modals.               
+1/-1     

Need help?
  • Type /help how to ... in the comments thread for any questions about Qodo Merge usage.
  • Check out the documentation for more information.
  • - Separate movement and interaction touch controls
    - Add new options for touch control types (modern/classic)
    - Refactor touch interaction handling and event management
    - Create a new GameInteractionOverlay component for touch interactions for less bugs in future
    - Update touch controls UI and positioning logic
    Copy link

    codesandbox bot commented Jan 31, 2025

    Review or Edit in CodeSandbox

    Open the branch in Web EditorVS CodeInsiders

    Open Preview

    @zardoy
    Copy link
    Owner Author

    zardoy commented Jan 31, 2025

    /deploy

    PR Reviewer Guide 🔍

    Here are some key observations to aid the review process:

    ⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
    🧪 No relevant tests
    🔒 No security concerns identified
    ⚡ Recommended focus areas for review

    Performance Concern

    Camera movement rate limiting uses a fixed 4ms threshold which may not be optimal for all devices and could cause stuttering on slower devices. Consider making this configurable or using requestAnimationFrame.

    // todo: limit camera movement for now to avoid unexpected jumps
    if (now - lastMouseMove < 4) return
    Possible Issue

    Touch event handling does not properly cleanup resources when component unmounts - captured pointers and timeouts should be cleared to prevent memory leaks and stale references.

    const pointerDownHandler = (e: PointerEvent) => {
      const clickedEl = e.composedPath()[0]
      if (!isGameActive(true) || clickedEl !== cameraControlEl || e.pointerId === undefined) {
        return
      }
      screenTouches++
      if (screenTouches === 3) {
        // todo maybe mouse wheel click?
      }
      const usingModernMovement = options.touchMovementType === 'modern'
      if (usingModernMovement) {
        if (!joystickPointer.pointer && e.clientX < window.innerWidth / 2) {
          cameraControlEl.setPointerCapture(e.pointerId)
          joystickPointer.pointer = {
            pointerId: e.pointerId,
            x: e.clientX,
            y: e.clientY
          }
          return
        }
      }
      if (capturedPointer) {
        return
      }
      if (options.touchInteractionType === 'classic') {
        cameraControlEl.setPointerCapture(e.pointerId)
        capturedPointer = {
          id: e.pointerId,
          x: e.clientX,
          y: e.clientY,
          sourceX: e.clientX,
          sourceY: e.clientY,
          activateCameraMove: false,
          time: Date.now()
        }
        virtualClickTimeout ??= setTimeout(() => {
          virtualClickActive = true
          document.dispatchEvent(new MouseEvent('mousedown', { button: 0 }))
        }, touchStartBreakingBlockMs)
      }
    }
    
    const pointerMoveHandler = (e: PointerEvent) => {
      if (e.pointerId === undefined) return
      const supportsPressure = (e as any).pressure !== undefined &&
        (e as any).pressure !== 0 &&
        (e as any).pressure !== 0.5 &&
        (e as any).pressure !== 1 &&
        (e.pointerType === 'touch' || e.pointerType === 'pen')
    
      if (e.pointerId === joystickPointer.pointer?.pointerId) {
        handleMovementStickDelta(e)
        if (supportsPressure && (e as any).pressure > 0.5) {
          bot.setControlState('sprint', true)
        }
        return
      }
      if (e.pointerId !== capturedPointer?.id) return
      // window.scrollTo(0, 0)
      e.preventDefault()
      e.stopPropagation()
    
      const allowedJitter = 1.1
      if (supportsPressure) {
        bot.setControlState('jump', (e as any).pressure > 0.5)
      }
      const xDiff = Math.abs(e.pageX - capturedPointer.sourceX) > allowedJitter
      const yDiff = Math.abs(e.pageY - capturedPointer.sourceY) > allowedJitter
      if (!capturedPointer.activateCameraMove && (xDiff || yDiff)) {
        capturedPointer.activateCameraMove = true
      }
      if (capturedPointer.activateCameraMove) {
        clearTimeout(virtualClickTimeout)
      }
    
      onCameraMove({
        movementX: e.pageX - capturedPointer.x,
        movementY: e.pageY - capturedPointer.y,
        type: 'touchmove',
        stopPropagation: () => e.stopPropagation()
      } as CameraMoveEvent)
      capturedPointer.x = e.pageX
      capturedPointer.y = e.pageY
    }
    
    const pointerUpHandler = (e: PointerEvent) => {
      if (e.pointerId === undefined) return
      if (e.pointerId === joystickPointer.pointer?.pointerId) {
        handleMovementStickDelta()
        joystickPointer.pointer = null
        return
      }
      if (e.pointerId !== capturedPointer?.id) return
      clearTimeout(virtualClickTimeout)
      virtualClickTimeout = undefined
    
      if (virtualClickActive) {
        // button 0 is left click
        document.dispatchEvent(new MouseEvent('mouseup', { button: 0 }))
        virtualClickActive = false
      } else if (!capturedPointer.activateCameraMove && (Date.now() - capturedPointer.time < touchStartBreakingBlockMs)) {
        document.dispatchEvent(new MouseEvent('mousedown', { button: 2 }))
        worldInteractions.update()
        document.dispatchEvent(new MouseEvent('mouseup', { button: 2 }))
      }
    
      capturedPointer = undefined
      screenTouches--
    }
    UI Glitch

    The joystick positioning calculation may cause the joystick to jump or be misaligned when the window is resized, as it uses window dimensions directly without accounting for scaling changes.

    ...pointer ? {
      left: `${pointer.x / window.innerWidth * 100}%`,
      top: `${pointer.y / window.innerHeight * 100}%`
    } : {}

    Copy link

    qodo-merge-pro-for-open-source bot commented Jan 31, 2025

    PR Code Suggestions ✨

    Explore these optional code suggestions:

    CategorySuggestion                                                                                                                                    Score
    Possible issue
    ✅ Add null check for bot entity

    Add a check for null/undefined bot entity before accessing its properties in
    moveCameraRawHandler to prevent potential runtime errors.

    src/cameraRotationControls.ts [43-44]

    +if (!bot?.entity) return
     const pitch = bot.entity.pitch - y
     void bot.look(bot.entity.yaw - x, Math.max(minPitch, Math.min(maxPitch, pitch)), true)

    [Suggestion has been applied]

    Suggestion importance[1-10]: 8

    Why: The suggestion addresses a critical potential runtime error by adding a null check for bot.entity before accessing its properties, which could prevent crashes when the bot is not initialized.

    8

    Copy link

    Deployed to Vercel Preview: https://prismarine-fh21miyd1-zaro.vercel.app
    Playground
    Storybook

    zardoy and others added 4 commits February 1, 2025 00:48
    Co-authored-by: qodo-merge-pro-for-open-source[bot] <189517486+qodo-merge-pro-for-open-source[bot]@users.noreply.github.com>
    @zardoy
    Copy link
    Owner Author

    zardoy commented Jan 31, 2025

    /deploy

    Copy link

    Deployed to Vercel Preview: https://prismarine-6qg91vclk-zaro.vercel.app
    Playground
    Storybook

    @zardoy zardoy merged commit 3794843 into next Jan 31, 2025
    2 of 3 checks passed
    @zardoy zardoy deleted the new-controls branch January 31, 2025 23:51
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Projects
    None yet
    Development

    Successfully merging this pull request may close these issues.

    1 participant