Qt Quick 3D Physics - CharacterController Example

 // Copyright (C) 2023 The Qt Company Ltd.
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 import QtQuick
 import QtQuick3D

 Item {
     id: root

     property real xSpeed: 0.1
     property real ySpeed: 0.1

     property bool xInvert: false
     property bool yInvert: true

     property bool mouseEnabled: true
     property bool keysEnabled: true

     readonly property bool inputsNeedProcessing: status.useMouse

     property alias acceptedButtons: dragHandler.acceptedButtons

     implicitWidth: parent.width
     implicitHeight: parent.height
     focus: keysEnabled

     property bool moveForwards: false
     property bool moveBackwards: false
     property bool moveLeft: false
     property bool moveRight: false
     property bool moveUp: false
     property bool moveDown: false

     property vector2d cameraRotation: Qt.vector2d(0, 0)

     property bool sprintActive: false
     property bool crouchActive: false

     DragHandler {
         id: dragHandler
         target: null
         enabled: mouseEnabled
         onCentroidChanged: {
             mouseMoved(Qt.vector2d(centroid.position.x, centroid.position.y))
         }

         onActiveChanged: {
             if (active)
                 mousePressed(Qt.vector2d(centroid.position.x,
                                          centroid.position.y))
             else
                 mouseReleased(Qt.vector2d(centroid.position.x,
                                           centroid.position.y))
         }
     }

     TapHandler {
         onTapped: root.forceActiveFocus()
     }

     Keys.onPressed: (event) => { if (keysEnabled) handleKeyPress(event) }
     Keys.onReleased: (event) => { if (keysEnabled) handleKeyRelease(event) }

     function mousePressed(newPos) {
         root.forceActiveFocus()
         status.currentPos = newPos
         status.lastPos = newPos
         status.useMouse = true
     }

     function mouseReleased(newPos) {
         status.useMouse = false
     }

     function mouseMoved(newPos) {
         status.currentPos = newPos
     }

     function forwardPressed() {
         moveForwards = true
         moveBackwards = false
     }

     function forwardReleased() {
         moveForwards = false
     }

     function backPressed() {
         moveBackwards = true
         moveForwards = false
     }

     function backReleased() {
         moveBackwards = false
     }

     function rightPressed() {
         moveRight = true
         moveLeft = false
     }

     function rightReleased() {
         moveRight = false
     }

     function leftPressed() {
         moveLeft = true
         moveRight = false
     }

     function leftReleased() {
         moveLeft = false
     }

     function upPressed() {
         moveUp = true
         moveDown = false
     }

     function upReleased() {
         moveUp = false
     }

     function downPressed() {
         moveDown = true
         moveUp = false
     }

     function downReleased() {
         moveDown = false
     }

     function sprintPressed() {
         sprintActive = true
         crouchActive = false
     }

     function sprintReleased() {
         sprintActive = false
     }

     function sneakPressed() {
         crouchActive = true
         sprintActive = false
     }

     function sneakReleased() {
         crouchActive = false
     }

     function handleKeyPress(event) {
         switch (event.key) {
         case Qt.Key_W:
         case Qt.Key_Up:
             forwardPressed()
             break
         case Qt.Key_S:
         case Qt.Key_Down:
             backPressed()
             break
         case Qt.Key_A:
         case Qt.Key_Left:
             leftPressed()
             break
         case Qt.Key_D:
         case Qt.Key_Right:
             rightPressed()
             break
         case Qt.Key_R:
         case Qt.Key_PageUp:
             upPressed()
             break
         case Qt.Key_F:
         case Qt.Key_PageDown:
             downPressed()
             break
         case Qt.Key_Shift:
             sprintPressed()
             break
         case Qt.Key_Control:
             sneakPressed()
             break
         }
     }

     function handleKeyRelease(event) {
         switch (event.key) {
         case Qt.Key_W:
         case Qt.Key_Up:
             forwardReleased()
             break
         case Qt.Key_S:
         case Qt.Key_Down:
             backReleased()
             break
         case Qt.Key_A:
         case Qt.Key_Left:
             leftReleased()
             break
         case Qt.Key_D:
         case Qt.Key_Right:
             rightReleased()
             break
         case Qt.Key_R:
         case Qt.Key_PageUp:
             upReleased()
             break
         case Qt.Key_F:
         case Qt.Key_PageDown:
             downReleased()
             break
         case Qt.Key_Shift:
             sprintReleased()
             break
         case Qt.Key_Control:
             sneakReleased()
             break
         }
     }

     Timer {
         id: updateTimer
         interval: 16
         repeat: true
         running: root.inputsNeedProcessing
         onTriggered: {
             processInputs()
         }
     }

     function processInputs() {
         if (root.inputsNeedProcessing)
             status.processInput()
     }

     QtObject {
         id: status

         property bool useMouse: false

         property vector2d lastPos: Qt.vector2d(0, 0)
         property vector2d currentPos: Qt.vector2d(0, 0)

         function processInput() {
             if (useMouse) {
                 // Get the delta
                 var delta = Qt.vector2d(lastPos.x - currentPos.x,
                                         lastPos.y - currentPos.y)
                 // rotate x
                 var rotateX = delta.x * xSpeed
                 if (xInvert)
                     rotateX = -rotateX
                 cameraRotation.x += rotateX

                 // rotate y
                 var rotateY = delta.y * -ySpeed
                 if (yInvert)
                     rotateY = -rotateY
                 cameraRotation.y += rotateY
                 lastPos = currentPos
             }
         }
     }
 }