Published
- 7 min read
Creating a simple AR game using AR.js

During university, I was required to make a gamification feature for a retail website to increase the number of customers visiting the store. In the real world, this may not be the scenario as many businesses encourage customers to use their mobile applications or the website.
However, for this scenario instead of a normal game, I decided to create an AR game where users will have to find hidden markers around the store. In this article, I will cover how to create a simple game exclusive of the logic behind the scoring system.
Create a blank HTML project
Once the project is created you need to add these scripts
<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="https://aframe.io/releases/0.8.0/aframe.min.js"></script>
<script src="https://jeromeetienne.github.io/AR.js/aframe/build/aframe-ar.js"></script>
This will import Jquery, AFrame and Ar.js
Once these are imported you need to create a few markers depending on the scale of the game, for my scenario I created 8 markers in total
- 4 Markers that will give the user with quests
- 4 Markers that will allow user to complete the quests
Use this site to train the markers so that the application will be able to recognize it later when we scan them, these are the markers that I created.
Markers — Quests
Markers — Task
Once the markers are trained we need to initialize AR.js in a scene tag, to do this add the following html tags.
<a-marker id="sbeve-marker" type="pattern" url="./builder-markers/people/sbeve.patt">
</a-marker>
For this you need to provide a few attributes
- The id
- The types (barcode, pattern, unknown)
- URL — The location of the patt file (the ones that were downloaded)
Once we add this the marker will be added and when we focus the marker on the camera you will be able to show any message you want. For my scenario, i’m going to add a 3d model whenever a marker is identified. To do this you need an entity tag
Entity tags are placeholder tags which allow you to plug components and give them an appearance within the frame.
<a-entity id="sbeve" gltf-model="url(./Resources/3dmodels/game/minecraft_-_steve/scene.gltf)" material="color: green"
rotation="0 180 0" position="0 0 0.5" scale="0.05 0.05 0.05"></a-entity>
For this you need to provide a few attributes
- ID
- URL for the 3d model
- Color(only if the model is not colored)
- Rotation- You can use this to face the model any way you need
- Position- You can use this place the model in any location in the screen
- Scale- To set the size for the model
Finally we need to add the camera. The camera component defines the perspective the user views the scene. The camera is usually added in a entity tag.
<a-entity camera></a-entity>
Once you add all these your final scene tag should look similar to this
<a-scene embedded arjs cursor="rayOrigin: mouse" accepts-clicks>
<a-marker id="sbeve-marker" type="pattern" url="./builder-markers/people/sbeve.patt">
<a-entity id="sbeve" gltf-model="url(./Resources/3dmodels/game/minecraft_-_steve/scene.gltf)"
material="color: green" rotation="0 180 0" position="0 0 0.5" scale="0.05 0.05 0.05"></a-entity>
</a-marker>
<a-entity camera></a-entity>
</a-scene>
This completes the HTML part of the application.
Create a two blank .js file
We will name them interaction.js and model.js
First we need to register a click component to the AFRAME. In this we will have two functions registered. Once to handle ‘touchend’ and one to handle ‘markerFound’
AFRAME.registerComponent('accepts-clicks', {
init: function() {
this.el.addEventListener('touchend', handleMarkerEvent);
this.el.addEventListener('markerFound', handleMarkerEvent);
},
tick: function() {
hideSpeechBubbleIfNoMarker();
}
});
Now we need a few components to get the logic of the game, which will be added in the model.js file
- Two arrays to hold the respective markers
- Function that will act as a constructor
- Function to show the dialogue specific to the scanner marker
- Function to show success dialogue
var citizens = [],
lostitems = [];
function ARModel(name, dialogue) {
this.name = name;
this.dialogue = dialogue;
}
ARModel.prototype.speak = function () {
return this.dialogue;
}
function Citizens(name, dialogue, lostitem, successDialogue) {
ARModel.call(this, name, dialogue);
this.lostitem = lostitem;
this.successDialogue = successDialogue;
}
Citizens.prototype = Object.create(ARModel.prototype);
function Tool(name, dialogue) {
ARModel.call(this, name, dialogue);
}
Tool.prototype = Object.create(ARModel.prototype);
Now we need to initialze the arrays with the data, here we will add the citizens and the tools/items which were lost. Then the array will be iterated and the items will be added to the two seperate arrays which were created before.
function initiateModels() {
var citizensArray = [
{
name: 'pewds',
dialogue: 'Hi there, I\'m pewds...! I have lost my chicken. Can you help me find it',
lostitem: new Tool('chicken', 'You have found pewds\'s chicken! Return it to pewds.'),
successDialogue: 'Thanks for finding my chicken! I have added a coupon to your account'
},
{
name: 'sbeve',
dialogue: 'Hi there, I\'m sbeve...! I have lost my cow. Can you help me find it ',
lostitem: new Tool('cow', 'You have found Biggie\'s blocks! Return it to Sbeve.'),
successDialogue: 'Thanks for finding my cow! I have added a coupon to your account'
},
{
name: 'merc',
dialogue: 'Hi there, I\'m merc...! I have lost my horse. Can you help me find it ',
lostitem: new Tool('horse', 'You have found Mercs\'s horse! Return it to Merc.'),
successDialogue: 'Thanks for finding my horse! I have added a coupon to your account'
},
{
name: 'sherrif',
dialogue: 'Hi there, I\'m the sherrif of minecraft...! The witch in custody has escaped. Can you help us capture her ',
lostitem: new Tool('witch', 'You have captured the witch! Return her to the sherrif.'),
successDialogue: 'Thanks for helping us capture the witch! I have added a coupon to your account'
},
{
name: 'meek',
dialogue: 'Hi there, I\'m meek... I have lost my pig. Can you help me find it',
lostitem: new Tool('pig', 'You have found Meek\'s pig!'),
successDialogue: 'Thanks for finding my pig! I have added a coupon to your account!'
}
];
citizensArray.forEach(function (citizen) {
citizens.push(new Citizens(citizen.name, citizen.dialogue, citizen.lostitem, citizen.successDialogue));
if (citizen.lostitem) lostitems.push(citizen.lostitem);
});
console.log('citizens', citizens);
console.log('lostitems', lostitems)
}
initiateModels();
Now we will go back to the interaction.js file to complete the interaction functions.
function toggleSpeechBubble(dialogue) {
var speechBubble = document.querySelector(".speech-bubble");
if (speechBubble.style.display === 'none' || !speechBubble.style.display) {
speechBubble.innerHTML = dialogue;
speechBubble.style.display = 'block';
} else {
speechBubble.style.display = 'none';
}
};
function toggleNotification() {
var speechBubble = document.querySelector(".notification");
if (speechBubble.style.display === 'none' || !speechBubble.style.display) {
speechBubble.style.display = 'block';
} else {
speechBubble.style.display = 'none';
}
}
toggleSpeechBubble function will toggle a speech bubble every-time a marker is scanned, showing the user the dialogue.
toggleNotification function will show a notification once a quest is completed.
Now add these 2 functions
function hideSpeechBubbleIfNoMarker() {
var speechBubble = document.querySelector(".speech-bubble");
var notification = document.querySelector(".notification");
if (speechBubble.style.display === 'none' || !speechBubble.style.display) return;
var shouldHide = true;
citizens.forEach(function (citizen) {
var citizenMarker = document.querySelector("#" + citizen.name + "-marker");
if (citizenMarker && citizenMarker.object3D.visible) shouldHide = false;
});
lostitems.forEach(function (lostitem) {
var lostitemMarker = document.querySelector("#" + lostitem.name + "-marker");
if (lostitemMarker && lostitemMarker.object3D.visible) shouldHide = false;
});
if (shouldHide) {
speechBubble.style.display = 'none';
if (notification.style.display === 'block') {
notification.style.display = 'none';
}
}
};
function handleMarkerEvent() {
citizens.forEach(function (citizen) {
var citizenMarker = document.querySelector("#" + citizen.name + "-marker");
if (citizenMarker && citizenMarker.object3D.visible) {
if (searchForBuilderTool(citizen)) {
toggleSpeechBubble(citizen.successDialogue);
updateProgress();
toggleNotification();
closeNotification = true;
} else {
toggleSpeechBubble(citizen.dialogue);
}
}
});
lostitems.forEach(function (lostitem) {
var lostitemMarker = document.querySelector("#" + lostitem.name + "-marker");
if (lostitemMarker && lostitemMarker.object3D.visible) {
toggleSpeechBubble(lostitem.dialogue);
if (!userState.hasBuilderTool(lostitem)) userState.addTool(lostitem);
}
});
}
function searchForBuilderTool(citizen) {
return userState.lostitems.some(function (lostitem) {
return lostitem.name === citizen.lostitem.name;
});
};
hideSpeechBubbleIfNoMarker function will check if any of the markers are set to visible, if any of the markers are visible the dialogue will be left on the screen, if no markers are visible the dialogue will be removed.
searchForBuilderTool function will search for the lost items that belong to the users.
handleMarkerEvent function will iterate through the citizens array and if the user has found the item, the user will be shown the success dialogue and a coupon will be added to the users account, else the user will be shown the quest stating which items they should find.
The final product should look similar to this