Add entity component system in cocos2d-x

One thing that I would like to have defined in cocos2d-x, is the order of the updates,signals that are emitted in cocos2d-x.
Something like this graphic from SpriteKit’s programmers guide:
https://developer.apple.com/library/ios/documentation/GraphicsAnimation/Conceptual/SpriteKit_PG/Introduction/Introduction.html

Using physics component will not break too much, it just put codes across cocos2d-x into one place. After doing it, it is very clear when physics codes are updated, and it is very easy to change it.

Using component will not change the effect of physics integration, it is just make logic more clear. Refactoring physics integration is another thing, it will take more time to understand issues and limitations. So i think we should refactoring it based on more clear logical codes.

yeah, I know that. But since we need to write new code that dispatch the updates messages to the physics component, it is a good time to fix and document the order in which those events are triggered.

Ok.
I will update the documentation after the codes are merged.

The design is that, physics update will be invoked last in Scheduler.

// PhysicsManager is registered with INT_MAX priority in Schedule in Director

_physicsManager = new (std::nothrow) PhysicsManager();
// PRIORITY_PHYSICS is INT_MAX
_scheduler->scheduleUpdate(_physicsManager, Scheduler::PRIORITY_PHYSICS, false);

Current scene processing is:

  1. Scheduler::update(): run all actions, update and registered call back
  2. EventDispatcher::dispatchEvent(): invoke all event handlers
  3. Scene::render(): invoke all Node's visit function
  4. EventDispatcher::dispatchEvent(): invoke event handler of after-visit event
  5. invoke notification node’s visit function
  6. draw a scene

I think the best point is between 2 and 3.

@Dredok
What’s your opinion?

@zhangxm
In your schema I think between 2 and 3 is a good fit, so yes. But in my opinion current physics needs more work, not only the scheduler.Right now, as I said previously, almost always I have to set the autoUpdate to false and manage all the physics myself, to avoid stuttering, performance issues and other artifacts.
See for example these forum posts by @Heyalda

http://discuss.cocos2d-x.org/t/does-the-built-in-physics-engine-physicsworld-update-need-a-low-pass-filter-for-dt/21534/6

There is also no support for the last chipmunk (7.0) and there is no support for things like rogue bodies and other stuff.

IMO these concerns are more important, I know we are talking about ECS here and not the physics code itself, should we open a new thread for this ?

yes, sure. go ahead.

Yep, please create a new thread for it. It is very useful when fixing physics issues.

here you have
http://discuss.cocos2d-x.org/t/physics-issues-to-be-fixed/22713

I would suggest for this and any other manager/system to make it easy for the developer to change. I’m sure there are situations where you would want to run a scheduled update after all physics processing is done.

I changed it, let PhysicsManager be invoked as the same sequence as it is in v3.6. Because after Scheduler::update() is EventDispatcher::dispatchEvent(), we may modify Node’s properties(position, scale, rotation) in event listener.

And i also modified that, every Scene has a PhysicsManager as it is in v3.6. Because it is reasonable that, after a scene is active again by Director::runWithScene(), physics can take effect again.

Hi @ricardo,
I have some progress on physics component, but meet some issues. You are appreciated if you can give me a hand.

current progress

Currently, we implemented 6 test cases in tests/cpp-tests/Classes/PhysicsTest/PhysicsComponentTest.cpp using physics component.

how to test it

problems left

If you run Pyramid Stack test case, you will found these issues:

  • grossinis are jumped all the time
  • you can not select grossini but can for the ball(turn on Toggle debug to see it)
  • can not drag ball if there are grossinis around it

The codes to synchronize Node’s coordinate and physics body’s coordinate are in ComponentPhysics2d::beforeSimulation() and ComponentPhysics2d::afterSimulation(). The Corresponding file is cocos/physics/CCComponentPhysics2d.cpp. I think there are some issues about these two functions, but i haven’t found a method to resolved.

PS: i commented codes about physics integration in Node, Scene and some other places included in #if CC_USE_PHYSICS.

@zhangxm

The first thing that I noticed is that you changed the “logic”.

The previous algorithm was:

  • Scene -> updatePhysicsWorld()
  • PhysicsWorld -> Iterate all over the bodies and update it

Your current algorithm is:

  • Scene -> physicsManager();
  • For every component: Physics2d::afterSimulation(): you get transform, scale and rotation in world coordinates and updates the physics code
  • Simulate physics code
  • For every component: Physics2d::beforeSimulation(): get scale, rotation and transform and update from physics and update the nodes

My questions are:

  • Why do you need both afterSimulation and beforeSimulation ?
  • Why are you trying to get the world coordinates of the sprites ? Does the old PhysicsBody/PhysicsWorld do that ?
  • In any case, you are using a expensive 3d code to obtain the scale, rotation, transform. (eg: `Mat4::decompose() ). I would avoid using that function if possible… perhaps 2d version of it.

The fastest physics code is the one that is implemented in PhysicsSprite, in particular this function:

void PhysicsSprite::syncPhysicsTransform() const
{
    // Although scale is not used by physics engines, it is calculated just in case
	// the sprite is animated (scaled up/down) using actions.
	// For more info see: http://www.cocos2d-iphone.org/forum/topic/68990
    
	cpVect rot = (_ignoreBodyRotation ? cpvforangle(-CC_DEGREES_TO_RADIANS(_rotationX)) : _CPBody->rot);
	float x = _CPBody->p.x + rot.x * -_anchorPointInPoints.x * _scaleX - rot.y * -_anchorPointInPoints.y * _scaleY;
	float y = _CPBody->p.y + rot.y * -_anchorPointInPoints.x * _scaleX + rot.x * -_anchorPointInPoints.y * _scaleY;
    
	if (_ignoreAnchorPointForPosition)
    {
		x += _anchorPointInPoints.x;
		y += _anchorPointInPoints.y;
	}
    
    float mat[] = {  (float)rot.x * _scaleX, (float)rot.y * _scaleX, 0,  0,
        (float)-rot.y * _scaleY, (float)rot.x * _scaleY,  0,  0,
        0,  0,  1,  0,
        x,	y,  0,  1};
    
    _transform.set(mat);
}

As you can see, the code is not decomposing the matrix and trying to get the world coordinates. This could be seen as a bug… or feature.
But in any case, we should be backwards compatible. I haven’t reviewed the v3.0 Physics code in depth, but if you are trying to replace that code, then you must be 100% backwards compatible with it… so I would use the algorithms that are being used by it.

UPDATE:
So, I believe that the old “PhysicsWorld” code actually uses the world coordinates for the nodes, but I’m not 100% sure.
The physicsworld code is very difficult to understand, and we must be 100% backwards compatible with it.

So, I guess that the 1st step is to understand it: what is the its design ? How does it work ?
And after that implement that design in the component.

Old code also do the same thing. As you can see, what it does is

  • Scene -> updatePhysicsWorld
  • PhysicsWorld: Scene::updatePhysicsBodyTransform(): get scale, rotation and transform and update from nodes to physics this is the same effect as PhysicsManager::beforeSimulation
  • PhysicsWorld: do physics simulation
  • Scene::Visit() -> Scene::processParentFlags() -> Scene::updateTransformFromPhysics() -> Scene::draw(): before drawing, it synchronizes physics to nodes, it is the same as PhysicsManager::afterSimulation()

So i didn’t change the logic, just make things more simple.

Physics world only accepts world coordinate. So i needs to get Node’s world coordinate, then pass it to physics world.

Yep, i will void using it if there is an easy way to get Node’s world transform. I will take a look of PhysicsSprite.

Ok. So the first step is to keep using the same logic. Don’t make it simpler. If that works, only then try to make it simpler.

Regarding beforeSimulation and afterSimulation I don’t think we need that… you are iterating all over the nodes twice. The iteration alone could be expensive when having many nodes. So I would try to avoid if possible.

Yes. Correct. If you use a flat hierarchy, then it should work with relative positioning… at least that is how PhysicsSprite work. But it is buggy. So, it is better to use World coordinates (also because that’s how PhysicsWorld works).

If so, then i have no idea what physics component will do. As you can see, now the component has less codes. And if using the old logic, then i don’t know what i can do with component codes.

We should synchronize nodes to physics before physics simulation and synchronize physics to nodes after simulation. The old codes just want to re-use Node::visit() iteration, that’s why it inserts codes into Node. I can re-use it too after the effect is correct. It is about optimization, but first, i should make the logic correct.

Yep, i just reviewed the codes of PhysicsWorld. It doesn’t think about anchor point and node tree relationship. It only works if the anchor point is center, and Node’s position is the same as world coordinate.

Now the problem is, when i get position, rotation from physics world, how can i set it to Node. Because the return value from physics is world coordinate, how can i translate to relative position and rotation and thinking about anchor point.

After looking into the codes in detail. I think it is reasonable to have a try. I will use component and physics manager instead with the same logic.

What I mean by that, is to use the “physics logic”. Your code is not using the same physics logic. As an example, your are using different math code.
So, the math should be the same, the algorithms should be the same.
But the workflow should be different.

If that works as expected, then feel free to simplify the math/algorithm.

the component, what should do is to use the same physics logic, but with a different workflow.
The component should be in charge doing the physics simulation, and not Node. But the the physics code should be the same.

What you are doing is 2 things at the same time:

  • change the workflow: from Node to Component (we need this)
  • Simplifying the physics code (we also need this).

But now you have some bugs… so we don’t know why… so the best thing to do, is to do one step at the time.

@ricardo
I changed the codes as you suggested in branch physics-component-use-old-logic.

As you can see, i used the same logic as before, just modify the logic of Node::updatePhysicsBodyTransform() and Node::updateTransformFromPhysics.

I am not sure if it is what you mean. But the result is the same. Also has the same bugs.