gamelab / kiwi.js

Kiwi.js is a blazingly fast mobile & desktop browser based HTML5 game framework. It uses CocoonJS for publishing to the AppStore.
www.kiwijs.org
MIT License
1.41k stars 194 forks source link

Add Abstraction to the geom #256

Open 1O1O1O opened 8 years ago

1O1O1O commented 8 years ago

It would be nice to abstract the way that geom items are used.

Actually we have to manage a bunch of static method, that required to know what kind of geom item are supposed to intersect.

I plan to make a "polygon" geom object for easy handle collision between boxes with complexe shapes in my game, but it's appearing to me that it's a bad deal to add a public static methode for each available combinaison of object, that's pretty not maintenable.

I humbly suggest to implement a general and more abstract method for compute intersections, and then check the type of objects given, and call the appropriate private computing method.

The ability to deal with geom items without knowing their concret type open a lot of nice perspective, plus offer a more sexy way to implement new kind of geom objects.

Of course that will need to separate more accurately some geom objects, and for example split the line object in two or more abstracts ones : segment and line at least, for example.

I just mean that the intersect method should not be coupled to the concret type of the objects concerned, it should only deal with one common geom "intersectable object" interface, and the determine the type of items and deal with all the private static methods necessary.

Not sure if i'm clear, sorry for my poor english ;)

I'll try to help around here as soon as I'll attack the collision part of my game.

BenjaminDRichards commented 8 years ago

I get what you're saying. It sounds sensible. Kiwi.Geom objects are differentiated via objType() strings, so it should be easy to discern the correct intersection method.

I suggest developing this as a plugin initially. Geom collisions are fairly rare in most of our game implementations; I don't know whether the extra logic necessary for a universal intersection computer is necessary in many games. But it is a good idea, and should be executed.

I shall discuss this with my colleagues. I've tagged as Enhancement, and set the milestone to 1.5.0. We might decide that, if we're adding all the intersection methods anyway, we might as well throw in the universal intersection computer. Not sure what the outcome will be.

To check, have you looked at the Chipmunk Physics plugin for KiwiJS? We've achieved good results with it for collisions more complex than square-on-square.

1O1O1O commented 8 years ago

I just watched the plug-in chipmunk physic and that is extremely relevant , however, I met two blocks to manage collisions :

First, the main problem is the lack of scaling on shapes

Scale does not affect Shapes, this is a major issue for auto scaling on mobile device with cocoon.js or cordova. I guess that there is probably a tricks for scale for cocoon or cordova, but I will anyway need to scale my entities a lot during the game, and that lack will be painful.

Second, even if the work of developers is excellent on this plug-in, collisions features are coupled to the physics features, and the physic is focused on falling items.

What if we don't want to implement a physic engine or emulate gravity but just collide complex shapes in the end of a movement with a standard pathfinding algorithm like A* or Dijkstra? The whole physic mechanic will be useless and consume resources.

It's is really a shame that the different features have to be so closely coupled, but it's chipmunk, and it's the way that chipmunk work, I understand that it's legit. (Legit and probably useful for a lots of projects)

BenjaminDRichards commented 8 years ago

You make good points. Our projects have never really required that particular kind of collision information, but as you say, pathfinding is a legit application with its own concerns.

It would be handy to apply scaling to Geom objects. There isn't a direct way right now, but I think we should consider it when revising scene hierarchies for v2.0. It would be very useful to simply append Geom objects into the scene graph and use a unified pipeline for computing where everything is. The exact mechanics are still up in the air, but it's worth discussing now.

Re scaling for Cocoon or Cordova: KiwiJS offers very convenient scaling mechanisms. When deploying to Cocoon, I'll always add the gameoption scaleType: Kiwi.Stage.SCALE_FIT. The game will automatically adjust rendering techniques while maintaining the same internal units. However, it actually renders to the full resolution of the area. If you load high-resolution assets for high-resolution devices, it should appear crisper than the technical resolution of the game. This won't apply to non-Cocoon modes, which just use CSS to scale up a canvas of fixed resolution, but game scaling could do with a rethink in general, and it might be worthwhile.

BenjaminHarding commented 8 years ago

Love the ideas of abstracting the Geom library and also opening them to matrix operations :)

1O1O1O commented 8 years ago

I'll try to expose my point of view, but my english may be painful for you so i'm sorry if i'm not clear, it's harder to speak about technic in another language than usual.

I thought about geom, and also checked the code more deeply, I feel like i'm missing one important point: what the purpose of this whole part of Kiwi?

Why did you code the geom features and how are they used by users?

Did you make it to allow people to draw basic shapes and move them around? And then add more and more features like the intersect computing?

Sometimes we want to do some stuff, and we find something useful (to extend) that do almost what we want to do, but if we look closer, wisely weigh the pros and cons, we may conclude that it is or it is not a so good idea to do so.

I see two main obstacles in the current implementation that will be difficult to avoid for my project to use geom for implement a collision plugin.

First of all, the lack of abstraction. As I said in my first comment , it would be convenient to harmonize the various concrete types to manipulate them more easily. But that's not so easy.

In a perfect world, any object can say what is his purpose, and it can say it all by his name. For example: a point...

What is a point? It's obvious, but if we go further in the current geom implementation, we can see that the point object do thousands of thing other than just being a point. It's what happened when we merge more than one responsability in one object, and of course that should be avoided at any cost.

Basicaly, we can define two architectual layers that allow to easily split the responsabilities and abstract the way you manage the objects.

_The first one: The DataObject, _his meaning is nothing more than a BUS of data. In my example, a point object would probably "extend" from a geom DataObject, and implement the specific properties that define a point. We already have the base for abstract the way we manage geom objects.

Every single type of geom object (rectangle, triangle, line, polygon, etc...) would extend the same geom "super item" and implement his own needed properties.

The second one is the service layer: This allow you to do whatever you want to do, but encapsulated in a themed service Object.

For example, a transformation service would allow to move, scale, transform... geom items without knowing their concret type.

With such an implementation, I would be able to use the geom dataobject for what they really are : Container of data, only responsible of their own integrity.

Actually, the point object isn't only a point, he is a point, but it is also the whole services collection that may manage/manipulate points objects, and then for some method it also manage some other kind of geom concrete object type.

By spiting responsabilities, you break a lot of coupling problems, and raise the cohesion of code.

For conclude, I will need to manage geometrics items, for compute collision and pathfinding, but I will split responsabilities this way:

One geometric object "interface" Multiples concretes geometric objects

One service per functionnalities:

I can add one or more abstraction layer, for example, with the factory design pattern, ask for a pathinderService, but may get in return differents concretes type of service, with the same public interface : One heuristic approach with A*, or one mathematic one with Dijkstra.

The second problem that will probably force me do not use geom is that i'm effraid to deploy to much complexity with no necessity (because of the features of each geom item that will be not used at all at any point for collisions or pathfinding).

Of course, my remarks may seem a bit irrelevant and arrogant, but I hope you do not take the trouble, I'm only bring my analyze the code to determine whether or not I can use as a base for my plug in or not.

The fact is that the issue of the architecture of geom is a very widespread problem, whatever the level or experience of the developers, or the application.

Doctrine2, if you know this ORM, raises precisely this kind of problem in its implementation by developers as choice, then believe me, I do not criticize you, you have done a great job;)

NOTE: All these considerations do not alter the fact that the library would be improved and simplified if it proposed a generic interface for geom objects to manipulate them without knowing necessarily the concrete type instances are manipulated. Applying transformations via matrix is a good example of very useful case.

I hope I have helped

BenjaminDRichards commented 8 years ago

First: Thanks for your thoughtful explanations! It's not just we Bens who appreciate it. It gives us plenty of food for thought. Your English is fine; you use the word "necessarily", which many native speakers can't even spell :)

To explain the purpose of the Geom features:

I cannot say what the original intent of Geom may have been. The original developers have long since departed for other projects. Most of what KiwiJS does is due to their original work; their architecture gives the project shape.

I joined the project later, just after v1.0.0 was released. I've done a fair amount of actual game development with KiwiJS, which is very helpful in discovering exactly what the library needs. My contributions have perforce been either fixes, enhancements, or plugins. The library is about 25% larger now than it was when I arrived, but much more than that has been altered and upgraded to make it work faster, better, or just work at all under circumstances that the original architects didn't foresee. Of course, it's not nearly all my work - @BenjaminHarding has been equally hard at work, and has been on the project much longer.

KiwiJS was developed as a library, which means it has to make trade-offs between completeness and appropriateness. A custom-built tool can always design appropriately for the task at hand, but a library cannot make any such predictions about what users will require of it. The architecture was thus designed to be complete. It has solutions for a wide range of problems, but those problems were at least partially hypothetical. You could say that the architecture was designed to do anything the original developers could imagine.

As we progress, we discover what problems actually occur in real game development, and improve the library to solve these issues. Sometimes the original, hypothetical design is discovered to be a clumsy solution - but for compatibility reasons, we need to maintain it.

Geom is used today for simple collisions and coordinate transformations. Most game objects have a Box component, which uses Geom.Rectangle objects to represent simple hitbox information. The Input system uses Pointer objects, which may have a Geom.Circle to represent the approximate touch area of a finger on a device touchscreen. Geom.Point is used in numerous places where we need to convert a coordinate from some transform state to another, from input methods to the WebGL rendering pipeline.

Point is a special case. Because it doesn't have a shape, it's the basis of a great many functions for finding distances and angles and transformed locations.

More recently, I've created the Primitives plugin, which confusingly also uses terms like "rectangle" and "circle". However, primitives are render objects, not collision objects. One major difference is that primitives were designed to use matrix transforms and live in the scene graph, while Geom objects were not. This makes Geom objects harder to manipulate. By the same token, primitives are not great for resolving collisions.

Right now, however, we've got to maintain Geom as it currently exists. This will certainly change in v2.0, but that's not on the immediate schedule. We can tweak the internals, but the API must remain as-is.

My Suggestion:

Have a go at implementing your ideal solution as a plugin. Feel free to loot any useful logic from the core KiwiJS, it's open. I recommend following the code style guide from the beginning - it links to numerous best-practice paradigms.

Try not to break the extant API. Note: this just means overwriting extant properties and methods with different functionality. Adding new functionality simply extends the API.

If you can build a compatible solution that offers superior ease of use, we can look into integrating it into an upcoming release. We may have to maintain Geom, but we are permitted to change its internals, and we are permitted to deprecate features if we find a better way of solving the problem.

If the solution doesn't fit the current API, that's OK. We've effectively deprecated some core features in favour of plugins for in-house work (such as Text, which is better than TextField in every way). If the solution is worth using, we're open to adopting it.

I'll remove this from the 1.5.0 milestone because we're still not sure exactly where it will belong, but that just means we want it to find the best home in the ecosystem. We'll be following it with great interest.

Whew, that was a bit of an autobiography. I hope it helps to explain the reality of our development situation, and what steps we can take to improve both short-term and long-term.

Looking forward to hearing more from you!