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

Durdles - 2-Player Action Game

 import QtQuick 2.0
 import Felgo 4.0
 import "../common" as Common
 import ".."
 import "../entities"
 import "../entities/powerUps"
 import "../scenes"

 // the movement field limits the player's control area to half the scene
 Rectangle {
   id: movement
   radius: GameInfo.radius
   opacity: GameInfo.opacity
   color: "transparent"
   focus: true

   // target player and tank is set from outside
   property var player
   property var tank
   property alias playerMovementImage: playerMovementImage
   property double originX
   property double originY

   // this image visualizes the controller and is used to calculate the player's movement
   Image {
     id: playerMovementImage
     source: Qt.resolvedUrl("../../assets/img/Control.png")
     opacity: GameInfo.testLevel ? 100 : 0
     z: 5
     width: 90
     height: 90
     x: originX
     y: originY
   }

   // the MultiPointTouchArea covers half the screen and is disabled when the game is paused
   MultiPointTouchArea {
     z: -1
     enabled: GameInfo.gamePaused ? false : true
     anchors.fill: parent

     // the images face the wrong direction and need to be rotated at the start of the game
     property bool rotateOnce: true

     // the referencePoint is the center of the circular controller pad within the MultiPointTouchArea
     property int referencePointX: 0
     property int referencePointY: 0

     // after releasing the mouse or finger, a new reference point is needed
     property bool didRegisterReferencePoint: false;

     // access the player's controller to add the calculated movement
     property variant playerTwoAxisController: tank.getComponent("TwoAxisController")

     // newPos is a point calculated from the center of the pad
     // the values are converted and vary around 1 (= border of the circular pad)
     // it is used to calculate the player's velocity
     // oldPos is the previous position used to calculate the movement on the frozen lake
     property real newPosX: 0.0
     property real newPosY: 0.0
     property real oldPosX: 0.0
     property real oldPosY: 0.0

     touchPoints: [
       TouchPoint {id: fieldPoint}
     ]

     onUpdated: {
       // if the player is being moved, play his animation and don't slow him down
       tank.circleCollider.linearDamping = 0
       tank.tankBody.playing = true

       // calculate the difference between the current field point and the reference point
       // the result is a value around 1; higher values are outside of the circle
       newPosX = ((fieldPoint.x - referencePointX + playerMovementImage.width / 2) / (playerMovementImage.width / 2) - 1)
       newPosY = ((fieldPoint.y - referencePointY + playerMovementImage.height / 2) / (playerMovementImage.height / 2) - 1)

       // distance between the center of the circle and the radius
       var distance = Math.sqrt((newPosX*newPosX) + (newPosY*newPosY))

       // if no referencePoint is loaded yet, get one
       if (didRegisterReferencePoint == false) {
         // note that there is a reference point
         didRegisterReferencePoint = true;

         // save new reference point
         referencePointX = fieldPoint.x;
         referencePointY = fieldPoint.y;

         // reposition the control image
         updatePlayerMovementImagePosition()
         return;
       }

       // if the new point is outside of the movement control circle
       if (distance > 1) {
         // angle is used to find a reference point at the border of the circular pad
         // calculate the angle between two points (zero and newPos) and convert it in degrees and the right coordinate system
         var angle = (Math.atan2(newPosX, newPosY) * 180 / Math.PI) - 180
         angle = angle * (-1)
         angle -= 90

         // find a new reference point at the border of the circular pad
         // start from the old referencePoint and pick a new point with radius distance in the right direction
         var newX = (playerMovementImage.width / 2) * Math.cos((angle) * Math.PI / 180) + referencePointX
         var newY = (playerMovementImage.height / 2) * Math.sin((angle) * Math.PI / 180) + referencePointY

         // calculate the difference between the border reference point and the point outside of the pad
         var diffX = fieldPoint.x - newX
         var diffY = fieldPoint.y - newY

         // calculate the new moved center of the cicular pad
         // make sure the new reference point leaves enough space (radius) for the circular pad or image
         // leave at least radius distance to each border
         if ((referencePointX + diffX) <= (playerMovementImage.width / 2)){
           referencePointX = playerMovementImage.width / 2
         }else if ((referencePointX + diffX) >= (movement.width - playerMovementImage.width/2)){
           referencePointX = movement.width - playerMovementImage.width/2
         }else{
           referencePointX += diffX
         }

         if ((referencePointY + diffY) <= playerMovementImage.height / 2){
           referencePointY = playerMovementImage.height / 2
         }else if ((referencePointY + diffY) >= (movement.height - playerMovementImage.height / 2)){
           referencePointY = movement.height - playerMovementImage.height / 2
         }else{
           referencePointY += diffY
         }
       }

       // reposition the control image according to the mouse or finger movement
       updatePlayerMovementImagePosition()

       // clamp the values between -1 and 1
       newPosY = newPosY * -1
       if (newPosX > 1) newPosX = 1
       if (newPosY > 1) newPosY = 1
       if (newPosX < -1) newPosX = -1
       if (newPosY < -1) newPosY = -1

       // recalculate the clamped values if the player is on the lake by adding old positionn values
       // clamp the values between -1 and 1
       if ((player.onLake)){
         newPosX = oldPosX +(newPosX * 0.03)
         newPosY = oldPosY +(newPosY * 0.03)
         if (newPosX > 1) newPosX = 1
         if (newPosY > 1) newPosY = 1
         if (newPosX < -1) newPosX = -1
         if (newPosY < -1) newPosY = -1
       }

       // update the movement
       updateMovement()
     }

     function updatePlayerMovementImagePosition() {
       // referencePoint is the center, subtract the radius to get the top left corner of the image
       var newX = referencePointX - playerMovementImage.width / 2;
       var newY = referencePointY - playerMovementImage.height / 2;

       // the value has to be positive to have the image inside of the field
       // the value has to be small enough to be inside of the field, leave at least the width of the image
       newX = Math.max(0, newX);
       newX = Math.min(movement.width - playerMovementImage.width, newX);
       newY = Math.max(0, newY);
       newY = Math.min(movement.height - playerMovementImage.height, newY);

       // assign the new position
       playerMovementImage.x = newX;
       playerMovementImage.y = newY;
     }

     // slows down the character after removing the finger from the tablet / releasing the mouse
     function damping(){
       if (!player.onLake){
         tank.circleCollider.linearDamping = GameInfo.damping
       }
     }

     // updates the speed and direction of the character
     function updateMovement(){
       // store the x and y values before they get be altered
       oldPosX = newPosX
       oldPosY = newPosY

       // Adjust the speed
       newPosX = newPosX * GameInfo.maximumPlayerVelocity
       newPosY = newPosY * GameInfo.maximumPlayerVelocity

       // calculate the distance from the center ( = speed) with the pythagoras
       var velocity = Math.sqrt(newPosX * newPosX + newPosY * newPosY)
       var maxVelocity = GameInfo.maximumPlayerVelocity

       // increase the player's speed if he has the accelerator powerup
       if (player.activateAccelerator){
         maxVelocity *= GameInfo.speed
         newPosX *= GameInfo.speed
         newPosY *= GameInfo.speed
       }

       // normalize the speed; the horizontal and vertical maximum distance to the center is 1
       // since it is a square MultiPointTouchArea, the diagonally distances to the center can be larger than 1
       // normalize the values to get a uniform maximum speed
       if (velocity > maxVelocity) {
         var shrinkFactor = maxVelocity / velocity
         newPosX = newPosX * shrinkFactor
         newPosY = newPosY * shrinkFactor
       }

       // now update the twoAxisController with the calculated values
       // the twoAxisController moves the player with this input
       playerTwoAxisController.xAxis = newPosX
       playerTwoAxisController.yAxis = newPosY

       // calculate the current angle to rotate the tank image
       var angle = calcAngle(newPosX, newPosY) - 90
       if (newPosX !=0 && newPosY != 0){
         tank.tankBody.rotation = angle
         tank.circleCollider.rotation = angle
         if (GameInfo.easyMode)tank.tankCannon.rotation = angle + 90
       }
     }

     // the user released the mouse or lifted the finger
     onReleased: {
       // the next input will be a new touch with a new referene point
       didRegisterReferencePoint = false;

       // stop the player's animation
       tank.tankBody.playing=false

       // slow down the character until it stops
       damping()
     }

     onEnabledChanged: {
       // rotate the tankCannon image once because of the image orientation
       if (rotateOnce){
         tank.tankCannon.rotation = 90
         if(player.isOfType("playerBlue")){
           tank.tankBody.rotation = 180
           tank.tankCannon.rotation = 270
         }
         rotateOnce = false
       }
     }
   }

   // calculate the angle between a zero point and a second one
   function calcAngle(touchX, touchY) {
     return -180 / Math.PI * Math.atan2(touchY, touchX)
   }

   function reset(){
     playerMovementImage.x = originX;
     playerMovementImage.y = originY;
   }

   // slow down the tank after sliding off the frozen lake
   Connections {
     target: gameScene

     function onOffLake(playerVariationType) {
       console.debug("Signal offLake")
       if (player.variationType === playerVariationType){
         if (player.onLake === false && tank.tankBody.playing === false){
           tank.circleCollider.linearDamping = GameInfo.damping
         }
       }
     }
Qt_Technology_Partner_RGB_475 Qt_Service_Partner_RGB_475_padded