Action Range and spooky Sounds in an AR Ionic app with Wikitude
Augmented Reality worlds bring more possibilities than traditional websites. In a previous tutorial, we only triggered actions when the user selected a marker, this a basic interaction: user clicks on something, something else happens.
In this tutorial we will create an ActionRange which will trigger an alarm sound as soon as the user enters an area: our Danger Zone ;).
Some small changes
Let's start by updating our previous tutorial's code:
var poisToCreate = 1;
for (var i = 0; i < poisToCreate; i++) {
poiData.push({
"id": meetingPoints[i].id,
"longitude": centerPointLongitude,
"latitude": centerPointLatitude,
.
.
.
});
}
Only one POI will be used here, once you master your first danger zone, you can add more.
In order to test it quickly, the danger zone will use the user's current latitude and longitude.
Moving on to the Marker Class:
class Marker {
constructor (poiData) {
this.poiData = poiData;
this.RESIZE_DURATION = 2000;
this.animationIncreaseGroup = null;
this.animationDecreaseGroup = null;
this.DANGER_RADIUS = 5;
.
.
.
}
}
Starting slowly by setting up some new properties.
The animationIncreaseGroup and animationDecreaseGroup will be used to show two animations.
The first one will make the Drawable bigger and the other one smaller. They will be called one after the other to make our danger sign scarier!
Action Range and Danger Sound
When entering the danger zone, an alarm sound will be triggered.
In Wikitude a sound is initialized like this:
this.alarm = new AR.Sound(
"http://soundbible.com/mp3/BOMB_SIREN-BOMB_SIREN-247265934.mp3",
{
onLoaded: () => {
AR.logger.debug("sound loaded");
},
onError: () => {
AR.logger.debug("sound not loaded");
}
}
);
this.alarm.load();
At the moment Wikitude has some issues loading a sound from a local file.
Instead of placing our file in our Ionic project, for simplicity and shareability we will use a mp3 hosted on a remote server.
Moving on to the actionRange object:
this.markerLocation = new AR.GeoLocation(
poiData.latitude,
poiData.longitude,
poiData.altitude
);
var actionRange = new AR.ActionRange(this.markerLocation, this.DANGER_RADIUS, {
onEnter: () => {
AR.logger.debug("entered danger zone");
this.alarm.play(-1);
},
onExit: () => {
if (this.alarm.state === AR.CONST.STATE.PLAYING) {
this.alarm.stop();
}
}
});
The actionRange will be positioned using the markerLocation.
The DANGER_RADIUS value is set to five.
When entering those five meters, the alarm will start playing. Passing a negative number to the play method, will create an infinite sound loop.
Finally when exiting the actionRange, the alarm will stop, but only if the sound is playing.
Then we create some traditional Drawables:
this.markerDrawable = new AR.ImageDrawable(World.markerDrawableIdle, 2.5, {
zOrder: 0
});
this.descriptionLabel = new AR.Label(poiData.shortDescription, 1, {
zOrder: 1,
translate: {
y: -0.55
},
style: {
textColor: "#FFFFFF",
fontStyle: AR.CONST.FONT_STYLE.BOLD
}
});
this.directionIndicatorDrawable = new AR.ImageDrawable(
World.markerDrawableDirectionIndicator,
0.1,
{
verticalAnchor: AR.CONST.VERTICAL_ANCHOR.TOP
}
);
this.markerObject = new AR.GeoObject(this.markerLocation, {
drawables: {
cam: [this.markerDrawable, this.descriptionLabel],
indicator: this.directionIndicatorDrawable
}
});
this.initAnimations();
At the end of it, the animations are initialized.
Before attacking the animations, a quick detour to the updateDistance method:
updateDistance() {
this.descriptionLabel.text = "Danger " +
Math.round(this.markerObject.locations[0].distanceToUser()) + " m";
}
We are not testing this method anymore so let's round the value received from the distanceToUser method and add some text.
Animations
Back to the initAnimations method:
initAnimations () {
var drawableResizeIncreaseAnimationX = new AR.PropertyAnimation(this.markerDrawable,
'scale.x', null, 3, this.RESIZE_DURATION,
new AR.EasingCurve(AR.CONST.EASING_CURVE_TYPE.LINEAR));
var drawableResizeIncreaseAnimationY = new AR.PropertyAnimation(this.markerDrawable,
'scale.y', null, 3, this.RESIZE_DURATION,
new AR.EasingCurve(AR.CONST.EASING_CURVE_TYPE.LINEAR));
var drawableResizeDecreaseAnimationX = new AR.PropertyAnimation(this.markerDrawable,
'scale.x', null, 1, this.RESIZE_DURATION,
new AR.EasingCurve(AR.CONST.EASING_CURVE_TYPE.LINEAR));
var drawableResizeDecreaseAnimationY = new AR.PropertyAnimation(this.markerDrawable,
'scale.y', null, 1, this.RESIZE_DURATION,
new AR.EasingCurve(AR.CONST.EASING_CURVE_TYPE.LINEAR));
}
Four PropertyAnimations are created, the first two will increase the X and Y sizes of the drawable and the last ones will do the opposite.
Followed by the creation of the AnimationGroups:
this.animationDecreaseGroup = new AR.AnimationGroup(
AR.CONST.ANIMATION_GROUP_TYPE.PARALLEL,
[drawableResizeDecreaseAnimationX, drawableResizeDecreaseAnimationY],
{
onFinish: () => {
this.animationIncreaseGroup.start();
}
}
);
animationDecreaseGroup will linearly decrease the size of the Drawable.
Once it's finished, it will trigger the other AnimationGroup which will increase its size again.
Which is as follow:
this.animationIncreaseGroup = new AR.AnimationGroup(
AR.CONST.ANIMATION_GROUP_TYPE.PARALLEL,
[drawableResizeIncreaseAnimationX, drawableResizeIncreaseAnimationY],
{
onFinish: () => {
this.animationDecreaseGroup.start();
}
}
);
this.animationIncreaseGroup.start();
After both AnimationGroups are created, the animationIncreaseGroup can start its animations.
Here is the result (minus the sound):
Conclusion
ActionRanges bring more interactions, we can use them with other factors like time, number of people in the area, weather, whatever comes to our mind.
Playing a sound from a remote file is easy, however using a local file requires some other tricks.
Finally we used two AnimationGroups to create an infinite loop. 3D models can be created embedded animations using softwares like 3ds Max or SketchUp, however in this case it was faster and more interesting to create those animations by hand.