📜 ⬆️ ⬇️

As I wrote a physical puzzle on Libgdx

Hello!

Screenshot for seed:

Does someone else read the picture tips?
')
Somehow aimlessly sitting on the Internet, I came across the game "Chains, balls and zombies . " I do not know why, but she hooked me strongly. Simple and at the same time interesting gameplay and some non-linearity - levels can be completed in several ways. Something she reminded me of the notorious Crazy Machines, which I also once hurt.

After killing the zombies, I had the idea to write my game - with poetess and preference balls and zombies, only better (you can rob cows) . No sooner said than done. As a result of a couple of weeks, the game was made, and posted on Google Play. If it is interesting to you to learn more detailed - I ask under kat.

For several years I have been writing games for mobile devices on Android. I use the libGDX engine. Before that, I tried AndEngine, and even wrote a prototype of a small toy on it - but then I moved to libGDX, which I don’t regret even now. The main feature is the ability to write and debug code on the desktop, and then transfer the game to Android with minimal edits. It turns out quickly and pleasantly, no need to wait for the launch of a buggy and slow emulator. Therefore, it is quite logical that I chose this particular engine for writing the game.

Idea


As you know, it all starts with an idea. In my case, the ideological inspirers were the games “Chains, balls and zombies”, “Crazy machines” (very mediocre for this game), “Stupid zombies”. The goal of the game in a nutshell - using all available means, to destroy all the zombies on the level. Heavy metal balls suspended on chains, bombs, boxes, planks, mines, car wheels acted as improvised means. Cutting the chain, you drop the ball on the zombie - profit, green creature is dead. In more advanced versions, you need to undermine bombs and cut chains at the right time - there is an arcade element. To make it more fun, time ticks in the game - the faster you complete the level, the more stars you get.

I decided to call the game Ugly Zombies - a kind of reference and an attempt to play on Stupid Zombies.

image
Typical gameplay - we recycle chains, roll bombs to boxes, explode, explosions, boxes kill zombies.

Video games:



Game structure


Structurally, the game is divided into "Menu" and "Game". From the “Menu” we can get to the “Screen of boxes”, and from there - to the “Screen of levels”. Also from the menu you can get to the "Shop". That is, the structure is traditional - there are 4 boxes in the game, each box has 18 levels (6 * 3 grid). The level becomes available only after passing the previous one. In the store we can buy additional levels (more will be further) and disable advertising.

image
An example of the selection of boxes. Each box is unique (its own picture and animation)

Control


Actually all control in the game comes down to cutting the chains and pressing the bombs. In the first version, when I debugged the game on the desktop, I deleted the chains and blew up the bombs on click. Then I started the game on the phone - and that was bad. Chains were cut through time, and the bombs were not undermined. I scratched my head, and changed the behavior of the click, at the touch (that is, touched, not yet pulled the finger from the screen, and the action took place). It helped, but not much. It was normal on the desktop, but it was still bad on the phone. The small screen made management inconvenient. It was necessary to do something.

Then an idea came to my mind - and if you cut it, you can cut it! In a sense, swipe across the screen - this is what we do, as in Fruit Ninja. I did, tried - yes, that was it! True, the bombs were still undermined by tapas. After a while, I did the svayp bombs too, it was more convenient. Some testimony to the convenience of control and clarity of the game is the following - I let my ten-year-old nephew play a rough version of the game, he passed a box and a half in an hour. I have nothing to compare with, but at least an adult should understand the game without problems.

Art


Art in the game was a national team. The interface for the menu, I took a free set, which dug on the Internet. I was still surprised that such a good casual set, and free. As it should be free graphics, it was not enough. Therefore, using Gimp and strictly censor words , I have drawn the rest of the graphics for the menu based on the downloaded pack. Here is the main menu of the game:
image

I made the graphics for the game screen partially, I partially took it from the Internet. Boards are drawn in Gimp, boxes are taken from OpenGameArt.org, bombs, wheels, zombies are safely downloaded from Ineta. The same applies to the game background. All the pictures were post-processed - placed on a transparent background, cropped, resized.

As for the zombies, it was more difficult with him. I downloaded the whole picture, and cut it into pieces - I also needed to make a Ragdoll from this picture. Zombies were the most difficult and painful part, and I think this is the least developed part of the game. The absence of the artist greatly affected the graphic component of the game. My girlfriend said that the game looks cute - but she could not say otherwise :)

I drew the graphics for Google Play (icon and promotional picture) myself, in Gimp. By the method of torment and trial this was created:
image

I recorded the video for Youtube with the vokoscreen program, then edited (cut out the necessary and inserted music) with the OpenShot Video Editor program. Before that, I tried the Avidemux programs (I used before) and LiVES - but both crashed when trying to export video. But OpenShot managed, despite its curved control.

Perhaps the issue with the graphics on this can be closed. Graphics is a painful process for me, the lack of an artist taught me the basics of working in Gimp, and the awareness of my own imperfection.

Sounds


Sounds were taken in part from OpenGameArt.org, partially torn out from the above flash games. For unpacking the flash, I used some kind of Java program, I don’t remember the name, if I’m interested in someone, I’ll look for it. The sound of a win (gong) was found on the Internet, cut off in Audacity. The sounds of great tension did not cause, the more they were relatively few, a dozen.

Music


I found the music on OpenGameArt.org. I found a jaunty rock track that, as for me, fit into the game well (I was lying on the couch, playing a toy with this music). In general, there was no big problem with music either. It would be necessary to loop it, but then my knowledge in Audacity was not enough, and I decided not to bother - especially since it would not greatly affect the gameplay.

Code


Let us turn to the most interesting for programmers - code.

The game was written in Eclipse, I used my own add-on engine over libGDX - DDE (Dark Dream Engine). From the possibilities of my engine:
- Visual editing of screens (very much accelerated development);
- convenient access to resources (sounds, graphics) - just put them in the right folders, then they load themselves. Moreover, resource loading is “lazy” - if we access a resource, but it is not loaded yet, it is loaded, cached, and returned;
- support extensions (in fact, the visual editor of the screens - this is the extension)
- Convenient start panel, where you can choose the resolution of the game to run - it is convenient to test multi-screen;
- the ability to export from the launchpad apk and desktop applications - once setting all the parameters, then click only "Build". It works crookedly, so I did not use it.
- ... and many more buns. I laid out the draft version on GitHub, but that was a long time ago and not true. As time appears, I want to finish DDE to the human species, and write a series of tutorials - I used it in several projects, it is still convenient.

For physics, as it should be, I used Box2D - it is very well integrated into libGDX, there were no questions about it. For debugging, I used the Box2dDebugRenderer class - it allows you to visualize physical bodies with lines on the screen.

Structurally, the game is divided into the following parts:
- The main controller class - I called it Zombie. This is a singleton that stores links to all resources. It is available from anywhere. You might think this is a God Object - but it is not. This is a simple class of 135 lines, 90% of which are getters and setters. I quote the entire class code so that you understand what I mean:

Zombie.java
package ua.com.integer.labs.zombie; import ua.com.integer.dde.kernel.DDKernel; import ua.com.integer.dde.res.screen.AbstractScreen; import ua.com.integer.dde.res.sound.SoundManager; import ua.com.integer.labs.zombie.screen.LoadingScreen; import ua.com.integer.labs.zombie.screen.game.GameController; import ua.com.integer.labs.zombie.screen.game.Level; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.utils.Logger; public class Zombie extends DDKernel { private static Zombie instance = new Zombie(); private SpriteBatch spriteBatch; private ShapeRenderer sRenderer; private Settings sets; private Level level; private Sounds sounds; private GameController gameController; private PlatformInterface platformInterface; private Zombie() { getConfig().relativeDirectory = "../zombie-android/assets/"; } public static Zombie getInstance() { return instance; } @Override public void create() { super.create(); getResourceManager().getManager(SoundManager.class).loadAll(); Gdx.app.setLogLevel(Logger.DEBUG); gameController = new GameController(); sets = new Settings(); sounds = new Sounds(); spriteBatch = new SpriteBatch(); AbstractScreen.batch = spriteBatch; sRenderer = new ShapeRenderer(); showScreen(LoadingScreen.class); } @Override public void dispose() { super.dispose(); getResourceManager().dispose(); sRenderer.dispose(); } public void setPlayLevel(Level level) { this.level = level; } public Level getLevel() { return level; } public ShapeRenderer getShapeRenderer() { return sRenderer; } public Settings getSettings() { return sets; } public void setPlatformInterface(PlatformInterface platformInterface) { this.platformInterface = platformInterface; } public PlatformInterface getPlatformInterface() { if (platformInterface == null) { platformInterface = new PlatformInterface() { @Override public void showAds() { System.out.println("Show ads."); } @Override public void hideAds() { System.out.println("Hide ads."); } @Override public void buyUnlockAds() { System.out.println("Buy unlock ads."); } @Override public void buyLevels() { sets.setBoxesOpened(true); System.out.println("Buy levels."); } @Override public void showInterstitial() { System.out.println("Show interstitial!"); } @Override public void rateForGame() { System.out.println("Rate for game"); } @Override public void shareViaFacebook() { System.out.println("Share via facebook"); } @Override public void shareViaTwitter() { System.out.println("Share via twitter"); } @Override public void updatePurchases() { System.out.println("Update purchase state"); } }; } return platformInterface; } public GameController getGameController() { return gameController; } public Sounds getSounds() { return sounds; } } 


Further there was a division into resource managers. The resource is almost everything in the game. This is sound, music, textures, screens - everything that can be divided into parts and edited. Thus, there is a sound manager, texture manager, screen manager.

A few details about the screen. One screen is one class.

Screenshot of the store screen:
image
And here is the screen code for the shopping class:
StoreScreen.java
  package ua.com.integer.labs.zombie.screen.menu; public class StoreScreen extends AbstractScreen { public StoreScreen() { addScreenEventListener(new ScreenListener() { @Override public void eventHappened(AbstractScreen screen, ScreenEvent event) { switch(event) { case SHOW: findByName("all-boxes").setVisible(!Zombie.getInstance().getSettings().isBoxesOpened()); findByName("no-ads").setVisible(!Zombie.getInstance().getSettings().isAdwareDisabled()); break; case BACK_OR_ESCAPE_PRESSED: getKernel().showScreen(MenuScreen.class); break; default: break; } } }); ActorUtils.deployConfigToScreen(this, Actors.getInstance().getConfig("store-screen")); findByName("back-button").addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { Zombie.getInstance().getSounds().playClick(); getKernel().showScreen(MenuScreen.class); } }); findByName("all-boxes").addListener(new ScaleActorListener(0.05f).setTime(0.1f)); findByName("all-boxes").addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { Zombie.getInstance().getSounds().playClick(); Zombie.getInstance().getPlatformInterface().buyLevels(); } }); findByName("no-ads").addListener(new ScaleActorListener(0.05f).setTime(0.1f)); findByName("no-ads").addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { Zombie.getInstance().getSounds().playClick(); Zombie.getInstance().getPlatformInterface().buyUnlockAds(); } }); } } 


As you can see, the code is rather short. I will not explain it in detail, just a couple of points.
one)
  ActorUtils.deployConfigToScreen(this, Actors.getInstance().getConfig("store-screen")); 
- expand the screen configuration (I have my own screen editor, remember?). Background, buttons - this is all visually dragged, this line does the rest.
2)
  findByName("all-boxes").addListener(new ClickListener()... 
- find the object with the specified name and assign a handler to it. The name of the object (actor, in the terminology of libGDX) we specify in the interface editor.

That is, the creation of such routine operations was automated, which allowed me to concentrate more on the game.

Snacks


What a game without snacks! By them, I mean small ornaments that allow you to visually “revive” the game. I made a few of these pieces, their description below.

Animated boxes. Each box must be “alive” - this will create a sense of the game’s development. Therefore, each has a unique pattern with a simple animation - the hand increases / decreases, the skull blinks, the bomb rotates, and the star spins round. These are very simple effects that are a pleasure to do with libGDX, thanks to its advanced system Actions. For example, the code to add a skull blink animation is
  findByName("head-item").addAction(Actions.forever(Actions.sequence(Actions.color(Color.WHITE, 0.4f), Actions.color(Color.BLACK, 0.4f)))); 


Animated dialogues. There are several dialog boxes in the game - winnings, purchase of levels, etc. It was ugly to simply show them - with two lines of code we make an animated appearance / closure. The code is, again, as simple as possible (showing \ closing the purchase window) -
  private void hideBuyDialog() { Actor dialogContent = findByName("dialog-content"); if (dialogContent != null) { findByName("dialog-content").addAction(Actions.scaleTo(0f, 0f, 0.1f)); } } private void showBuyDialog() { Actor dialogContent = findByName("dialog-content"); if (dialogContent != null) { dialogContent.addAction(Actions.scaleTo(1f, 1f, 0.1f)); } } 


To store the settings, I selected a separate class - Settings. Access to this class can be obtained through the class Zombie. Turning on / off music, buying levels, disabling advertising is what he does.

To interact with Android code from the main project, I created an interface (in Java terms), implemented it in the Android part, and transferred it to the main project. Interface Code -
 package ua.com.integer.labs.zombie; public interface PlatformInterface { public void buyLevels(); public void buyUnlockAds(); public void showAds(); public void hideAds(); public void showInterstitial(); public void rateForGame(); public void shareViaFacebook(); public void shareViaTwitter(); public void updatePurchases(); } 


As you can see, just a set of methods without parameters. All parts are implemented in the Android part, in the desktop project only stubs.

Game screen


The game screen was more difficult for the rest. I divided it into HUD and physical. model. Physical model and called - Model.java. Abstraction was introduced - GameObject. This same GameObject contained a physical body, a sprite for rendering and an update () method for rendering a sprite. The model managed objects - add, delete, call update (), start \ stop physical. of the world is all she is. A kind of manager turned out.

To implement various objects - balls, boxes, etc. - GameObject subclasses were created. An ObjectLoader was created that could load GameObjects. Thus, if I wanted to add a new object, I made a subclass from GameObject, and a subclass from ObjectLoader. Perhaps this is somewhat overhead, but this allowed to keep the game screen and model relatively clean - only the number of relatively small classes has grown.

A level editor has been created to create levels. Written in Java, good old swing. Levels are saved in JSON, the weight of one level is about 2 kilobytes. Using the JSON-worker built into libGDX, I loaded \ saved the levels from a file with one line.

If I did all 72 levels manually - I would go crazy, I suspect. For those who make games with levels, it makes sense to think about creating an editor as early as possible, then there will be less hemorrhoids. Perhaps this is a truism, but still.

There are quite a lot left overboard - a description of handling physical collisions, creating a ragdoll, adapting to different screen resolutions - but this will draw on an article, and not even one. If someone is interested - write in comments, I can paint something. And we move on.

Monetization


Like any person, I want to eat. Therefore, I thought about at least some income from the game. After reading articles on the Internet, I decided to monetize advertising and in-game purchases.

Advertising. The banner is below (not visible in the game screen), and the interstitial advertisement on the win screen (interstitial ad). Advertising can be turned off for one dollar.

Buying levels. Initially, there are two boxes in the game - half the levels. The rest is $ 1.99. I took the prices "from the ceiling", I would be glad to hear comments on this issue from knowledgeable people in the comments.

Basically, that's the whole monetization. When you click on a closed box, an offer to buy boxes appears, and there is also a separate store screen, which is accessible from the main menu. I do not have much experience in this area, again I would be happy to read the comments of people who are professionally involved in this.

Localization


I did not do any localization. There is a minimum of text in the game, and I thought it was an unnecessary cost. Therefore, as originally the game was in English, it remained.

Promotion


I did not make much progress. Post here (without a link, who is interested - write in a personal, reset). Post on w3bsit3-dns.com, on the site libGDX, on the site gcup.ru. I also plan to multiply in the forums. Of course I’ll post it on my blog (I don’t know if I can put a link here. My blog is non-commercial, there’s no advertising. There is a blog on game development, quite a lot is on libGDX). I will be glad to advice from knowledgeable people.

I posted the game on Google Play. In principle, there is no great difficulty to build a desktop version - just where to upload it and why - I do not know. If someone can tell if it makes sense to do this - I will be very grateful

findings


The game took several weeks. The main work was done in a week - from eight in the evening to three in the morning. Motivation is very important. If you are set to work, you are very quickly doing everything.

Very well, if the team has an artist. Otherwise, websites with free gaming resources are yours. Possession of Gimp or Photoshop is a very good help. This also includes possession of audio and video editors.

This game is my trial project. I wonder if there will be some kind of exhaust from it, some kind of popularity. So to say, carefully try to heel the water - is it cold? I am pleased to hear your comments, welcome the discussion on this.

It seems like everyone else. Waiting for feedback!

UPD: Added a video from the game at the beginning

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


All Articles