
The tale of how to write a plug-in for the
Unity Asset Store , break your head over the solution of the well-known problems of isometry in games, and even some money for coffee to fuck with it, as well as understand how much Unity has an extensible editor. Pictures, implementations, graphics and thoughts under the cut.
Outset
It was evening, there was nothing. The new year did not foretell anything special about program life (unlike personal, but this is another story). The idea to shake gray hairs and write at least something personal and yours, and even aimed at at least some commercial benefit, is ripe (to warm the soul, that at least someone else needs your development, except the employer). All this coincided with the fact that for a long time I wanted to feel the expansion possibilities of the Unity editor and evaluate its platform for selling my engine extensions.
Allocated a day to study the asset store: models, scripts, integration with various services, at first glance it seemed that everything was already written, integrated, and even in different versions, of different quality of execution and study, with different prices and support. Immediately narrowed your search range:
')
- only code (as a programmer)
- only 2D (since I love damn 2D + his sane out-of-box support just appeared in Unity)
And then I remembered how many cacti I ate and how many mice died when we were doing an isometric game, how much time was spent searching for good solutions, and how many copies were broken in trying to sort and isometric it out. Unusually trembling in his hands, I searched for different key words and words, and did not see anything except an heap of art in isometry. Well, it is decided there will be an isometric plugin.
Goal setting
The first thing that needed to be done was to briefly describe what problems this plugin would solve and how it would help in the difficult work of the developer of an isometric game. Isometric problems:
- sorting objects by distance to render them correctly
- editor extension for creating, positioning and moving isometric objects in the editor
The main tasks for the first version were set, I spent 2-3 days for the first draft version. It was impossible to delay, enthusiasm is a fragile thing and if you do not see something ready in the first days, it is easy to ruin it. Yes, and the New Year holidays are not as long as it may seem, but I would like to have time to roll out the first version in them.
Sorting
In brief, isometry is an attempt for 2D sprites to pretend and look like 3D models, which translates into various kinds of problems. The main problem is that the sprites must be sorted in the order in which they are drawn, in order to avoid incorrect overlapping.
The screenshot first draws a green sprite (2.1), then on top of it is blue (1.1)
The screenshot shows incorrect sorting when the blue is drawn first.Sorting, in this simple case, is not difficult and there are various options, for example:
- sort by screen position Y, which = (isoX + isoY) * 0.5 + isoZ
- draw from the farthest isometric cell from left to right, from top to bottom [(3,3), (2,3), (3,2), (1,3), (2,2), (3,1), ...]
- and a lot of interesting and not so ways
All of them are good, fast and work, but only in the case of such unicellular objects or elongated (isoZ) columns :) I was also interested in a more general solution for objects elongated along one coordinate, or even "fences" that do not have width, but stretched in the same direction with the desired height.
In the screenshot, properly sorted 3x1 and 1x3 stretched objects with "fences" of 3x0 and 0x3 sizesAnd here the problems begin and the place begins where you need to make a decision on which way to go:
- break up "multicellular" objects into "unicellular", i.e. cut vertical strips and sort these strips
- think over another sorting option, more complex and interesting
I chose the second option, I did not want to get involved with the special preparation of art, with cutting (even automatic), with a special approach to logic. For reference: the first method was used in the well-known games of
Fallout 1 and
Fallout 2 , and these strips can be observed by gaming the game data.
The second approach does not imply a criterion for sorting objects, that is, there is no special calculated value by which they can be sorted. Whoever does not believe (and many who didn’t make isometry, don’t believe), take a piece of paper and draw objects with dimensions
2x8 and, for example,
2x2 , if something happens and you somehow manage to deduce some number to calculate the depth and sorting Add an 8x2
object to the example and try to sort them in different positions relative to each other.
Okay, there are no numbers, but we can calculate the relationship between them (roughly speaking someone overlaps), and this, in turn, can be used in
topological sorting . Object dependencies can be calculated using the projections of isometric coordinates on the isometric axes.
In the screenshot of the blue cube dependence on red
In the screenshot, the green cube is dependent on bluePseudocode for determining dependencies along two axes (with the Z axis in the same way):
bool IsIsoObjectsDepends(IsoObject obj_a, IsoObject obj_b) { var obj_a_max_size = obj_a.position + obj_a.size; return obj_b.position.x < obj_a_max_size.x && obj_b.position.y < obj_a_max_size.y; }
Using this approach, we build dependencies between objects and recursively traverse all objects and their dependencies by exposing the display Z coordinate. The approach is quite universal, and most importantly it works. A more detailed description of this algorithm can be read, for example,
here and
here . This approach is also used in the popular isometric flash library (
as3isolib ).
Everything is good, but the algorithmic complexity of this approach is
O (N ^ 2) , since to build dependencies you need to compare each object with each. But the optimizations were postponed to later versions, except that I did a lazy re-sorting so that nothing would be sorted when nothing moves. About optimizations later.
Editor extension
The goals were set as follows:
- sorting objects should work in the editor (not only in the game)
- there must be other Gizmos-Arrow (arrows moving objects)
- optional tile alignment when moving
- automatic application of tile sizes and their task in the isometric world inspector
- drawing AABB objects with isometric dimensions
- output of isometric coordinates in the object inspector, changing which changes the position of the object itself in the game world
All goals have been achieved, Unity really allows you to greatly expand its editor. Add new tabs, windows, buttons, new fields to the object inspector, if you wish, you can even make your own custom inspector for the component of the desired type. Displaying additional information in the editor window (in my case of AABB objects), it is also possible to replace the standard arrows for moving objects. The sorting inside the editor was solved by the magic flag
ExecuteInEditMode , which allows the components of the object to run in editor mode, that is, do the same thing as in the game.
All this was done, of course, not without problems and all sorts of tricks, but there was no problem over which I fought for more than a couple of hours (Google, forums and the community helped solve the issues not described in the documentation).
The screenshot shows my gizmos to move objects in an isometric world.Release
The first version is written, screenshots are taken, the icon is somehow drawn, a description has been compiled. It is time. I set a symbolic price of $ 5, fill the plug into the page and wait for the results of the device from the side of Unity. I didn’t think over the price for a long time, there was no goal to earn, there was a goal to find out if there is any demand at all, and if so, which one. There was also a goal to help the developers of isometric games, which for some reason were completely cheated by the possibilities and additions.
The painful 5 days (about the same I spent on writing the first version, but I knew what I was doing, without any special searches and thoughts, which gave me more speed than people who are just starting to dig towards isometry) and a letter came from Unity, that the plugin is approved and you can go to admire it in the store and watch zero (for now) sales. Marked on the local forum, embedded Google Analytics on the page in the side and waited for the weather from the sea.
I did not have to wait long and went to the first sales, as well as reviews on the forum and in the store. For the rest of January, 12 copies of the plug-in were sold, I considered this as a present interest to him and continued.
Optimization
The main complaints were about two things:
- Algorithmic sorting complexity - O (N ^ 2)
- Problems with the garbage collector and overall performance
Algorithm
With 100 objects and O (N ^ 2), there were 10'000 checks to find the dependencies, but you still need to go over all of them and display Z for sorting. It was necessary to solve something. In the end, I tried a huge pile of options, I could not sleep tormenting decisions. I will not describe all the tested mechanisms, I will describe better on what I stopped at the moment.
First, of course, we sort only the visible. This means that you always need to know who is in the frame, when you see an object in a frame, add an object to the sort, and when you leave, forget it. Unity does not give us the opportunity to learn the
Bounding Box of an object together with its children in the scene tree, to run through all the children (and each time, because they can be added and removed) is not an option - slowly. Events
OnBecameVisible and others like them also can not, because they work only for the parent object. But it is possible to get all the
Renderer components from the desired object and its children. This option does not shine with beauty, but I did not find the same universal method with acceptable performance.
List<Renderer> _tmpRenderers = new List<Renderer>(); bool IsIsoObjectVisible(IsoObject iso_object) { iso_object.GetComponentsInChildren<Renderer>(_tmpRenderers); for ( var i = 0; i < _tmpRenderers.Count; ++i ) { if ( _tmpRenderers[i].isVisible ) { return true; } } return false; }
Here there is a subtlety that the GetComponentsInChildren function is used , with which you can get components without allocations to the desired buffer, unlike the one that returns a new array with componentsSecondly, you still have to do something with
O (N ^ 2) . I tried many variants of space breaking, but I stopped at a simple two-dimensional grid in the display space, where I project my isometric objects, in each such sector there is a list of isometric objects that intersect it. The idea is simple: if the projections of objects do not overlap, then there is no point in building dependencies between objects. Next, we run over all visible objects and build dependencies only in the necessary sectors, thereby lowering the algorithmic complexity and increasing the valuable speed. The size of the sector is chosen as the average between the sizes of all objects, the result suits me.
Overall performance
Here you can write a separate article of course ... In short, we cache the components (
GetComponent is looking for them and this is not fast), be careful with everything regarding
Update , you should always understand that every frame is done and you need to be careful. We remember about all sorts of interesting features like
custom comparison with null . And so on and so forth, in the end, you learn about this in the built-in profiler and you begin to decide and remember.
Also there you learn about the pain of a garbage collector. Need performance? Forget about everything that can allocate memory, and in
C # (and especially in the local old
Mono ) it can do everything, from
foreach (!) To the created lambdas, and any
LINQ is contraindicated even in the simplest cases. In the end, from beautiful syntactic constructions and other sugar,
C # turns into a sort of
C with ridiculous possibilities.
I will give useful links on the topic:
Part1 ,
Part2 ,
Part3results
I have never seen this algorithm so improved, so I was especially pleased with the results. If in the first versions literally on 50 moving objects, the game turned into a slide show, now for 800 objects in the frame everything is spinning at extreme speeds, re-sorted out in just 3-6ms, which is very good for so many objects and isometries. Plus, after initialization, there is almost no allocation in the memory frame at all.
Additional features
After reading the reviews and suggestions, there are several possibilities that I added in the latest releases.
2D and 3D mix
Mixing 2d and 3d in isometric games is an interesting feature that allows you to minimize the drawing of different variants of movement and turns (for example, 3D character models with animations). It is not difficult, but you need to embed it all into the sorting system. All you need is a
Bounding Box model with all the children and move the model along the display Z by its width.
Bounds IsoObject3DBounds(IsoObject iso_object) { var bounds = new Bounds(); iso_object.GetComponentsInChildren<Renderer>(_tmpRenderers); if ( _tmpRenderers.Count > 0 ) { bounds = _tmpRenderers[0].bounds; for ( var i = 1; i < _tmpRenderers.Count; ++i ) { bounds.Encapsulate(_tmpRenderers[i].bounds); } } return bounds; }
So you can calculate the Bounding Box model with all its children.
But this is how it all ends up.Custom isometric settings
Everything is relatively simple, they asked to make it possible to set the isometry angle, aspect, height of the tiles. Having suffered a little with math you can get this:

Physics
And here is more interesting. Since the isometry simulates the 3D world, then the physics must be three-dimensional, with height and so on. Was come up with an interesting trick. I duplicate all the components of physics, such as
Rigidbody ,
Collider, and so on for the isometric world. According to these descriptions and settings, I repeat the invisible physical three-dimensional world by means of the engine itself and
PhysX 's built into it. Then I take the computed simulation data and return to those duplicate components for the isometric world. In the same way I simulate the events of collisions and triggers.
Gifka physical demo toolsetDecoupling and outcome
After implementing all the suggestions on the forum, I decided to raise the price to $ 40 and not look like a cheap plug-in with five lines of code. I would be very happy questions and suggestions, as well as criticism of the article, since I am writing to Habr for the first time, thanks. And now, for a snack, collected monthly sales statistics:
Month | $ 5 | $ 40 |
---|
January | 12 | 0 |
February | 22 | 0 |
March | 17 | 0 |
April | 9 | 0 |
May | 9 | 0 |
June | 9 | 0 |
July | 7 | four |
August | 0 | four |
September | 0 | five |
A reference to the asset page in Unity Asset Store:
Isometric 2.D Toolset