Another Snake not in 30 lines on Android

    Hello everybody! Today I want to tell the story of the creation of one toy for Android. What will happen:
    • Why another snake for android ?! Explanation (with description);
    • How I did it - used techniques, a bit of code;
    • A bit about design;
    • Google Play Games, non-standard use.



    All interested please ask for a cut!


    Why another snake for android ?!


    It so happened that in the fall my girlfriend and I were relaxing at sea. Warm water, calm, swimming - all this leads to the birth of various thoughts in the 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 was decided to code! On what? So Neideus recently installed AIDE on 7 ". What? A game, simple. But with a twist. So the idea was born to write a Snake with hexagonal tiles - not trivial, interesting.

    Immediately the question arose of choosing technologies. Past my game ( Pacman) I wrote in C ++ + OpenGL ES 2.0, here it is an explicit overhead. I heard something out of my corner about Canvas, read a little and understood - what’s needed, give two. 4.5 hours on the plane - the engine is ready, the snake walks, crashes into itself, and responds to svaypas in six directions. And then ... then there was another month or so of finishing.

    What happened as a result:

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

    • 2 types of games - mazes and classic;
    • 3 skins;
    • 7 difficulty levels, opening one after another.


    The bonus was a primitive map editor with the ability to immediately start the game and try, as well as a bunch of new experience.

    How i did it


    Let's start in order.

    Main screen


    He is a pause screen with a different layout of components.
    On this screen, pay attention to the background. I spent the whole evening on it, and still it sometimes seems to me that he looks a little different from what I want. The idea of ​​making it that way was partly taken from Google, partly from 2Gis - I like these intersecting broad rays. The background is vector, that is, it takes a few bytes in apk. It is drawn on Canvas, in 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 learn from this code? The first (and easiest) way to draw on Canvas. We will inherit from View, redefine the method void onDraw(Canvas canvas)and draw on the Canvas whatever your heart desires. It was in this way that my first prototype, written in an airplane, was drawn. To dynamically update, you must call the invalidate()y method View, after which onDraw () will be called sometime in the future . No warranties. A more correct way to get a canvas for intensive drawing is described below .

    Settings screen



    Here, in principle, there is nothing special, except for the preview of the game. This seems to be a rather controversial decision, but I wanted the player to immediately see what awaited 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



    While the game does not have too many labyrinths - 8. In the future I plan to add them. Each line has an icon of the labyrinth, its name, personal and world records. In the event that a player is not logged in to Google Play Games, his personal record automatically becomes world. Labyrinths open 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 ...

    Custom use of Google Play Games


    Or rather, the subsystem of achievements. I called it share-over-achievements. Surely I was not the first one who came up with this, but I did not find descriptions of such a method. Do not kick =)
    When certain “unlocks” appear in the game - for example, opening a level or a maze, as in my case, the question immediately arises - what if a 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 openwork, you can fumble through it. And if, like mine, neither one nor the other, then the following trick may come up:
    At the moment when the user has unlocked some content, unlock the achievement.
    Cap code
    Games.Achievements.unlock(getApiClient(), achievementName);
    

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

    At the same time, it’s better to cache downloaded achievements somewhere from where you can quickly get them. And also to cache unlocked achievements, in case the player is not logged in - you will need to unlock 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 achievements are such a whistle for raising the mood of the user. 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 game field is square, 15x15 hexagons. Logically represented by a two-dimensional array of tiles, each of which has:
    • The coordinates of the "following" tile - used in the body of a snake and teleports;
    • The flag of "mortality" is, again, the body of the snake and the walls of the labyrinths;
    • Type of tile - food, super food, snake, wall, teleport, empty;

    Thus, a death check is equal to checking a single flag, moving through a teleport is not more expensive than just moving. The field is drawn using classes that implement the interface IDrawer, so the number of skins can increase over time.
    Above, I described the easiest way to render on Canvas. A more correct way for dynamic rendering is to inherit from SurfaceViewand render in a separate thread. I will dwell on the main points: We
    inherit from SurfaceView, implement SurfaceHolder.Callbackfor events SurfaceView:
    The 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 flow of rendering DrawThread:
    The 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 ours SurfaceViewand start the rendering flow when creating the Surface:
    The 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 the destruction of the Surface:
    The 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 battle code IDrawerand GameI pass in GameView. In the rendering stream, the logic is recalculated in the same way.

    conclusions


    Writing small games (even clones), as well as your own non-gaming projects is extremely useful. Especially useful is the way out of the comfort zone; it allows you to quickly learn a lot of new and useful information. From a small projection for a few hours you can make a very interesting game.
    Canvas drawing technology is well suited for small games where special effects are not needed.
    Writing games in Java for Android is much easier than writing in C ++, especially if you are a loner.
    Have a good weekend, and Happy New Year!

    Also popular now: