/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
import QtQuick
import QtQuick3D
import QtQuick.Particles
import QtQuick3D.Particles3D
import QtQuick.Controls
Window {
id: mainWindow// Scaling helpper
readonly property realpx: 0.2+Math.min(width, height) /800// This is false until the first game has started
property boolplayingStarted: false// This is true whenever game is on
property boolgameOn: false// Sizes of our 3D models
readonly property realballSize: 40
readonly property realtargetSize: 120// Playing time in seconds
readonly property realgameTime: 60
property realcurrentTime: 0// Amount of balls per game
readonly property intgameBalls: 20
property intcurrentBalls: 0// Scores
property intscore: 0
property inttimeBonus: 0
property intballsBonus: 0width: 800height: 600visible: truetitle: qsTr("Quick3D Quick Ball")
color: "#000000"View3D {
id: view3Danchors.fill: parentfunction createLevel1() {
// Simple level of target items
var level1 = [{ "x": 0, "y": 100, "z": -100, "points": 10 },
{ "x": -300, "y": 100, "z": -400, "points": 10 },
{ "x": 300, "y": 100, "z": -400, "points": 10 },
{ "x": -200, "y": 400, "z": -600, "points": 20 },
{ "x": 0, "y": 400, "z": -600, "points": 20 },
{ "x": 200, "y": 400, "z": -600, "points": 20 },
{ "x": 0, "y": 700, "z": -600, "points": 30 }];
targetsNode.addTargets(level1);
}
function startGame() {
ballModel.resetBall();
targetsNode.resetTargets();
createLevel1();
score=timeBonus=ballsBonus=0;
currentBalls=gameBalls;
gameOn=true;
playingStarted=true;
}
function endGame() {
if (targetsNode.currentTargets==0) {
// If we managed to get all targets down -> bonus points!timeBonus=currentTime;
ballsBonus=currentBalls*10;
}
gameOn=false;
}
environment: SceneEnvironment {
antialiasingMode: SceneEnvironment.MSAAantialiasingQuality: SceneEnvironment.High
}
camera: viewCameraPerspectiveCamera {
id: viewCameraposition: Qt.vector3d(0, 200, 800);
// Rotate camera a bit
SequentialAnimation on eulerRotation.y {
loops: Animation.InfiniteNumberAnimation {
to: 2duration: 2000easing.type: Easing.InOutQuad
}
NumberAnimation {
to: -2duration: 2000easing.type: Easing.InOutQuad
}
}
}
PointLight {
x: 400y: 1200castsShadow: trueshadowMapQuality: Light.ShadowMapQualityHighshadowFactor: 50quadraticFade: 2ambientColor: "#202020"brightness: gameOn ? 200 : 40
Behavior on brightness {
NumberAnimation {
duration: 1000easing.type: Easing.InOutQuad
}
}
}
MouseArea {
anchors.fill: parentenabled: gameOn&& !ballModel.ballMovingonPressed: {
ballModel.moveBall(mouseX, mouseY);
}
onPositionChanged: {
ballModel.moveBall(mouseX, mouseY);
}
onReleased: {
ballModel.throwBall();
}
}
Model {
id: ballModel
property realdirectionX: 0
property realdirectionY: 0// How many ms the ball flies
readonly property realspeed: 2000
readonly property realballScale: ballSize/100
property varmoves: []
readonly property intmaxMoves: 5
readonly property boolballMoving: ballAnimation.runningsource: "#Sphere"scale: Qt.vector3d(ballScale, ballScale, ballScale)
materials: DefaultMaterial {
diffuseMap: Texture {
source: "images/ball.jpg"
}
normalMap: Texture {
source: "images/ball_n.jpg"
}
bumpAmount: 1.0
}
function resetBall() {
moves= [];
x=0;
y=ballSize/2;
z=400;
}
function moveBall(posX, posY) {
var pos = view3D.mapTo3DScene(Qt.vector3d(posX, posY, ballModel.z+ballSize));
pos.y=Math.max(ballSize/2, pos.y);
var point = {"x": pos.x, "y": pos.y };
moves.push(point);
if (moves.length>maxMoves) moves.shift();
// Apply position into ball modelballModel.x=pos.x;
ballModel.y=pos.y;
}
function throwBall() {
currentBalls--;
var moveX = 0;
var moveY = 0;
if (moves.length>=2) {
var first = moves.shift();
var last = moves.pop();
moveX=last.x-first.x;
moveY=last.y-first.y;
if (moveY<0) moveY=0;
}
directionX=moveX*20;
directionY=moveY*4;
ballAnimation.start();
}
ParallelAnimation {
id: ballAnimationrunning: false// Move forwardNumberAnimation {
target: ballModelproperty: "z"duration: ballModel.speedto: -ballModel.directionY*5easing.type: Easing.OutQuad
}
// Move up & down with a bounceSequentialAnimation {
NumberAnimation {
target: ballModelproperty: "y"duration: ballModel.speed* (1/3)
to: ballModel.y+ballModel.directionYeasing.type: Easing.OutQuad
}
NumberAnimation {
target: ballModelproperty: "y"duration: ballModel.speed* (2/3)
to: ballSize/4easing.type: Easing.OutBounce
}
}
// Move sidewaysNumberAnimation {
target: ballModelproperty: "x"duration: ballModel.speedto: ballModel.x+ballModel.directionX
}
onFinished: {
if (currentBalls<=0)
view3D.endGame();
ballModel.resetBall();
}
}
NumberAnimation on eulerRotation.z {
running: ballModel.ballMovingloops: Animation.Infinitefrom: ballModel.directionX<0 ? 0 : 720to: 360duration: 10000/ (2+Math.abs(ballModel.directionX*0.05))
}
onZChanged: {
// Loop through target items and detect collisions
var hitMargin = ballSize/2+targetSize/2;
for (var i = 0; i<targetsNode.targets.length; ++i) {
var target = targetsNode.targets[i];
var targetPos = target.scenePosition;
var hit = ballModel.scenePosition.fuzzyEquals(targetPos, hitMargin);
if (hit) {
target.hit();
if (targetsNode.currentTargets<=0)
view3D.endGame();
}
}
}
}
Node {
id: targetsNode
property vartargets: []
property intcurrentTargets: 0function addTargets(items) {
items.forEach(function (item) {
let instance = targetComponent.createObject(
targetsNode, { "x": item.x, "startPosY": item.y, "z": item.z, "points": item.points});
targets.push(instance);
});
currentTargets=targets.length;
}
function removeTarget(item) {
var index = targets.indexOf(item);
targets.splice(index, 1);
currentTargets=targets.length;
}
function resetTargets() {
while (targets.length>0)
targets.pop().destroy();
currentTargets=targets.length;
}
}
Component {
id: targetComponentNode {
id: targetNode
property intpoints: 0
property realhide: 0
property realstartPosY: 0
property realposY: 0
property realpointsOpacity: 0function hit() {
targetsNode.removeTarget(this);
score+=points;
hitAnimation.start();
var burstPos = targetNode.mapPositionToScene(Qt.vector3d(0, 0, 0));
hitParticleEmitter.burst(100, 200, burstPos);
}
y: startPosY+posY
SequentialAnimation on posY {
running: gameOn&& !hitAnimation.runningloops: Animation.InfiniteNumberAnimation {
from: 0to: 150duration: 3000easing.type: Easing.InOutQuad
}
NumberAnimation {
to: 0duration: 1500easing.type: Easing.InOutQuad
}
}
SequentialAnimation {
id: hitAnimationNumberAnimation {
target: targetNodeproperty: "hide"to: 1duration: 800easing.type: Easing.InOutQuad
}
NumberAnimation {
target: targetNodeproperty: "pointsOpacity"to: 1duration: 1000easing.type: Easing.InOutQuad
}
NumberAnimation {
target: targetNodeproperty: "pointsOpacity"to: 0duration: 200easing.type: Easing.InOutQuad
}
ScriptAction {
script: targetNode.destroy();
}
}
Model {
id: targetModel
readonly property realtargetScale: (1+hide) * (targetSize/100)
source: "#Cube"scale: Qt.vector3d(targetScale, targetScale, targetScale)
opacity: 0.99-hide*2materials: DefaultMaterial {
diffuseMap: Texture {
source: "images/qt_logo.jpg"
}
normalMap: Texture {
source: "images/qt_logo_n.jpg"
}
bumpAmount: 1.0
}
Vector3dAnimation on eulerRotation {
loops: Animation.Infiniteduration: 5000from: Qt.vector3d(0, 0, 0)
to: Qt.vector3d(360, 360, 360)
}
}
Text {
anchors.centerIn: parentscale: 1+pointsOpacityopacity: pointsOpacitytext: targetNode.pointsfont.pixelSize: 60*pxcolor: "#808000"style: Text.OutlinestyleColor: "#f0f000"
}
}
}
Model {
source: "#Rectangle"scale: Qt.vector3d(50, 50, 1)
eulerRotation.x: -90materials: DefaultMaterial {
diffuseMap: Texture {
source: "images/grass.jpg"tilingModeHorizontal: Texture.RepeattilingModeVertical: Texture.RepeatscaleU: 25.0scaleV: 25.0
}
normalMap: Texture {
source: "images/grass_n.jpg"
}
bumpAmount: 0.6
}
}
Model {
id: sky
property realscaleX: 100
property realscaleY: 20source: "#Rectangle"scale: Qt.vector3d(sky.scaleX, sky.scaleY, 1)
position: Qt.vector3d(0, 960, -2000)
// We don't want shadows casted into skyreceivesShadows: falsematerials: DefaultMaterial {
diffuseMap: Texture {
source: "images/sky.jpg"
}
}
// Star particlesNode {
z: 500y: 30// Stars are far away, scale up to half the resolutionscale: Qt.vector3d(2/sky.scaleX, 2/sky.scaleY, 1)
ParticleSystem {
anchors.horizontalCenter: parent.horizontalCenteranchors.top: parent.topwidth: 3000height: 400ImageParticle {
source: "qrc:///particleresources/star.png"rotationVariation: 360color: "#ffffa0"colorVariation: 0.1
}
Emitter {
anchors.fill: parentemitRate: 4lifeSpan: 6000lifeSpanVariation: 4000size: 30sizeVariation: 20
}
}
}
}
ParticleSystem3D {
id: psystemSpriteParticle3D {
id: spritesprite: Texture {
source: "images/particle.png"
}
color: Qt.rgba(1.0, 1.0, 0.0, 1.0)
colorVariation: Qt.vector4d(0.4, 0.6, 0.0, 0.0)
unifiedColorVariation: truemaxAmount: 200
}
ParticleEmitter3D {
id: hitParticleEmitterparticle: spriteparticleScale: 4.0particleScaleVariation: 2.0particleRotationVariation: Qt.vector3d(0, 0, 180)
particleRotationVelocityVariation: Qt.vector3d(0, 0, 250)
velocity: VectorDirection3D {
direction: Qt.vector3d(0, 300, 0)
directionVariation: Qt.vector3d(200, 150, 100)
}
lifeSpan: 800lifeSpanVariation: 200depthBias: 100
}
Gravity3D {
magnitude: 600
}
}
}
// Game time counter
NumberAnimation on currentTime {
running: gameOnduration: gameTime*1000from: gameTimeto: 0onFinished: {
view3D.endGame();
}
}
// Show time, balls and scoreItem {
width: parent.widthheight: 60*pxText {
anchors.verticalCenter: parent.verticalCenteranchors.left: parent.leftanchors.leftMargin: 20*pxfont.pixelSize: 26*pxcolor: "#ffffff"style: Text.OutlinestyleColor: "#000000"text: currentTime.toFixed(2)
}
Image {
anchors.verticalCenter: parent.verticalCenteranchors.verticalCenterOffset: 1*pxanchors.right: ballCountText.leftanchors.rightMargin: 8*pxwidth: 26*pxheight: widthmipmap: truesource: "images/ball_icon.png"
}
Text {
id: ballCountTextanchors.verticalCenter: parent.verticalCenteranchors.right: parent.rightanchors.rightMargin: 20*pxfont.pixelSize: 26*pxcolor: "#ffffff"style: Text.OutlinestyleColor: "#000000"text: currentBalls
}
Text {
anchors.centerIn: parentfont.pixelSize: 36*pxcolor: "#ffffff"style: Text.OutlinestyleColor: "#000000"text: score
}
}
// Game logoImage {
anchors.centerIn: parentwidth: Math.min(parent.width*0.6, sourceSize.width)
height: width*0.6fillMode: Image.PreserveAspectFitsource: "images/quickball.png"opacity: !gameOnscale: 2.0-opacity
Behavior on opacity {
NumberAnimation {
duration: 400easing.type: Easing.InOutQuad
}
}
}
// Show bonus and total score when the game endsItem {
property boolshow: playingStarted&& !gameOnanchors.centerIn: parentanchors.verticalCenterOffset: -200*pxonShowChanged: {
if (show) {
showScoreAnimation.start();
} else {
showScoreAnimation.stop();
timeBonusText.opacity=0;
ballsBonusText.opacity=0;
totalScoreText.opacity=0;
}
}
SequentialAnimation {
id: showScoreAnimationNumberAnimation {
target: timeBonusTextproperty: "opacity"to: 1duration: 1000easing.type: Easing.InOutQuad
}
NumberAnimation {
target: ballsBonusTextproperty: "opacity"to: 1duration: 1000easing.type: Easing.InOutQuad
}
NumberAnimation {
target: totalScoreTextproperty: "opacity"to: 1duration: 1000easing.type: Easing.InOutQuad
}
}
Text {
id: timeBonusTextanchors.horizontalCenter: parent.horizontalCentery: opacity*60*pxfont.pixelSize: 26*pxcolor: "#ffffff"style: Text.OutlinestyleColor: "#000000"textFormat: Text.StyledTexttext: qsTr("TIME BONUS <b>%1</b>").arg(timeBonus)
opacity: 0
}
Text {
id: ballsBonusTextanchors.horizontalCenter: parent.horizontalCentery: timeBonusText.y+opacity*40*pxfont.pixelSize: 26*pxcolor: "#ffffff"style: Text.OutlinestyleColor: "#000000"textFormat: Text.StyledTexttext: qsTr("BALLS BONUS <b>%1</b>").arg(ballsBonus)
opacity: 0
}
Text {
id: totalScoreTextanchors.horizontalCenter: parent.horizontalCentery: ballsBonusText.y+opacity*60*pxfont.pixelSize: 66*pxcolor: "#ffffff"style: Text.OutlinestyleColor: "#000000"textFormat: Text.StyledTexttext: qsTr("SCORE <b>%1</b>").arg(score+timeBonus+ballsBonus)
opacity: 0
}
}
RoundButton {
anchors.horizontalCenter: parent.horizontalCenteranchors.bottom: parent.bottomanchors.bottomMargin: 40*pxwidth: 140*pxheight: 60*pxvisible: !gameOnfont.pixelSize: 26*pxtext: qsTr("START")
onClicked: {
view3D.startGame();
}
background: Rectangle {
border.color: "#000000"border.width: 2*pxradius: height/2
}
}
}