We draw, code for libGDX and other little joys from the life of an indie developer


    Hello, Habr! In this topic, I would like to share my impressions of the libGDX game engine, talk about the everyday life of an ordinary indie developer and open the veil of secrecy over the game that I have been doing for the past few months in my free time from office slavery. I hope these notes of mine will be useful to those who are just starting to do something on libGDX or to those who choose the engine for the "game of their dreams."

    And sorry for the cats. They have absolutely nothing to do with igrostroy. I’m simultaneously learning (trying to learn) to draw and now these training cats of mine are just everywhere! They demand that they, loafers, be shown to someone.

    Painting


    Speaking of drawing. Of course, seasoned CG-artists will throw me slippers (and they will be absolutely right), but how am I trudging from the process! I didn’t think that drawing is so cool! For a long time, among all the rubbish, I had a gift for someone on a graphic tablet. Bamboo is something there, I think everyone is buying the same "to indulge." And then, once, I caught my eye on the book by Mark Kistler "You can draw in 30 days." No, of course, learning to draw in 30 days is the same as learning C ++ in the same time. Everyone understands this. But it turns out that drawing is not some intangible talent there, not elves grazing unicorns in the meadows of uncharted islands. It is just a set of rules, as simple as the syntax of a programming language. Plus experience, of course! Stuffing hands. But a good programmer is developing in a similar way,


    So, in this book (more precisely, the book, it’s not serious at all) there are all the basic rules to start drawing something more than a stick-stick-cucumber. And when the hand-drawn squiggles cease to cause horror in you at least, it's cool! The process itself begins to bring pleasure. For me personally, this was a revelation.


    Also, taking this opportunity, I want to advise an excellent editor - SketchBook Pro. Pros are also drawn in it, but it’s exactly what a non-professional needs. Userfriendly, not overloaded with functionality. A license is relatively inexpensive, unlike Photoshop. Although this disgusting “subscription software” scheme applies here, the basic features are available for free.

    I use only 3 tools: pencil, ballpoint pen, brush. There is, I don’t know how to call it correctly, the function of straightening your curvules on the go is a mega useful thing! We take a pen in one hand, the second on a space (rotation and scaling of the canvas) and go! Create creativity.

    Why not Unity?


    But I was distracted. When it comes to choosing a game engine, the first and only question that now arises is “why not Unity, man?” And therefore! Because I'm used to Android Studio (I wrote a corporate application recently), I know Java more or less, Android SDK. And the use of such a multifunctional monster as Unity for simple 2D games is a bit of a “sparrow-gun”, don’t you? An unfamiliar IDE, another approach, again. And the size of the empty project is 20 MB? Yes, I have on libGDX the whole game along with the graphics (which is not so small here) weighs less! I understand that we live in an age of fast Internet, but nevertheless, there is a correlation between the size of the application and the number of installations.

    If you dig deeper, the bottom line is that the warning “The application size is too large, are you sure you want to download it not via Wi-Fi?” (literally, I don’t remember, but the essence is this) - cuts off a certain number of impulsive settings.

    In general, if you have previously dealt with the Android SDK, libGDX is an excellent choice for a smooth transition to cross-platform development. I will try to formulate all the pros and cons that I learned for myself from my first acquaintance with libGDX.

    pros


    • Free.

    • Cross-platform. The desktop build builds and runs equally seamlessly under Windows and Mac (on Linux there was no way to check, but I'm sure the same way). For HTML, I came across a small nuance: all packages that you add must be registered in the Core project in gwt.xml, otherwise it will not build. There are no problems with Android at all, iOS is only in my plans so far.

    • Low entry threshold for Android developer. The same Studio, the same Gradle. We have a Core project in which we describe all the game logic and an Android project - this is, roughly speaking, the MainActivity on which the View lies, and the Core project is executed in it. From the core project, we can easily call the platform-dependent part that we write in the Android project. And what is very nice, we implement the platform code (advertising, analytics, game services) directly from the tutorials from Google. As in a regular application. That is, no libraries, third-party extensions, and so on!

    Minuses


    • I did not find a relevant, sensible tutorial. There is a lot of information, even here on Habré, but usually it comes down to how to display a sprite on the screen. In practice, this is not necessary, why? It is wiser to use abstractions of a higher level: Actor, Scene (more on this below). When getting acquainted with the new engine, in the Quickstart guide, I would like to see the following: correctly scaling the game for various screens, background loading of resources (this is all good ), managing screens / scenes and objects on them.


    Continuing the conversation about the cons, I have a feeling of slight understatement in libGDX. There are excellent abstractions here: Screen - a game screen, Stage - a scene or layer, Actor - any object on this scene. In my game I decided to make one Screen and many Stage on all game screens: levels, menus, etc. We inherit game objects from Actor, then just do stage.addActor (ball) and that’s it. Next, Stage itself is engaged in rendering, moving, etc. this object. So Actor, by itself, does nothing. It does not display sprites (and 99% of game objects are sprites), you can’t check for a collision between two actors, in general, nothing!

    You have to make your own SimpleActor and already inherit game objects from it:

    The code
    public class SimpleActor extends Actor {
        private final TextureRegion region;
        public SimpleActor(TextureRegion region) {
            this.region = region;
            setSize(region.getRegionWidth(), region.getRegionHeight());
            setBounds(0, 0, getWidth(), getHeight());
        }
        @Override
        public void draw(Batch batch, float parentAlpha) {
            batch.draw(region, getX(), getY(), getOriginX(), getOriginY(), getWidth(), getHeight(), getScaleX(), getScaleY(), getRotation());
        }
    }
    

    If you need animated game objects (well, where without them?) We write SimpleAnimatedActor:

    The code
    public class SimpleAnimatedActor extends Actor {
        private Animation animation;
        private TextureRegion currentFrame;
        private float stateTime;
        private boolean started;
        public SimpleAnimatedActor(float frameDuration, Array frames) {
            animation = new Animation(frameDuration, frames);
            currentFrame = animation.getKeyFrame(stateTime);
            setBorders(frames.get(0));
        }
        private void setBorders(TextureRegion sample) {
            setSize(sample.getRegionWidth(), sample.getRegionHeight());
            setBounds(0, 0, getWidth(), getHeight());
        }
        public void start() {
            stateTime = 0;
            started = true;
        }
        public boolean isFinished() {
            return animation.isAnimationFinished(stateTime);
        }
        @Override
        public void act(float delta) {
            super.act(delta);
            if (!started) {
                return;
            }
            stateTime += delta;
            currentFrame = animation.getKeyFrame(stateTime);
        }
        @Override
        public void draw(Batch batch, float parentAlpha) {
            batch.draw(currentFrame, getX(), getY(), getOriginX(), getOriginY(), getWidth(), getHeight(), getScaleX(), getScaleY(), getRotation());
        }
    }
    

    To implement a primitive collision check, you can add Polygon to our SimpleActor:

    The code
    Polygon polygon = new Polygon(new float[]{0, 0, getWidth(), 0, getWidth(), getHeight(), 0, getHeight()});
    polygon.setPosition(getX(), getY());
    polygon.setOrigin(getOriginX(), getOriginY());
    polygon.setScale(getScaleX(), getScaleY());
    polygon.setRotation(getRotation());
    

    Etc. Of course, this does not cause any particular difficulties. But still, I can’t understand why not do something similar in the engine? And so you have to drag your appended code from project to project.

    Fragmentation


    How to make the game look equally cool on the entire zoo of Android devices? For me, this is one of the key issues. Therefore, I will tell in more detail how I solved the problem of scaling a picture in libGDX.

    I like the already classic approach when we show more environment on tablets (4: 3), and less on smartphones (16: 9). In this case, nothing is stretched anywhere, and game objects, one might say, do not change their sizes. We store all the graphics in one resolution, I have it 960x1280 (4: 3). In the center of the screen, we have a “game area” 720x1280 (16: 9) where all the objects that the player interacts with are required to get into, the rest is the scenery and can be cut depending on the particular device. The easiest way to illustrate this principle is with a picture:


    LibGDX provides all the features for this. The only caveat - some objects may need to be attached to the edge of the screen. Like the Home button in the image above. To do this, we need to know the actual left and right edges of the screen in game coordinates (with top and bottom easier - it's always 1280 and 0, respectively). Let's write some code:

    The code
    public class MyGame extends Game {
        public static final float SCREEN_WIDTH = 960f;
        public static final float SCREEN_HEIGHT = 1280f;
        public static float VIEWPORT_LEFT;
        public static float VIEWPORT_RIGHT;
        private GameScreen mGameScreen;
        @Override
        public void create() {
            mGameScreen = new GameScreen();
            setScreen(mGameScreen);
        }
        @Override
        public void resize(int width, int height) {
            super.resize(width, height);
            float aspectRatio = (float) width / height;
            float viewportWidth = SCREEN_HEIGHT * aspectRatio;
            VIEWPORT_LEFT = (SCREEN_WIDTH - viewportWidth) / 2;
            VIEWPORT_RIGHT = VIEWPORT_LEFT + viewportWidth;
        }
        @Override
        public void dispose() {
            super.dispose();
            mGameScreen.dispose();
        }
    }
    

    What's going on here? This is the main class of the game inherited from Game. Here we create and set Screen (the screen, in my case, the only one) and redefine resize () . This event for Android is triggered when the application starts, and here we can calculate the left and right edges of the picture (VIEWPORT_LEFT and VIEWPORT_RIGHT).


    In GameScreen we need to install FillViewport , it is he who is responsible for ensuring that the image is cropped if the aspect ratio does not match the target. Viewport is our window to the game world. They are of several types, the documentation about this will tell even better than me, and I just give the code:

    The code
    public class GameScreen implements Screen {
        private final OrthographicCamera mCamera;
        private final Viewport mViewport;
        private final AssetManager mAssetManager;
        private Stage mActiveStage;
        public GameScreen() {
            mCamera = new OrthographicCamera();
            mViewport = new FillViewport(MyGame.SCREEN_WIDTH, MyGame.SCREEN_HEIGHT, mCamera);
            mAssetManager = new AssetManager();
            mActiveStage = new MyStage(mViewport, mAssetManager);
        }
        @Override
        public void render(float delta) {
            mCamera.update();
            Gdx.gl.glClearColor(65 / 255f, 65 / 255f, 65 / 255f, 1f); // просто цвет фона
            Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
            mActiveStage.act(delta);
            mActiveStage.draw();
        }
        @Override
        public void resize(int width, int height) {
            mViewport.update(width, height, true);
        }
        @Override
        public void dispose() {
            mActiveStage.dispose();
            mAssetManager.dispose();
        }
    }
    

    Here, in general, everything should be intuitively clear, do not forget to just do mViewport.update (width, height, true) with resize () .

    Mechanical Box


    Now that I have briefly talked about the engine, I would like to say a few more words about the game itself. I haven’t been playing games for a year, the corporate swamp almost sucked me. But that did not stop me from dreaming. Dreaming of a quest, a puzzle as complex as possible. Without clues for money and other free-play stupid things.

    So the idea of ​​a Mechanical Box - a device created with one single purpose: to reliably protect what is inside. Where each layer of protection is a separate, independent puzzle with new game mechanics.

    A huge plus indie, we can afford to do what we want. I don't care about Retention Rate and other marketing stuff. Yes, the gamedise of any commercial studio will shoot itself when it sees the percentage of players getting stuck on the first level! And I can just make a game that I would like to play myself.


    In general, in August of this year I started building the Box. Having drawn a little, I realized that I could not draw out such volumes, and my art did not suit me in quality. He is, to put it mildly, peculiar. As a result, an artist was found on gamedev.ru who agreed to get involved in this adventure. Surprisingly, it turned out that Vladimir and I live not only in the same city, but also on the neighboring streets. It was he who gave the Mechanical Box an appropriate appearance. I even kept “as it was” and “how it became” as a keepsake. The difference, I think, is obvious.



    While I continue to work on the Mechanical Box, I have some thoughts on the development of this idea. If you decide to test your strength, the game is already published on Google Play. I don’t give a link, so as not to attract UFO attention once again . And although the Box was quickly disassembled into parts on one popular resource, for a single person, this puzzle is a pretty serious challenge. Which, personally, pleases me. I can even predict where you will be stuck. This is either a level with a crane (how to move this damn claw to the right?), Or three multi-colored squares (this riddle seems easy to me, but statistics are stubborn thing). Or, welcome to the "club of the lost zero" - well, this is the elite.


    Thanks for reading, ask questions, write comments!
    Do good and throw it into the water.

    Also popular now: