Effugium Escape Room VR Puzzle: How I Did It
Effugium Escape Room VR Puzzle: How I Did It
In conjunction with two others, I are designing an escape room experience and one of the puzzles lives inside virtual reality. To build this puzzle I used https://www.aframe.io. Aframe is a web framework used for building virtual reality experiences. It is mostly based on html and that is all I have used so far. Javascript can also be used to increase interactivity. This is the second prototype of the escape room and the VR puzzle. I used node live-server to develop.
https://wasgunnabut.github.io
Code Structure
The code structure is very similar to html. If the webvr app is not being embedded into a webpage the structure of the html is very similar to a traditional webpage. First, declare the html document. Then create a head and add in the Aframe release script so the browser knows to use Aframe. Then create a body and inside the body goes the a-scene tag. This is where all the Aframe html will go. The assets tag is used to preload any assets.
<html>
<head>
<script src="https://aframe.io/releases/0.7.0/aframe.min.js"></script>
</head>
<body>
<a-scene>
<assets>
</assets>
</a-scene>
</body>
a-sky
The puzzle is created using html so it was very simple to learn to use and I didn’t encounter any real struggles. The biggest hurdle I had to overcome was creating and working with 360 degree images, or equirectangular images, to be used for the sky. I tried to make my own using Photoshop but they did not look very good. So I ended up downloading some from Adobe’s stock photo library which worked well.
The code for the sky is very simple. I preloaded the seamless sky image using the Asset Management System. This is better for performance and ensures the asset is not missing.
<assets>
<img id="sky" src="/assets/SeamlessSky.jpeg">
</assets>
Next I create the sky using the <a-sky>
primitive. Aframe provides a wide variety of primitives which are just pre-built components. Each primitive has a wide variety of attributes that can be used to modify the primitive. Some common ones are position, scale, radius, color, opacity and rotation.
<a-sky src="#sky"></a-sky>
I simply use the src attribute to point to the seamless sky asset using it’s id.
This gives me the sky in my scene.
a-plane
Next I needed to create a floor so the player is not suspended in midair. I created a simple floor using the a-plane
primitive. By default the plane appears perpendicular to the floor so the rotation attribute is required to rotate the plane along the X axis. Next I scaled the plane so that it was large enough. Finally, I would need to texture the plane. I created a simple texture in Illustrator.
I preloaded it in <assets>
<assets>
<img id=floor src="/assets/grid.png">
<img id="sky" src="/assets/SeamlessSky.jpeg">
</assets>
and used the id floor with the SRC attribute to create my plane.
<a-plane rotation="-90 0 0" scale="30 30 30" src="#floor"></a-plane>
The Puzzle
The puzzle consists if three shapes that hide a digit. The shapes and digit correspond to a lock that the players need to unlock. To reveal the digit the player must focus the reticle on the shape, causing the shape to fade and the digit to be visible. To make the experience more dynamic, the shapes also moved. There are three different shapes with digits: a square, circle and triangle.
a-box
While there is a square primitive, the 2D nature of the primitive made it very difficult to see in the position I wanted it. By using a box the shape is visible from all angles. I needed several attributes with this and the final line of code looked like this.
<a-box color="#4d7de9" depth=".1" height="1" width="1" position="-3 2 -3" rotation="0 90 0">
To animate the motion I used <a-animation>
. This allowed me to create motion and change opacity when the reticle was focused on the shape. To indicate which shape to animate, I simply had to make the animation a child of the desired shape. Each animation does something specific and the attributes allow for controls like duration, direction, and when to begin. I had to create separate animations All said and done, the code looked like this.
<a-box color="#4d7de9" depth=".1" height="1" width="1" position="-3 2 -3" rotation="0 90 0">
<a-animation attribute="position" dur="3000" from="-3 2 -3" to="-3 2 3" direction="alternate" repeat="indefinite"></a-animation>
</a-box>
a-circle
Since the circle was visible from the camera location, and since the playres would not be able to move I was able to use the 2D primitive, a-cirlce
. I initially tried to use a-shere
but it was very difficult to hide text behind it. The animations are very similar to what I used for the box.
<a-circle position="2.99 5 -1" rotation="0 -90 0" color="#f5f941" radius=".5" metalness="0">
<a-animation attribute="position" dur="3000" from="2.99 5 -1" to="2.99 1 -1" direction="alternate" repeat="indefinite"></a-animation>
</a-circle>
a-triangle
Like the circle, the triangle was visible from the camera position and the only option to create a 3D triangle is the <a-tetrahedron>
. Like the sphere, this was a struggle to hide text behind. With animation the code looked like this.
<a-triangle position="1.5 1 1.49" rotation="0 180 0" color="#f53030">
<a-animation attribute="position" dur="3000" from="1.5 1 1.49" to="-3 1 1.49" direction="alternate" repeat="indefinite"></a-animation>
</a-triangle>
a-text
To create the digit I used the <a-text>
primitive. The only attributes I needed were the value and color attributes.
<a-text value="value" color="#000000"</a-text>
Since I wanted the text to move with the shapes I made all the text the child of the corresponding shape. I then added another shape in front of the text and added an opacity animation to that shape. When it was all put together it looked like this:
<a-box color="#4d7de9" depth=".1" height="1" width="1" position="-3 2 -3" rotation="0 90 0">
<a-animation attribute="position" dur="3000" from="-3 2 -3" to="-3 2 3" direction="alternate" repeat="indefinite"></a-animation>
<a-animation attribute="opacity" begin="mouseenter" from="100" to="0"></a-animation>
<a-animation attribute="opacity" begin="mouseleave" from="0" to="100"></a-animation>
<a-text value="N" color="#000000"</a-text>
</a-box>
<a-box color="#4d7de9" depth=".1" height="1" width="1" position="-3.1 2 -3" rotation="0 90 0">
<a-animation attribute="position" dur="3000" from="-3.1 2 -3" to="-3.1 2 3" direction="alternate" repeat="indefinite"></a-animation>
</a-box>
<a-circle position="3 5 -1" rotation="0 -90 0" color="#f5f941" radius=".5" metalness="0">
<a-animation attribute="position" dur="3000" from="3 5 -1" to="3 1 -1" direction="alternate" repeat="indefinite"></a-animation>
<a-text value="R" color="#000000"</a-text>
</a-circle>
<a-circle position="2.99 5 -1" rotation="0 -90 0" color="#f5f941" radius=".5" metalness="0">
<a-animation attribute="position" dur="3000" from="2.99 5 -1" to="2.99 1 -1" direction="alternate" repeat="indefinite"></a-animation>
<a-animation attribute="opacity" begin="mouseenter" from="100" to="0"></a-animation>
<a-animation attribute="opacity" begin="mouseleave" from="0" to="100"></a-animation>
</a-circle>
<a-triangle position="1.5 1 1.5" rotation="0 180 0" color="#f53030">
<a-animation attribute="position" dur="3000" from="1.5 1 1.5" to="-3 1 1.5" direction="alternate" repeat="indefinite"></a-animation>
<a-text value="L" color="#000000"</a-text>
</a-triangle>
<a-triangle position="1.5 1 1.49" rotation="0 180 0" color="#f53030">
<a-animation attribute="position" dur="3000" from="1.5 1 1.49" to="-3 1 1.49" direction="alternate" repeat="indefinite"></a-animation>
<a-animation attribute="opacity" begin="mouseenter" from="100" to="0"></a-animation>
<a-animation attribute="opacity" begin="mouseleave" from="0" to="100"></a-animation>
</a-triangle>
The Decoy Shapes
To make it a little harder to find the correct shapes I added five decoy shapes: a-cone
, a-decohedron
, a-torus-knowt
, a-tetrahedron
, a-octahedron
. Each had a similar animation for position.
<a-cone radius-bottom="1" radius-top=".1" position="0 2 -4" color="#d3f947">
<a-animation attribute="position" dur"=2000" from="0 2 -4" to="-3 0 -2" direction="alternate" repeat="indefinite"></a-animation>
</a-cone>
<a-dodecahedron radius=".5" position="0 4 0" color="#fcff6b">
<a-animation attribute="position" dur"=12000" from="0 4 0" to="3 1 2" direction="alternate" repeat="indefinite"></a-animation>
</a-dodecahedron>
<a-torus-knot arc="180" p="1" q="8" radius=".5" radius-tubular="0.05" position="-3 2 4" color="#B84A39">
<a-animation attribute="position" dur"=1000" from="-3 2 4" to="1 2 1" direction="alternate" repeat="indefinite"></a-animation>
</a-torus-knot>
<a-tetrahedron radius=".5" position="-3 1 -2" color="#FF926B">
<a-animation attribute="position" dur"=9000" from="-3 1 -2" to="0 5 -4" direction="alternate" repeat="indefinite"></a-animation>
</a-tetrahedron>
<a-octahedron color="#ff00a8" radius=".5" position="3 5 -4">
<a-animation attribute="position" dur"=17000" from="3 5 -4" to="6 1 -1" direction="alternate" repeat="indefinite"></a-animation>
</a-octahedron>
The Instructions
The last part is to create the instructions and have them vanish after a certain amount of time.
<a-text value="Look all around you. Examine each shape carefully." position="-2.3 1.5 -3.5" color="#ff1b1b">
<a-animation attribute="opacity" delay="30000" from="100" to="0"></a-animation>
</a-text>
Conclusion
We implemented the puzzle using google cardboard and it worked very well. As we continue to develop the escape room, the puzzle will begin to model the actual room, showing the player the location of a hidden object in the real world. You can access all of the code and assets here.
Demo Video
EscapeRoomPuzzle from Jim Murphy on Vimeo.