PySide QML Tutorial Advanced 2: Difference between revisions

From Qt Wiki
Jump to navigation Jump to search
No edit summary
(Rename category "LanguageBindings::PySide" -> "PySide")
 
(4 intermediate revisions by 3 users not shown)
Line 1: Line 1:
[[Category:LanguageBindings::PySide]]
 
 
[[Category:PySide]]


= PySide QML Advanced Tutorial 2: Populating the Game Canvas =
= PySide QML Advanced Tutorial 2: Populating the Game Canvas =
Line 5: Line 7:
== Generating the blocks in JavaScript ==
== Generating the blocks in JavaScript ==


Now that we’ve written some basic elements, let’s start writing the game.
Now that we've written some basic elements, let's start writing the game.


The first task is to generate the game blocks. Each time the New Game button is clicked, the game canvas is populated with a new, random set of blocks. Since we need to dynamically generate new blocks for each new game, we cannot use Repeater to define the blocks. Instead, we will create the blocks in JavaScript.
The first task is to generate the game blocks. Each time the New Game button is clicked, the game canvas is populated with a new, random set of blocks. Since we need to dynamically generate new blocks for each new game, we cannot use Repeater to define the blocks. Instead, we will create the blocks in JavaScript.
Line 11: Line 13:
Here is the JavaScript code for generating the blocks, contained in a new file, ''samegame.js''. The code is explained below.
Here is the JavaScript code for generating the blocks, contained in a new file, ''samegame.js''. The code is explained below.


<code><br />var blockSize = 40;<br />var maxColumn = 10;<br />var maxRow = 15;<br />var maxIndex = maxColumn * maxRow;<br />var board = new Array(maxIndex);<br />var component;
<code>
var blockSize = 40;
var maxColumn = 10;
var maxRow = 15;
var maxIndex = maxColumn * maxRow;
var board = new Array(maxIndex);
var component;
 
//Index function used instead of a 2D array
function index(column, row) {
return column + (row * maxColumn);
}
 
function startNewGame() {
//Delete blocks from previous game
for (var i = 0; i < maxIndex; i+'') {
if (board[i] != null)
board[i].destroy();
}


//Index function used instead of a 2D array<br />function index(column, row) {<br /> return column + (row * maxColumn);<br />}
//Calculate board size
maxColumn = Math.floor(background.width / blockSize);
maxRow = Math.floor(background.height / blockSize);
maxIndex = maxRow * maxColumn;


function startNewGame() {<br /> //Delete blocks from previous game<br /> for (var i = 0; i &lt; maxIndex; i+'') {<br /> if (board[i] != null)<br /> board[i].destroy();<br /> }
//Initialize Board
<br /> //Calculate board size<br /> maxColumn = Math.floor(background.width / blockSize);<br /> maxRow = Math.floor(background.height / blockSize);<br /> maxIndex = maxRow * maxColumn;
board = new Array(maxIndex);
<br /> //Initialize Board<br /> board = new Array(maxIndex);<br /> for (var column = 0; column &lt; maxColumn; column) {<br /> for (var row = 0; row &lt; maxRow; row''+) {<br /> board[index(column, row)] = null;<br /> createBlock(column, row);<br /> }<br /> }<br />}
for (var column = 0; column < maxColumn; column) {
for (var row = 0; row < maxRow; row''+) {
board[index(column, row)] = null;
createBlock(column, row);
}
}
}


function createBlock(column, row) {<br /> if (component == null)<br /> component = Qt.createComponent(&quot;Block.qml&amp;quot;);
function createBlock(column, row) {
if (component == null)
component = Qt.createComponent("Block.qml");


// Note that if Block.qml was not a local file, component.status would be<br /> // Loading and we should wait for the component's statusChanged() signal to<br /> // know when the file is downloaded and ready before calling createObject().<br /> if (component.status  Component.Ready) &amp;#123;
// Note that if Block.qml was not a local file, component.status would be
// Loading and we should wait for the component's statusChanged() signal to
// know when the file is downloaded and ready before calling createObject().
if (component.status  Component.Ready) {
         var dynamicObject = component.createObject(background);
         var dynamicObject = component.createObject(background);
         if (dynamicObject  null) {<br /> console.log(&quot;error creating block&amp;quot;);<br /> console.log(component.errorString());<br /> return false;<br /> }<br /> dynamicObject.x = column * blockSize;<br /> dynamicObject.y = row * blockSize;<br /> dynamicObject.width = blockSize;<br /> dynamicObject.height = blockSize;<br /> board[index(column, row)] = dynamicObject;<br /> } else {<br /> console.log(&quot;error loading block component&amp;quot;);<br /> console.log(component.errorString());<br /> return false;<br /> }<br /> return true;<br />}<br /></code>
         if (dynamicObject  null) {
console.log("error creating block");
console.log(component.errorString());
return false;
}
dynamicObject.x = column * blockSize;
dynamicObject.y = row * blockSize;
dynamicObject.width = blockSize;
dynamicObject.height = blockSize;
board[index(column, row)] = dynamicObject;
} else {
console.log("error loading block component");
console.log(component.errorString());
return false;
}
return true;
}
</code>


The ''startNewGame()'' function deletes the blocks created in the previous game and calculates the number of rows and columns of blocks required to fill the game window for the new game. Then, it creates an array to store all the game blocks, and calls ''createBlock()'' to create enough blocks to fill the game window.
The ''startNewGame()'' function deletes the blocks created in the previous game and calculates the number of rows and columns of blocks required to fill the game window for the new game. Then, it creates an array to store all the game blocks, and calls ''createBlock()'' to create enough blocks to fill the game window.
Line 32: Line 83:
* If ''createObject()'' returned null (i.e. if there was an error while loading the object), print the error information.
* If ''createObject()'' returned null (i.e. if there was an error while loading the object), print the error information.
* Place the block in its position on the board and set its width and height. Also, store it in the blocks array for future reference.
* Place the block in its position on the board and set its width and height. Also, store it in the blocks array for future reference.
* Finally, print error information to the console if the component could not be loaded for some reason (for example, if the file is missing).<br />Connecting JavaScript components to QML¶
* Finally, print error information to the console if the component could not be loaded for some reason (for example, if the file is missing).
Connecting JavaScript components to QML¶


Now we need to call the JavaScript code in ''samegame.js'' from our QML files. To do this, we add this line to ''samegame.qml'' which imports the JavaScript file as a module:
Now we need to call the JavaScript code in ''samegame.js'' from our QML files. To do this, we add this line to ''samegame.qml'' which imports the JavaScript file as a module:


<code><br />import &quot;samegame.js&amp;quot; as SameGame<br /></code>
<code>
import "samegame.js" as SameGame
</code>


This allows us to refer to any functions within ''samegame.js'' using “SameGame” as a prefix: for example, ''SameGame.startNewGame()'' or ''SameGame.createBlock()''. This means we can now connect the New Game button’s ''onClicked'' handler to the ''startNewGame()'' function, like this:
This allows us to refer to any functions within ''samegame.js'' using "SameGame" as a prefix: for example, ''SameGame.startNewGame()'' or ''SameGame.createBlock()''. This means we can now connect the New Game button's ''onClicked'' handler to the ''startNewGame()'' function, like this:


<code><br /> Button {<br /> anchors { left: parent.left; verticalCenter: parent.verticalCenter }<br /> text: &quot;New Game&amp;quot;<br /> onClicked: SameGame.startNewGame()<br /> }<br /></code>
<code>
Button {
anchors { left: parent.left; verticalCenter: parent.verticalCenter }
text: "New Game"
onClicked: SameGame.startNewGame()
}
</code>


So, when you click the New Game button, ''startNewGame()'' is called and generates a field of blocks, like this:
So, when you click the New Game button, ''startNewGame()'' is called and generates a field of blocks, like this:

Latest revision as of 03:30, 5 June 2016


PySide QML Advanced Tutorial 2: Populating the Game Canvas

Generating the blocks in JavaScript

Now that we've written some basic elements, let's start writing the game.

The first task is to generate the game blocks. Each time the New Game button is clicked, the game canvas is populated with a new, random set of blocks. Since we need to dynamically generate new blocks for each new game, we cannot use Repeater to define the blocks. Instead, we will create the blocks in JavaScript.

Here is the JavaScript code for generating the blocks, contained in a new file, samegame.js. The code is explained below.

var blockSize = 40;
var maxColumn = 10;
var maxRow = 15;
var maxIndex = maxColumn * maxRow;
var board = new Array(maxIndex);
var component;

//Index function used instead of a 2D array
function index(column, row) {
 return column + (row * maxColumn);
}

function startNewGame() {
 //Delete blocks from previous game
 for (var i = 0; i < maxIndex; i+'') {
 if (board[i] != null)
 board[i].destroy();
 }

 //Calculate board size
 maxColumn = Math.floor(background.width / blockSize);
 maxRow = Math.floor(background.height / blockSize);
 maxIndex = maxRow * maxColumn;

 //Initialize Board
 board = new Array(maxIndex);
 for (var column = 0; column < maxColumn; column) {
 for (var row = 0; row < maxRow; row''+) {
 board[index(column, row)] = null;
 createBlock(column, row);
 }
 }
}

function createBlock(column, row) {
 if (component == null)
 component = Qt.createComponent("Block.qml");

// Note that if Block.qml was not a local file, component.status would be
 // Loading and we should wait for the component's statusChanged() signal to
 // know when the file is downloaded and ready before calling createObject().
 if (component.status  Component.Ready) {
        var dynamicObject = component.createObject(background);
        if (dynamicObject  null) {
 console.log("error creating block");
 console.log(component.errorString());
 return false;
 }
 dynamicObject.x = column * blockSize;
 dynamicObject.y = row * blockSize;
 dynamicObject.width = blockSize;
 dynamicObject.height = blockSize;
 board[index(column, row)] = dynamicObject;
 } else {
 console.log("error loading block component");
 console.log(component.errorString());
 return false;
 }
 return true;
}

The startNewGame() function deletes the blocks created in the previous game and calculates the number of rows and columns of blocks required to fill the game window for the new game. Then, it creates an array to store all the game blocks, and calls createBlock() to create enough blocks to fill the game window.

The createBlock() function creates a block from the Block.qml file and moves the new block to its position on the game canvas. This involves several steps:

  • Qt.createComponent() is called to generate an element from Block.qml. If the component is ready, we can call createObject() to create an instance of the Block item.
  • If createObject() returned null (i.e. if there was an error while loading the object), print the error information.
  • Place the block in its position on the board and set its width and height. Also, store it in the blocks array for future reference.
  • Finally, print error information to the console if the component could not be loaded for some reason (for example, if the file is missing).

Connecting JavaScript components to QML¶

Now we need to call the JavaScript code in samegame.js from our QML files. To do this, we add this line to samegame.qml which imports the JavaScript file as a module:

import "samegame.js" as SameGame

This allows us to refer to any functions within samegame.js using "SameGame" as a prefix: for example, SameGame.startNewGame() or SameGame.createBlock(). This means we can now connect the New Game button's onClicked handler to the startNewGame() function, like this:

 Button {
 anchors { left: parent.left; verticalCenter: parent.verticalCenter }
 text: "New Game"
 onClicked: SameGame.startNewGame()
 }

So, when you click the New Game button, startNewGame() is called and generates a field of blocks, like this: