… is 32Kb. Two entries and a little bit fit in the size of this image.
OK, there’s an exception for the WebXR category, the library of choice doesn’t count towards the 13Kb. But still :)
The theme of the jam this year was ‘Death’.
The first idea I played with a little bit was a Beat Saber-type game, but you are the Grim Reaper and instead of 2 swords you hold a scythe. Souls come at you with arms in a specific position and you have to make a clean cut to get their souls. Challenges would be getting the different poses in, and tracking a two-handed weapon.
The second idea was a Brookhaven Experiment clone, in 13k. Hordes of zombies coming at you and you’ll have to fight them off. Graphics would have been the most challenging in this one.
I also played with the idea of doing something along the lines of Dia de Muertos, the day of the dead. Again graphics and characters would be very difficult.
It was tough to think of something. Most ideas I came up with were nice for games, but impossible to do in only 13k because they rely heavily on graphics. A humanoid-type character is so hard to create in only 13k. I needed to either come up with a very clever idea for doing 3D characters (animated with bones) or do something without characters.
The environment the game is going to take place in is probably going to be a graveyard or a morgue. I think a nice, dark, graveyard is possible.
I’ve decided to go with the second idea, the Brookhaven clone, except you’re in a graveyard and instead of zombies, skeletons rising from the ground.
This year I decided to go with Babylon.js as the engine of choice. I’ve used Babylon in the past, but mainly in combination with Unity. A great thing about Babylon is that the graphics look nice pretty easily, with shadows and everything working without a hassle. The materials are easy to create and there are a couple of post-processing effects you can just attach to your camera. Since Babylon is more of a graphics engine and not a WebXR engine, like A-Frame, getting all the WebXR stuff in was a challenge. Also, Babylon does not come with an easy way
to control all the entities in the game. I’ve added that myself.
The game itself is not very complex. I split things up into a couple of different files to make it easier to work with, but that’s most of it. Started out with everything in classes nicely exported and imported. That had to go at some point, more about that in a moment…
All text that is shown in the game is generated onto dynamic textures. There’s a single function that can be reused for every text. The title image is also generated but this is done slightly differently. First, the text is drawn onto a canvas, and the text is given a couple of outlines and a shadow. Then, the pixels of the image are scanned and drawn onto another canvas. Based on how dark or light the pixel is, a pixel from the grave texture on the spritesheet is taken. Y is used for light/dark and a random x for variation.
The geometry is created in Blender. The skeleton is rigged and could be animated into a walk cycle. I decided early on that having the animations created on the bones would take too much space in the game. I only used the bones to create a pose for the character. I might expand on this game idea in the future and really animate the skeletons.
The sprites are drawn using Pyxel Edit. The game uses 1 image that is exported from this sheet. And converted to WEBP, because that is slightly smaller than PNG. And then included in the code as a base64 encoded string. I’ve done a lot of testing and this was the smallest way in the zipfile. Babylon made it pretty easy to load the texture this way, and I could also access it from my own code to use it in other ways. For example on the ground. I wanted to have a path that had a slightly different color than the grass. This texture is the same as the grass but with a brownish coloring.
For level editing, I used Xem’s
this year. There are 3 levels in the game. All are stored as a string. The code for converting a back to a usable thing is very small. In the screenshot, the level looks like crap, but it saved me a ton of time building something myself.
Babylon comes with a built-in debug tool (similar to A-Frame), that can be activated. I used a special directive to have it in my development code and not in my production code. One of the things I like about that tool is that it comes with an animation editor. The animations of rising and walking are created with this tool and then copied into the code and finetuned by hand.
I ran into a couple of issues while building the game.
The most frustrating challenge came about halfway through the competition. It still was going great and I had a lot of space below the 13Kb. It turned out my code was only compiling half. I was using imports and exports in every file. I used WebPack to convert everything into 1 file in my development environment. To create the most optimized version I used the same technique I used last year, using Terser and Roadroller to make the code the smallest. Last year I just concatenated all files to create 1 file, but last year I didn’t use imports and exports. Also, I didn’t include the folders where most of the code was in… So, I removed all imports and exports, switch to my Gulp setup, and ended up with a zipfile that was way over 13Kb. After cutting some features and removing a big chunk of the audio (the game had crickets and bells in the background) I got it back below the 13Kb.
Entity Component System
Babylon.js does not come with an easy way of controlling entities in the game and filter for them. There are a couple of frameworks out there that can help with this, but they are all too big for this jam. So, I created my own little thing based on random bits and pieces I’ve found on the internet. The AI of the skeletons, movement of the VR controllers and things like that are all handled by the Simple Entity Component System.
Workaround + Fix in BabylonJs
My sound effects (or effect in the end) use an Web Audio audiobuffer that is played when a skeleton is hit. At first, I created a player myself, but after studying the code of Babylon.js a bit a learned that Babylon is using a similar way of playing audio. By accessing a couple of ‘private’ properties of the Sound class I got around the limitations. Then I figured it might be nice to have this feature in Babylon itself. Because Babylon is open source I just added the feature and send a
. It was accepted quickly. The organization of the compo was so kind to also update the allowed version to the latest with my change :). This way I saved another 70 bytes.
Optimizations getting weird
To really minimize the code I create a custom gulp plugin that renames and substitutes a couple of very common words, for example, I replaced Babylon.Vector3 with BV3. This saved a whole bunch of bytes in the end. This felt pretty odd, having a gulp step like this.
I really enjoyed building the game this year, even though I had some challenges. I’m looking forward to what the judges think of it and hopefully, they provide some feedback to improve the game. I am planning on adding the features I could not include in the 13Kb limit and having the game released in the future. In the meantime, you can play the game here:
Every year, from the 13th of August until the 13th of September, there’s a gamejam called js13kgames. The objective of the jam is to build a game that fits in a .zip file of max 13Kb. The nice thing about this jam is that there’s a special WebXR Catergory. When participating in this category you are allowed to use one of the provided WebXR frameworks, and that doesn’t count towards the 13Kb. The theme of the jam this year is Death. For a framework I chose to use Babylon.js. I’ve used it a while a go and would like to see where it’s at at the moment.
In this blogpost I’m going to keep track of the development, like a devlog. I started out doing that in my personal Discord (
, writing a few lines pretty much every day after I done some developing of the game. In the end it will be a long list of small daily posts with thoughts and the process of building the game. Here we go!
The jam has started, and the theme is “Death”…
First idea… Beat saber, but you are the Grim Reaper and instead of 2 swords you hold a scythe. People come at you with arms in a specific position and you have to make a clean cut to get their souls…
Second idea. A Brookhaven experiment clone, in 13k. Hordes of zombies coming at you and you’ll have to fight them off.. Graphics will be the most challenging in this one.
It’s tough. Most ideas I can come up with are nice for games, but impossible to do in only 13k because they rely heavily on graphics. A humanoid type character is so hard to create in only 13k. I need to either come up with a very clever idea for doing 3D characters (animated with bones) or do something without characters.
Anyway. The environment the game is going to take place is is probably going to be a graveyard or a morgue. I think a nice, dark, graveyard is possible.
It could be that I’m spending too many precious bytes on this character, but. I have a rigged character in WebXR 🙂 Now lets see what the impact of an animation is, or that I have to animate it from code. Or just make it wobble or something.
Here another screenshot, this time from the desktop.
The skeleton with bones in Blender 😄
It starts to look like an active graveyard! 🙂
Worked on a flashlight and using the VR controllers in Babylonjs
Working on a baseball bat to hit the skeletons with. Maybe they should scatter into particles when they’re struck. Particles in the direction of movement. Or maybe you’ll have to hit the multiple times.
It’s a start:
I’ve added the start of walls to be able to create guidance for the player to where the skeletons will be coming from. You will start in a corner and skeletons are going to approach you only from 1 direction. After a few waves they will come from all sides.
I also changed the texture of the baseball bat and removed the blurring from the shadows.
Todo for the coming days:
Make the skeletons move towards you
Turn the screen red for a moment when they hit your (not sure yet if you are dead instant or if you can take a few hits)
Spawn the skeletons (preferably animate them coming from graves)
Explode the skeletons into particles when you hit them and have the particles move in the right direction depending on the movement of the bat for optimal satisfaction…😈
I’m at 7Kb now with no real optimizations.
I might be pushing the possibilities of what can be fitted in 13Kb here, but I’ve implemented a simple entity component system (SECS 😂) . This makes it easier, for example to attach a mesh to a controller. I don’t believe that that is something that is in BabylonJS by default. Also, controlling various enemies and particle effects might be easier to implement this way.
I am a little bit afraid that I might have to remove a lot of stuff in the end to compress it down to 13Kb. But, at this point I’m near 8Kb and I know there’s a lot of stuff in the scene file that can be removed and optimized.
I plan on having the main game loop complete by the weekend. Thus at that point it should be possible to spawn enemies, kill them or they’ll kill you.
No updated video today, since nothing has changed visually. Except maybe this experiment on the title screen:
This is generated from one of the sprites based on the lightness of the pixels in another canvas.
With an added randomness.
The fun thing with the choice for BabylonJS is that this year I need to think about complete other things than previous years. Where the shaders, shadows and post-processing of the image is no problem and comes out of the box, I need to think about how to make my enemies move.
Making progress again. I’m already having the benefits from my own crappy ECS. Skeletons have “AI”, so that walk towards you no matter where they spawn. Right now there are 3 spawners in different locations in front of your. Also the collision with the bat is working. Next up is dying when a skeleton gets to you (or a couple) and adding the title/game over screens. At that point there’s have a “full” game and the fun begins. At that point I can start adding multiple levels or changes so they come from behind and eventually from all sides. And I can start to make the game look better by adding particle effects and such.
Today I worked on adding some particles when a skeleton is hit. That wasn’t as hard as I thought. Although I would really like to have the particles go in the direction of the swing of the bat.
I also added the start of game state management and a title screen. Lots more work need to be done here. I hope to be able to reuse most of the code to create the game over screen. Probable it’s going to be 1 string different.
One last thing I finally did is make a start of adding the sprite textures as a base64 encode webp image to the code. For the text I needed to have the string again. I have another place where 1 sprite is used instead of the entire sheet. That need to be replaced as well, hopefully the code to extract that 1 image for the ground texture will be smaller then the entire string.
Current file size: 9289 bytes
The goal for today was to finish the gameloop. To make it into a game you need to be able to die, to go ‘game over’. To get whole sequence to work I needed to have game states, track them, change them and have the game respond correctly. I added a switch statement to the RenderLoop to have the AI not running when on the title screen or the gameover screen. I also added a couple of Babylon Nodes (these are comparable to the base GameObjects in Unity) as parents of various things like the different texts that need to be shown and the controllers. By having these with a single parent I can just disable the parent and things do not show.
I’m still very happy with the choice to create a simple Entity Component System. It made things really easy to update in places. It is missing a way to communicate to the main script though, resulting in sometimes needed to directly calling methods on the app class. I don’t like this and it might be better to do this with events or something, but I only have 13Kb. And when I was writing the code today I was pretty sure I would go way beyond the limit. (spoiler, I didn’t 😝).
Biggest challenge today was an issue that came up when removing the enemies when switching to the gameover state. After removing the meshes and entities,and changing the state the RenderLoop still ran with the old state once causing a lot of errors because the entities were removed. I bypased this issue by adding a small delay between changing the state and actually removing the stuff.
Current file size: 9568 bytes
Today I finally figured out a way to have the particles fly in the direction of the swing of the bat (I’m thinking of changing the bat to a shovel to better fit the story). I also add 5x more particles. The more particles the better. Having these particles really make the game more fun. Next up is the UI and counting the number of hits.
Over the weekend I got this weird idea for backgound ambience. I wanted to try and generate a couple of different sounds with the Web Audio API. I’ve used this api in the past and it is really powerful. I started with creating a howling wind sound by generating noise and filtering that. The filter frequency changes slowly over time.
Having only wind is a bit boring. I searched around a bit and found a video explaining how to create a bell sound. Perfect! I added a looping timer that randomly plays a low bell sound every once in a while.
The last thing I wanted was the sound of crickets in the night. I’ve analysed a couple of cricket sounds and noticed it’s basically a tone with a couple of LFOs on the amplitude. So, I created that. I added a few lines extra to slowly and slightly change the volume and pitch over time.
Here’s the result, and endless stream of wind with bells and crickets:
Current file size: 9836 bytes
Big plans for today. When you are hit by a skeleton your are game over. But, I want to show how many skeletons you have killed before they got you. I used almost the same code for creating a texture that shows text with your score as I did for the title screen. Except this one is only shown when you are game over. The texture itself is generated each time you die.
I was on a roll last monday with generating background sounds, so I thought it would be a nice idea to do something similar for the soundfx. The audio library I’ve used in all my previous jam entries used a Flash tool to create the sound presets and, well, Flash is really dead now. I came across this
. It uses math to fill a buffer and that is played. I wrote a little bit of code to make the buffer play more efficiently in my game, and voila, a very small way to play sound efffects.
The only thing is, that at this point I just went over 10k, but I didn’t do any real optimization yet.
Current size: 10040 bytes
I really do not like the way the skeletons just pop into existance when they are spawned. Today I added an animation that make them rotate from lying flat to a standing position. I haven’t mentioned how to create this, so let’s talk a little bit about that. The way this animation is created is similar to how I created the walking animation.
First of all, there’s an editor/inspector you can insert into your game. There are a few lines in the codebase (that are automatically removed when building a production version), that show the editor when pressing Shift+Ctrl+Alt+I.
This tool is very useful to inspec stuff in your scene and debug issues that might occur. It comes with an animation editor as well.
In this you can create key frame animations that can be played in your game. I exported these and added them to my scene.js file manually. When a skeleton is spawned I play the ‘Rise’ animation and after a couple of ms I start playing the walk animation. Very simple stuff basically :)
Current size: 10126 bytes
NOOO! I found a bug in my build system. It was ignoring a couple of files :(. This meant that instead of having about 3Kb left, I’m already 4Kb over. There seems to be more going wrong now…
Ok. I managed to get it back down to just below 13Kb. Changes I made:
Removed all the exports and imports
Back to just concatenating all files
Started testing the zip with advzip on linux instead of 7zip on windows.
Removed the bells and crickets 😭 I loved those.
Hacked the sound effect into Babylon instead of handling the AudioContext myself.
The reason for the weird changes in the build is that tools like Webpack and Babel add a lot of overhead. Just concatenating the files into one big one let you keep everything in separate files, and still have only one file without the overhead. Biggest downside is that things like autoreload don’t work anymore. I use Gulp solely now, with a couple of different tasks, a production that does everything as minimized as possible, a dev and a watch task. I run a simple-http server to host the game locally.
There are a couple of more places that need to be optimized, like the scene file (which is just a JSON file basically) and a lot more duplicate code that could be improved.
Over the weekend I didn’t spend a whole lot of time on the app. But I did a conversion of the scene file. The object shown in the game are created from code now. Everything that is not needed from the scene json is now not in the game anymore. Also, for the grave stones only the UVs are different per asset so instead of having all vertex data and indices for all 3 different grave stones I have them only once in the code. This brought the filesize down quite a bit and open the door for some features that the game really needs.
For example predefined levels. To get anything implemented this time I’ve decided to give the
the amzing Xem created (yes, the same that build that audio tool). The editor creates a very compressed string that can be decompressed using a very small piece of code. After that I replaced the random placement of thumbstones and walls to be done by an interpreter of the level data.
Current size: 12174 bytes
I slowly start to run out of bytes :) Which means there are still bytes to use. So, I added 2 more levels. I also fixed a couple of bugs with graveyard facing the player. As you can see in the screenshot, the background is purple now and there are scary trees. There’s still a few bugs that need to be removed which need to be addressed. I also want to have a fade to back when switching from and to VR and when changing levels.
I also found a couple of places that had some bytes that could be removed…
Current size: 13102 bytes
Today I finished the fading between the levels and when entering VR. I think this does the game real well and makes it feel more polished. I’ve found an issue when switching levels and clearing everything from the previous level. This caused way to many skeletons to spawn. I also improved the algoritm for spawning a bit.
The last thing that was on the list and I didn’t think I’d have time or space for was text between the levels. But, since I already had some code to generate the text to show the score when you are game over, I refactored that a little bit and reused it.
Current size: 13296 bytes (16 bytes left)
Since there’s no space to add more features I did some playtesting today. And… Found a little bug when entering level 3: The path should be in a cross to indicate that skeletons come from all 4 directions, but it was showing only 2.
After fixing the level, I went over the 13Kb limit again. After about an hour of searching and tweaking lines of code I managed to get it under again.
In my latest game
Get off my Lawn!
I added a skydome that shows a rendered image. Because the styles of the 3D geometry and the game itself are the same it looks as of the entire world is rendered. I have worked as a 3D modeler in the past but have never done anything like this before. I’ve learned everything in this tutorial recently and I believe it might be useful for more people to add great background to your 3D games and apps.
This game is built with the
Town Pack from Synty Studios
. In the end, I pretty much recreated everything for the game to reduce a lot of polygons.
But, for the background, I used an example scene from the pack. I just removed everything I didn’t want in the render. So the rendering process would be a bit faster and not have things in the rendering that otherwise would be too close to the camera.
By default, there’s no background in the renderings in Blender. I wanted to have a clear sky. Luckily there’s one in Blender. To add one, you’ll have to go to the World Properties and add a Sky Texture by clicking on the yellow dot next to Color. Make sure the render engine is set to Cycles in the Render Properties otherwise the sky won’t render. Then, I kept changing the value of the Sky Texture until I was happy with the way the sky looked. I made the Sun Size a lot smaller and lowered the Air and Dust values. I changed the Sun Elevation and Sun Rotation in such a way that the sun would appear in the same place as the light in the scene would originate from.
Now that there’s a scene and a background it’s time to render the skydome. A normal camera won’t work in our use case. Luckily there’s a camera setting in Blender that can be used for these cases.
In the demo scene, I placed a camera roughly where the player would stand while playing the game.
Under Lens select the Panoramic Type and Equirectangular Panorama Type
To get the background sharp and crisp we need to render a very big image. I’ve set mine to 4096 by 2160 pixels.
After rendering the image should look something along these lines. It’s an image that can be projected onto the inside of a sphere and can be around your 3D scene. Keep in mind that this image might be pretty big in size.
Creating a Skydome
Depending on the framework you are using it might not be needed to create custom geometry for your skydome. For example, A-Frame has a Sky component that can be textured with the skydome texture we just created. But, for anyone that doesn’t have that luxury, here’s how to use the skydome anywhere.
First, we need to stay in Blender for a little bit longer and create a new Blendfile with everything deleted. Next, create a sphere. A big one. In Get Off My Lawn! the sphere has a 100m radius, thus it’ll be about 100m away from the player in every direction. This way there’s never anything clipping through the sphere. I added the previously rendered image as a texture map to the sphere. (If anyone has trouble doing this, let me know and I’ll do a separate post on UV mapping.).
Now before we export, there’s one tricky thing we need to do. It’s not visible in Blender by default, but most engines only render the front side of the faces of the object. Backsides are normally not visible. But since we will be standing INSIDE the sphere we need to see all the backsides. So, we’ll need to flip all the faces of the sphere. To do this, select the sphere in Blender and hit Tab to enter edit mode. Hit A to select everything and then hit ALT+N to get to the normals menu. Select Flip to flip all the normals around, thus making them point inwards.
Normals are used to determine the in- or outside of the faces. It’s very hard to see this in Blender, but there’s a little trick to make it really visible. This can also be used to “debug” issues with faces in models. To see the orientation of the faces you can enable the Face Orientation Overlay. This can be found by clicking on the overlay dropdown on the editor.
By checking that little checkbox the editor will add a blue or red color to the faces, where the blue faces are pointing towards you and the red faces away. In this case, when we look at the entire sphere from the outside it should be completely red.
And when we move inside the sphere, everything should be blue.
Great! The last thing to do is export the sphere to FBX through File->Export->FBX.
The sphere is ready to use now.
SkyDome in Wonderland
To use the skydome in Wonderland Editor just drag and drop the .fbx file into the /models folder. And then drag and drop it into your project. We don’t want the skydome to be affected by lighting. We need to make sure the pipeline of the material of the skydome is set to Flat Opaque Textured. You can do this by selecting it in the dropdown.
Now your skydome is ready to be admired in VR and all that there’s left to do is add everything else to make it into a game.
To hide the horizon of the skydome I used a big fence that fits the game perfectly. If you don’t have that option you might want to add other geometry close to the camera to hide it.
That’s pretty much all that there’s to it. If you want to have a look at this skydome in action, go to
Get off my lawn! at Construct Arcade
. Let me know if you have any questions or if you have requests for other tutorials.
Making some money with your WebXR can be really tough. Selling any web game is already hard, but doing that in a VR headset is near impossible. Unless you maybe add your credit card to Facebook or type it into the browser in your headset. Traditional ads like Google Ads are also not going to work. There are only shown in desktop mode and are not going to work in VR. They also look really bad most of the time and have very low payouts.
Zesty solves this problem in a very modern and unique way using blockchain technology, NFTs, and smart contracts. This sounds really complicated, but you don’t have to know any details about all of this to use it in your apps.
This blog post is not sponsored. I believe in the platform and think it can become a really big thing. With more people joining the platform and more games using the ads, we all benefit.
How does it work?
On the Zesty Market developers can create spaces they can auction for ads. Other people can have their ad shown in that space in VR in your app. Think of this as renting out a billboard.
The Zesty Market uses a
model where the price declines over time until it reaches 0 at the moment the space expires. You can specify the price, the timeslot, and the moment the auction starts.
The ad spaces are tokenized into NFTs. NFTs have a really bad name from people selling stupid images for millions of dollars, but there’s more to it than just that. NFTs are a safe way of storing anything digital, including music, drawings, or ad spaces.
The goal of Zesty is to provide creators with a method of capturing the value they create on a decentralized platform. They hope to one day be able to exit the community and have the platform owned by the community of creators and advertisers.
How to use it in your game?
To use an ad space in your game, you need a few things. First of all, you need a crypto wallet. I use
connected to the
By connecting Zesty to your wallet you are logged in and you can start creating your ad spaces. To create an ad space on the
you’ll need to enter a few things like an example image, a name, the URL to your game, and a description. I usually create a screenshot from VR with an ad shown in my app by just using an example code and edit something in the same spot afterward. When everything is entered you need to approve and deposit the NFT to the Zesty Smart Contract. At this point, you need to have a few Matic in your wallet. Every interaction with the network will cost a couple of cents in fees, also known as gas. You might have looked at NFTs on the Ethereum network at some point in time. Gas prices on Ethereum are 10s of dollars, sometimes even 100s. On the Polygon network, it’s 0.01s of dollars.
If you like to read a more detailed tutorial on creating an ad space, you should check out the
Zesty documentation on the subject
Now that you have an ad space, you need to integrate it into your WebXR game. Well, I mention WebXR game, but you can also use the ads in your website, in Unity games, on Crytovoxel, Decentraland, or Muse and even OBS is supported. Integration in WebXR is supported in the most used frameworks, like A-Frame, Babylon.js React Three Fiber, Three.js, and Wonderland Engine (as shown in the image below).
You don’t have to struggle with figuring out how to get the integration with one of these frameworks to work. Zesty is generating examples on the fly with the spaces you created. In the screenshot below you can see an example of how to integrate it into an A-Frame project. All you have to do is copy-paste a few lines of code to get it to work.
From that point on you can integrate the ad into your VR world. You can place it in a nice visual place below the main menu for example. I prefer to really find a nice spot, like a picture on the wall. This way the ads are not annoying like they are on most web pages that have ads.
Whenever an ad is shown or when a user clicks on it there’s a notification sent to Zesty. When you have the Beacon turned on anyone can see how the ads are doing. To have people place their ads in your space you have to create an auction time slots. You decide the duration of a timeslot and you decide how long before the slot expires the auction starts. I’m still experimenting a little bit with different durations. But, for my best performing game
Back to Space
, I have time slots of 1 day. I recently started opening the auctions 1 week before the slots expire. You have options to start directly or 3 days to 4 weeks before they expire. Don’t be afraid to experiment with different prices. Mine range from 10$ to 25$ a day. As the auction runs the price will drop until you accept the bid.
Now that you have rented out a space and that time has passed you get the amount of money you sold it for. You can find this on your dashboard. You can find the amount that you can claim and the amount that is coming through time slots that are not expired yet. These show in $, but the payout will be in
, a digital coin linked to the US Dollar. The amount will go to your wallet, MetaMask in my case. I’m not sure if I’m using the most efficient path, but I convert the USDC to Matic on MetaMask and then go through the
exchange to get to euros and into my bank account as ‘real’ money. Keep in mind that every interaction with your crypto-money will cost a small fee. The route I’m taking only costs a few $, because I stay away from Ethereum.
If you have any questions on how to set things up feel free to ask. Just hit me up through the
and I’m happy to help you get going on the platform.
You might have noticed that when you are playing games on your VR headset, you can feel the controller vibrating. This haptic feedback is a great way to get a little bit of extra feeling when you are playing. Here’s how you can add it to your WebXR game.
In this tutorial, I’m showing how to use haptics in a Wonderland component, but the part that is actually sending a pulse to the controller is not specific to Wonderland. If you’re looking for an example in A-Frame, you can find it in a
game I build a while ago
for the GameDevJS Game Jam.
Haptic Feedback API
In the WebXR Device API, there’s no support for haptic feedback, but instead,you can use the
Gamepad API for it
. The support is still experimental, but since it is implemented in the Meta browser it should be safe to use. The slightly difficult part is that you need to get access to the Gamepad API, luckily you can get that through the WebXR Device API.
Getting to the Gamepad API
As an example, I’m going to add a little vibration of the controller to a shooting component. To handle the input from the controller I’m using the default
of Wonderland. When the ‘gun’ component is added to the same object as the input component we can access it very easily.
I’d like to “shoot” my gun when I pull the trigger of the controller. There’s an event on the WebXR session we can subscribe to,
the ‘select’ event
. This event is not specific to any framework. We can get access to the WebXR session in Wonderland when the app switches to VR mode by using WL.onXRSessionStart. The ‘session’ object is passed as a parameter to the callback function. You can see this in context in lines 6 and 8 of the
code example below
To get access to the gamepad, we get use the
of the ‘select’ event callback. The ‘inputSource’ contains a lot of information about the input, but we are only interested in the ‘gamepad’ property and the ‘handedness’. The ‘handedness’ is either the left or the right hand and we just use this property to check if we are about to vibrate the correct controller.
Once we have the gamepad object we pass this off to a function that will handle the haptic feedback, pulse in this case.
The get the controller to vibrate we use the
. This function takes two parameters, the first is the duration of the pulse and the second is the strength of the pulse. The strength is a value between 0 and 1. The duration is in milliseconds. The pulse function can be found on the ‘hapticActuators’ array on the gamepad object. There could be more than 1 haptic actuator, but we’re just going to assume we only need the first one. If there’s none we just return out of the pulse function. You can find the entire function in lines 20 to 26 in the example below.
The example below shows a component I’ve reused in pretty much all my Wonderland games in some shape or form. Extra to the description above, I’ve made sure the ‘select event’ is only added once. If the app switches to VR a second time, the initialized variable is already set thus the event is not added again.