8/17/2020 Admin
Introducing Blazor Automatic Kingdom
The Blazor application, Automatic Kingdom, is designed to allow users to create programs, using visual blocks, to control 3D objects and animation.
The code in this article is based on the 00.00.01 Alpha Release. To run the code, you will need Visual Studio Studio 2019 Preview (or higher) with .Net Core 5.
The application is built using the following technology stack:
- Blockly – A JavaScript program that allows you to create block-based visual programming languages. Widely used in programs such as MIT’s Scratch 3.0 and Microsoft’s MakeCode, this provides a visual tool to allow the end user to control the intent of the final program using a visual design.
- IronBlock – Blockly automatically generates XML, JavaScript, Python, PHP, Lua, and Dart, but not C#. IronBlock converts the XML output to C#.
- CS-Script.Core – While Iron Block creates valid C#, executing that C# code, inside a Blazor application, is actually quite complicated. The CS-Script library provides that important functionality.
- EventHorizon.Blazor.Babylon.js – The ultimate goal is to display rich 3D graphics and animation using Babylon.js. However, Babylon.js is a JavaScript library. The EventHorizen library allows Babylon.js to be programmed using C# code.
- Babylon.js – This library provides a powerful 3D graphics engine to display the final result.
Walk Thru
You can download and run the code, or use it at: https://automatickingdom.com.
The toolbox provides access to the blocks, including custom blocks, that allow you to define program execution logic on the main design screen.
In the current implementation, a input WorldObject that consists of either “Idle”, “Attacked”, or “Calm” will be passed to the code block, and a AnimationLoopResult value, consisting of either “Idle”, “Attack”, or “Walk” needs to be passed as the output.
Clicking the C# Code button displays the current logic as C# code.
Clicking the Run Code button displays an Avatar using Babylon.js.
You can select a value to input to the code, and view the result in the Babylon.js animation.
Blockly
Blockly is implemented in the Blazor Server application by using a Div with a @ref to the @blocklyDiv variable:
<div style="width: 100%"><div @ref="@blocklyDiv" style="display: inline-block; height: 480px; width: 100%"></div></div>
When the .razor control loads, the following code runs:
protected override async Task OnAfterRenderAsync(bool firstRender){if (firstRender)
{await AutomaticKingdomInterop.DemoWorkspace(JSRuntime,blocklyDiv,toolbox,startBlocks);}}
This calls the following JavaScript Interop code that calls the JavaScript that creates the Blockly workspace:
internal static ValueTask<object> DemoWorkspace(IJSRuntime jsRuntime,ElementReference blocklyDiv,ElementReference toolbox,ElementReference startBlocks){return jsRuntime.InvokeAsync<object>("BlocklyFunctions.createDemoWorkspace",
blocklyDiv,toolbox,startBlocks);}
The JavaScript that actually creates the Blockly workspace, and passes the result back to the Div, is contained in the _Host.cshtml file:
window.BlocklyFunctions = {
createDemoWorkspace: function (blocklyDiv, toolbox, startBlocks) {
demoWorkspace = Blockly.inject(blocklyDiv,{media: 'media/',toolbox: toolbox});Blockly.Xml.domToWorkspace(startBlocks,demoWorkspace);}
Note: You can use the Blockly Developer Tools to create custom Blockly blocks and the custom toolbox.
IronBlock
IronBlock takes the XML created by Blockly, that is retrieved using this line of code:
XMLText = await AutomaticKingdomInterop.GetXML(JSRuntime);
… and turns it into C# code:
var parser = new Parser()
.AddStandardBlocks().Parse(XMLText);var syntaxTree = parser.Generate();string code = syntaxTree.NormalizeWhitespace().ToFullString();
// Get C# Code
var script = GenerateScript(code);
CS-Script.Core
The C# code, produced by IronBlock, needs to be adjusted (for example to add required imports and namespaces). We do that using the ConvertScript method:
public static string ConvertScript(string code){string FinalCode = "";
// Remove existing opening bracket
FinalCode = code.Substring(1, (code.Length - 1));string StartingCode =
@"using System.Collections.Generic;
using System.Linq;public class Script{";// Add a class around existing code
FinalCode = StartingCode + FinalCode;// Make AnimationLoop Public
FinalCode = FinalCode.Replace("dynamic AnimationLoop(dynamic WorldObject)",
"public dynamic AnimationLoop(dynamic WorldObject)");
return FinalCode;
}
CS-Script allows that code to loaded and executed:
// Load the code
dynamic CsScript = CSScript.Evaluator.LoadCode(ConvertScript(code));// Call the AnimationLoop methods, passing it the WorldObject
var result = CsScript.AnimationLoop(WorldObject);
The result variable represents the output of the C# code.
EventHorizon.Blazor.BabylonJS
We now need to display the animation in Babylon.js.
EventHorizon.Blazor.BabylonJS allows us to use C# code to easily do that.
First, a method set up the scene:
public async ValueTask CreateScene()
{var canvas = await Canvas.GetElementById("game-window"
);var engine = await Engine.NewEngine(canvas,true
);var scene = await Scene.NewScene(engine);var light0 = await PointLight.NewPointLight("Omni",
await Vector3.NewVector3(0,100,8),scene);var light1 = await HemisphericLight.NewHemisphericLight("HemisphericLight",
await Vector3.NewVector3(0,100,8),scene);var Player = await SceneLoader.ImportMesh(null,
"assets/",
"Player.glb",
scene,new EventHorizon.Blazor.Server.Interop.Callbacks.ActionCallback<
AbstractMesh[], IParticleSystem[], Skeleton[], AnimationGroup[]>(async (arg1, arg2, arg3, arg4) =>{foreach (var animation in arg4){await animation.stop();_animationMap.Add(await animation.get_name(), animation);}if (_animationMap.Count > 0)
{_runningAnimation = _animationMap.First().Value;await _runningAnimation.start(true);
}}));var camera = await ArcRotateCamera.NewArcRotateCamera("ArcRotateCamera",
(decimal)(System.Math.PI / 2),
(decimal)(System.Math.PI / 4),
3,await Vector3.NewVector3(0, 1, 0),scene);await camera.set_lowerRadiusLimit(2);await camera.set_upperRadiusLimit(10);await camera.set_wheelDeltaPercentage(0.01m);await scene.set_activeCamera(camera);await camera.attachControl(canvas,false
);await engine.runRenderLoop(() => Task.Run(() => scene.render(true, false)));_engine = engine;}
Then another method allows the animations to be triggered:
public async ValueTask RunAnimation(string name){if (_runningAnimation != null){await _runningAnimation.stop();_runningAnimation = null;
}if (_animationMap.ContainsKey(name))
{await _animationMap[name].play(true);
_runningAnimation = _animationMap[name];}}
Future plans for Automatic Kingdom
- Multiplayer applications
- Allowing custom blocks to be created in the application
Links
AutomaticKingdom Github (00.00.01 Alpha Release)
https://github.com/richorama/IronBlock
https://github.com/canhorn/EventHorizon.Blazor.TypeScript.Interop.Generator