• Series
  • Blazor
  • BabylonJS
  • C#
  • .NET

BabylonJS and Blazor - Lanterns

For this article we will go over Lanterns implemented in C#, here we load in the lanterns models and place them around the map, this step did not have any major differences so checkout the Demo and Source Code below!

Next Article in the works, follow me on Twitter @CodyAnhorn to get notified when I publish it!

Demo and Source Code

Below is a demo of the Blazor application after Step 8 is implemented, you can open the demo in a new tab by going to BabylonJS Blazor Step 08.

Use the Arrow keys to move the around the map. Space key to Jump and the Shift key to Dash while in the Air. Jump into the Lanterns to see them light up!

You can see the full source code in GitHub: canhorn/BabylonJS.Blazor.Game.Tutorial at step/08_Lanterns

If you want to see the original step in the Series: Lanterns | Babylon.js Documentation (babylonjs.com)

Implementation Overview

As part of the GameEnvironment.Load the Lantern mesh is now loaded, there are 22 lanterns spread across the map. Using the Mesh.clone function we are able take the loaded Lantern mesh we are able to easily create copies that can be be placed around the map. Using the loaded Environment we look for a TransformNode with the name of "lantern {i}", we can then use this Node to set the Lanterns Position.

To give the Lantern a unique paper like look we are using the PBRMetallicRoughnessMaterial, we give this material a Texture and emissiveColor adding to the Lanterns aesthetics. We pass this Light Material into each lanterns encapsulated instance, when the Lantern is lit it will set its Mesh.material property. When the Lantern is lit it will create a PointLight that will only illuminate any meshes in an area around it based on a Mesh created during the instantiation of the Lantern.

To light a Lantern the Player needs to intersect with the Lantern Mesh, this is done by attaching Code Actions to the Player.Mesh.actionManager. The trigger for the action is ActionManager.OnInstersectionEnterTrigger and the parameter is the lantern.Mesh. During the intersection the player will have two distinct action taken, checking the lantern is lit or not. The first action checked against the Player is the lantern lit state and the Players sparkler state, if both are true the Players.LanterLit will go up by one, the lantern will be lit, and the Sparkler timer will be reset. If the first action does not pass the next action will check if the lantern intersected with was lit, if it was then the Player Sparkler timer will be reset.

Source Code Example(s)

With this code sample we are importing the Lanterns and placing them on the level.

// --- Part of GameEnvironment.Load Method
// Load Lanterns Mesh
var lanternResult = await SceneLoader.ImportMeshAsync(
    "",
    "./models/",
    "lantern.glb",
    _scene
);
// Extract the actual lanterns mesh from the root of the mesh that is imported
var lantern = lanternResult.meshes[0].getChildren()[0];
// Removing the parent helps with positioning clones
lantern.parent = null;

// --- Part of GameEnvironment.LoadAsset Method
// Original mesh is not visible
assets.Lantern.isVisible = false;
// A transform node to hold all lanterns
var lanternHolder = new TransformNode(
    "lanternHolder",
    _scene,
    true
);
// We have 22 lanterns on the map, this creates those Lanterns.
for (int i = 0; i < 22; i++)
{
    // We clone the lantern mesh 
    var lanternInstance = assets.Lantern.clone(
        $"lantern{i}",
        lanternHolder
    );
    lanternInstance.isVisible = true;
    lanternInstance.setParent(lanternHolder);

    // Create new Lantern
    var newLantern = new Lantern(
        _lightMaterial,
        lanternInstance,
        _scene,
        // Position the Lantern based on a node loaded by the Environment
        assets.Env.getChildTransformNodes(false)
            .FirstOrDefault(a => a.name == $"lantern {i}")
            .getAbsolutePosition()
    );
    _lanternObjs.Add(
        newLantern
    );
}

Here we are registering the IntersectionEnterTrigger action on the lantern, so when the player interacts with a lantern and it is not lit it will lite the lantern.

player.Mesh.actionManager.registerAction(
    new ExecuteCodeAction(
        new
        {
            trigger = ActionManager.OnIntersectionEnterTrigger,
            parameter = lantern.Mesh
        },
        new EventHorizon.Blazor.Interop.Callbacks.ActionCallback<ActionEvent>(
            _ =>
            {
                if (!lantern.IsLit
                    && player.SparkLit
                )
                {
                    // Increment the lantern count
                    player.LanternsLit += 1;
                    // Light up the lantern
                    lantern.SetEmissiveTexture();

                    // reset the Sparkler 
                    player.SparkRest = true;
                    player.SparkLit = true;
                }
                // If the lantern is lit already, reset the Sparkler
                else if (lantern.IsLit)
                {
                    player.SparkRest = true;
                    player.SparkLit = true;
                }

                return Task.CompletedTask;
            }
        )
    )
);

This code creates a PBRMetallicRoughnessMaterial assigning it a Texture and emissiveColor for our Lanterns to give them a really fancy look.

// Create emissive material for when a Lantern is lit
var lightMaterial = new PBRMetallicRoughnessMaterial(
    "lantern-mesh-light",
    _scene
);
lightMaterial.emissiveTexture = new Texture(
    _scene,
    "/textures/litLantern.png",
    true,
    false
);
lightMaterial.emissiveColor = new Color3(
    0.8784313725490196m,
    0.7568627450980392m,
    0.6235294117647059m
);

Cody's logo image, it is an abstract of a black hole with a white Event Horizon.

Cody Merritt Anhorn

A Engineer with a passion for Platform Architecture and Tool Development.