An example of some VR "games" written in Clojurescript and Babylon.js. I have quotes around "games" because most are just simple scenes, that is to say more like prototypes of some ideas for fuller games or simulations. After attempting to write a few "real" games, and realizing how hard it is, and feeling the need to get in some more quick iterations of the game writing process (to get more feedback and avoid getting bogged down in a single scene), I decided to just create a project where I would metaphorically start with a simple cube on a plane, the archetypal VR "hello world" program, and then try to expand it in the smallest possible way possible and hope to actually complete something. Thus the name "cube-test".
If you want to try it out you can run it directly off github pages here .
- Click on the "Enter VR" icon in the lower right corner to enter VR. You can do a certain amount without entering VR, but this is really intended to be a pure VR app.
- You can move around using standard Babylon.js teleportation buttons, or by pressing the left grip button and then flinging and releasing to get more of a flying motion (hard to describe, but easy once you get it). Note: this is using an oculus rift controller as a reference, but it should work with other controllers as well.
- Click on the arrows to rotate the "choice carousel" and then select the desired sub-scene. Note: if you ever lose the "vr selection ray", just click on the floor and it should reemerge.
- The right "A button" can be used to bring up a menu to allow you to return to the top-scene if you are in a sub-scene.
- The buttons at the bottom are just left-overs from development.
The main thing about this project is it serves as a useful reference (at least to me) for how to do certain things in Babylon and clojurescript. I'm always forgetting the details of how to load a model from blender, or do a map in clojure etc., so I just search on this project from git bash with something like:
vturner2@LAPTOP-F0QM306P MINGW64 /d/vtstuff_d/github/cube-test/src (master)
$ find . -name "*.clj[cs]*" | xargs grep -i "load-model"
and then I can find examples of how I did it before.
The directory structure is becoming quite elaborate, as I get more opinionated about how things should be. That's the beauty of having multiple scenes in one project -- you don't think of a lot of things when there's just a single scene. I have a main-scene (common to all scenes), and then scenes at the app level, and main-game, and an app-level game object. I have 'cljs' and 'cljc' files as necessary and each sub-scene has it's own private directory and typically has it's own view, events, subs, rules, and db files.
I use re-frame as my main app engine. When I first used re-frame, I didn't really exploit a reactive design. Once I started to understand reactive apps (about how you update the db to trigger an indirect update in a separate concern), I Unfortunately discovered that re-frame is interactive but not real-time. That is to say re-frame events take on the order of 200ms which is about 10 ticks (at 60 fps) and it's uninterruptable. This might be acceptable in a web app (an interactive app), but is unacceptable in a game (a real-time app), causing a noticeable jerk in any real-time action that may be going on.
To get around this, I first experimented using web workers, which does address the problem, but has a pretty high overhead to using. I then tried o'doyle rules which worked better and appears to be real-time. You can see examples of both of these methods in the code base if you're motivated enough to search for them.
This is really more just a note for me, the developer. I normally develop with:
lein.bat dev
However, to create a production build do:
lein.bat prod
This creates a file 'cube-test/resources/public/js/compiled/app.js'. Copy this file to 'app.prod.js' in the same directory and then upload to the git repo. After a couple of minutes (you may need to clear your cache) you should get an updated module at 'https://vt5491.github.io/cube-test/resources/public/index.prod.html'
A re-frame application designed to ... well, that part is up to you.
- Architecture: Single Page Application (SPA)
- Languages
- Front end (re-frame): ClojureScript (CLJS)
- Dependencies
- Build tools
- Project task & dependency management: Leiningen
- CLJS compilation, REPL, & hot reload:
shadow-cljs
- Development tools
- Debugging: CLJS DevTools
/
: project config filesdev/
: source files compiled only with the dev profilecljs/user.cljs
: symbols for use during development in the ClojureScript REPL
resources/public/
: SPA root directory; dev / prod profile depends on the most recent buildindex.html
: SPA home page- Dynamic SPA content rendered in the following
div
:<div id="app"></div>
- Customizable; add headers, footers, links to other scripts and styles, etc.
- Dynamic SPA content rendered in the following
- Generated directories and files
- Created on build with either the dev or prod profile
- Deleted on
lein clean
(run by alllein
aliases before building) js/compiled/
: compiled CLJS (shadow-cljs
)- Not tracked in source control; see
.gitignore
- Not tracked in source control; see
src/cljs/cube_test/
: SPA source files (ClojureScript, re-frame)core.cljs
: contains the SPA entry point,init
Use your preferred editor or IDE that supports Clojure/ClojureScript development. See Clojure tools for some popular options.
- Install JDK 8 or later (Java Development Kit)
- Install Leiningen (Clojure/ClojureScript project task & dependency management)
- Install Node.js (JavaScript runtime environment) which should include NPM or if your Node.js installation does not include NPM also install it.
- Clone this repo and open a terminal in the
cube-test
project root directory - (Optional) Download project dependencies:
lein deps
Browser caching should be disabled when developer tools are open to prevent interference with
shadow-cljs
hot reloading.
Custom formatters must be enabled in the browser before CLJS DevTools can display ClojureScript data in the console in a more readable way.
- Open DevTools (Linux/Windows:
F12
orCtrl-Shift-I
; macOS:⌘-Option-I
) - Open DevTools Settings (Linux/Windows:
?
orF1
; macOS:?
orFn+F1
) - Select
Preferences
in the navigation menu on the left, if it is not already selected - Under the
Network
heading, enable theDisable cache (while DevTools is open)
option - Under the
Console
heading, enable theEnable custom formatters
option
- Open Developer Tools (Linux/Windows:
F12
orCtrl-Shift-I
; macOS:⌘-Option-I
) - Open Developer Tools Settings
(Linux/macOS/Windows:
F1
) - Under the
Advanced settings
heading, enable theDisable HTTP Cache (when toolbox is open)
option
Unfortunately, Firefox does not yet support custom formatters in their devtools. For updates, follow the enhancement request in their bug tracker: 1262914 - Add support for Custom Formatters in devtools.
Start a temporary local web server, build the app with the dev
profile, and serve the app with
hot reload:
lein dev
Please be patient; it may take over 20 seconds to see any output, and over 40 seconds to complete.
When [:app] Build completed
appears in the output, browse to
http://localhost:8280/.
shadow-cljs
will automatically push ClojureScript code
changes to your browser on save. To prevent a few common issues, see
Hot Reload in ClojureScript: Things to avoid.
Opening the app in your browser starts a ClojureScript browser REPL, to which you may now connect.
See
Shadow CLJS User's Guide: Editor Integration.
Note that lein dev
runs shadow-cljs watch
for you, and that this project's running build id is
app
, or the keyword :app
in a Clojure context.
Alternatively, search the web for info on connecting to a shadow-cljs
ClojureScript browser REPL
from your editor and configuration.
For example, in Vim / Neovim with fireplace.vim
- Open a
.cljs
file in the project to activatefireplace.vim
- In normal mode, execute the
Piggieback
command with this project's running build id,:app
::Piggieback :app
-
Connect to the
shadow-cljs
nREPL:lein repl :connect localhost:8777
The REPL prompt,
shadow.user=>
, indicates that is a Clojure REPL, not ClojureScript. -
In the REPL, switch the session to this project's running build id,
:app
:(shadow.cljs.devtools.api/nrepl-select :app)
The REPL prompt changes to
cljs.user=>
, indicating that this is now a ClojureScript REPL. -
See
user.cljs
for symbols that are immediately accessible in the REPL without needing torequire
.
See a list of shadow-cljs CLI
actions:
lein run -m shadow.cljs.devtools.cli --help
Please be patient; it may take over 10 seconds to see any output. Also note that some actions shown may not actually be supported, outputting "Unknown action." when run.
Run a shadow-cljs action on this project's build id (without the colon, just app
):
lein run -m shadow.cljs.devtools.cli <action> app
The debug?
variable in config.cljs
defaults to true
in
dev
builds, and false
in prod
builds.
Use debug?
for logging or other tasks that should run only on dev
builds:
(ns cube-test.example
(:require [cube-test.config :as config])
(when config/debug?
(println "This message will appear in the browser console only on dev builds."))
Build the app with the prod
profile:
lein prod
Please be patient; it may take over 15 seconds to see any output, and over 30 seconds to complete.
The resources/public/js/compiled
directory is created, containing the compiled app.js
and
manifest.edn
files.
The resources/public
directory contains the complete, production web front
end of your app.
Always inspect the resources/public/js/compiled
directory prior to deploying the app. Running any
lein
alias in this project after lein dev
will, at the very least, run lein clean
, which
deletes this generated directory. Further, running lein dev
will generate many, much larger
development versions of the files in this directory.