Revision control
Copy as Markdown
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
import QtQuick
import QtQuick3D
import QtQuick3D.Helpers
import QtQuick3D.Physics
import QtQuick.Controls
import QtQuick.Layouts
Window {
width: 1280
height: 720
visible: true
title: qsTr("QtQuick3DPhysics character controller test")
PhysicsWorld {
id: physicsWorld
running: true
forceDebugDraw: false
// enableCCD: true
scene: viewport.scene
}
View3D {
anchors.fill: parent
id: viewport
environment: SceneEnvironment {
clearColor: "#d6dbdf"
backgroundMode: SceneEnvironment.SkyBox
antialiasingMode: SceneEnvironment.MSAA
antialiasingQuality: SceneEnvironment.High
lightProbe: Texture {
source: "maps/OpenfootageNET_lowerAustria01-512.hdr"
}
skyboxBlurAmount: 0.22
probeExposure: 5
}
StaticRigidBody {
eulerRotation: Qt.vector3d(-90, 0, 0)
collisionShapes: PlaneShape {}
}
PrincipledMaterial {
id: stairMaterial
metalness: 0
roughness: 0.5
baseColor: "orange"
alphaMode: PrincipledMaterial.Opaque
}
PrincipledMaterial {
id: wallMaterial
metalness: 0
roughness: 0.5
baseColor: "gray"
alphaMode: PrincipledMaterial.Opaque
}
Node {
id: scene
DirectionalLight {
eulerRotation.x: -25
eulerRotation.y: 45
castsShadow: true
brightness: 1
shadowMapQuality: Light.ShadowMapQualityVeryHigh
shadowFactor: 50
}
StaticRigidBody {
collisionShapes: TriangleMeshShape {
id: roomShape
source: "meshes/room.mesh"
}
Model {
id: room
source: "meshes/room.mesh"
materials: [
wallMaterial
]
}
}
StaticRigidBody {
x: -600
y: 0
z: 600
collisionShapes: TriangleMeshShape {
id: stairShape
source: "meshes/stairs.mesh"
}
Model {
id: stairs
source: "meshes/stairs.mesh"
materials: [
stairMaterial
]
}
}
Model {
id: scenery
source: "#Cube"
materials: stairMaterial
z: -5000
scale: "10, 0.1, 10"
}
Model {
id: scenery2
source: "#Cube"
materials: wallMaterial
z: -7000
scale: "10, 0.1, 10"
}
Model {
id: scenery3
source: "#Cube"
materials: stairMaterial
z: -15000
scale: "10, 0.1, 100"
}
Repeater3D {
model: 10
StaticRigidBody {
id: platform
x: 300
z: -1300 - 600 * index
scale: "2, 0.3, 2"
y: 500 + 50 * index
collisionShapes: BoxShape {}
Model {
source: "#Cube"
materials: stairMaterial
}
}
}
CharacterController {
id: character
position: "100, 100, 300"
eulerRotation.y: wasd.cameraRotation.x
gravity: flying ? Qt.vector3d(0,0,0) : physicsWorld.gravity
midAirControl: midAirButton.checked
property bool physicallyCorrect: correctnessButton.checked
property bool flying: flyingButton.checked
property bool grounded: (collisions & CharacterController.Down)
property bool jumpingAllowed : false
Timer {
id: groundednessTimer
// Wile E. Coyote mode: how long after you started to fall
// can you still jump? (Only in non-physically-correct mode.)
interval: 150
}
onGroundedChanged: {
groundednessTimer.stop()
if (grounded) {
// Landing is always immediate, but we don't jump in flying mode
jumpingAllowed = !flying
} else if (!midAirControl || wasd.jump) {
// Jumping is always immediate (no double jumping)
jumpingAllowed = false
} else {
// Walking off a ledge in mid-air mode has a grace period
groundednessTimer.restart()
}
}
property string collisionText: ""
onCollisionsChanged: {
if (collisions === CharacterController.None) {
collisionText = " No collisions"
} else {
collisionText = (collisions & CharacterController.Up ? " Above" : "")
+ (collisions & CharacterController.Side ? " Side" : "")
+ ((collisions & CharacterController.Down) ? " Below" : "")
}
console.log("Collision state:" + collisionText + " (" + collisions + ")")
}
movement.x: wasd.xFactor * 500;
movement.z: wasd.zFactor * 500;
movement.y: ((wasd.jump && jumpingAllowed) ? 500 : 0)
+ wasd.yFactor * 500
Behavior on movement.z {
PropertyAnimation { duration: 200 }
}
Behavior on movement.x {
PropertyAnimation { duration: 200 }
}
collisionShapes: CapsuleShape {
id: capsuleShape
}
Model {
// not visible from the camera, but casts a shadow
eulerRotation.z: 90
geometry: CapsuleGeometry {}
materials: PrincipledMaterial {
baseColor: "red"
}
}
PerspectiveCamera {
id: camera2
position: Qt.vector3d(0, capsuleShape.height, 0)
eulerRotation.x: wasd.cameraRotation.y
clipFar: 10000
clipNear: 10
}
}
}
Wasd {
id: wasd
property real sprintFactor: sprintActive ? 3 : 1
property real xFactor: (moveLeft ? -1 : moveRight ? 1 : 0) * sprintFactor
property real zFactor: (moveForwards ? -1 : moveBackwards ? 1 : 0) * sprintFactor
property real yFactor: (character.flying ? (moveUp ? 1 : moveDown ? -1 : 0) : 0) * sprintFactor
property bool jump: false
Timer {
id: preJumpTimer
interval: 200 // how long before you land can you press jump?
onTriggered: wasd.jump = false
}
function startJump()
{
wasd.jump = true
preJumpTimer.restart()
}
Keys.onPressed: (event)=> {
handleKeyPress(event);
if (event.key === Qt.Key_Space && !event.isAutoRepeat) {
wasd.startJump()
} else if (event.key === Qt.Key_G) {
}
}
Keys.onReleased: (event) => {
handleKeyRelease(event)
if (event.key === Qt.Key_Space) {
wasd.jump = false
}
}
}
} // View3D
ButtonGroup {
buttons: column.children
}
Column {
id: column
RadioButton {
id: midAirButton
checked: true
text: qsTr("Mid-air controls")
focusPolicy: Qt.NoFocus
}
RadioButton {
id: correctnessButton
text: qsTr("Physically accurate free fall")
focusPolicy: Qt.NoFocus
}
RadioButton {
id: flyingButton
text: qsTr("Flying (no gravity)")
focusPolicy: Qt.NoFocus
}
Button {
text: "Teleport"
onClicked: character.teleport(Qt.vector3d(-700, 3000, -6850))
focusPolicy: Qt.NoFocus
}
Text {
text: "Current position: " + character.position
}
Text {
text: "Collision state:" + character.collisionText
}
}
}