Newbie Alert, is this component system logical?

Hello, have a nice day!

I am new at JME yet and so sorry for my bad English. I used Unity Engine before -not professionaly- actually, i am not very good on Java but, i really liked JME and i wanna be an advanced user.

I wanted a basic floor, 3 box npc and a simply crosshair. After build that scene, i wanted modular abilities which is attachable on this npc boxes, What are these abilities? -Flammeable -Corruptible -Rotatable. They should have been remotable on one common BoxControler. I hope, i made myself clear.

Here is simple video of my work;

So, here is the my component system based on only custom controls.

i have a basic GameAppState and this is only appState on this work so Main class runs this appState directly.

package mycomponentsystem;
// for make shorter i cut the imports and empty @override methods if you want see i can add that stuffs.
public class GameAppState extends BaseAppState{
    
    protected SimpleApplication app;
    protected Node              rootNode;
    protected Node              guiNode;
    protected Node              shootable;
    protected AssetManager      assetManager;
    protected AppStateManager   stateManager;
    protected InputManager      inputManager;
    protected ViewPort          viewPort;
    protected Node              shootables;
    protected Camera            cam;
    protected BitmapFont        guiFont;
    protected AppSettings       settings;
    
    @Override
    protected void initialize(Application app) {
        this.app = (SimpleApplication)app;
        this.rootNode = this.app.getRootNode();
        this.guiNode = this.app.getGuiNode();
        this.assetManager = this.app.getAssetManager();
        this.stateManager = this.app.getStateManager();
        this.inputManager = this.app.getInputManager();
        this.viewPort = this.app.getViewPort();
        this.cam = this.app.getCamera();
        this.settings = this.app.getContext().getSettings();
        initScene();
        initKeys();
    }

    
    private void initScene(){
        app.setDisplayStatView(false);
        
        Geometry floor = makeBox(10f,.5f,10f,"Floor");
        Geometry box1g = makeBox(.3f,.3f,.3f,"Box1");
        Node box1 = new Node("Box1");
        box1g.addControl(new BoxControl(this,box1));
        box1.setLocalTranslation(-2f,1.5f,-4f);
        Geometry box2g = makeBox(.3f,.3f,.3f,"Box2");
        Node box2 = new Node("Box2");
        box2g.addControl(new BoxControl(this,box2));
        box2.setLocalTranslation(0f,1.5f,-4f);
        Node box3 = new Node("Box3");
        Geometry box3g = makeBox(.3f,.3f,.3f,"Box3");
        box3g.addControl(new BoxControl(this,box3));
        box3.setLocalTranslation(2f,1.5f,-4f);
        
        BitmapText crosshair = makeCrosshair();
        
        cam.setLocation(new Vector3f(0f,2f,0f));
        
        rootNode.attachChild(floor);
        box1.attachChild(box1g);
        box2.attachChild(box2g);
        box3.attachChild(box3g);
        shootable = new Node("Shootable");
        shootable.attachChild(box1);
        shootable.attachChild(box2);
        shootable.attachChild(box3);
        rootNode.attachChild(shootable);
        guiNode.attachChild(crosshair);
    }
    
    private void initKeys(){
        inputManager.addMapping("Shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
        inputManager.addListener(actionListener, "Shoot");
    }
    
    public ActionListener actionListener = new ActionListener(){
        @Override
        public void onAction(String name, boolean isPressed, float tpf) {
            if(name.equals("Shoot")&&!isPressed){
                CollisionResults results = new CollisionResults();
                Ray ray = new Ray(cam.getLocation(), cam.getDirection());
                shootable.collideWith(ray, results);
                if(results.size()>0){
                CollisionResult result = results.getClosestCollision();
                result.getGeometry().getControl(BoxControl.class).onShoot();
                }
            }
        }       
    };
    
    private Geometry makeBox(float x, float y, float z, String name){
        Box box = new Box(x,y,z);
        Geometry boxGeo = new Geometry(name,box);
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", ColorRGBA.LightGray);
        boxGeo.setMaterial(mat);
        CollisionShape shape = CollisionShapeFactory.createBoxShape(boxGeo);
        return boxGeo;
    }
    
    private BitmapText makeCrosshair(){
        guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
        BitmapText bt = new BitmapText(guiFont,false);
        bt.setText(">o<");
        bt.setSize(25f);
        bt.setLocalTranslation(settings.getWidth() / 2 - bt.getLineWidth()/2, settings.getHeight() / 2 + bt.getLineHeight()/2, 0);
        return bt;
    }
}

and here my common BoxController for all npc in my scene;

package mycomponentsystem;
//again sorry for imports :)
public class BoxControl extends AbstractControl{
    
    protected GameAppState  appState;
    protected Node          node;
    
    public BoxControl(AppState appState, Node node){
        this.appState = (GameAppState)appState;
        this.node = node;
    }
   
    @Override
    public void setSpatial(Spatial spatial) {
        super.setSpatial(spatial);
        if (spatial != null){ // initialize
            switch (spatial.getName()) {
                case "Box1":
                    spatial.addControl(new FlammableComponent(appState,node));
                    break;
                case "Box2":
                    spatial.addControl(new CorruptibleComponent(appState,node));
                    break;
                case "Box3":
                    spatial.addControl(new FlammableComponent(appState,node));
                    spatial.addControl(new RotatableComponent(appState,node));
                    break;
                default:
                    break;
            }
        }else{ // cleanup
        }   
    }
    
    public void onShoot(){
        Geometry geo = (Geometry)spatial;
        geo.getMaterial().setColor("Color", ColorRGBA.randomColor());
        switch (spatial.getName()) {
            case "Box1":
                spatial.getControl(FlammableComponent.class).onShoot();
                break;
            case "Box2":
                spatial.getControl(CorruptibleComponent.class).onShoot();
                break;
            case "Box3":
                spatial.getControl(FlammableComponent.class).onShoot();
                spatial.getControl(RotatableComponent.class).onShoot(); 
                break;
            default:
                break;
        }
    }
}

and now i will show only one component for make this topic shorter here is flammeable;

public class FlammableComponent extends AbstractControl{
    
    protected GameAppState appState;
    protected Node node;
    protected Boolean isInfected = false;
    
    public FlammableComponent(AppState appState, Node node){
        this.appState = (GameAppState)appState;
        this.node = node;
    }
    
    protected void onShoot(){
        if(!isInfected)
        node.attachChild(makeParticle());
    }
    
    protected ParticleEmitter makeParticle(){
        ParticleEmitter fire = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 5);
        Material mat = new Material(appState.getApplication().getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
        mat.setTexture("Texture", appState.getApplication().getAssetManager().loadTexture("Effects/Explosion/flame.png"));
        fire.setMaterial(mat);
        fire.setImagesX(2);
        fire.setImagesY(2);
        fire.setEndColor(  new ColorRGBA(1f, 0f, 0f, 1f));
        fire.setStartColor(new ColorRGBA(1f, 1f, 0f, 0.5f));
        fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 1, 0));
        fire.setStartSize(1f);
        fire.setEndSize(0.1f);
        fire.setGravity(0, 0, 0);
        fire.setLowLife(1f);
        fire.setHighLife(2f);
        fire.getParticleInfluencer().setVelocityVariation(0.3f);  
        isInfected = true;
        return fire;
    }
    
    @Override
    protected void controlUpdate(float tpf) {
        
    }

    @Override
    protected void controlRender(RenderManager rm, ViewPort vp) {
    }
    
}

The FlammeableComponent and the RotatableComponent effects together the third npc (as you can see -Box3 Node- rotates and burns when shooted) this system fells like Plug and Play for me and i like it :slight_smile:

here is the final questions and purpose of this topic;

1-How is affected performance when i work with that system, -imagine- I have tousand NPCs in my scene?
2-Is this system logical for JME code basement? so i mean, if i dont want use the ES system (i see that system too complex for me) can i use this system?

----Update----

i tested the system i use with 10.000 object.



Vsync is not ON

There’s a thread active right now one batching, instancing and other mechanics to improve performance for high element count scenes. Optimizing forest framerate - #4 by 8Keep123

Sure you can. Make sure to go through the tutorials if the controller-based approach suits you better - there should be lots of examples doing this. I myself find the ES and data driven design incredibly intuitive and easy to work with (in my mind its way easier than the object oriented approach), but - whatever works for you, go for it.

1 Like

Thank you so much for your interesting.

I will examine that topic and restudy on ES System again. If ES system came easier to me i will use it else i continue on my component system.

Do you know any topic is for newbie users about ES system?

I wrote a serie of blog articles which should give you a good start see this topic here Started a Blog Series how to use an Entity System

1 Like

Look through the forum in the Zay-es sub category. It will probably have all threads pertaining to the system I have chosen, made by @pspeed.

Here: https://hub.jmonkeyengine.org/c/user-code-projects/zay-es

1 Like

Thank you i attach it on favs.

Thank you, i will compare ES system and my system -if i learn that ES thing- and then i choose one of them.

by the way, i tested my system with 10.000 object (NPC thing :slight_smile: ) and there is 3 fps differency between system on and system off. I will edit first post for new small video.

If you haven’t already, you should try it with VSync off. That might reveal bigger framerate differences.

it is already VSync Off mode. Actually, when do not any optimization on this scene, ~10000 object causes 30 fps on my system;

g3258 and R9 270

also, this 10k boxes has their own material, if i use one material to all of them then fps is increasing about ~35 -when my control component system was off-

I think the graphic card will be the bottleneck in most of the cases, at least in my experience. But I might be wrong.

10000 objects is quite a lot. Usually you should try to stay under 1000. Batching reduces object count, but of course you can’t really batch the NPCs probably if they are animated…? And they would need to have the same material in order to be batched.

Instancing I believe helps when you have complex objects. But it doesn’t reduce the object count. Which is usually the reason for bad performance.

Nothing really helpful here but just stating the problems :slight_smile:

i decided to use this system and we will see if there is any problem in the future :slight_smile:

You right batching is totally unscathed in this type. If i did batching geometries the controls become disfunctional. Also i think, 3 fps differency is acceptable for 10k objects when system on.

edit: 1000 object results;
System Off : 328fps
System On : 310fps

Is this really making such a huge difference? I would recommend to check out Zay-Es which is made by a very active jme user, @pspeed.
By the way, @pspeed, does using an entity component system can lead to such huge fps differences?

Two things to consider here:
FPS are Penis-Length Comparisons by Gamers. Professionals use Frame Time (Which is 1000ms / FPS).

Why? Because 10 FPS Difference (20 FPS → 10 FPS) is doubling the Frame Time, where 10 FPS Difference (320 → 310 FPS) is only 3.225ms instead of 3.125ms, which means your game update loop only took 1/10ms longer, which is insanely fast.

I took this example to show you a) that the system load is neglatable and b) that fps differences are useless without a reference fps (or better use frame time).

Then: An Entity System CAN decrease the Performance and maybe Zay-ES will be more heavyweight than what he already has, because he doesn’t have a full blown ECS but rather a collection of controls (which are actually really controls, because they are visual and not really related to game- or object-state. An Entity-System rather shines by reducing development time and maybe multithreading.
Also don’t forget that he has particle emitters running, which is also draining power.

So yes: Everything in update() will effect the fps and the more objects you have to run update() for the more power will it consume.
But complaining about 0.1ms is really premature optimization. Throw together a prototype and then use the profiler to find out what is actually the problem.

To OP:

Well, Controls will affect performance, but that’s not a thing you could change actually. Thousand NPCs might sound ambitious though, if it’s not something like the settlers or something but really powerful NPCs. But your problems will rather lie on the gpu side of things then, not in the controls. Instancing has been mentioned already. Tesselation might also come to mind.

Yeah Controls are actually more appropriate than an ES System since what you have are components which actually handle spatial’s visual behavior.

What I find odd though is the BoxControl. Not only is relying on Spatials Name bad, but it also seems more logical to have an app state or whoever generates the spatials should just add the correct controls instead of having a helper control which does nothing than that (Because they are pretty useless after setSpatial()).

And: If you’d make all Controls inherit something like Shootable (an interface providing onShoot) you could leave out the switch in onShoot of BoxControl or (better) move the code there, where you call BoxControl.onShoot from.

1 Like

Firstly thank yo so much for be interested.

Finally i found a person who agree with me about our already have control system might better than ES systems, i think, UserData system and custom control system enoug for us to make a modular component system. In this case, i dont alreay need the functions which is bring with ES system(s).

Also i find odd though is BoxControl too. Check spatials name was bad idea and i changed already that constant fields in BoxControl is controled by (set/get)UserData;

And thank you again for interface idea. It is thing that i missed :blush:

Does traveling cross-country using a different method burn fewer calories?

If I use different building materials on my house will the food I cook there taste better?

The two things are orthogonal. A particular design will be very different with an ES to the point where comparisons are invalid. For example, with a certain ES approach you might have a 2-3 FPS drop but gain back 6 months of development time and be able to throw twice as many objects into your game, have smaller save-game files, and more maintainable code.

Anyway, nothing about this thread so far has been even remotely ES based except for all of the (potentially unhelpful) recommendations to use one. OP’s code is nothing like an ES-based game… except that it uses the word “component” for something else other than what an ES would use that word for.

1 Like

Anything CAN decrease performance. There is nothing inherent in the ES design here and Zay-ES is extremely light-weight “for an ES framework”. (At best, you could drop some garbage-collection worries about… which are mostly irrelevant on desktop.)

Often performance increases simply because you’ve had to redesign and separate game objects from their visualization. Suddenly, you can have efficient game objects on their own and only worry about what the player sees as far as visualization goes. You don’t have to have 10,000 objects in your scene just because “How else will I manage my game objects?”

All of that being said, an ES is definitely 100% not for “newbie” developers (as OP has introduced himself).

But that being said, technically: neither is a game with 10,000 visualized objects in it. For a hobby project, it will be a great learning experience, though.

I was as well a newbie game dev wise and ES did open my eyes, but you are right it took me quite some time to understand it. Before ES I also threaded visual things as game objects and painted my self in many corners of the room, I couldn’t imagine how many corners a room can have :wink:

That to be said, but in the end it is OPs decision not my one :slight_smile:

And OP has a decision.

while he has custom controls and userdata he will never need to ES system. By the way i read your blogs and thank you for explaining the ES system so well. I see that, i can make the same game with my modular controls and spatial user datas and that will be more easier and understandable for me :slight_smile:

This is only curing symptoms. Why do you need to know what type of box it is?
The idea rather is that you have one control per functionality and an object is the sum of the functions of it’s controls.

And don’t use UserData to save your scene at least. Use something different like SaveGame or some manual XML stuff, because else your savefiles will be multiple hundreds of MB and so will your loading times be.

Because i want generated boxes randomly and all boxes must have a common behaviour. If someone shoots at me i will changed my color.

And you are still right, if i use save scene for save game, user datas will takes a lot disk space in crowd scene. So i will use XML for save/load.