📜 ⬆️ ⬇️

Another Snake is not in 30 lines on Android

Hello to all! Today I want to tell the story of creating one toy for Android. What will happen:



All interested in asking under the cat!


Why another snake for android?


It so happened that in the fall my girlfriend and I rested on the sea. Warm water, calm, swimming - all this has the birth of various thoughts in my head. One of these thoughts was “What to do on the plane on the way back?”. Rest was in full swing, and I missed programming a bit. So it’s decided to code! On what? So on 7 "Nexus recently installed AIDE. What? A simple game. But - with a twist. So the idea was born to write a Snake with hexagonal tiles - not trivial, interesting.
')
Immediately there was a question of the choice of technology. I wrote my last game ( Pacman ) in C ++ + OpenGL ES 2.0, here it is an obvious overhead. Something vaguely heard about Canvas, read a little and understood - what you need, give two. 4.5 hours in the plane - the engine is ready, the snake walks, crashes into itself, and responds to the swipe in six directions. And then ... then there was still about a month of finishing.

What happened in the end:

In the screenshots: Main screen, Labyrinth "Mill" (dark skin), Labyrinth "Teleport" (light skin)



Bonus went to a primitive map editor with the ability to immediately start the game and try, as well as a bunch of new experiences.

How i did it


Let's start in order.

Main screen


It is the pause screen, with a different layout of components.
On this screen, pay attention to the background. I spent the whole evening on him, and still, sometimes it seems to me that he looks a little different from what I want. The idea of ​​making it exactly like this was partially taken from Google, partly from 2Gis - I like these intersecting broad rays. Vector background, that is, it takes in apk read bytes. It is drawn on the Canvas, in a custom view.
Some code
public class BackgroundView extends View { /*  */ private Bitmap mBmp; @Override protected void onDraw(Canvas canvas) { /*  ,         */ if(mBmp == null){ mBmp = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Config.ARGB_8888); Canvas c = new Canvas(mBmp); drawBackground(c); } canvas.drawBitmap(mBmp, 0, 0, null); } private void drawBackground(Canvas canvas){ canvas.drawRGB(180, 220, 70); /*       ,   */ /*    Path    ,   ,   */ } } 


What do we get out of this code? The first way (and the easiest) drawing on Canvas. Inherit from View , override the void onDraw(Canvas canvas) method and draw on the Canvas whatever your heart desires. It was this way that my first prototype written on the plane was drawn. For a dynamic update, you must call the invalidate() method on the View , and then sometime in the future, onDraw () will be called. Without guarantees. A more correct way to get a Canvas for intensive drawing is described below .

Settings screen



Here, in principle, nothing special is there, except for the preview of the game. It seems to be a rather controversial decision, but I wanted the player to immediately see what was waiting for him when changing the skin. In the future, I plan to increase the number of skins. Also, on devices that support vibration, you can turn it on / off on this screen.

Labyrinth selection screen



So far there are not too many labyrinths in the game - 8. In the future I plan to add them. Each line has a labyrinth icon, its name, personal and world records. In the case when a player is not logged in to Google Play Games, his personal record automatically becomes global. Labyrinths are opened sequentially, as well as difficulty levels. And here, perhaps, the time has come to talk about a non-standard way of using Google Play Games ...

Non-standard use of Google Play Games


Or rather, the achievement subsystem. I called it share-over-achievements. Surely I was not the first to invent it, but I did not find descriptions of this method. Do not kick with your feet =)
When there are some "unlocking" in the game - for example, opening a level or a maze, as in my case, the question immediately arises - what if the player has two devices? Smartphone and tablet, for example? How to let him use content unlocked on one device, everywhere? If you have a server, and time to support it, then everything is in openwork, you can fumble through it. And if, like me, neither one nor the other, then the following trick might do:
At the moment when the user unlocks some content, unlock the content.
Cap code
 Games.Achievements.unlock(getApiClient(), achievementName); 

And when you need to check the content for openness, check this achivka at the same time (you can in advance).
Another code
 PendingResult<LoadAchievementsResult> pendingResult = Games.Achievements.load(getApiClient(), true); pendingResult.setResultCallback(new LoadAchievementsResultCallback()); 

In this case, the loaded achievements are better to cache somewhere, from where you can quickly get them. And also to cache unblocked achievements, in case the player is not logged in - you will need to unblock them at the moment when he does log in.

Why is this a non-standard way to use Google Play Games? Because the documentation basically shows us that achievement is such a whistle to raise the user's mood. But they can also be really useful. And I think it's cool. I use this method to unlock mazes and difficulty levels.

Now let's talk about the game itself.


The field of the game is square, 15x15 hexagons. Logically represented by a two-dimensional array of tiles, each of which has:

Thus, checking for death is equal to checking one flag; moving through a teleport is not more expensive than just moving. The field is drawn using classes that implement the IDrawer interface, so the number of skins may increase with time.
Above, I described the easiest way to draw on Canvas. A better way for dynamic rendering is to inherit from SurfaceView and draw in a separate thread. I will focus on the main points:
Inherit from SurfaceView , implement SurfaceHolder.Callback to receive SurfaceView events:
Code
 public class GameView extends SurfaceView implements SurfaceHolder.Callback{ public GameView(Context context){ super(context); getHolder().addCallback(this); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceCreated(SurfaceHolder holder) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { } } 


We write the DrawThread drawing DrawThread :
Code
 public class DrawThread extends Thread { private boolean mRunning = false; private SurfaceHolder mSurfaceHolder; public DrawThread(SurfaceHolder surfaceHolder) { mSurfaceHolder = surfaceHolder; } public void setRunning(boolean running) { mRunning = running; } @Override public void run() { Canvas canvas; while (mRunning) { canvas = null; try { canvas = mSurfaceHolder.lockCanvas(null); if (canvas == null){ continue; } /*   canvas */ } finally { if (canvas != null) { mSurfaceHolder.unlockCanvasAndPost(canvas); } } try{ //let other threads do they work Thread.sleep(15); }catch(InterruptedException e){ } } } } 


We return to our SurfaceView and start the rendering stream when creating Surface:
Code
  @Override public void surfaceCreated(SurfaceHolder holder) { /*      , Canvas   ,     */ mDrawThread = new DrawThread(holder); mDrawThread.setRunning(true); mDrawThread.start(); } 


And do not forget to clean up after you break the Surface:
Code
  @Override public void surfaceDestroyed(SurfaceHolder holder) { destroy(); } /*   ,  surfaceDestroyed()   ,    destroy() "" */ public synchronized void destroy(){ if(mDrawThread == null){ return; } boolean retry = true; mDrawThread.setRunning(false); while (retry){ try{ mDrawThread.join(); retry = false; } catch (InterruptedException ignored){ } } mDrawThread = null; } 



In the IDrawer and Game combat code, I GameView to GameView . In the rendering stream, the logic is recalculated as well.

findings


Writing small games (even clones), as well as own non-game projects is extremely useful. A way out of the comfort zone is especially useful, it allows you to learn a lot of new and useful things in a short time. From a small project for a few hours, you can make quite an interesting game.
Drawing technology on Canvas is quite suitable for small games where special special effects are not needed.
Writing Java games for Android is much easier than C ++, especially if you are a loner.
Have a great weekend, everyone, and Happy New Year!

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


All Articles