📜 ⬆️ ⬇️

Casual games on libgdx, subtle points in development

The article will be useful for both beginners and experienced developers, because it covers the basic aspects of game development, and non-trivial problems that had to be solved. If you are interested, please under the cat. Also to developers on libdgx the links given at the end of the article will be useful.

Tired of the difficult game, the development of which has been delayed for almost a year, we decided to write something less large-scale, simple and go beyond the beta version. I really wanted to see what was beyond the release, to touch marketing and, of course, to please users with their vision of a time killer.

The concept of the game was invented by arranging a brainstorm, generated about 20 ideas, chose the best and developed to an acceptable option.

I'll tell you a little about the game design, it will be easier to understand the subtle points in the development.
')
We wanted to create a timekiller with a funny network that wouldn’t strain the user and help relax a little in the game. In the first version of the game, it was just a ball that flew up between the beams, and was controlled by an accelerometer. One of the features of the game is that the complexity of the game increases to the 1st minute, and then everything depends on your skill.

The first version that saw the light


As a result, after talking with users and having more than one beta test, they added various heroes, game bonuses, achievements and records to the game.

The hero as a person is a rather interesting step, even the attitude of people to the game has changed a lot when, instead of a purple ball, a gloomy monster in a helmet started to fly, or if a soul appeared.

How the game looks like today




Gradle and libgdx.


Very pleasantly surprised by the project build system in libgdx. Starting from version 1.0, when creating a project through their ui utility, gradle scripts are also created to collect the project for the required platforms. This is very convenient for novice developers, because I think not everyone wants to learn from scratch how to build an iOS project from Idea and run it on an emulator or on a device. Using standard tools, this is done in one click.

A small example of what gradle does for you. The method unpacks libraries from natives-ios.jar to * .a and puts them into build / libs / ios:

task copyNatives << { file("build/libs/ios/").mkdirs(); configurations.natives.files.each { jar -> def outputDir = null if (jar.name.endsWith("natives-ios.jar")) outputDir = file("build/libs/ios") if (outputDir != null) { copy { from zipTree(jar) into outputDir include "*.a" } } } } 


Use atlases


This is written in all books on game development, when it comes to optimizing work with graphics.

Advantages of using atlases textures.

For ourselves, we found the gui extension gdx-texturepacker-gui . In principle, there are no complaints, but as far as I know, its support and development ended at the end of 2012.

Singlet for scene


The standard approach when using libgdx, described in all tutorials, is to create a new screen for a new screen:
habrahabr.ru/post/224175
github.com/libgdx/libgdx/wiki/Extending-the-simple-game

  game.setScreen(new GameScreen(game)); 

Everything is fine, the new game does not depend on the previous one, the new life is a new screen.
But we realized that not everything is so joyful when we implemented the function of rebirth after death and the logic of “start over”. The process could be as follows: New game -> revival -> revival -> start over -> revival -> start over.

Here problems with memory started, GC started to turn on, sometimes it was met with the fact that some objects referred to the screen that had already been killed.

The problem had to be solved.

Here, singletons come to the rescue, we only need one object of each screen, we don’t need to multiply several game and game screens, you can simply manage their state.

As with the rest of the screenshots, you do not need to create a new store screen every time you enter it, it’s enough to do it once.

Pools for identical objects


Those who know can skip this section, but when I first started developing games, I was very happy if I had been told about pools before. You can read more here .
Who would have thought that our old friend GC would hang the game for 0.2-0.4 seconds at an enviable frequency.

One of the main rules for creating games is to build architecture and write code in such a way that all objects are reusable and GC is not invoked (preferably never).

For example, in our case, these are reusable blocks, coins, particles, which we create only once in small quantities, for blocks it is 25 pieces, put in a queue and, if necessary, use when the object disappears / collapses, we put it back in the queue until the next use .

Pool implementation
  public class MultiPool<T> { private final List<Pool<T>> mPools = new ArrayList<>(); public void registerPool(final int pID, final Pool<T> pPool) { this.mPools.add(pID, pPool); } public T obtainPoolItem(final int pID, float x, float y, int type) { final Pool<T> pool = this.mPools.get(pID); if (pool == null) { return null; } else { return pool.newObject(x, y, type); } } public void recyclePoolItem(final int pID, final T pItem) { final Pool<T> pool = this.mPools.get(pID); if (pool != null) { pool.free(pItem); } } } public class Pool<T> { public interface PoolObjectFactory<T> { public T createObject(float x, float y, int type); } private final Array<T> freeObjects; private final PoolObjectFactory<T> factory; private final int maxSize; public Pool(PoolObjectFactory<T> factory, int maxSize) { this.factory = factory; this.maxSize = maxSize; this.freeObjects = new Array<T>(false, maxSize); } public T newObject(float x, float y, int type) { T object = null; if (freeObjects.size == 0) { object = factory.createObject(x, y, type); } else { object = freeObjects.pop(); } return object; } public void free(T object) { if (freeObjects.size < maxSize) { freeObjects.add(object); } } } 


Optimization of objects using pools is one of the most significant, which can be carried out in the game. In various versions it is used in applications, but there is a little easier, because if the application is used in a more or less passive mode, the user may not even notice the work of the GC.

PS useful comment from 1nt3g3r

LibGDX has its own class for pools (Pool), and there is a pool of pools (Pools). As a result, the work comes down to:

Monster m = Pools.obtain (Monster.class); // get the object from the pool. Simply indicate which class object we need.
... // used the object
Pools.free (m); // returned the object to the pool

Stream: play with difficulty


One of the main points in the development of this kind of game is protection against misuse, in our case it was a crazy phone shaking. This problem was solved by limiting the maximum speed along the X axis, it turns out that no matter how much the user is shaking the phone, he will not fly faster.

The very setting of the game speeds required a very large number of tests, since It was necessary to balance 3 indicators: acceleration of the camera, acceleration of the hero in x and acceleration of the hero in y. Mathematically, we calculated the boundary conditions - those speeds after which the game becomes unreal and meaningless, and then all the data was selected experimentally. We rewrote the logic of acceleration of the hero several times. I think many have seen a graph of the dependence of the game on progress.

Graph of game complexity versus progress


In general terms, he describes how the complexity of the game he is supposed to grow. We naively thought “we don’t have levels, it’s not about us, we’ll make a linear relationship with a gradual increase in speed.” When they did, they realized that the next question was how to increase speed quickly.

If the game is difficult from the first seconds, people do not have time to get comfortable. In our first variant, a person could die in less than 10 seconds after the start, if he did not immediately understand what to do.

We raised the bar, up to 10 seconds the game was very calm, and then again increased the pace. We still heard reviews that it was too difficult and impossible to play, we were advised to reduce the level of hardcore by about 20%.

Again we played with complexity and conducted another experiment. The first 20-30 seconds, people were satisfied, but then, to our surprise, everyone said about the same: “It’s somehow simple. Slowly accelerated. I should have died already. ”
In the end, we have come to the classical diagram of complexity, in our version it has slightly modified, due to the fact that the complexity does not grow endlessly.

Our dependence of the game on progress


It was also very important to choose the right platform layout algorithm. In our case, the algorithm looks like this. A total of 5 platforms fit in one line, the first platform is created with a 80% chance for each subsequent platform, this percentage decreases. So that the whole line is not built up with platforms, we have to count the number of generated platforms, and if 4 are already standing, then the 5th should not be set. We also store the position of the passage for the previous line, it is necessary to build a path of coins that the player must collect.

Libgdx and 2D graphics.


The developers of libgdx write that they have a very convenient system for creating interfaces. I think many who have worked with libgdx know how hard and long it is.
github.com/libgdx/libgdx/wiki/Scene2d.ui
Yes, they have a set of classes that facilitate the work, there are layouts, there are widgets, but here is a simple parting of them on the screen - it was a huge piece of work.

Correct, if I am wrong, I will be glad to learn something new, but I have not found the means to quickly prototype and create a GUI. More recently, when I once again began to change the design, move buttons and draw transitions between screens, I realized that I could not do it anymore. Began the search for the interface of the builder, even at Habré in the articles I met the mention of the fact that some write their desktop applications in Java to create a GUI.

As a result, I found a very interesting solution on the githaba. It turns out that the Cocos2d-x framework has its own studio for creating interfaces, and - oh, a miracle! - its earlier versions use json as an exported format.
One craftsman from China has already written a parser for this studio. The essence of the work is very simple: it is taken json, serialized into objects, and then a GUI is created for these objects using Scene2d.

I finished this version a bit to make it compatible with later versions of cocostudio, as well as the version for OSX.

Versions of cocostudio currently supported:

v1.0.0.0 Beta for Mac
v1.5.0.1 Beta for Windows

I will be glad to contributors, the library has room to develop (coconut has already switched to version 2+), I hope to achieve good compatibility of these tools.

I want to note that they collected everything on the android, because they decided to run on this platform. Under iOS, the build is also fine. The blessing gradle build is already written for us. The only difficulty with iOS is that all the near-game services will have to be rewritten to robovm.

Thanks to those who read to the end. I will be glad to answer your questions in the comments. As promised, links to resources.

Coco-libgdx-gui: github.com/xPutnikx/cocostudio-ui-libgdx/tree/kotlin
Texture packer: code.google.com/p/libgdx-texturepacker-gui

Source: https://habr.com/ru/post/246185/


All Articles