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

How to make a game like Pong with Felgo - Special Effects

\contentspageHow to make a game like Pong with Felgo - Special Effects

Tutorial Chapters

  1. Overview
  2. Creation of a Felgo game
  3. GameWindow, Scenes and Physical Worlds
  4. The Level and the Ball
  5. Level Boundaries
  6. Paddles
  7. HUD
  8. Menus
  9. AI
  10. Special Effects
  11. Music and Sound
  12. Further Perspectives

Special Effects

Create a new folder called particles in your qml folder. Add a FireParticle.json which will be used for the respawn animation. The particle can be created with Particle Editor Demo but for the tutorial you can just copy following code into your file.

 { "FireParticle" : { "angleVariance" : 0, "blendFuncDestination" : 1, "blendFuncSource" : 770, "duration" : 0, "emitterType" : 0, "finishColor" : "#000000", "finishColorAlpha" : 0, "finishColorVariance" : "#000000", "finishColorVarianceAlpha" : 0, "finishParticleSize" : 45, "finishParticleSizeVariance" : 10, "gravity" : "0.0, 0.0", "maxParticles" : 42, "maxRadius" : 0, "maxRadiusVariance" : 0, "minRadius" : 0, "minRadiusVariance" : 0, "particleLifespan" : 0.9000000000000000222, "particleLifespanVariance" : 0.2000000000000000111, "positionType" : 0, "radialAccelVariance" : 0, "radialAcceleration" : 0, "rotatePerSecond" : 0, "rotatePerSecondVariance" : 0, "rotation" : 0, "rotationEnd" : 0, "rotationEndVariance" : 0, "rotationStart" : 0, "rotationStartVariance" : 0, "sourcePositionVariance" : "0.0, 0.0", "speed" : 85, "speedVariance" : 2, "startColor" : "#c2401f", "startColorAlpha" : 1, "startColorVariance" : "#000000", "startColorVarianceAlpha" : 0, "startParticleSize" : 7, "startParticleSizeVariance" : 2, "tangentialAccelVariance" : 0, "tangentialAcceleration" : 0, "textureFileName" : "particleFire.png", "x" : 0, "y" : 0 }}

Now the new particle effect needs to be added in the Level.qml after the background image and before the paddle items.

 ...

 import "particles"

 ...
   GameParticle {
     id: backgroundParticle
     anchors.centerIn: parent
     fileName: Qt.resolvedUrl("particles/FireParticle.json")
     speed: 0
   }


   // Player 1 is the right player
   Paddle {
 ...

The particle effect should start when the ball is restarted. Therefore, the particle needs to be started in the reStart() function and it will be stopped in the timer function used in Level.qml.

 ...
   Timer {
     id: startTimer
     interval: 1000;
     onTriggered: {
       ball.reStart(gameWindowAnchorItem.width/2, parent.height/2)
       backgroundParticle.stop()
       collisionSound.play()
     }
   }

   // Restarts the ball
   function reStart() {
     // move the ball to the middle and stop it
     ball.reset(gameWindowAnchorItem.width/2, parent.height/2)
     // restart ball after 1s
     startTimer.start()
     backgroundParticle.start()
   }
 ...

Now a small particle effect is triggered when the ball spawns.

It is very easy to add functionality. For instance, when the ball hits the wall it could increase the maximal speed or the angle of the ball hitting the paddle should be decreased. Change Ball.qml and add a collision function in the collision collider.

 ...
   property int speed: 400

   // collisions with obstacles
   property int collisions: 0

 ...

   bullet: true
   body.fixedRotation: true

   fixture.onBeginContact: {
     collisions++

     if(!(collisions%5)) {
       speed += collisions*5
     }

     var fixture = other;
     var body = other.getBody();
     var entity = body.target;
     var collidedEntityType = entity.entityType;
     var collidingType = entity.entityType;

     // The ball should act a little bit different, it should not be able to toggle between top and bottom.
     // Therefore, the angle needs to be calculated and adjusted with an impulse so it acts between the top and bottom border.
     if(collidingType === "paddle") {
       var normalX = contactNormal.x;
       var normalY = contactNormal.y;
       var localForward = circleCollider.body.linearVelocity;
       var newAngle = 0.0;

       if((normalX === 1) || (normalX === -1) ) {

         // ATTENTION: mention that atan2 requires arguments y, x and NOT x,y!
         //console.debug("atan2(y=0,x=1):", Math.atan2(0,1));
         //                var currentAngle = Math.atan2(localForward.y, localForward.x);
         //                currentAngle *= 180/Math.PI;
         //                console.debug("currentAngle:", currentAngle, "rotation:", rotation);

         // perform mirroring and calculate the new angle
         localForward.x*=-1.0;
         newAngle = Math.atan2(localForward.y, localForward.x);
       }
       // if normalY is -1 (from the ball to the target), this means the collision was pointing DOWNWARDS! mention that this is vice versa than the graphics system, with y axis pointing down not up!
       // a positive forward direction means going down (the positive y axis is pointing downwards)
       else if((normalY === -1 ) || (normalY === 1) ) {
         // perform mirroring and calculate the new angle
         localForward.y*=-1.0;
         newAngle = Math.atan2(localForward.y, localForward.x);
       }

       // convert from rad to deg
       newAngle *= 180/Math.PI;


       // Adjust ball speed with paddle speed
       speed+=Math.abs(component.owningEntity.paddleSpeed)

       // Adjust ball angle with paddle speed
       if(Math.abs(newAngle) < 90) { // left paddle
         if( newAngle < 0)
           newAngle-=Math.abs(component.owningEntity.paddleSpeed)*2
         else
           newAngle+=Math.abs(component.owningEntity.paddleSpeed)*2
       }
       else {// right paddle
         if( newAngle < 0)
           newAngle-=Math.abs(component.owningEntity.paddleSpeed)*2
         else
           newAngle+=Math.abs(component.owningEntity.paddleSpeed)*2
       }

       // limit angle from paddles
       if(Math.abs(newAngle) < 90) { // left paddle
         if(Math.abs(newAngle) > 65) {
           if( newAngle < 0)
             newAngle = -65
           else
             newAngle = 65
         }
         if(Math.abs(newAngle) < 5) {
           if( newAngle < 0)
             newAngle = -5
           else
             newAngle = 5
         }
       }
       else { // right paddle
         if(Math.abs(newAngle) > 155) {
           if( newAngle < 0)
             newAngle = -155
           else
             newAngle = 155
         }
         if(Math.abs(newAngle) < 110) {
           if( newAngle < 0)
             newAngle = -110
           else
             newAngle = 110
         }
       }
       // manually set the entity rotation, because it is the target and its rotation will be used for the physics body
       entity.rotation = newAngle;
       // ATTENTION: do NOT only set the new rotation to the entity, because the position isn't forwarded automatically!
       // rather, set it for the body, which will automatically set the entity rotation to the body's
       // it also must be set to the body, because otherwise the calculation by setting the linearVelocity and impulse below would not be done with the updated rotation value!
       circleCollider.body.rotation = newAngle;


       // this is important, otherwise the ball would get faster every time it collides with a paddle!
       circleCollider.body.linearVelocity = Qt.point(0,0);

       applyForwardImpulse(speed)
     }
   }
 ...

Now, the ball increases its speed every fifth hit of an obstacle. To make the speed change even more visible you could add a new particle which spawns when the speed is increased. The new particle can be added directly to the source, because you use the fire particle effect and only change some properties. Add some more particles so it looks good and remove the speed and add a duration. Finally you have to change the blending states and the colors to make it visible on the white background.

 ...
       speed += collisions*5
       speedParticle.start()
     }
 ...
     // Speed particle
     GameParticle {
       id: speedParticle
       fileName: Qt.resolvedUrl("../particles/FireParticle.json")

       speed: 0
       duration: 0.9
       startColor: Qt.rgba(1,1,1,1);

       blendFuncSource: 1
       blendFuncDestination: 771

       z: sprite.z-1
     }
 ...

Now the speed change is indicated by a small fire burst.

Qt_Technology_Partner_RGB_475 Qt_Service_Partner_RGB_475_padded