The application I’m working on ( a planetarium to be used primarily as a teaching aid on Oculus ) involves lots of individual objects representing planets, the background sky etc etc. I’ve settled on Babylon as the underlying framework, but before that I did some playing with aframe. The aframe component model seems to provide a very natural way of encapsulating the logic associated with updating individual objects so that it’s easy declaratively to create new ones, remove old ones, and update them only when dependencies have changed.
I’m trying to work out what the corresponding best practice is within Babylon. At the moment, I’m working on a small library which has entities with init and update methods, all part of an overall scene which has a set of string parameters. Each entity depends on one or more of the parameters and is updated only if a relevant parameter has changed since the last time round the render loop. Internally, each entity keeps track of a single mesh, or possibly a small set of related ones.
So far this works for my use-case, but am I reinventing the wheel, or completely missing the right way to use babylon’s observer model to achieve the same thing?
Hi bilkusg!
I think the Babylon analogue to what you are looking for would be an asset container.
https://doc.babylonjs.com/how_to/how_to_use_assetcontainer
This is an issue I’ve been trying to figure out myself for quite a while. I agree that belfortk’s suggestion of asset containers works for encapsulating assets but I don’t know if it works as well for the logic.
Babylon is pretty flexible when it comes to how you handle your logic, so I’ve tried three options (no big projects so I’m not sure how any of them scale, also keep in mind that I use TypeScript which means I can’t just add methods to defined Babylon classes):
-
Extending mesh class. Works fine. I’m sure there must be an efficient way to check if a mesh is an instance of your new class but I just iterated over active meshes and checked instanceof
. You can sort of take advantage of Babylon’s scene tree with this but I still find it a tad clumsy.
-
ECS. I personally used tick-knock which was a lot of fun and works very well. I find ECS to be pretty logical and declarative. The downside is it’s an external library and it feels like two competing systems (the ECS lib and the Babylon scene tree).
-
Babylon Behaviours & Observables. This one has been my favourite so far though I’m fairly certain it’s not the idiomatic way of using these features. Essentially what I’d do is combine a collection of assets (say meshes that make up a character) under a single parent mesh (you can use an empty mesh like new BABYLON.Mesh('character')
) and attach a behaviour to it. In the behaviour I’d add an observer to the onBeforeRenderObservable
and that would contain my logic. The nice thing about this is you can attach multiple behaviours (like components) to a mesh and remove them at will.
I’m sure people have much better systems for handling their logic but these have worked for me in the past.
3 Likes
I just learned about Observables today! Very cool. I would agree with you that it would be a good approach for managing logic/reacting to events.
1 Like
Thanks for the various replies so far. And thanks in particular to @DisownedWheat for the analysis.
It sounds as though Babylon doesn’t have a compelling built-in alternative to some kind of ECS-like approach, which is kind of the way I was going.
I’ve knocked up a small library which I call babylife ( for babylon lifecycle ) which seems to work in my particular use-case and is small enough not to be intrusive. If my needs get any more sophisticated I think I need to consider either getting seriously involved with Observables or choosing a well-featured external ECS library
Here’s a code snippet of how I use my current library - further feedback welcome. If it works out well for me, I will be happy to publish it.
let bScene = new BabyLife.BabyScene(scene, {"latitude":52,"longitude": -0.1,"time":new Date()});
// rotates to correspond to the position of the sky for the observer
let bSky = new BabyLife.BabyEntity(bScene,null); // no parent
bSky.dependsOn(["latitude","longitude","time"]);
bSky.setInit((me,bScene)=>{
me.data = // create meshes and do any other initialisation here
});
bSky.setUpdate((me,bScene)=>{
// called just after setInit, and then every time one of the dependent parameters has changed
let newLatitude = Number(bScene.valueOf("latitude")); ..
// etc, update the meshes as appropriate
});
let bMars = new BabyLife.BabyEntity(bScene,bSky); // bMars is a child of bSky
bMars.dependsOn("time"); // not latitude or longitude as these are handled by the rotation of the sky
bSky.setInit((me,bScene)=>{
,,, set up
}); etc etc
// Next we can set up for example a function called on a keystroke which does:
bScene.changeState("time":bScene.valueOf("time") + 1000); // you get the idea
// and we get it all to work by having:
bScene.init(); // initialize everything
scene.onBeforeRenderObservable.Add(()=>{
bScene.update(); // only update entities where dependencies have changed
});
Thanks.
2 Likes