LATEST NEWS: for an AFRAME & file-format-agnostic version see the XR Fragments standard instead
A single-player-verse component for AFRAME:
TRY THE ONLINE DEMO
- ❤️ easily teleport between aframe apps & aframe-verse clusters
- ❤️ does not exit immersive-mode when navigating to different aframe experiences
- ❤️ standalone & serverless: no servers (NAF/signaling) needed
- ❤️ HTML-first: even runs from wordpress, no ninja javascript-skills needed
- ❤️ #networkless #decentralized #noblockchain #permissionless-first #federatedpullrequests
Similar to a WEBring, you can easily create DOMrings and VERSErings with this component, that can be Zuckerburgered by yourself (or friends).
<script src="aframe-verse-component.js"></script>
<a-scene>
<a-entity aframe-verse="register: /aframe-verse.json">
<!-- everything nested under `aframe-verse`, will be replaced upon navigation -->
<a-box href="/"></a-box> <!-- home = the cluster-client (index.html) -->
<a-box href="./app2.html"></a-box>
<a-box href="https://somefriend.com/some_aframe_app.html"></a-box>
<a-box href="https://somefriend.com/supercustom_webxr_app.html"></a-box>
<a-entity>
<!-- entities below survive during teleporting -->
<!-- use 'wearable' to indicate sticking objects to controller in immersive mode -->
<a-entity id="player">
<a-entity camera position="0 1.6 0" wasd-controls look-controls mouse-cursor></a-entity>
<a-entity id="leftHand" laser-controls="hand: left" raycaster="objects: [button]" raycaster="lineColor: #888; lineOpacity:0.5"></a-entity>
<a-entity id="rightHand" laser-controls="hand: right" raycaster="objects: [button]" raycaster="lineColor: #888; lineOpacity:0.5"></a-entity>
<a-entity navigator position="0 1.092 -0.595" rotation="-45 0 0" wearable="el: #leftHand; rotation: -45, 0, 0; position: -0.02, -0.03, -0.1"></a-entity>
</a-entity>
<!-- ps. multiple (nested) aframe-verse components are supported! -->
</a-scene>
Recursive description of a verse (aframe-verse.json
):
{
"schema":"aframe-verse/0.1",
"destinations":[
{"url":"./index.html"}, // immersive navigation
{"url":"./app2.html" }, //
{"url":"https://coderofsalvation.gitlab.io/aframe-urwhatuthink/experience.html", "title":"Leon Du Star - URWHATUTHINK", "scripts":true},
{"url":"https://fabien.benetou.fr/pub/home/future_of_text_demo/engine/",
"author":"Fabien Benetou",
"newtab": true // opens in new tab (does not contain aframe-verse component)
}
],
"verses":[ // import trusted destinations
{"url":"https://coderofsalvation.github.io/aframe-verse-leondustar/aframe-verse.json", "scripts":true}
]
}
property | type | info |
---|---|---|
debug | bool (false) | shows info in the browserconsole |
register | string | location of aframe-verse.json destinations |
hrefEvents | stringarray (click, collide) | events which trigger teleport to href |
fade | integer (100) | amount (in ms) of fade-in fade-out time |
fadeColor | string ('black') | (hex)color(name) for fading |
component | property | promise | info |
---|---|---|---|
aframe-verse | registerJSON | no | fires when loading aframe-verse JSON file(s) |
href | beforeNavigate | yes | fires before navigation fadeout |
href | navigate | yes | fires after navigation fadeout |
href | loadHTML | yes | fires before inserting new DOM content |
href | loaded | yes | fires after all DOM content is loaded ('domready' e.g.) |
See chapter
Customizing (with code)
>Customizing navigation Further
for flowcontrol using promises.
A visitor in an aframe-verse just teleports to other destinations and clusters ("beam me up scotty!").
aframe-verse.json
is just a telephone-book of destinations.
When a visitor surfs to a cluster-client (index.html), it loads all components, which other linked experiences use.
How does this works in large?
The concept above is an answer to the fact that each tile-based 'metaverse' will always turn into some kind of hypercentralized client-project.
Instead, a visitor in the aframe-verse just teleports to other destinations and clusters.
When the visitor surfs to a cluster-client (index.html), it basically loads all components, which other linked experiences use.
This is a security-limitation and a performance-feature, because this:
- makes traveling between experiences (within a cluster) very fluid and fast.
- it creates a decentralized incentive between developer(s) to:
- collaborate on a seamless & secure end-visitor cluster-client (index.html)
- consistent UX because of:
- shared components
- shared global objects: wearables, UI, AR/VR controller-support e.g.
As an exception to the rule, the developer(s) (YOU) of a cluster-client (index.html) can load remote (trusted) components/scripts, which is demonstrated by aframe-verse-component-scripts.
Worstcase, a destination can be loaded in a new tab (newtab:true
which exits immersive navigation ), which then basically becomes the new cluster.
aframe-verse describes a verse using the lowest common denominator between Aframe authors (=a webdirectory)
This could be a github-repo, or linuxserver where:
- the maintainer(s) maintain a pool of trusted aframe apps (& components)
- the maintainer(s) allow DOM-sharing (a DOM-ring) between eachothers aframe-apps
- the maintainer(s) agree on shared garbage collection
Ideally, the maintainers need to approve new (website-specific) scripts/components, and include them in index.html when a new app arrives thru merge requests.
This is all up to the maintainers of a verse, just think of it as running a shared website & linksharing.
For more info read this
Out of the box, this component is good enough for seamlessly navigating between simple read-only aframe experiences (galleries, portfolios, vr movies, viewing scenes e.g.).
A monoverse is the opposite of a 'metaverse'-concept (in which multiplayer-communication is fundamental).
Therefore, the following is out of scope, but can still be used to progressively enhance an aframe-verse
:
- multiplayer: see the (way more complex) NAF approach which requires you to run your own server.
- hardened security/privacy: introduce activitypub-layer, p2p webrtc like yjs
Just check index.html and app2.html, Basically:
- put your aframe apps in
apps/*
(they should have anaframe-verse
-attribute set somewhere) - add
href
-attributes to clickable items (see example) - use
href="./afile.html"
to teleport to relative files - whitelist
href="https://..."
-links by including them inaframe-verse.json
(see browserconsole for errors) - use
href="/"
to guide the visitor back to the original cluster
Typically these are included in the cluster-client index.html.
As an exception to the rule, you can load remote (trusted) components/scripts, which is demonstrated by aframe-verse-component-scripts.
Rule of thumb: load (or extend loading) components in the cluster-client (index.html)
By defining hrefEvents
, you can trigger navigation for other events too:
<... aframe-verse="register: /yourverse.json; hrefEvents: click, mouseenter, collide, foobar">
<a-box href="./show.html"/>
</...>
Profit! Now navigation is triggered to
show.html
whenever it is clicked, mousehovered or colliding with another object
calling $('[aframe-verse] [href]').emit('foobar', {})
would trigger navigation too
You can control navigation-events by creating a custom component:
// use like: <a-entity aframe-verse="..." navigate></a-entity>
AFRAME.registerComponent('navigate', {
init: function(){
console.log("initing navigation")
this.el.addEventListener('beforeNavigate', (e) => this.beforeNavigate(e) )
this.el.addEventListener('navigate', (e) => this.navigate(e) )
this.el.addEventListener('loadHTML', (e) => this.loadHTML(e) )
this.el.addEventListener('registerJSON', (e) => this.registerJSON(e) )
},
beforeNavigate(e){
// let promise = e.detail.promise()
console.log("about to navigate to: "+e.detail.destination.url)
// promise.resolve()
// promise.reject("not going to happen")
},
navigate(e){
// let promise = e.detail.promise()
console.log("navigating to: "+e.detail.destination.url)
// promise.resolve()
// promise.reject("not going to happen")
},
loadHTML(e){
let newdom = e.detail.dom.querySelector("[aframe-verse]")
// let promise = e.detail.promise()
console.log("loading html")
// promise.resolve()
// promise.reject("not going to happen")
},
registerJSON(e){
let json = e.detail.json
/* example: skip non-immersive navigation links */
// json.destinations = json.destinations.filter( (d) => d.newtab ? null : d )
/* example: launch external verses in a new tab (so its components get loaded too) */
// json.destinations.map( (d) => d.url.match(/index\.html$/) ? d.newtab = true : null )
}
})
This is the place to show a consent popup e.g. (most trusted experiences can do fine without that in the beginning).
For navigation, you can add external verses to the .verses
-array in aframe-verse.json
, that's all!
Optionally, you can secure the import-behaviour further using the registerJSON
-event as shown above in 'Customizing navigation further'.
You can have multiple persisting verses at the same time. Usecases for this are: a menu system, mini-games, inventory or a teleporting-maze e.g.:
<a-entity aframe-verse="register: aframe-verse.json">
...
</a-entity>
<a-entity aframe-verse="register: menu.json; fade: 0"> <!-- NOTE: superfast fade in ms (0=off) -->
...
</a-entity>
NOTE: for heavy scenes you can set
fade: 4000
(4seconds fade) e.g.
3 ways of hosting:
- click the fork-button on github or gitlab
- rename the repository to
aframe-verse-*
(aframe-verse-myorganisation e.g.) for easy discoverability - github: go to settings-tab > enable github pages (use the main-branch)
- profit! your verse can now be accessed thru
- github:
https://yourusername.github.io/aframe-verse-myorganisation/apps
- gitlab:
https://yourusername.gitlab.io/aframe-verse-myorganisation/apps
- github:
- REMIX this glitch
- rename the project to
aframe-verse-*
(aframe-verse-myorganisation e.g.) for easy discoverability - your verse can now be accessed thru
https://aframe-verse-myorganisation.glitch.me/apps/
Later: please connect your verse to this repo, by submitting a PR or mentioning your json-URL in an issue. That way, future verses (forks) will automatically include your verse too.
All feedback,bugfixes are very welcome ❤️
Other things (features/privacy/security/consent-stuff e.g.) should be published as separate components (see the navigate
custom-component example in the aframe-verse README.md in the Customizing (with code) > Customizing navigation further
section.
Please publish any useful components under reponame
aframe-verse-component-mycomponent
for discoverability.
As a startingpoint for extending, you can simply fork the scripts-component as well