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.
Building the VR world
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.
Setting up the world
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>
Updating the component
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 follwing 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
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.
Pulling views from Cloudinary
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.
Adding customization
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'
Then we'll add a call to that method inside of the component.
const { loading, data } = useQuery(WORLDS)
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
}
}
`
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 fetch. Below the useQuery
line, add the following lines.
if (loading) {
return <div>Loading...</div>
}
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'
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}`}
/>
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.
Now we'll add the back-end and database setup to support the front-end we just created.
Setting up the back-end
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"
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
}
Running the migration
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 the your Postgres instance now, you should see the new table there.
Set up the GraphQL server
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.
Finished code
You can check out the finished code in this Code Sandbox or in this GitHub repo in the 'vr-in-redwood` folder.
Conclusion
Hopefully you can see how quickly you can create 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.
Top comments (1)
Thank you for the detailed explanation. The experts at iwanta.tech/outstaffing/ and I are in the process of building a VR app.