Learn what Felgo offers to help your business succeed. Start your free evaluation today! Felgo for Your Business

StackTheBoxWithEditor Demo

 import Felgo 3.0 // for the gaming components
 import QtQuick 2.0 // for the Image element
  // for the MouseJoint
 import "entities"

 GameWindow {
   id: gameWindow
   //displayFpsEnabled: false

   // You get free licenseKeys from https://felgo.com/licenseKey
   // With a licenseKey you can:
   //  * Publish your games & apps for the app stores
   //  * Remove the Felgo Splash Screen or set a custom one (available with the Pro Licenses)
   //  * Add plugins to monetize, analyze & improve your apps (available with the Pro Licenses)
   //licenseKey: "<generate one from https://felgo.com/licenseKey>"

   activeScene: scene

   EntityManager {
     id: entityManager
     entityContainer: scene
     // required for LevelEditor, so the entities can be created by entityType
     dynamicCreationEntityList: [ Qt.resolvedUrl("entities/Obstacle.qml") ]
   }

   Rectangle {
     anchors.fill: parent
     color: "black"
   }

   // balancing settings for the MouseJoint
   property real maxForce: 30000
   property real dampingRatio: 1
   property real frequencyHz: 2

   // balancing properties for the game:
   property int minIntervalBetweenBoxGeneration: 1000
   property int maxIntervalBetweenBoxGeneration: 3000
   // these are the values the interval gets reduced each call
   property int minIntervalReduction: 10
   property int maxIntervalReduction: 15

   EditableComponent {
     // The default target: parent does not work when the EditableComponent is located directly in the GameWindow because the parent is not the actual GameWindow. Therefore, use the concrete GameWindow id assigned to the target.
     target: gameWindow
     editableType: "Balancing"
     defaultGroup: "Environment"
     properties: {
       "minIntervalBetweenBoxGeneration": {"min": 100, "max": 10000, "label": "minIntBetwBox"},
       "maxIntervalBetweenBoxGeneration": {"min": 100, "max": 50000, "label": "maxIntBetwBox"},
       "minIntervalReduction": {"min": 1, "max": 1000, "label": "minIntReduction"},
       "maxIntervalReduction": {"min": 1, "max": 1000, "label": "maxIntReduction"},
       "maxForce": {"min": 1, "max": 100000},
       "dampingRatio": {"min": 0, "max": 1,"stepsize": 0.1 },
       "frequencyHz": {"min": 0, "max": 100, "stepsize": 0.1 }
     }
   }

   // initialize with the default value, is reset every time the game restarts
   property int currentMinInterval: minIntervalBetweenBoxGeneration
   property int currentMaxInterval: maxIntervalBetweenBoxGeneration

   Scene {
     id: scene
     width: 480
     height: 320

     // gets increased when a new box is created, and reset to 0 when a new game is started
     // start with 1, because initially 1 Box is created
     property int createdBoxes: 1

     // this is the required minimum distance from the left and from the right (the scene.width)
     // when the box is rotated at 90°, its distance from the center is box1.width*Sqrt2, because width and height are the same
     // the hint about this issue was kindly provided by Martin Eigel
     property real safetyDistance: -1

     // gets set by Obstacle
     property variant selectedEntity

     // state: "levelEditing" enables dragging and clicking of obstacles
     // change the state in the next line to start in levelEditing mode:
     state: "playing" //state: "levelEditing"

     onStateChanged: {
       if(state === "levelEditing") {
         stopGame()
       }
     }

     function entitySelected(entity) {
       // reset the internal state of the old selected entity, to stop blinking of the selected obstacle
       if(selectedEntity && selectedEntity !== entity) {
         selectedEntity.entityState = ""
       }
       selectedEntity = entity
     }

     function stopGame() {
       // remove all entities of type "box", but not the walls
       entityManager.removeEntitiesByFilter(["box"]);
       // reset the createdBoxes amount
       scene.createdBoxes = 0;

       // reset the interval levels to ther start values
       currentMinInterval = minIntervalBetweenBoxGeneration
       currentMaxInterval = maxIntervalBetweenBoxGeneration
     }

     ItemEditor {
       id: itemEditor
       opacity: 0.7
       z:1
       // start visible
     }

     LevelEditor {
       id: levelEditor
       toRemoveEntityTypes: [ "obstacle" ]
       toStoreEntityTypes: [ "obstacle" ]
     }

     LevelSelectionList {
       id: levelSelectionList
       width: 150
       z: 3
       // at the beginning it is invisible, only gets visible after a click on the Levels button
       visible: false
       anchors.centerIn: parent
       levelMetaDataArray: levelEditor.authorGeneratedLevels

       onLevelSelected: {
         levelEditor.loadSingleLevel(levelData)
         // make invisible afterwards
         levelSelectionList.visible = false
       }
     }

     Text {
       z: 2
       text: "Current Level:" + levelEditor.currentLevelNameString
       anchors.horizontalCenter: parent.horizontalCenter
     }

     // display the amount of stacked boxes
     Text {
       text: "Boxes: " + scene.createdBoxes
       color: "white"
       z: 1 // put on top of everything else in the Scene
     }

     PhysicsWorld {
       id: physicsWorld
       gravity.y: 9.81 // make the objects fall faster
       z: 10 // draw the debugDraw on top of the entities
       debugDrawVisible: false

       // these are performance settings to avoid boxes colliding too far together
       // set them as low as possible so it still looks good
       updatesPerSecondForPhysics: 60
       velocityIterations: 5
       positionIterations: 5
     }

     Component {
       id: mouseJoint
       MouseJoint {
         // make this high enough so the box with its density is moved quickly
         maxForce: gameWindow.maxForce * physicsWorld.pixelsPerMeter
         // The damping ratio. 0 = no damping, 1 = critical damping
         dampingRatio: gameWindow.dampingRatio
         // The response speed
         frequencyHz: gameWindow.frequencyHz
       }
     }

     // when the user presses a box, move it towards the touch position
     MouseArea {
       anchors.fill: parent

       property Body selectedBody: null
       property MouseJoint mouseJointWhileDragging: null

       onPressed: {

         // set the state of the entitySelection of the selectedEntity to "", otherwise the fade animation is continuously played
         // this stops blinking of the old selected obstacle
         scene.entitySelected(null)

         selectedBody = physicsWorld.bodyAt(Qt.point(mouseX, mouseY));
         console.debug("selected body at position", mouseX, mouseY, ":", selectedBody);
         // if the user selected a body, this if-check is true
         if(selectedBody) {
           // create a new mouseJoint
           mouseJointWhileDragging = mouseJoint.createObject(physicsWorld)

           // set the target position to the current touch position (initial position)
           mouseJointWhileDragging.target = Qt.point(mouseX, mouseY)

           // connect the joint with the body
           mouseJointWhileDragging.bodyB = selectedBody
         }
       }

       onPositionChanged: {
         // this check is necessary, because the user might also drag when no initial body was selected
         if (mouseJointWhileDragging)
           mouseJointWhileDragging.target = Qt.point(mouseX, mouseY)
       }
       onReleased: {
         // if the user pressed a body initially, remove the created MouseJoint
         if(selectedBody) {
           selectedBody = null
           if (mouseJointWhileDragging)
             mouseJointWhileDragging.destroy()
         }
       }
     }

     Box {
       id: box1
       entityId: "box1"
       x: scene.width/2
       y: 50 // position a bit to the bottom so it doesn't collide with the top wall

       Component.onCompleted: {

         // initialize the safetyZoneHoriztonal after the box is known
         if(scene.safetyDistance == -1) {
           // add a little addtional offset, to avoid generation at the very border
           scene.safetyDistance = box1.width*Math.SQRT2/2 + leftWall.width + 5
           console.debug("init safetyZoneHorizontal with", scene.safetyDistance)
         }

       }

     }

     Wall {
       entityId: "bottomWall"
       height: 20
       anchors {
         bottom: scene.bottom
         left: scene.left
         right: scene.right
       }
     }

     Wall {
       entityId: "leftWall"
       id: leftWall
       width: 20
       height: scene.height
       anchors {
         left: scene.left
       }
     }

     Wall {
       entityId: "rightWall"
       width: 20
       height: scene.height
       anchors {
         right: scene.right
       }
     }
     Wall {
       entityId: "topWall"
       height: 20
       width: scene.width
       anchors {
         top: scene.top
       }
       color: "red" // make the top wall red
       onCollidedWithBox: {
         // gets called when the wall collides with a box, and the game should restart

         scene.stopGame()
       }
     }

     // for toggling audio and particles
     Column {
       anchors.right: parent.right

       spacing: 5
       SimpleButton {
         text: "Toggle Audio"
         onClicked: settings.soundEnabled = !settings.soundEnabled
         anchors.right: parent.right
       }
       SimpleButton {
         text: "Toggle Particles"
         onClicked: settings.particlesEnabled = !settings.particlesEnabled
         anchors.right: parent.right
       }

       SimpleButton {
         text: "ItemEditor"
         onClicked: itemEditor.visible = !itemEditor.visible
         anchors.right: parent.right
       }

       SimpleButton {
         text: scene.state === "playing" ? "Level Mode" : "Game Mode"
         onClicked: {
           if(text === "Level Mode")
             scene.state = "levelEditing"
           else
             scene.state = "playing"
         }

         anchors.right: parent.right
       }

       SimpleButton {
         text: "New Level"
         onClicked: levelEditor.createNewLevel()
         anchors.right: parent.right
         visible: scene.state === "levelEditing"
       }

       SimpleButton {
         text: "Save Level"
         onClicked: nativeUtils.displayTextInput("Enter levelName", "", levelEditor.currentLevelName)
         anchors.right: parent.right
         visible: scene.state === "levelEditing"

         Connections {
           target: nativeUtils
           onTextInputFinished: {
             if(accepted) {
               levelEditor.saveCurrentLevel( {levelMetaData: {levelName: enteredText}} )
             }
           }
         }
       }

       SimpleButton {
         text: "Show All Levels"
         anchors.right: parent.right
         visible: scene.state === "levelEditing"
         onClicked: {
           levelEditor.loadAllLevelsFromStorageLocation(levelEditor.authorGeneratedLevelsLocation)
           levelSelectionList.visible = !levelSelectionList.visible
         }
       }

       BuildEntityButton {
         visible: scene.state === "levelEditing"
         toCreateEntityTypeUrl: Qt.resolvedUrl("entities/Obstacle.qml")

         width: 50
         height: 50
         anchors.right: parent.right

         // the obstacle is just a grey entity, we can customize the look of the button here
         Rectangle {
           color: "grey"
           anchors.fill: parent
         }
       }
     }

     Timer {
       id: timer
       interval: generateRandomInterval()
       running: scene.state === "playing"// start running from the beginning, when the scene is loaded
       repeat: true // otherwise restart wont work

       onTriggered: {

         var newEntityProperties = {
           // vary x between [ safetyZoneHoriztonal ... width-safetyZoneHoriztonal]
           x: utils.generateRandomValueBetween(scene.safetyDistance, scene.width-scene.safetyDistance),
           y: scene.safetyDistance, // position on top of the scene, at least below the top wall
           rotation: Math.random()*360
         }

         entityManager.createEntityFromUrlWithProperties(
               Qt.resolvedUrl("entities/Box.qml"),
               newEntityProperties);

         // increase the createdBoxes number
         scene.createdBoxes++

         timer.interval = generateRandomInterval()

         // decrease with every box, so it gets harder the longer the user plays
         currentMinInterval -= minIntervalReduction
         currentMaxInterval -= maxIntervalReduction

         // restart the timer
         timer.restart()
       }

       function generateRandomInterval() {

         // recalculate new interval between 1000 and 3000, and it gets less with every call
         return utils.generateRandomValueBetween(currentMinInterval, currentMaxInterval);
       }
     }
Qt_Technology_Partner_RGB_475 Qt_Service_Partner_RGB_475_padded