Add entity component system in cocos2d-x

Add entity component system in cocos2d-x
0.0 0

#41

How about using fastsig as an inspiration? Take a look at the speed comparisons.
http://endl.ch/content/fastsig

Yeah, that’s what most developers use it for.

Some developers just propose ECS as a “Component Based Architecture Approach”, like Ray Wenderlich:
http://www.raywenderlich.com/24878/introduction-to-component-based-architecture-in-games


#42

Speaking of messaging, I think processing components inside systems instead of a virtual update function call for each component would do the trick, the only problem here is the of advanced preprocessing features in c*+, since we would have to use hash maps to link the references inside the system to their correct component slot, this will involve more coding from the side of the programmer, I overcome this problem by using an external tool with a gui, so you design the system and the components then you update the h/hpp files with a simple click.


#43

Speaking of messaging, I think processing components inside systems instead of a virtual update function call for each component would do the trick, the only problem here is the lack of advanced preprocessing features in c*+, since we would have to use hash maps to link the references inside the system to their correct component slot, this will involve more coding from the side of the programmer, I overcome this problem by using an external tool with a gui, so you design the system and the components then you update the h/hpp files with a simple click…


#44

Like the signal/slot connector UI of Qt Creator?


#45

tbh I do not like to do a lot of macro tricks neither using a precompiler / external tool. This would increase the learning curve and impose entry barriers to newcomers. C++ is already very difficult for a lot of people.

There exists a tradeoff between productivity and performance that can not be forgotten.

BTW there is no need of special preprocessor stuff for processing components inside systems and using messaging. And there is no sin in using virtual functions here and there. Customizing messages using virtuals is not that bad … just not spam thousands in a frame.


#46

No, it’s a bit more complicated independent organism witch rely on converting components to big continuous tables with th ease of access, and it use systems witch are like shaders in gl programming, it take advantage of cache by using data driven architecture, I use it in the beginning to make physicly driven particle system, the performance gain was huge, about 10-20 times faster than CCParticleSystem…
@Dredok I learned programming at the age of 11…


#47

@Packora I think you missed what I mean. Sure, you may be an expert programmer but if you make a product hard to mantain or hard to use, nobody except you will use it. The tradeoff between complexity and productivity is very important, is not only performance the most important thing in the world. If performance was the only thing, then nobody would use Unity / Unreal Engine and other AAA engines.
C++ is way more complex than other languages. It is my preferred language but you must realize that is harder than others and productivity is way lower. That’s the reason lot of people prefer other languages (this and rapid prototyping). So if you add more complexity you must gain a clear advantage.


#48

Sounds great. I’m curiously waiting to look at the code, once you release it on github.

DOD can lead to a big performance gain. E.g., Ogre 2.x had a nice speedup of around 2.x-3.x, after they implemented some parts with DOD in mind according to the code review done by Mike Acton.

Could you provide a public link to some comparison videos or even the .apk file you talked about?

As you are just 17, your possibilities to access information and the widespread of tutorials, mentoring, tools and such, are/were very different to that of the 80s and 90s :smile:


#49

This is a simple game witch I’m working on, the video is a bit old now, the game has changed a bit more currently…

Note: I stopped all my programming activities :cry: for 20 days now because of exams, I will resume after two weeks :smile:


#50

A simplified version of of QT’s Signals/Slots, one that does not require macros may be of use here. I recently created a simple signal only class based on Robert Penner’s AS3 Signals. It was an opportunity for me to use modern C++ features. It supports both method and lambda callbacks and uses variadic templates for simplified syntax. The code is very compact in less than 200 lines. Although I suspect it would grow a bit to properly cope with multi-threaded use.

For example, say the entity class provided some rudimentry signals

Signal<float> signalUpdate;     // update() called
Signal<> signalEnteredScene; // onEnter
Signal<> signalExitedScene; // onExist

From the component you could then do :

entity.signalUpdate.add([](float dt) { /* get updates */}); 

or if you want to use member functions :

entity.signalUpdate.add(std::bind(&ThisClass::callbackFunction,this,std::placeholders::_1));
entity.signalUpdate.add(SIGNAL_CB_METHOD1(ThisClass:callbackFunction));

With the callback function looking like :

void ThisClass::callbackFunction(float dt);

SIGNAL_CB_METHOD1 is just an example of a macro that would hide the std::bind nastiness in the line above

The entity’s update() function would then just simply do a :

signalUpdate.dispatch(deltaTime);

Components could use the same system to interact with each other. Say we wanted to play a sound when two physics bodies collided :

The PhysicsComponent could define

Signal<Body,Body> signalBodiesCollided;

and dispatch at the end of the collision code

signalBodiesCollided.dispatch(bodyA,bodyB);

In the SoundComponent's initialization :

entity.getComponent<PhysicsComponent>().signalBodiesCollided.add(SIGNAL_CB_METHOD1(SoundComponent::onBodiesCollieded))

void SoundComponent::onBodiesCollieded(Body a, Body b); // play a sound in here

It really is just a simple observer pattern. I am definitely no expert in ECS or Event systems but I’ve used this signaling method in a few projects and have been happy with the results, so just tossing my ideas out there.

Rick


#51

Yep, it sounds reasonable.
But what i think is that we should play with CBS API first, then start using physical2d as a test.

I think a CBS should have:

  • entity(it is Node in cocos2d-x): it can
    • add a component
    • remove a component
    • find a component
  • component, it has
    • some data
    • some functions
  • system
    • it make component take effect

Take physical2d component as example, it may look like

class Physical2dComponent : public Component
{
    public:
        PhysicsBody  *_physicsBody;
        Node *_entity;  // refer to entity;

        virtual void update();
};

And there should be a PhysicalSystem that make physical2d components take effect.

class PhysicalSystem : public System
{
    public:
        void update()
        {
            physicalWorld->step(); // start physical simulation
        }
       
        void registerComponent(Physics2dComponent *component)
        {  
           physicalWorld->addBody(component->_physicsBody);
        }
};

void Node::addComponent(Component *component)
{
     if it is a physical component, then register it to PhyscicalSystem
}

This is a simple example. I am not sure if Physical2dComponent needs a update method or not. Because it seems developers used to modify PhysicsBody directly in some logical codes.

Because component hold a pointer of the Entity, so it can use it to refer to any other component.

Yep, physics2d component can help to test something, but it can not test if it is a good CBS or not. As the above example, it seems physics component can work. But when developer wants to add their own component, then how the new added component take effect? Developers should implements their own system, right? Then how their own system take effect?

So i think we should have a clear design in high level. How component take effect, what’s the sequence the components take effect? For example, physics2d component should take effect before renderer component.

Edit: And what i mean refactor physics2d is not to say refactor the codes in cocos/physics. I mean refactor the codes in #if CC_USE_PHYSICS in Node, Director and other places.


#52

Yep, in theory, many things can use CBS. But i think the most important part is Nodes. After finishing that, we can continue to refactor other parts.


#53

I think pub/sub message system can work.

What’s CRTP?


#54

I am curious about your method too.


#55

It is interesting. Can you public your codes?

I think a component should not post a signal. It’s physical system’s duty.


#56

@Rick_S
I think physics component should response on collide event, then it can invoke audio component to play a effect/music.


#57
I think physics component should response on collide event, then it can invoke audio component to play a effect/music.

That is in a sense what it is doing. In the PhysicsComponent, when a collision happens it sends a signal to any other component that cares about a collision. In this case our audio component cares because it needs to respond to that. It should not directly invoke an audio component because then it would introduce a dependency, that can be avoided in a system like this.

I may have made a poor choice in naming with my example. Let me clarify In my example AudioComponent was a custom per-project user created component that would “glue” things together. It would probably interface with a sound component to do the actual playing, so PhysicsComponent would be the actual component dealing with physics simulation. SoundComponent would be a cocos2d provided component that interfaces with the sound system (so SoundComponent would provide playSound(), etc). Then the end user (the developer) would provide a GlueComponent. This is where it would listen for signals from PhysicsComponent and play sounds via SoundComponent. Sorry if I wasn’t clear originally.

So if I wanted to make a sprite on the screen play a sound when it collided with something, in theory I would :

cocos2d::Node entity = new cocos2d::Node(new cocos2d::SpriteComponent(),
new cocos2d::SpriteComponent(),
new cocos2d::PhysicsComponent(),
new cocos2d::SoundComponent(),
new GlueComponent()
);

Where GlueComponent is :

    class GlueComponent : public Component {
        PhysicsComponent &physicsComponent;
        SoundComponent &soundComponent;

    //Would presumable be called for each component once added to the entity
    void init() {
        physicsComponent = entity.getComponent<PhysicsComponent>();
        soundComponent = entity.getComponent<SoundComponent>();
        physicsComponent.signalBodiesCollided.add(SIGNAL_CB_METHOD2(GlueComponent::onBodiesCollieded));
    }
    
    void GlueComponent::onBodiesCollided(Body A, BodyB) {
        soundComponent.playSound("mycollision.mp3");
    }
}

The approach allows user code to be neatly tied up in one place without the need to subclass cocos2d components to add functionality.

I think a component should not post a signal. It's physical system's duty.

I don’t think I understand what you mean by this. Do you mean it should be in a centralized system? The way I described means a new component could be added at any time without the need to modify any other system, the only other way to achieve this is to use an event system that has event names (“PHYSICS_COMPONENT_COLLIDED”). The problem with those systems, in my opinion is the need to create classes/struct to wrap around whatever data you want to send along with the event.

It is interesting. Can you public your codes?

Sure, https://github.com/rickms/SimpleSignal


#58

What’s your PhysicsComponent looks like? It seems that it is a system as what i said.


#59

@zhangxm, @iQD and @Dredok, my engine looks like this :

    Nuclear::initializeTheReactor();// Truly I'm not kidding xD!
    NUInt particle_system = Nuclear::generateType("particle_system");
    Nuclear::bindType(particle_system);
    Nuclear::assignComponent<Physics>();
    Nuclear::assignPredefinedComponent(PredefinedComponent::Render);
    Nuclear::assignPredefinedComponent(PredefinedComponent::MinimalTransform2D);
    Nuclear::allocate(64);
    Nuclear::linkSystem<ParticleSystem>(particle_system);// This is a variadic function..leaving it empty will link the system to every type..
    Nuclear::linkPredefinedSystem(PredefinedSystem::MinimalRender2D,partilce_system);
    Nuclear::addEntity(60);
    Nuclear::atEntity(0);
    do
    {
        auto phy = Nuclear::get<Physics>();/// This an accessor not a real component, in Nuclear components doesn't exist...
        phy._velocity[0] = rand().....
        ......
    }while(Nuclear::next())
    Nuclear::getLocalAccessor(PredefinedSystem::MinimalRender2D)._texture = TextureCache::....;
    Nuclear::getLocalAccessor(PredefinedSystem::MinimalRender2D)._node = Node::create....;
    addChild(Nuclear::getLocalAccessor(PredefinedSystem::MinimalRender2D)._node);
    Nuclear::bindType(0);
    schedule([]()
    {
       Nuclear::tick(dt);
    });

#60

I would suggest to not having an update function inside the component itself. Let the physics system iterate over the physics components and do the update.
All components behave the same and the system can pull the date it needs from the component.
Additionally it’s more DOD compared to the OO design and maybe more easier to adopt for parallelism.