js13kgame competition 2023 diary

I’m entering the js13kgames competition this year. Here’s my diary.

Here’s the code on GitHub.
And here’s the game.

I’m semi-retired having worked with real-time 3d for nearly 30 years. I create video courses mainly teaching game programming.

js13kgames post-mortem page

13th August

The theme of this years competition is announced. 13th century. I gave it some thought and decided on a quest for the Holy Grail. I’m doing a WebXR game using ThreeJS. I’ve already created a working project framework using npm and webpack with help from Matt McKenna.

With a 13k size limit Blender models are a no-no. All assets need to be created in code. I fiddled with the ThreeJS Editor and came up with this as the player character.

Sir Coadalot in the ThreeJS Editor

Downloading this as a JSON file is 12K uncompressed. Let’s remake it in code.

14th August

Big day. Before the competition theme was announced I’d been working on the key components I thought my game would need. A VRButton. I’d already created one for my WebXR course. But I made a tweak to it for displaying the VR Cardboard icon from an svg string.

vr-cardboard.svg icon

I had created the most basic 3D physics engine. If you look in the source you’ll find it in the src>SimplePhysics folder. Just 3 files,

SPWorld.jsThis is where rigid bodies are added and collisions calculated
SPBody.jsA single rigid body
SPCollider.jsA SPBody instance has a single collider which can only be a Sphere or a AABB ( Axis Aligned Bounding Box )
SimplePhysics demo

Minified and zipped it comes in under 2K.

If you want to see it in action and you’ve downloaded the repo then rename index.js as index-game.js and rename index-sp.js as index.js. If you’ve got the game running that’s npm run start then you can see it in a browser using localhost:8080. The physics isn’t perfect to say the least but needs must when the entire game budget is only 13k.

My first step in creating the game was to change a sphere into my player character. The downloaded json file from the ThreeJS editor game the necessary geometries, material colours and mesh positions and orientations. Here’s the code to create the knight.

createModel(){
  const gSkirt = new THREE.CylinderGeometry(0.4, 0.6, 0.5, 32, 1, true );
  const gHead = new THREE.SphereGeometry(0.4, 24, 10);
  const pHelmet = [
    new THREE.Vector2(0.5, 0),
    new THREE.Vector2(0.5, 0.2),
    new THREE.Vector2(0.45, 0.2),
    new THREE.Vector2(0.4, 0.3),
    new THREE.Vector2(0.3, 0.4),
    new THREE.Vector2(0, 0.5),
  ];
  const gHelmet = new THREE.LatheGeometry(pHelmet, 12);
  const pTunic = [
    new THREE.Vector2(0.45, 0),
    new THREE.Vector2(0.43, 0.1),
    new THREE.Vector2(0.4, 0.2),
    new THREE.Vector2(0.32, 0.3),
    new THREE.Vector2(0.16, 0.4),
    new THREE.Vector2(0.05, 0.5),
  ];
  const gTunic = new THREE.LatheGeometry(pTunic, 12);
  const gBelt = new THREE.CylinderGeometry(0.45, 0.45, 0.2, 32, 1, false);

  const mSkirt = new THREE.MeshStandardMaterial( { color: 15991041 } );
  const mHead = new THREE.MeshStandardMaterial( { color: 16373422 } );
  const mHelmet = new THREE.MeshStandardMaterial( { color: 0xC7C7C7 } );
  const mTunic = new THREE.MeshStandardMaterial( { color: 16777215 } );
  const mBelt = new THREE.MeshStandardMaterial( { color: 12615993 } );

  const root = new THREE.Group();
  const skirt = new THREE.Mesh( gSkirt, mSkirt );  
  skirt.matrix.fromArray(
    [1,0,0,0,0,1,0,0,0,0,1,0,0,0.25,0,1]
  );
  root.add(skirt);
  const head = new THREE.Mesh( gHead, mHead ); 
  head.matrix.fromArray(
   [1,0,0,0,0,1,0,0,0,0,1,0,0,1.3466628932086855,0,1]
  );
  root.add(head);
  const helmet = new THREE.Mesh( gHelmet, mHelmet );
  helmet.matrix.fromArray(
   [1,0,0,0,0,1,0,0,0,0,1,0,0,1.4010108612494776,0,1]
  );
  root.add(helmet);
  const tunic = new THREE.Mesh( gTunic, mTunic );
  tunic.matrix.fromArray(
    [1,0,0,0,0,1,0,0,0,0,1,0,0,0.6106004423389476,0,1]);
  root.add(tunic);
  const belt = new THREE.Mesh( gBelt, mBelt );
  belt.matrix.fromArray(
    [1.2,0,0,0,0,1,0,0,0,0,1,0,-0.04,
     0.5495005511829094,0,1]
  );
  root.add(belt);

  root.traverse( object => {
    if ( object.matrixAutoUpdate ){
      object.matrix.decompose( object.position, object.quaternion, object.scale );
     }
   });

  return root;
}
Sir Coadalot

I also created a castle tower in code. I added my JoyStick control for testing on the desktop. Put it all together and had this – not bad for day 1

August 15th

I worked on animations for the player character today. Given the tight 13k budget. Using a 3D content creator like Blender and exporting as a GLB is a none starter. So I used the ThreeJS Editor, carefully moving and rotating the sword root object into various poses then writing down its position and rotation.

Inspector panel in the ThreeJS Editor

Having got a set of keyframes. I created a JS object.

const config1 = {
  duration: 0.4,
  times: [0, 0.1, 0.3],
  pos:[{ x:0, y:0, z:0 }, { x:-0.261, y:0.522, z:0.201 }, { x:-0.293, y:0.722, z:0.861 }],
  rot:[{ x:0, y:0, z:0 }, { x:21.69, y:13.79, z:-9.18 }, { x:-2.23, y:4.21, z:175.94 }]
}

And a function to convert this into a ThreeJS AnimationClip

createAnim(name, config){
  const pvalues = [], qvalues = [];
  const v = new THREE.Vector3(), q = new THREE.Quaternion(), e = new THREE.Euler();
  const d2r = Math.PI/180;

  for(let i=0; i<config.times.length; i++){
    const pos = config.pos[i];
    const rot = config.rot[i];
    v.set(pos.x, pos.y, pos.z).toArray( pvalues, pvalues.length );
    e.set(rot.x*d2r, rot.y*d2r, rot.z*d2r);
    q.setFromEuler(e).toArray( qvalues, qvalues.length );
  }

  const pos = new THREE.VectorKeyframeTrack( '.position', config.times, pvalues );
  const rot = new THREE.QuaternionKeyframeTrack( '.quaternion', config.times, qvalues );

  return new THREE.AnimationClip( name, config.duration, [ pos, rot ] );
}

I used a little test code to see it in action.

Sir Coadalot and sword

Of course the player needs an enemy. Meet the Black knight. Just the same with different material colours and one point on the helmet LatheGeometry points array changed.

August 16th

Today I coded the castle walls and towers. Added a DebugControls class to allow keyboard entry when testing using the WebXR emulator on a desktop. I also added some bad guys. Super primitive AI they just move toward the player character. The bad news is I’ve only got 1k left to complete the game. Something might have to go!!! Here’s a screengrab from my Quest2

August 17th

Today I refactored the game. Removed the BasicUI. Removed the OBJParser and the Rock OBJ String. Instead I create a rock using an IcosahedronGeometry instance then randomly perturb the vertex positions.

class Rock extends THREE.Mesh{
  constructor(radius=0.5){
    const geometry = new THREE.IcosahedronGeometry(radius, 8, 6);
    geometry.translate( 0, radius, 0 );
    const vertices = geometry.getAttribute('position');
    for(let i=0; i<vertices.array.length; i++){
      vertices.array[i] += (Math.random()-0.5) * 0.35;
    }
    vertices.needsUpdate = true;
    const material = new THREE.MeshPhongMaterial( {color: 0xaaaaaa } );
    super(geometry, material);
  }
}

I limited the scene to one tree type. This gained me 2K. I was unfeasibly happy by this. That’s what happens with this competition! And makes it fun.

I updated the castles, created Player and Enemy classes that extend the Knight class so I can create the models using the Knight class but have different behaviour for the Player and an Enemy. And I created some new props.

Props

August 18th

Today I setup patrolling for the bad guys. Just a four cornered path and the enemy moves around this path unless the player is within 10 world units. I also started work on the introduction panel and gameover panel. No way in the byte allowance I can use a custom font. That would blow the budget straightaway.

Patrolling

August 19th

Main thing today was making the sword functional. I added an Object3D to the end of the sword. In the Player update method I do a check using the physics engine to see if this object position intersects any colliders. If the ThreeJS object associated with the physics body has the name ‘Gate’ or ‘Enemy’, I call methods of the object. For Gate that is the method openGate. I have a problem though I only have 33 bytes left. I did some checking, removing the sfx increases the bytes to 330. But removing the CollisionEffect increases the remaining bytes to over 2K. All assets are nearly complete. So 2K should be enough. Looks like I need to simplify the CollisionEffect.

Opening Gate

August 20th

A week into the competition and the game is developing well. I was travelling today so didn’t do much. I created a ForceField that will be visible for 10secs after a Shield pickup. It uses an InstancedMesh. An InstancedMesh instance takes geometry and material just like a Mesh. In addition it has a third parameter, count. The count parameter is the number of duplicates of the geometry. To position and orientate each mesh you use the setMatrixAt method. Passing an index and a matrix. Here’s the update method showing how the motion of the shields is handled.

update(dt){
  this.time += dt;
        
  const PI2 = Math.PI * 2;
  const inc = PI2/ForceField.count;
  let index = 0;

  for(let row=0; row<ForceField.rows; row++){
    const n = (row % 2) ? 1 : -1;
    const y = (ForceField.height/ForceField.rows) * row;
    for(let i=0; i<ForceField.count; i++ ){
      const t = (this.time * n) % PI2;
      const r = (this.time * -1) % PI2;
      const z = Math.sin(t+i*inc) * ForceField.radius;
      const x = Math.cos(t+i*inc) * ForceField.radius;
      this.obj.position.set(x,y,z);
      this.obj.rotation.set(0,t,0);
      this.obj.updateMatrix();
      this.meshes.setMatrixAt( index ++, this.obj.matrix );
    }
  }

  this.meshes.instanceMatrix.needsUpdate = true;
}
ForceField

August 21st

Travelling again today so didn’t achieve much. Main thing was rewriting the CollisionEffect as a InstancedMesh, rather than extending the custom class GPUParticleSystem. Gained nearly 1700 bytes. Well worth it.

August 22nd-23rd

Lot’s of debugging. I now have the basis of a game. Lots of fine tuning to do. I have 384 bytes left. But a bit of tiding up might gain me enough to add some sound.

4th September

I was away for the last few days with my daughter and the grandkids. Didn’t get anything done! I did a session of debugging yesterday, added a little sound and with 23 bytes left submitted!

13K is a serious limit and restricted what I could add as gameplay. But I really enjoyed working within this restriction. Particularly happy with the physics engine. Looking forward to next years theme.

Disappointingly there was a bug on the js13kgames site which made my game unplayable on the voting site so it received no votes and came last in the WebXR category!!! The problem was a cross-origin problem meaning the Three.JS library wouldn’t load from the path provided by the organisers. Frustrating after the spending many hours creating the game. Heh-ho, nevertheless I enjoyed the challenge.

The Universal Render Pipeline Cookbook: Recipes for Shaders and Visual Effects

The Universal Render Pipeline Cookbook cover

The latest cookbook, I’ve written for Unity is now live. It is all about Universal Render Pipeline (URP) effects and is now available to download for free. The e-book provides 12 recipes for popular visual effects that can be applied to a wide range of games, art styles, and platforms. Get ready to cook up Renderer Features, GPU-instantiated meshes, decals, volumetric materials, and more. You can use it alongside my other Unity e-book, Introduction to the Universal Render Pipeline for advanced Unity creators , which offers a wealth of information about how to use URP for creators that have developed projects with the Built-In Render Pipeline.

To celebrate the launch of the new e-book my Udemy course “The Complete Guide to Unity’s Universal Render Pipeline (URP)”, is available for less than $10 until 9th July 2023.

Here’s a handy overview of the recipes you’ll find in the book.

1. Stencils

Renderer features provide you with ample opportunities to experiment with lighting and effects. This recipe focuses on Stencils, using only the bare minimum of required code. If you work alongside the sample project, open the sample scene via Scenes > Renderer Features > SmallRoom – Stencil in the Editor.

The sample project uses the magnifying glass over desk example, and the aim is to convert the lens of the magnifying glass so that it allows you to see through the desk like an x-ray image. The approach uses a combination of Layer Masks, shaders, and Renderer features.

Renderer Features are a great way to achieve dramatic custom effects or gameplay possibilities.

GitHubDownload the sample

Stencils in action: As the magnifying glass moves over the desk, it can see through the drawers to reveal what’s inside.
Stencils in action: As the magnifying glass moves over the desk, it can see through the drawers to reveal what’s inside.

2. Instancing

Exchanging data between CPU and GPU is a major bottleneck in the rendering pipeline. If you have a model that needs to be rendered many times using the same geometry and material, then Unity provides some great tools to do so, which are covered in the cookbook’s instancing chapter.

This recipe uses a field full of grass to illustrate the concept of instancing. It uses the SRP Batcher, GPU instancing, RenderMeshPrimitives, and ComputeBuffers.

A field of grass rendered using an SRP Batcher-compatible material
A field of grass rendered using an SRP Batcher-compatible material

3. Toon and outline shading

Often used together, toon and outline shaders present two distinct challenges. The toon shader takes the cooler that would be created using a URP-compatible Lit shader, and ramps the output rather than allowing continuous gradients, thereby requiring a custom lighting model.

The example in this recipe uses Shader Graph. However, Shader Graph doesn’t support custom lighting, so there’s no node available to directly access the Main and Additional Lights. Instead, you can leverage a custom node to access those.

Check out the Toon and outline shading recipe to get the full details.

One scene, three different looks: Standard shading (left), with post-processing (center), and per-material shading (right)
One scene, three different looks: Standard shading (left), with post-processing (center), and per-material shading (right)

4. Ambient Occlusion

Ambient Occlusion is a post-processing technique available from Unity 2020.2. This effect darkens creases, holes, intersections, and surfaces that are close to one another. In the real world, such areas tend to block out or occlude ambient light, thereby appearing darker.

See how you can implement real-time Screen Space Ambient Occlusion (SSAO) effect as a Renderer Feature using URP.

Screen Space Ambient Occlusion
Screen Space Ambient Occlusion

5. Decals

Decals are a great way to insert overlays onto a surface. They’re often used to add visuals such as bullet holes or tire treads to the game environment as the player interacts with the scene.

If you want to follow along this recipe, you’ll work with URP Decal Projection properties, creating the material, and even adding a decal with code.

A new Decal Projector in action
A new Decal Projector in action

6. Water

The water recipe is created in Shader Graph to make the steps more accessible. It’s built in three stages:

  • Creating the water color
  • Moving tiled normal maps to add wavelets to the surface
  • Adding moving displacement to the vertex positions to create a swell effect

While this recipe forms the basis of a simple water shader, you can enhance it using Caustic Reflections, Refraction, and Foam.

Simple water shader in motion

7. LUT for color grading

Using LUT Textures is an efficient way to create dramatic color grading, and this approach can be useful in many games. It involves using one filter, but the steps employed apply to all of them.

Using Color Lookup to create grading effects
Using Color Lookup to create grading effects

8. Lighting

Lighting with URP is similar to using the Built-in Render Pipeline. The main difference is where to find the settings.

This chapter in the cookbook covers related recipes for real-time lighting and shadows, including baked and mixed lighting using the GPU Progressive Lightmapper, Light Probes, and Reflection Probes. You’ll pick up enough instruction for a five-course meal!

A few things to keep in mind about shaders and color space: When using lighting in URP, you have a choice between a Lit Shader and Simple Lit Shader, which is largely an artistic decision. If you want a realistic render, you can use the Lit Shader, but if you want a more stylized render, you can use Simple Lit for stellar results.

The diorama scene mixing baked and real-time lighting
The diorama scene mixing baked and real-time lighting

9. Shadows

Shadow settings are set using a Renderer Data object and a URP Asset using URP. You can use these assets to define the fidelity of your shadows.

The URP Asset
The URP Asset

This recipe includes tips for: Main Light and Shadow Resolution, Shadow Cascades, baking lights, and more.

Texel size by scale setting: In the top-left image, texel size is set to 0.5; in the top-right image, 0.2; in the bottom-left image, 0.1, and in the bottom-right image, 0.05.
Texel size by scale setting: In the top-left image, texel size is set to 0.5; in the top-right image, 0.2; in the bottom-left image, 0.1, and in the bottom-right image, 0.05.

10. Light Probes

Light Probes save the light data at a particular position within an environment when you bake the lighting by clicking Generate Lighting via Window > Rendering > Lighting panel. This ensures that the illumination of a dynamic object moving through an environment reflects the lighting levels used by the baked objects. It will be dark in a dark area, and in a lighter area it will be brighter.

Follow this recipe to find out how to position Light Probes with a code-based approach in order to speed up your editing, how to use Reflection Probes in your scene, and how to blend them.

The robot inside and outside of the cave, with lighting level affected by Light Probes
The robot inside and outside of the cave, with lighting level affected by Light Probes

11. Screen Space Refraction

Screen Space Refraction uses the current opaque texture created by the render pipeline as the source texture to map pixels to the model being rendered. This method and recipe is about deforming the UV used to sample the image.

Learn how to use a normal map to create refraction effects as well as tint a refraction effect.

An example of Screen Space Refraction
An example of Screen Space Refraction

12. Volumetrics

This is a recipe for using ray marching to render a 3D texture. Unity supports 3D textures, which are an array of images placed in a grid on a single texture, rather like a Texture Atlas. The difference is that each image is the same size. Using a 3D UV value, you can source a texel from the grid of images with UV.Z defining the row and column of the individual image to use.

You can also use Houdini when creating the 3D texture. Alternatives to a 3D texture include using multilayered Perlin noise, or prebaking a tileable noise texture using Unity.

A cloud with ray marching
A cloud with ray marching

More resources

This cover image shown here is from PRINCIPLES, an adventure game from COLOPL Creators, the technology brand of COLOPL, Inc., who developed the series of Shironeko Project and Quiz RPG: The World of Mystic Wiz.
This cover image shown here is from PRINCIPLES, an adventure game from COLOPL Creators, the technology brand of COLOPL, Inc., who developed the series of Shironeko Project and Quiz RPG: The World of Mystic Wiz.

There are many advanced resources available for free from Unity. As mentioned at the beginning of the blog post, the e-book Introduction to the Universal Render Pipeline for advanced Unity creators is a valuable resource for helping experienced Unity developers and technical artists migrate their projects from the Built-in Render Pipeline to the URP.

All of the advanced e-books and articles are available from the Unity best practices hub. E-books can also be found on the advanced best practices documentation page.

The Complete Guide to Unity’s Universal Render Pipeline (URP)

And don’t forget my Udemy URP course is available for less than 10 bucks until 9th July

Converting older projects to the new Wonderland Engine framework

When the Wonderland editor moved from 0.9.5 to 1.0.0 there was a significant change to the code framework. In this article we’ll look at how you can easily migrate an old project to work in the latest editor. If you plan to code-along then download my Wonderland course resources.

With the switch by default Wonderland now uses npm, NodeJS Package Manager, and esbuild bundler. The WL.registerComponent method no longer exists. For each of your custom components you need to do some editing. If you want to code-along then open the code-along Migrate project. As you can see, Wonderland creates an npm project.

node modules

After running you’ll find a node_modules folder, two new json files; package.json and package-lock.json and index.js. The node_modules folder by default contains the wonderlandengine, glMatrix and howler and a few other modules. Index.js is auto generated by the editor and this file handles registering your components. 

Console errors

Looking at the project console window we can see there are errors when packaging. WL not defined. Notice it refers to the blockHandler component let’s fix this file.

WL.registerComponent('blockHandler', {
vrCamera: {type: WL.Type.Object, default: null},
speed: {type: WL.Type.Float, default: 5.0 }
}, {

Where we have WL.registerComponent this becomes export class, then the class name, BlockHandler here and then extends Component

export class BlockHandler extends Component {

If you’re familiar with JavaScript classes then you will instantly say, hang on where does Component come from. If so go to the top of the class. We need to add an import. It comes from the wonderlandengine api. From the module we saw in the node_modules folder. 

import {Component} from '@wonderlandengine/api';

Each component needs a TypeName, Wonderland uses the type name in the editor not the class name. 

static TypeName = "blockHandler";

Parameters are defined using a static Properties object. The Wonderland api has a Property class and we use this to specify property types and default values. If we’re using something from the api we need to import it so we add Property as a second import from wonderlandengine/api. 

import {Component, Property} from '@wonderlandengine/api';

We specify vrCamera as a Property.object. This means the editor will include a scene objects dropdown for this component property. Speed is a Property.float, for floats we can pass a default value to the constructor. 

static Properties = {
   vrCamera: Property.object(),
   speed: Property.float( 5.0 )
}

 Now we are dealing with classes we need to remove ‘: function’.

init() {

And we remove all the commas linking the functions in what was previously an object. 

This component uses the glMatrix library and now we are using the new framework we need to add each class we use from the library as an import. For this component we need vec3 and quat. They come from gl-matrix which you can find in the node_modules folder.

import { vec3, quat } from "gl-matrix";

The easiest way to remove glMatrix. Is to use find and replace all. 

this.rotation = quat.create();
quat.fromEuler(this.rotation, 1, 1, 0);

This component also uses HowlerAudioSource. When using this we need to add it as an import.

import {HowlerAudioSource} from '@wonderlandengine/components';

This is a Wonderland component. Because it is not directly attached to a scene object. We need to make sure the component is registered. Any dynamically created component needs to do this. We add a static onRegister method, this method will receive the engine, which was previously the WL global, as a parameter. We use the registerComponent method of the engine and pass the class name. Without this linking would fail and you would get an error in the browser.

static onRegister(engine){
   engine.registerComponent( HowlerAudioSource );
}

We add the HowlerAudioSource in the start method. What was previously a string now takes the class name. 

this.sfxSwish = this.object.addComponent(HowlerAudioAource, {src: 'sfx/swish.mp3', spatial: false});

At this point the project should package correctly. But you will then notice that Wonderland has added several useful warnings about legacy methods. You’ll see several methods have a line through them and if you hover over them a panel will appear explaining what method you should switch to. Position, for example, no longer uses Translation, now it is Position instead.

this.vrCamera.getPositionWorld( this.tmpVec1 );

Instead of translate you choose between translateLocal and translateWorld.

this.object.translateWorld( this.tmpVec );

And getForward is now getForwardWorld.

this.vrCamera.getForwardWorld( this.tmpVec1 );

Some other changes you’ll come across. 

WL.onXRSessionStart is now an instance of the Emitter class. We replace WL with this.engine. Engine is always available to a class that extends Component. And replace push with add.  

this.engine.onXRSessionStart.add(this.setupVREvents.bind(this));

And finally getters and setters have been replaced.

  • this.object.translationLocal use this.object.getPositionLocal(out) and this.object.setPositionLocal(v)
  • this.object.translationWorld use this.object.getPositionWorldl(out) and this.object.setPositionWorld(v)
  • this.object.rotationLocal use this.object.getRotationLocal(out) and this.object.setRotationLocal(q)
  • this.object.rotationWorld use this.object.getRotationWorld(out) and this.object.setRotationWorld(q)
  • this.object.scalingLocal use this.object.getScalingLocal(out) and this.object.setScalingLocal(v)
  • this.object.scalingWorld use this.object.getScalingWorld(out) and this.object.setScalingWorld(v)
  • this.object.transformLocal use this.object.getTransformLocal(out) and this.object.setTransformLocal(q2)
  • this.object.transformWorld  use this.object.getTransformWorld(out) and this.object.setTransformWorld(q2)

Follow the steps in this article and you’ll soon have your projects running smoothly using the latest Wonderland editor.
You’ll find more information in the ‘Migrate Your JavaScript to 1.0.0’ article.

Unity URP Course FREE for 4 days

The Complete Guide to Unity’s Universal Render Pipeline (URP). Is FREE for the next 4 days.

https://www.udemy.com/course/unity-urp/?couponCode=JUNE23_FREE

If you use Unity then you really should get up to speed with this pipeline which will replace the Built-in Render Pipeline. This course by the author of Unity’s e-book will show you how.

Grab it here https://www.udemy.com/course/unity-urp/?couponCode=JUNE23_FREE

Pre-summer SALE!



The days are long, but that doesn’t mean you should neglect your studies. To encourage you I’m having a pre-summer SALE. Grab yourself a bargain and kick back and learn a new skill!!

The Complete Guide to Unity’s Universal Render PipelineWith URP to become Unity’s default pipeline learn how to use it in this course by the author of Unity’s URP e-books
https://www.udemy.com/course/unity-urp/?couponCode=SUMMERSALE23

Create WebXR, VR and AR app, using the Wonderland EngineWonderland is a high performance engine for WebXR apps. The editor makes creating VR experiences super-easy. In this course I show you how.
https://www.udemy.com/course/webxr-wle/?couponCode=SUMMERSALE23

Model viewer: Web 3D made easyModel-viewer is a web component created by Google. It makes displaying user interact-able 3D models on a web page a walk in the park. In this course I show you how.
https://www.udemy.com/course/model-viewer/?couponCode=SUMMERSALE23

The Beginners Guide to 3D Web Game Development with ThreeJSLearn to write JavaScript code while having fun making 3D web games using the most popular Open Source WebGL library ThreeJS
https://www.udemy.com/course/beginners-3d-web-game-development-with-threejs/?couponCode=SUMMERSALE23

Learn to write Unity Compute ShadersLearn to harness the power of the GPU for processing intensive jobs.
https://www.udemy.com/course/compute-shaders/?couponCode=SUMMERSALE23

Learn to Create WebXR, VR and AR, experiences with ThreeJSLearn how to create VR and AR experiences that work directly from the browser, using the latest API from Google and Amazon and our favourite Open Source WebGL library, ThreeJS
https://www.udemy.com/course/learn-webxr/?couponCode=SUMMERSALE23

Learn Unity Shaders from ScratchLearn the black-art of Unity shaders in this comprehensive course on HLSL. Shaders for both the Built-in Render Pipeline and URP are included.
https://www.udemy.com/course/learn-unity-shaders-from-scratch/?couponCode=SUMMERSALE23

Learn GLSL Shaders from ScratchLearn how to harness the power of the GPU in your web pages by learning to code GLSL shaders.
https://www.udemy.com/course/learn-glsl-shaders-from-scratch/?couponCode=SUMMERSALE23

Create a 3D Multi-Player Game using ThreeJS and SocketIOLearn how to use nodeJS, socketIO and ThreeJS to create a 3d multi-player game
https://www.udemy.com/course/create-a-3d-multi-player-game-using-threejs-and-socketio/?couponCode=SUMMERSALE23

Create HTML5 Games using Adobe AnimateAdobe Animate used to be Flash. Learn how you can use your Flash skills to create HTML5 games that use no plugins.
https://www.udemy.com/course/create-html5-games-using-adobe-animate/?couponCode=SUMMERSALE23

Create a 3D Car Racing Game with ThreeJS and CannonJS
Learn to combine the physics engine CannonJS and ThreeJS to create a fun car racing game
https://www.udemy.com/course/create-a-3d-car-racing-game-with-threejs-and-cannonjs/?couponCode=SUMMERSALE23

Create a 3D RPG Game with ThreeJSLearn how to harness the ThreeJS library to create a 3D RPG game
https://www.udemy.com/course/create-a-3d-rpg-game-with-threejs/?couponCode=SUMMERSALE23

HTML5 Game Development: Beginner to ProLearn how to create 2D games that work in the browser. From card games to puzzle games to action games.
https://www.udemy.com/course/html5-game-development-beginner-to-pro/?couponCode=SUMMERSALE23

JavaScript in 12 Easy LessonsNew to JavaScript or coding then this FREE course is for you
https://www.udemy.com/course/javascript-in-12-easy-lessons/?referralCode=086EC53154E29AD37EA1

The ThreeJS PrimerNew to ThreeJS then this FREE course is for you
https://www.udemy.com/course/the-threejs-primer/?referralCode=ABB270C1AE32EF9E7174

Adding Variants to a GLB model and displaying them using ThreeJS

(This article is taken from my Udemy course ‘The ThreeJS Cookbook’ coming Summer 2023. Get the course assets from GitHub. I recommend using VSCode and the Live Server extension when working along with the recipe )

If you’re creating a ThreeJS app for a store. You may be selling an item that comes in a variety of finishes. The GLB format has an extension that allows you to embed multiple materials for an object, then with code you can allow the user to switch materials to view the item with different finishes.

Before we get into coding this recipe take a look at complete > Materials > variants.html . Using the dropdown selector you can view the chair in one of four different finishes. The code reads the options directly from the model file. You can use the same technique in your own GUI.

Before we look at the code let’s look at how we create a glb file that includes material variants. We’re going to use Blender, I’m using Blender 3.5 but anything from version 3.3 will be fine. You can download Blender for free at blender.org. Blender is a complex application but there are loads of tutorials available both free and paid.

In this recipe I assume you have some knowledge of Blender and I focus exclusively on variants. To keep things simple we’ll just create materials that change the colour of the default cube. 

First open Blender and choose the General new project. You’ll get a 2m grey cube centre screen. Right-click on the light and camera and choose Delete. Lights and cameras will be added using code when we create the ThreeJS app to display our model.

Using the buttons at the top right set the display to Material Preview mode.

Select Edit > Preferences…. Select Add-ons and type ‘gltf’ in the Search field. Make sure that for the Import-Export: glTF 2.0 format add-on that the Material Variants checkbox is checked. You can close the preferences panel now.

Select the Cube. Select the Materials tool, the chequered sphere. Click the white on black Material word and rename it Grey. 

Expand the Sidebar and select glTF Variants. In the new panel press the Add Material Variant button.

Rename the variant Grey.

In the materials panel slide down to find glTF Material Variants. Press the Add a new Variant Slot button.

The new variant should take the name you’ve renamed Grey.

Click the + button to add a new material. Rename it red and set the Base Color. 

Now select Grey and press the – button. You can now use the glTF Variants Sidebar tool to add a new variant. Rename it Red and then use the Material glTF Material Variants panel to Add a new Variant Slot.

Repeat from adding a new material for each variant. 

To test whether the variants are working, use the glTF Variants Sidebar panel. Select the variant you’re interested in and press the Display Variant button. You should see the cube change to your chosen material. Now choose File > Export > glTF 2.0 (.glb/.gltf), select a file location and name.

Time to focus on code. The assets include the chair we’ve seen that includes variants with different textures and roughness. We’ll use that in the coding. But feel free to use your model if you prefer.

Before we start coding take a look at a glTF formatted file assets > variant-cube.gltf. This is the text formatted version. Usually you save a file in binary format, a glB file. But it is easy to read a glTF file in any text editor. First notice the extensions object, and see this includes KHR_materials_variants. This object contains a variants array. Each entry in the array is an object with a single property, name. 

{
   "asset":{
      "generator":"Khronos glTF Blender I/O v3.5.30",
      "version":"2.0"
   },
   "extensionsUsed":[
      "KHR_materials_variants"
   ],
   "extensionsRequired":[
      "KHR_materials_variants"
   ],
   "extensions":{
      "KHR_materials_variants":{
         "variants":[
            {
               "name":"Red"
            },
            {
               "name":"Yellow"
            },
            {
               "name":"Default"
            }
         ]
      }
   },

The glTF structure includes, a scene and scenes array. Scene is simply and index into the scenes array. A scene includes a nodes array, which in this case has a single index. 

   "scene":0,
   "scenes":[
      {
         "name":"Scene",
         "nodes":[
            0
         ]
      }
   ],

The nodes array includes a series of objects. Here we have a properties mesh, an index into the meshes array and a name.

   "nodes":[
      {
         "mesh":0,
         "name":"Cube"
      }
   ],

Now the meat of our variants data. The materials array includes a series of objects. Here simply setting the pbrMetallicRoughness.baseColorFactor, metallicFactor and roughness

   "materials":[
      {
         "doubleSided":true,
         "name":"Red",
         "pbrMetallicRoughness":{
            "baseColorFactor":[
               0.8000000715255737,
               0.011036576703190804,
               0.00618034927174449,
               1
            ],
            "metallicFactor":0,
            "roughnessFactor":0.5
         }
      },
      {
         "doubleSided":true,
         "name":"Yellow",
         "pbrMetallicRoughness":{
            "baseColorFactor":[
               0.8000000715255737,
               0.6312862634658813,
               0,
               1
            ],
            "metallicFactor":0,
            "roughnessFactor":0.5
         }
      },
      {
         "doubleSided":true,
         "name":"Material",
         "pbrMetallicRoughness":{
            "baseColorFactor":[
               0.800000011920929,
               0.800000011920929,
               0.800000011920929,
               1
            ],
            "metallicFactor":0,
            "roughnessFactor":0.5
         }
      }
   ],

The meshes array contains each mesh in every scene. But in this simple example there is only the single Cube. Notice that the Cube has an extensions object, which contains a KHR_materials_variants object. This in turn contains a mappings array. Each entry in the mappings array contains a material index and a variants array containing a single index in this instance. 

   "meshes":[
      {
         "name":"Cube",
         "primitives":[
            {
               "attributes":{
                  "POSITION":0,
                  "TEXCOORD_0":1,
                  "NORMAL":2
               },
               "extensions":{
                  "KHR_materials_variants":{
                     "mappings":[
                        {
                           "material":0,
                           "variants":[
                              0
                           ]
                        },
                        {
                           "material":1,
                           "variants":[
                              1
                           ]
                        },
                        {
                           "material":2,
                           "variants":[
                              2
                           ]
                        }
                     ]
                  }
               },
               "indices":3,
               "material":2
            }
         ]
      }
   ],

The code we need to generate should read root level extensions object, look for KHR_materials_variants and read the names of the variants. Using this to populate a GUI. Then we need a function that given a variant name, finds the index of this variant. Then iterates over each mesh in the scene, checks if the mesh has a KHR_materials_variants extension. If it has then it checks the mappings object to see if its variants array includes the variant index found for the name given. If it does then we get the material using the index and apply it to the mesh. If that sounds complex it might be clearer once we’ve added the code. 

We’ll source the variant names from the root level extension first. First we create a new GUI using the lil-gui library. Then we grab a reference to the gltf.parser object. More about that later. After parsing, extensions are stored in the userData object for each Object3D instance in the hierarchy. We check if gltfExtensions is a gltf.userData property. If it is we attempt to assign the extension KHR_materials_variants. At this stage variantsExtension is either null if missing or a reference to the data. 

gui = new GUI();

const parser = gltf.parser;

let variantsExtension;

if ( 'gltfExtensions' in gltf.userData ){
  variantsExtension = gltf.userData.gltfExtensions[ 'KHR_materials_variants' ];
}

If variantsExtension is not null then we use the JavaScript array method map to return a new array containing just an array of names. Then we add a select control. The state object is defined at the start of the script with a single property variant.

if (variantsExtension != null){
   const variants = variantsExtension.variants.map( ( variant ) => variant.name );
   const variantsCtrl = gui.add( state, 'variant', variants ).name( 'Variant' );
}

If you try the app now you should get a list of variant names in the GUI. 

Now we need a function to call when a name is selected in the GUI. It needs a reference to the scene, the parser we stored, the variants extension and the variant name. We need to convert the variant name into an index, so we use the findIndex method of a JavaScript array. 

function selectVariant( scene, parser, extension, variantName ) {

   const variantIndex = extension.variants.findIndex( ( v ) => v.name.includes( variantName ) );

Now we can traverse the scene. We do this asynchronously since a scene may contain a lot of Object3D instances and we don’t want to hold up processing. The callback retrieves each object in the gltf scene. We’re only interested in meshes, so we check if the isMesh flag is set. And we need userData to contain a gltfExtensions object. If either of these checks fail we return from the callback for the current object and move on to the next. Then we assign meshVariantDef as the gltfExtensions object KHR_materials_variants. That’s the extension stored in the object definition. The one we saw that includes mappings.

scene.traverse( async ( object ) => {

if ( ! object.isMesh || ! object.userData.gltfExtensions ) return;

const meshVariantDef = object.userData.gltfExtensions[ 'KHR_materials_variants' ];

If meshVariantDef is null then we can also return. Now we store the current material as userData.originalMaterial, assuming this hasn’t already been set.

if ( ! meshVariantDef ) return;

if ( ! object.userData.originalMaterial ) {

   object.userData.originalMaterial = object.material;

}

To find the mapping we’re interested in we use the find method of a JavaScript array, searching through the variants array for one that includes the variantIndex.

const mapping = meshVariantDef.mappings
                .find( ( mapping ) => mapping.variants.includes( variantIndex ) );

If we find a mapping, we use the parser method getDependency, use material as the property and the mapping.material index. This returns the material in the materials array with the given index. The parser object also contains a method, assignFinalMaterial that ensures the material is correctly assigned and compiled. If mapping is not found then we restore the original material from the one we stored in userData. We also call render because this app doesn’t use a render loop. Instead we call render on each change.

if ( mapping ) {

   object.material = await parser.getDependency( 'material', mapping.material );
   parser.assignFinalMaterial( object );

} else {

   object.material = object.userData.originalMaterial;

}

render();

It just remains to update our GUI code to call this function on a change event. 

variantsCtrl.onChange( ( value ) => selectVariant( scene, parser, variantsExtension, value ) );

Now the GUI is populated and on a change event the variant name is passed to the selectVariant method. 

Variants are a great solution for allowing users to view an object using a variety of finishes.

May the 4th Be With You!

May the 4th be with you. Starry sky poster, star force and hand drawn stars. Wars movie slogan banner, futuristic stars glow poster or space star fantasy vector illustration

It’s Star Wars Day. My courses are at Udemy’s Best Price for the next few days. Grab yourself a bargain and May the Fourth be with you!!

Or use the menu to check-out my tutorial links.

The Complete Guide to Unity’s Universal Render Pipeline

With URP to become Unity’s default pipeline with Unity 6. Learn how to use it in this course by the author of Unity’s URP e-books

https://www.udemy.com/course/unity-urp/?couponCode=MAY24_FOURTH

Create WebXR, VR and AR app, using the Wonderland Engine

Wonderland is a high performance engine for WebXR apps. The editor makes creating VR experiences super-easy. In this course I show you how.

https://www.udemy.com/course/webxr-wle/?couponCode=MAY24_FOURTH

Model viewer: Web 3D made easy

Model-viewer is a web component created by Google. It makes displaying user interact-able 3D models on a web page a walk in the park. In this course I show you how.

https://www.udemy.com/course/model-viewer/?couponCode=MAY24_FOURTH

The Beginners Guide to 3D Web Game Development with ThreeJS

Learn to write JavaScript code while having fun making 3D web games using the most popular Open Source WebGL library ThreeJS

https://www.udemy.com/course/beginners-3d-web-game-development-with-threejs/?couponCode=MAY24_FOURTH

Learn to write Unity Compute Shaders

Learn to harness the power of the GPU for processing intensive jobs.

https://www.udemy.com/course/compute-shaders/?couponCode=MAY24_FOURTH

Learn to Create WebXR, VR and AR, experiences with ThreeJS

Learn how to create VR and AR experiences that work directly from the browser, using the latest API from Google and Amazon and our favourite Open Source WebGL library, ThreeJS

https://www.udemy.com/course/learn-webxr/?couponCode=MAY24_FOURTH

Learn Unity Shaders from Scratch

Learn the black-art of Unity shaders in this comprehensive course on HLSL. Shaders for both the Built-in Render Pipeline and URP are included.

https://www.udemy.com/course/learn-unity-shaders-from-scratch/?couponCode=MAY24_FOURTH

Learn GLSL Shaders from Scratch

Learn how to harness the power of the GPU in your web pages by learning to code GLSL shaders.

https://www.udemy.com/course/learn-glsl-shaders-from-scratch/?couponCode=MAY24_FOURTH

Create a 3D Multi-Player Game using ThreeJS and SocketIO

Learn how to use nodeJS, socketIO and ThreeJS to create a 3d multi-player game

https://www.udemy.com/course/create-a-3d-multi-player-game-using-threejs-and-socketio/?couponCode=MAY24_FOURTH

Create HTML5 Games using Adobe Animate

Adobe Animate used to be Flash. Learn how you can use your Flash skills to create HTML5 games that use no plugins.

https://www.udemy.com/course/create-html5-games-using-adobe-animate/?couponCode=MAY24_FOURTH

Create a 3D Car Racing Game with ThreeJS and CannonJS

Learn to combine the physics engine CannonJS and ThreeJS to create a fun car racing game

https://www.udemy.com/course/create-a-3d-car-racing-game-with-threejs-and-cannonjs/?couponCode=MAY24_FOURTH

Create a 3D RPG Game with ThreeJS

Learn how to harness the ThreeJS library to create a 3D RPG game

https://www.udemy.com/course/create-a-3d-rpg-game-with-threejs/?couponCode=MAY24_FOURTH

HTML5 Game Development: Beginner to Pro

Learn how to create 2D games that work in the browser. From card games to puzzle games to action games.

https://www.udemy.com/course/html5-game-development-beginner-to-pro/?couponCode=MAY24_FOURTH

JavaScript in 12 Easy Lessons

New to JavaScript or coding then this FREE course is for you

https://www.udemy.com/course/javascript-in-12-easy-lessons/?referralCode=086EC53154E29AD37EA1

The ThreeJS Primer

New to ThreeJS then this FREE course is for you

https://www.udemy.com/course/the-threejs-primer/?referralCode=ABB270C1AE32EF9E7174

Happy Lunar New Year!

Just an update to my 36,000 students. I’m currently developing a course teaching how to get the best from Unity’s Universal Render Pipeline (URP). It’s based on the e-book I wrote for Unity.

Introduction to the Universal Render Pipeline for Advanced Unity Creators.

The course, aimed more at intermediate level, is going well and should be live in March.

If you plan to do the course then take a look at these two tutorial videos on YouTube as a taster to what to expect.

Converting custom shaders to URP

Make your shaders Scriptable Render Pipeline Batcher compatible. This step by step video tutorial will show you how to convert a custom unlit Built-in shader to the Universal Render Pipeline (URP).

Three ways to use URP Renderer Features

A Renderer Feature is a C# script that can be used at any stage in the Render Pipeline to affect the final render. Renderer Features provide a great way of adding custom rendering effects to your scene, often with no code required, making them artist-friendly.

Nik Lever 22 Jan 2023

Nik Lever’s New Year Sale

Hope you’re all enjoying the holidays. To celebrate the New Year. All my courses are available at the lowest price Udemy will allow this week with the coupon code NEWYEAR23 . Read through to find two FREE courses

Create WebXR, VR and AR app, using the Wonderland Engine

Wonderland is a high performance engine for WebXR apps. The editor makes creating VR experiences super-easy. In this course I show you how.

https://www.udemy.com/course/webxr-wle/?couponCode=NEWYEAR23

Model viewer: Web 3D made easy

Model-viewer is a web component created by Google. It makes displaying user interact-able 3D models on a web page a walk in the park. In this course I show you how.

https://www.udemy.com/course/model-viewer/?couponCode=NEWYEAR23

The Beginners Guide to 3D Web Game Development with ThreeJS

Learn to write JavaScript code while having fun making 3D web games using the most popular Open Source WebGL library ThreeJS

https://www.udemy.com/course/beginners-3d-web-game-development-with-threejs/?couponCode=NEWYEAR23

Learn to write Unity Compute Shaders

Learn to harness the power of the GPU for processing intensive jobs.

https://www.udemy.com/course/compute-shaders/?couponCode=NEWYEAR23

Learn to Create WebXR, VR and AR, experiences with ThreeJS

Learn how to create VR and AR experiences that work directly from the browser, using the latest API from Google and Amazon and our favourite Open Source WebGL library, ThreeJS

https://www.udemy.com/course/learn-webxr/?couponCode=NEWYEAR23

Learn Unity Shaders from Scratch

Learn the black-art of Unity shaders in this comprehensive course on HLSL. Shaders for both the Built-in Render Pipeline and URP are included.

https://www.udemy.com/course/learn-unity-shaders-from-scratch/?couponCode=NEWYEAR23

Learn GLSL Shaders from Scratch

Learn how to harness the power of the GPU in your web pages by learning to code GLSL shaders.

https://www.udemy.com/course/learn-glsl-shaders-from-scratch/?couponCode=NEWYEAR23

Create a 3D Multi-Player Game using ThreeJS and SocketIO

Learn how to use nodeJS, socketIO and ThreeJS to create a 3d multi-player game

https://www.udemy.com/course/create-a-3d-multi-player-game-using-threejs-and-socketio/?couponCode=NEWYEAR23

Create HTML5 Games using Adobe Animate

Adobe Animate used to be Flash. Learn how you can use your Flash skills to create HTML5 games that use no plugins.

https://www.udemy.com/course/create-html5-games-using-adobe-animate/?couponCode=NEWYEAR23

Create a 3D Car Racing Game with ThreeJS and CannonJS

Learn to combine the physics engine CannonJS and ThreeJS to create a fun car racing game

https://www.udemy.com/course/create-a-3d-car-racing-game-with-threejs-and-cannonjs/?couponCode=NEWYEAR23

Create a 3D RPG Game with ThreeJS

Learn how to harness the ThreeJS library to create a 3D RPG game

https://www.udemy.com/course/create-a-3d-rpg-game-with-threejs/?couponCode=NEWYEAR23

HTML5 Game Development: Beginner to Pro

Learn how to create 2D games that work in the browser. From card games to puzzle games to action games.

https://www.udemy.com/course/html5-game-development-beginner-to-pro/?couponCode=NEWYEAR23

JavaScript in 12 Easy Lessons

New to JavaScript or coding then this FREE course is for you

https://www.udemy.com/course/javascript-in-12-easy-lessons/?referralCode=086EC53154E29AD37EA1

The ThreeJS Primer

New to ThreeJS then this FREE course is for you

https://www.udemy.com/course/the-threejs-primer/?referralCode=ABB270C1AE32EF9E7174

My Unity URP e-book is published today!

URP for Advanced Unity Creators

Download this valuable guide to fully leverage the benefits of URP for professional game development. I hope that you find the e-book helpful.

To celebrate the publication of my Unity URP e-book my Udemy Unity courses are available for just $10.99 for the next few days.

Unity Compute Shaders
Learn to Write Unity Compute Shaders

Learn to Write Unity Compute Shaders

Unity Shaders
Learn to Write Unity Shaders from Scratch

Learn to Write Unity Shaders from Scratch

Includes URP shaders and ShaderGraph