Skip to content

Creating a Virtual Reality App

VR has grown over the past few years as the number of compatible devices increase. There are a ton of uses for it, both practical and for entertainment. If you know JavaScript, you can even start making your own VR apps right in the browser.

In this tutorial, we’re going to make a quick search and find game. There will be a few objects hidden around the world and the player will have to find them all to win. We’ll be using Redwood and A-frame to handle all of our VR and user experience needs.

We’ll start by making a new Redwood app. In a terminal, run the following command.

yarn create redwood-app vr-in-redwood

This bootstraps a new Redwood app with a lot of folders and files that have been auto-generated. We’re going to start on the front-end so that we jump into the VR part. All of our front-end code is in the web directory.

We’re going to a new page called World and it will point to the root of the app. To create this page, we’ll run this command.

yarn rw g page world /

After this finishes, go to the web > src > pages directory and you’ll see a WorldPage folder. It has the code for the home page and it has a few other files to help with testing. If you take a look at Routes.js, you’ll also notice the new routes have automatically added.

We need to add Aframe to the project because this is the library we’re going to use to make our VR world. Import this library in the index.html file with the following line at the end of the <head> element.

<script src="https://aframe.io/releases/1.0.4/aframe.min.js"></script>
Code language: HTML, XML (xml)

Using this import, we have access to the different Aframe components available in the library. We can start building our new world in the WorldPage component. Open that file and add the following code.

You can delete the import and the current contents of the return statement inside of the WorldPage component. We won’t be using any of the template code.

const WorldPage = () => {
  return (
    <a-scene>
      <a-assets>
        <img
          id="room"
          crossorigin="anonymous"
          src="https://res.cloudinary.com/milecia/image/upload/room-360_nag5ns.jpg"
        />
      </a-assets>
      <a-sky id="image-360" radius="10" src="#room"></a-sky>
      <a-camera look-controls-enabled={true}></a-camera>
    </a-scene>
  )
}

export default WorldPage
Code language: JavaScript (javascript)

This is what your WorldPage component should look like now. We’re using a few of the Aframe components.

  • <a-scene> creates the entire world for the VR app.
  • <a-assets> is how we import external resources, like images and audio files, into the world.
  • <a-sky> uses a picture to create the background for the world. This is how you can create a static environment for your world if you don’t need the user to move around much.
  • <a-camera> is how we add a camera to the world so that a user can look around the world.

You can learn more about how the Aframe library and components work by checking out their docs.

Right now there’s a placeholder image that drops users into a nice room, but you’ll probably want something different for your app. We’ll use Cloudinary to host the images because that’ll decrease our load time and we won’t have to deal with a lot of large files.

So you can go to the Cloudinary site and sign up for a free account and upload any panoramic images you want to use. Then you can update the src for the image in the <a-assets> element.

You’ll need to update milecia in the asset URL to match the cloud name for your Cloudinary account so that you can use your images.

Since we have the option to upload as many images as we want, users might like it if they can switch between images and have their own worlds load when they come to the app.

We can add this by creating a new variable that will come from the back-end we’ll be making in a bit. We’ll start by adding a few GraphQL methods. Import a method from Redwood at the top of the WorldPage component file.

import { useQuery } from '@redwoodjs/web'
Code language: JavaScript (javascript)

Then we’ll add a call to that method inside of the component.

const { loading, data } = useQuery(WORLDS)
Code language: JavaScript (javascript)

Now we need to add the GraphQL definition for the query. So at the bottom of the component, above the export statement, add the following code.

const WORLDS = gql`
  query Worlds {
    worlds {
      id
      imageName
    }
  }
`
Code language: JavaScript (javascript)

With our GraphQL request defined, let’s update the component to use our new data. First, we’ll add a loading state so that we don’t have issues while data is being fetched. Below the useQuery line, add the following lines.

if (loading) {
  return <div>Loading...</div>
}
Code language: JavaScript (javascript)

Below this, we’ll add a new variable that will contain the URL users have recently uploaded for the world. It’ll default to an image if there isn’t a user-selected one to load.

const worldUrl = data?.worlds[data.worlds.length - 1].imageName || 'room-360_nag5ns.jpg'
Code language: JavaScript (javascript)

Then we’ll make the URL dynamic by updating the URL in the assets.

<img
  id="room"
  crossorigin="anonymous"
  src={`https://res.cloudinary.com/milecia/image/upload/${worldUrl}`}
/>
Code language: HTML, XML (xml)

With all of this in place, you can finally run the app with this command.

yarn rw dev

You should see something similar to this.

room VR world

Now we’ll add the back-end and database setup to support the front-end we just created.

Go to the api > db directory and open schema.prisma. This is where we’ll add the schema to save the URL that the user wants for their world. We’re going to update the provider to use a Postgres database.

provider = "postgresql"
Code language: JavaScript (javascript)

Then we’ll update the existing placeholder schema with our real schema. You can replace the UserExample schema with the following.

model World {
  id    Int     @id @default(autoincrement())
  imageName String
}
Code language: JavaScript (javascript)

Before we run the migration, we’ll need to update the .env file to use the database instance you want. You can set up Postgres locally. Update your DATABASE_URL with your credentials. It might look similar to this.

DATABASE_URL=postgres://postgres:admin@localhost:5432/vr_worlds

With the schema in place, we’ll be able to do our first migration.

yarn rw prisma migrate dev

This will make Prisma set up our new database. You’ll be prompted to name your migration and then it will run. If you check your Postgres instance now, you should see the new table there.

All that’s left is to create the GraphQL types and resolvers. The great thing about Redwood is that it has a command to generate these things for us.

yarn rw g sdl world

Now if you go to api > src > graphql, you’ll see worlds.sdl.js with all of the types you need for GraphQL. Then if you go to api > src > services, you’ll see a new worlds folder with a few files. The worlds.js file has the one resolver that we need to fetch the data on the front-end.

That’s all! Now you have a full-stack VR app that works.

You can check out the finished code in this Code Sandbox or in this GitHub repo in the ’vr-in-redwood` folder.

Hopefully, you can see how quickly you can create a new VR app in the JavaScript ecosystem. One thing that could be added to this app is the actual ability for users to push their preferred world in. This is a little tricky, but not terribly hard. You can definitely add that functionality as a challenge if you want to get more into VR.

Back to top

Featured Post