This video features the following 3D object under CC-BY-4.0: Fantasy_Island by Omar Faruq Tawsif and Finding titles is nightmare by Duhan.
In this tutorial, you’ll build a simple game. Starting with 3 lives, the user scores points when they tap a blue alien and loses lives when they tap a pink alien. You'll learn:
Download the example project to follow along.
Inside the unfinished project we’ve already positioned the game assets in the scene, including a 2D stack containing empty text. We'll populate these later using a script.
The project includes a customized plane-tracker block. This block lets the player manipulate the position, rotation and scale of the World object in their scene before triggering a 3 second countdown. After the countdown, the World object stays in position even if the player taps the screen.
Now we’ve set up our scene, we can focus on scripting. First, create a new JavaScript file and open it in Visual Studio Code.
We want to load in:
Add this code to your script:
const Scene = require("Scene"); const Patches = require("Patches"); const Persistence = require("Persistence"); const Reactive = require("Reactive"); const Diagnostics = require('Diagnostics');
The text in the unfinished project is empty. Next, we’ll use a script to access and populate the livesText and scoreText.
In the previous lessons, we fetched assets one at a time using the async/await keywords. This time, we need to access several assets and signals, so we’ll wrap the operation in a Promise.all()
method.
This method takes an iterable of all the objects to be found as an input, and returns a single promise that resolves to list the results of the input promises.
(async function () { // Enables async/await in JS [part 1] // To access scene objects const [livesText, scoreText, highScoreText] = await Promise.all([ Scene.root.findFirst('livesText'), Scene.root.findFirst('scoreText'), Scene.root.findFirst('highScoreText'), ]); })(); // Enables async/await in JS [part 2]
Now we’re ready to populate the text objects. We’ll use the text
property to set the livesText to 3 and the scoreText to 0.
Add this code to your script, below the promise:
livesText.text = "Lives: 3", scoreText.text = "Score: 0"
All together, your script will now look like this:
(async function () { // Enables async/await in JS [part 1] // To access scene objects const [livesText, scoreText, highScoreText] = await Promise.all([ Scene.root.findFirst('livesText'), Scene.root.findFirst('scoreText'), Scene.root.findFirst('highScoreText'), ]); livesText.text = "Lives: 3", scoreText.text = "Score: 0" })(); // Enables async/await in JS [part 2]
Your script now controls the LIVES and SCORE value that appears on screen:
We’re now ready to set up the interactive gameplay using Script to Patch Bridging. This lets us pass information between the script and the Patch Editor using shared variables and the methods provided by the Patches Module API.
In our gameplay, once the player positions the World object and the countdown is over, we want them to tap the blue aliens and score points.
To set this up:
Now, every time the player taps blueAlien1, a pulse signal is emitted. We want to send this signal to the script.
To send the pulse variable from the Patch Editor:
Your patch graph will look like this:
To access the pulse signals sent from the Patch Editor in a script, use the get methods provided by the PatchesOutputs Module.
Inside the Promise.all
method, add a variable called blueAlien1
and blueAlien2
and two additional lines of code that fetch those signals:
// Added new variables const [livesText, scoreText, highScoreText, blueAlien1, blueAlien2] = await Promise.all([ Scene.root.findFirst('livesText'), Scene.root.findFirst('scoreText'), Scene.root.findFirst('highScoreText'), // get signals from patch editor Patches.outputs.getPulse('BlueAlien1'), Patches.outputs.getPulse('BlueAlien2'), ]);
The Patches.outputs.getPulse
signal emits an eventSource that can be subscribed to and that invokes a function every time the event (the pulse) is emitted.
In our game, we want to count the number of times the player taps the target and increment the score accordingly. To do this, we’ll add a reusable block of code, called a function.
Add the score points function to your script, just below the promise method. This one piece of code can be used multiple times by multiple targets:
let score = 0; function scorePoints() { score++; Diagnostics.log('score: ' + score) } blueAlien1.subscribe(scorePoints), blueAlien2.subscribe(scorePoints)
Here, we have:
let score = 0
.score++
.scorePoints
callback function every time the blueAlien1.subscribe(scorePoints)
and blueAlien2.subscribe(scorePoints)
pulse is emitted.So far, your complete script will look like this:
const Scene = require("Scene"); const Patches = require("Patches"); const Persistence = require("Persistence"); const Reactive = require("Reactive"); const Diagnostics = require('Diagnostics'); (async function () { // Enables async/await in JS [part 1] // To access scene objects const [livesText, scoreText, highScoreText, blueAlien1, blueAlien2] = await Promise.all([ Scene.root.findFirst('livesText'), Scene.root.findFirst('scoreText'), Scene.root.findFirst('highScoreText'), // get signals from patch editor Patches.outputs.getPulse('BlueAlien1'), Patches.outputs.getPulse('BlueAlien2') ]); // Set initial UI texts livesText.text = "Lives: 3", scoreText.text = "Score: 0" // Set initial variables let score = 0; // score points incrementally function scorePoints() { score++; Diagnostics.log('Score: ' + score) } // execute function every time a signal // is sent from the patch editor to the script blueAlien1.subscribe(scorePoints) blueAlien2.subscribe(scorePoints) })(); // Enables async/await in JS [part 2]
By using the Diagnostics Module to print the variable score
in the console — Diagnostics.log('Score: ' + score)
— we see it increasing on each object tap:
Similarly, we want the player to lose a life every time they tap the wrong target, the pink alien. In other games, this might happen when the character hits an obstacle or collides with a projectile from an enemy.
Select pinkAlien1 in the Scene panel, then:
Your patch graph looks like this:
Now, we're ready to make some changes to our script:
// To access scene objects const [livesText, scoreText, highScoreText, blueAlien1, blueAlien2, pinkAlien1] = await Promise.all([ Scene.root.findFirst('livesText'), Scene.root.findFirst('scoreText'), Scene.root.findFirst('highScoreText'), // get signals from patch editor Patches.outputs.getPulse('BlueAlien1'), Patches.outputs.getPulse('BlueAlien2'), Patches.outputs.getPulse('PinkAlien1') ]); // Set our initial numbers of life to 3 let lives = 3; // lose lives decrementally function loseLives() { lives--; Diagnostics.log('Lives : ' + lives) } // execute loseLives( ) every time a pinkAlien1 signal // is sent from the patch editor to the script pinkAlien1.subscribe(loseLives)
You'll see that we have added pinkAlien1
to the promise and signals section of our script. We also:
let lives = 3;
loseLives()
function that changes life decrementally: lives--
pinkAlien1
pulse is detected: pinkAlien1.subscribe(loseLives)
We also use Diagnostics.log('Lives: ' + lives)
to log this event in the console:
If the player continues to tap the pink alien, the lives will continue to decrease into minus numbers. You don’t need to worry about this, as our game will end and UI text will be hidden when the number of lives reaches 0.
So far, our script looks like this:
(async function () { // Enables async/await in JS [part 1] // To access scene objects const [livesText, scoreText, highScoreText, blueAlien1, blueAlien2, pinkAlien1] = await Promise.all([ Scene.root.findFirst('livesText'), Scene.root.findFirst('scoreText'), Scene.root.findFirst('highScoreText'), // get signals from patch editor Patches.outputs.getPulse('BlueAlien1'), Patches.outputs.getPulse('BlueAlien2'), Patches.outputs.getPulse('PinkAlien1'), ]); // Set initial UI texts livesText.text = "Lives: 3", scoreText.text = "Score: 0" // Set initial variables let score = 0; let lives = 3; // score points incrementally function scorePoints() { score++; Diagnostics.log('Score: ' + score) } // lose lives decrementally function loseLives() { lives--; Diagnostics.log('Lives: ' + lives) } // execute function every time a signal // is sent from the patch editor to the script blueAlien1.subscribe(scorePoints); blueAlien2.subscribe(scorePoints); pinkAlien1.subscribe(loseLives); })(); // Enables async/await in JS [part 2]
In part two of this patch bridging and AR games tutorial, you'll update your project to: