-
Notifications
You must be signed in to change notification settings - Fork 444
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
A nicer love.physics api #1130
Comments
Original comment by David Serrano (Bitbucket: Bobbyjones, GitHub: Bobbyjones). Maybe something like a love.physics-light and then regular love.physics. Of course it probably should not be called love.physics-light. The light version would be a layer over the physics api to make it easier. But I think it would be important to define what is easier. I feel as though the biggest hurdle with love.physics is that there is so many methods that a body, fixture or shape have. So it makes doing even the most simplest thing a chore to get right. I don't think there would be a good way to make dealing with all those methods and settings easier. Now if by easier you mean object creation then I think that can be accomplished relatively easily. |
Original comment by Sasha Szpakowski (Bitbucket: slime73, GitHub: slime73).
An small addition to LÖVE could be to have shortcut methods in
Although those method names are kind of misleading, since they return Fixtures rather than Shapes. |
Original comment by Sasha Szpakowski (Bitbucket: slime73, GitHub: slime73). You also wouldn't be able to explicitly set the density during Fixture creation with those (although there is It would probably also be good in general to move
|
Original comment by Bruce Hill (Bitbucket: spilt, GitHub: spilt). One aspect of the current API that I find pretty user-unfriendly is collision groups/categories/masks. I think my ideal collision API would be something like: world:addCollisionCategories("terrain","player","enemy","playerProjectile","enemyProjectile")
local projectile = {"playerProjectile","enemyProjectile"}
world:ignoreCollisionsBetween(projectile, projectile)
world:ignoreCollisionsBetween({"playerProjectile"},{"player"})
world:ignoreCollisionsBetween({"enemyProjectile"},{"enemy"})
...
player.fixture:setCollisionCategories({"player"})
...
environmentalHazard.fixture:setCollisionCategories(projectile) -- This would collide with both players and enemies If this system were used, it would also be nice to have a collision callback API that worked something like this: -- This sets callbacks that are called once, immediately before the physics engine handles two bodies making contact:
player.fixture:setCollisionCallbacks({
-- Mapping from collision category -> callback function
enemy = function(self, other, contact)
self:getUserData():takeDamage(other:getUserData().damage)
end,
terrain = function(self, other, contact)
local nx, ny = contact:getNormal()
if ny < 0 then contact:setEnabled(false) end -- one-way platforms
end,
})
-- Similarly, this sets callbacks that are called once, just after two fixtures lose contact
player.fixture:setEndCollisionCallbacks({})
...
-- There would be no equivalent to the current callbacks that get called once per frame, instead, you could just query what the current contacts are
function love.update(dt)
world:update(dt)
-- Get a list of all contacts with an optional list of categories
local terrainContacts = player.fixture:getContacts({"terrain"})
player.onGround = false
for terrainFixture,contact in pairs(terrainContacts) do
local ix,iy = contact:getImpulse()
if iy < 0 then
player.onGround = true
break
end
end
...
end (Collision callbacks would be called in an unspecified order.) |
Original comment by adn adn (Bitbucket: adonaac, ). I've been using love.physics for a while now so I feel like I have a good grasp of its pain points. Most of what I'm gonna say can be seen in action in my hxdx library. So let me start with what other people already said:
I think you can go even further and introduce the concept of Colliders. These would be new object types that are built on top of the existing objects box2d provides. Like you said, usually what most people do ends up being one body + one shape + one fixture. A collider would just be all this done automatically. In hxdx Colliders are just that and you can create a new one via something like:
You can then access the body, fixture and shape via collider.body, collider.fixture, collider.shape. .fixture and .shape are aliases for the 'main' fixture/shape pair, since because you can have multiple fixture/shape pairs they have to be in a list, but since most people will never do that it makes sense to present them as .fixture and .shape instead of something like .fixtures_list['main'] and .shapes_list['main']. If you want to have multiple pairs though you can add new shape fixture pairs to the list and operate like you would normally via some function like collider:addShape(shape_name, ...) and then it adds the shape + fixture automatically. I don't remember if I added this function on my own library. The point is, a Collider can expose normal "advanced" functionality but also make things easier for new users. You could go a step further than I did with hxdx and instead of having the main way of users to interact with physics object be via collider.body/fixture, you can just add the most used functions in a body/fixture, like applyLinearImpulse or applyForce or setRestitution to the Collider object and make it so that for the most common use cases people don't even have to know about bodies, fixtures or shapes. Like:
Most people only want to use physics to apply forces to objects and to make collision easy, so handling those use cases via the Collider object can dramatically decrease the number of things people have to care about. And if they wanna go deeper then sure, they can still access the body, fixture and shape all they want. This would create a nice tutorial-like feel to the API where it has multiple layers to it that people can dive into at their own pace instead of being showered with tons of concepts at once.
I agree with your solution and it's pretty much the same one that I arrived. Dealing with categories/masks is really unintuitive. The solution I arrived at is similar to yours (https://github.com/adonaac/hxdx#add-collision-classes, https://github.com/adonaac/hxdx/tree/master/docs#addcollisionclasscollision_class_name-collision_class) it just has a few differences in how things are specified but the idea is the same. The code for doing this is not trivial but not super hard. There was a good thread about it a while ago here https://love2d.org/forums/viewtopic.php?f=4&t=75441. It's not trivial because there's a maximum number of categories you can use (16) so you wanna make sure you do it in the optimal way.
I personally think callbacks are bad so I disagree with this. However this all comes down to personal preferences in the end I guess. The way I solved collision detection and callbacks was like this https://github.com/adonaac/hxdx/tree/master/docs#enterother_collision_class_name. You just have an enter and exit function that will return true on the frame those events happen and from there you can do whatever. preSolve and postSolve functions have to still be callbacks though because they happen in the middle of some box2d operation being resolved, so they can't be queued for later like I did with enter and exit events. One of the reasons I think callbacks are bad is exactly because of this. preSolve and postSolve will never be able to not be callbacks because they're completely tied internally to how box2d works so this will leak forever and force every API on top of it to use callbacks too. It's much better to not repeat the mistake where possible and NOT make things callbacks unless absolutely necessary. Anyway, the way to easily deal with collisions is to use the earlier concept of collision categories to check for enter/exit collision events. In other engines I think this is typically called a collision tag. You'd have something like:
And then inside that if you'll do whatever you want to handle that collision. One of the things missing is being able to just check if an object is on top of another or not, like,
The last important thing that no one mentioned is the idea of binding the parent object to the physics object. Currently this can be done via fixture:setUserData(). This binding is extremely useful because it's through it that when a collision event happens you can get the other object that the current one collided with and do things with it, like deal damage to it or whatever. Assuming the idea of Colliders, one common pattern I found is something like this:
And then whenever in a collision event you want to get the other game object you can do something like:
|
Original comment by Bruce Hill (Bitbucket: spilt, GitHub: spilt).
Yeah, I agree that the Collider (not sure about the name, maybe "PhysicsObject" instead?) model simplifies things well. Almost all of my use cases are 1-userData/body/fixture/shape.
It's similar, but I can't tell from looking at your implementation how it will behave in this example:
Would players and enemies collide? The above code is ambiguous. That's why in my API example, I made ignoring collisions be symmetric, so this is how you would unambiguously express that you want players and enemies to pass through each other (argument order does not matter):
It's not just a matter of personal preference, it's also a matter of performance. Box2D has really efficient collision detection algorithms and using callbacks allows you to only run your code when Box2D determines that a collision happens, instead of performing a check on every object, every frame. Switching the API to use collidingWith() instead of callbacks would force people to use the less performant option. Personally, I also prefer callbacks for collision handling because they more closely match what I'm trying to express, which is usually "when I collide with X, do this behavior", not "every frame, ask if I just collided with X, and if I did, do this behavior". Two other API suggestions that came to me recently: The first is to replace world:queryBoundingBox(...) with world:queryShape(shape). Right now, if you want to find the objects that overlap a circle, you need to create a Body, Shape, Fixture, set the fixture to be a sensor, update the world with a timestep of 0, and then iterate over the contacts. This all seems wasteful and cumbersome to me. The second suggestion is to have a fixture:draw(drawMode) (or PhysicsObject:draw(drawMode)) method, because it’s irritating to have to type all this just to draw an arbitrary physics object:
|
Original comment by Sasha Szpakowski (Bitbucket: slime73, GitHub: slime73). Apple's SpriteKit APIs also wrap Box2D, it's worth looking at how they expose things: https://developer.apple.com/library/mac/documentation/SpriteKit/Reference/SKPhysicsBody_Ref/index.html#//apple_ref/occ/cl/SKPhysicsBody |
Original comment by adn adn (Bitbucket: adonaac, ). SpriteKit seems to do with SKPhysicsBody the same idea I have with Colliders. Unity also does something similar although not from box2d. Their SKPhysicsWorld also seems to have the needed query functions (raycast, point, rect, missing circle and polygon I guess) as well as Fields, which is something that Unity's physics system also has (https://unity3d.com/learn/tutorials/modules/beginner/live-training-archive/2d-physics-fun-with-effectors) that are a really good idea on top of everything everyone has mentioned here. |
Original comment by Sasha Szpakowski (Bitbucket: slime73, GitHub: slime73). Yeah, I also noticed SpriteKit has fields: https://developer.apple.com/library/mac/documentation/SpriteKit/Reference/SKFieldNode_Ref/index.html#//apple_ref/occ/cl/SKFieldNode Although that might be a thing for a separate bitbucket issue. |
Original comment by airstruck (Bitbucket: airstruck, GitHub: airstruck). Two quick thoughts: I really like the current physics API (with one exception). I hope this new API will be a higher-level convenience thing on top of the current API, and the current API will be pretty much left alone. The one part of the current API I dislike is setMeter. I wonder if setMeter might be a better fit for the new API, and could be removed entirely from the current API. If nothing else, this will make things much easier to document accurately (for example, we can simply say that some force is measured in Newtons rather than something contorted like "kilogram units per second squared, where units means meters divided by the value most recently passed to setMeter"). |
Original comment by Bart van Strien (Bitbucket: bartbes, GitHub: bartbes). We've discussed that particular before, and I still think you're blowing it out of proportion. The unit is Newton (aka kg·m/s²), rather than something involving pixels (kg·px/s²), setMeter simply determines the conversion rate between meters and pixels. If you're using meters internally, setMeter(1) accurately reflects that. (And any place it doesn't, is a bug.) |
Original comment by airstruck (Bitbucket: airstruck, GitHub: airstruck). @bartbes, what you're saying now seems to contradict what we discussed earlier. To recap the conversation, I asked if gravity was measured in m/s². Your response was: "No, it's not m/s², it's unit/s², where "unit" is whatever is defined using setMeter." Then @pgimeno replied: "I tend to think that setMeter is the pixels per metre conversion and that's not quite correct. It's actually for length unit conversion." From that conversation, my understanding is that force is actually measured in kg·px/s². I might just be confused about the whole thing, but that just indicates that setMeter is confusing (that, or I'm just too dense to understand it properly). I'm attaching a .love that might explain it better. My thinking is that if gravity were really measured in m/s², I could set the gravity of both worlds to the same value and they'd have the same gravity. Sorry if this is going off-topic, but in my view this could be relevant to both improving and simplifying the physics API. |
Original comment by Bart van Strien (Bitbucket: bartbes, GitHub: bartbes). I do have this horrible habit of contradicting myself. Which I will now do again, by saying I was correct before. So basically, I got confused (which illustrates your point I guess), and yes, it would be measured in kg·px/s². I will defend that that makes more sense in code, if I set a speed of 50, I'd expect it to be 50 pixels further, not 50 meters, which may very well be off-screen. |
Original comment by airstruck (Bitbucket: airstruck, GitHub: airstruck). I agree, that does make sense, although I think it makes more sense for a simplified API than it does for an API that mostly mirrors the Box2D API in a Lua-fied sort of way. I just mentioned it again here because I wanted to get the suggestion about moving it to the simplified API on the record (and because I promised @slime73 I'd update the wiki, but I honestly can't get my head around how to document units of measurement, so it's been on my mind again). |
Original comment by Pedro Gimeno Fortea (Bitbucket: pgimeno, ). I don't know, setLengthScale maybe? |
Original comment by itraykov (Bitbucket: itraykov, GitHub: itraykov). I like Sasha's idea of simplifying the API:
This would be sort of like the old version of Box2D were fixtures/shapes were combined in one object. The only benefit of separating the two is you want to "re-use shapes" which is not very practical in Lua anyways. My personal gripe with the Love2D API is that some of "constructors" are out hand (ie newPulleyJoint).
|
Original attachment: |
How about:
|
Sorry for a ping from the future, but as I have had to solve this (overcomplication) recently, I would definitely vote for having some high level API for simple physics. It took me maybe a solid couple of hours to grasp what are the differences between body, shape, fixture etc. and why do I need In the end I decided to build a helper that solves this by simplifying shape(s)->body/ies->fixture creation into one function. That function still returns a fixture, but PS: I liked the approach of HC although a couple of things could be further simplified (e.g. just generic function to create a collider instead of individual shape ones). |
#1130. - Add love.physics.newCircleBody(world, bodytype, x, y, radius) - Add love.physics.newRectangleBody(world, bodytype, x, y, w, h [, angle]) - Add love.physics.newPolygonBody(world, bodytype, coords) - Add love.physics.newEdgeBody(world, bodytype, x1, y1, x2, y2 [, onesided]) - Add love.physics.newChainBody(world, bodytype, loop, coords) All new functions return a Body object. The body's world position is at the center of the given coordinates, and the shape's local origin is at its center.
#1130 - Shapes are now directly attached to Bodies when they're created (similar to love 0.7 and older). - Fixtures are removed. - All methods that were in Fixtures now exist in Shapes. - All APIs that used or returned a Fixture now do the same with a Shape. - Add new love.physics.new*Shape variants that take a Body as the first parameter. - Deprecate the new*Shape APIs that don't take a Body. - Deprecate love.physics.newFixture (the deprecated function now returns a Shape). - Replace Body:getFixture and Body:getFixtures with Body:getShape and Body:getShapes (Body:getFixtures is deprecated). - Replace World:queryFixturesInArea and World:getFixturesInArea with World:queryShapesInArea and World:getShapesInArea (queryFixturesInArea is deprecated). - Replace Contact:getFixtures with Contact:getShapes (Contact:getFixtures is deprecated). - Replace all love.physics callback Fixture parameters with Shape parameters. - Deprecate ChainShape:getChildEdge.
Original report by Bart van Strien (Bitbucket: bartbes, GitHub: bartbes).
As highlighted in #1023, the current physics API isn't exactly nice. It's not the worst, but Fixtures especially are pains to deal with. I understand why Fixtures can be nice to have, but in most of the code I've written or read, one Shape becomes one Fixture, is attached to one Body.
Ideally we'd have some api that kind of matches the love "Hello world" feel, it's just a few lines to do the simple things, but if you want to, you can get a more advanced api. In this case, if you want a circle that has collisions, you'd probably want to be able to quickly create a Body, a Circle shape and the corresponding Fixture, then attach that Fixture to the Body, ideally using only one or two functions.
Perhaps a nice starting point, though I have only glanced at it, is the library @adonaac linked in #1023: https://github.com/adonaac/hxdx
The text was updated successfully, but these errors were encountered: