Developers Club geek daily blog

1 year, 4 months ago
image

I want to share the experience creations of the first game for the Android platform in this article, to tell all way from origin of idea before the publication.

Background


Since 2012 I closely began to be engaged in programming, in particular development of programs for Windows in the C# language. During all the time I got a wide experience of development of .NET of applications, participated in different projects including command. So left that I wanted to be engaged in development for Android. I was faced by the choice C# (Xamarin) or Java. As I programmed the last 3 years on C# and there were a wish as it is possible to start quicker development, but not to understand new IDE and nuances of programming on Java the choice was obvious.

Idea "what to develop?" was born quickly and consisted in application programming for listening and downloading of music in VK. The application was almost complete, but in connection with some problems is not published. Later half a year, at me appeared a great lot of "free" time and I decided to return to applications programming under Android. Having got acquainted with one programmer and having communicated to him about benefits and shortcomings of Java (Android) and C# (Xamarin), I began to study under its manual books on Java and to master IDE IntelliJ IDEA.

Idea or what everything began with


After two weeks of acquaintance with the principles of development (which came to me quite easily) the familiar designer suggested to start instead of reading books and repetition of examples development of game at once, and the possible problems connected with programming, to solve in process of their receipt. Quite so I also arrived, it was necessary only to think up idea for game, but in 5 minutes it was thought up by the designer.

Game is aimed at the development of memory and its essence consists in the following: are shown to the player within several seconds the image with several figures of a different form, color, the size and an arrangement which he has to manage to remember then to repeat. The player receives "asterisks" which allow to open new more difficult levels for each passable level.

For determination of an assessment (quantity of the received asterisks) of the passable level it was decided to use 3 parameters: color of a figure, its location and radius. For adding of complexity the condition was entered into game process that the player needs to repeat forms of all provided figures, taking into account their screen location, level will be thought by not passed differently.

Design


With development of the graphic interface I was helped by the same designer who not the first time participated in development of UI/UX design for mobile applications. He developed a logo, an icon, selected primary colors, fonts, the sizes of buttons, etc. Then provided the ready solution of the interface drawn in a vector. Prepared materials for release ("preview", advertizing images, etc.).

Results of work can be seen on screenshots:
As I got the first experience of development of game on Android

As I got the first experience of development of game on Android

As I got the first experience of development of game on Android

As I got the first experience of development of game on Android

Development


After receipt of ready design of game I was accepted to development at once. Difficulties which it was necessary to face were connected only with implementation of drawing of figures. In total it was necessary to implement 6 methods for drawing of figures (a circle, a square, a triangle, a pentagon, a star, a hexagon), two of which are already implemented in Java. Also one of conditions was the fact that the figure has to be drawn from the center extensively, and has to be inscribed in a circle with a radius which size was determined by already user. As it was my first similar project, I decided to implement independently methods of drawing of the remained four figures, but not to use ready libraries. For drawing of figures canvas.drawPath was used () (a method of drawing of lines), I needed only to define points through which have to pass a way of the line. In this regard it was necessary to remember the school program and to open the textbook on geometry.

The following nuance was the fact that the figure can be drawn in several ways: with the designated center, with a frame, without frame and the center, with a frame and the center, at the same time the frame can be continuous or dotted. For a solution of this problem the interface with three methods was created (drawing of a figure, frame, drawing of a figure with a frame). This interface was used at implementation of classes of drawing for figures.

The interface for drawing of a figure
public interface DrawFigure {
    public void draw(Canvas canvas, PointF startPoint, PointF finishPoint, Paint mPaint);

    public void drawWithBorder(Canvas canvas, PointF startPoint, PointF finishPoint, Paint mPaint, Paint borderPaint);

    public void drawBorder(Canvas canvas, PointF startPoint, PointF finishPoint, Paint mPaint, Paint borderPaint);
}


Class of drawing of a triangle
public class DrawThreeAngle implements DrawFigure {

    private float mRadius;
    private int[] angles = new int[]{30, 150, 270, 30};
    private float[] xValues = new float[4];
    private float[] yValues = new float[4];

    @Override
    public void draw(Canvas canvas, PointF startPoint, PointF finishPoint, Paint mPaint) {
        mRadius = Tools.getRadius(startPoint, finishPoint);
        Path threeanglePath = new Path();
        for (int i = 0; i < angles.length; i++) {
            xValues[i] = startPoint.x + (float) (mRadius * Math.cos(Math.toRadians(angles[i])));
            yValues[i] = startPoint.y + (float) (mRadius * Math.sin(Math.toRadians(angles[i])));
            if (i==0){
                threeanglePath.moveTo(xValues[i], yValues[i]);
            }
            else {
                threeanglePath.lineTo(xValues[i], yValues[i]);
            }
        }
        threeanglePath.close();
        canvas.drawPath(threeanglePath, mPaint);
    }

    @Override
    public void drawWithBorder(Canvas canvas, PointF startPoint, PointF finishPoint, Paint mPaint, Paint borderPaint) {
        draw(canvas, startPoint, finishPoint, mPaint);
        drawBorder(canvas, startPoint, finishPoint, mPaint, borderPaint);
    }

    @Override
    public void drawBorder(Canvas canvas, PointF startPoint, PointF finishPoint, Paint mPaint, Paint borderPaint) {
        for (int i = 0; i < angles.length - 1; i++) {
            canvas.drawLine(xValues[i], yValues[i], xValues[i + 1], yValues[i + 1], borderPaint);
        }
    }
}


Class of drawing of a star
public class DrawStar implements DrawFigure {

    private float mRadius;
    private int[] angles = new int[]{-18, -54, 270, 234, 198, 162, 126, 90, 54, 18, -18};
    private float[] xValues = new float[11];
    private float[] yValues = new float[11];

    @Override
    public void draw(Canvas canvas, PointF startPoint, PointF finishPoint, Paint mPaint) {
        mRadius = Tools.getRadius(startPoint, finishPoint);
        Path starPath = new Path();
        for (int i = 0; i < angles.length; i++) {
            if (i % 2 == 0) {
                xValues[i] = startPoint.x + (float) (mRadius * Math.cos(Math.toRadians(angles[i])));
                yValues[i] = startPoint.y + (float) (mRadius * Math.sin(Math.toRadians(angles[i])));
            } else {
                xValues[i] = startPoint.x + (float) (mRadius / 2 * Math.cos(Math.toRadians(angles[i])));
                yValues[i] = startPoint.y + (float) (mRadius / 2 * Math.sin(Math.toRadians(angles[i])));
            }
            if (i==0){
                starPath.moveTo(xValues[i], yValues[i]);
            }
            else {
                starPath.lineTo(xValues[i], yValues[i]);
            }
        }
        starPath.close();
        canvas.drawPath(starPath, mPaint);
    }

    @Override
    public void drawWithBorder(Canvas canvas, PointF startPoint, PointF finishPoint, Paint mPaint, Paint borderPaint) {
        draw(canvas, startPoint, finishPoint, mPaint);
        drawBorder(canvas, startPoint, finishPoint, mPaint, borderPaint);
    }

    @Override
    public void drawBorder(Canvas canvas, PointF startPoint, PointF finishPoint, Paint mPaint, Paint borderPaint) {
        for (int i = 0; i < angles.length - 1; i++) {
            canvas.drawLine(xValues[i], yValues[i], xValues[i + 1], yValues[i + 1], borderPaint);
        }
    }
}


Implementation of levels


After implementation of all game process, I was accepted to implementation of levels. In total 60 levels of varying complexity were created. Complexity of levels depends on quantity of figures, their sizes and an arrangement. Also complexity of levels increased at the expense of a possibility of drawing of the stopped figures.

For storage of all levels the database consisting of two tables with the list was created. As figures in the database are set by coordinates (the center of a figure and a point on its radius), on different screens they will be displayed in different scale or in general to go beyond. Therefore there was a need for a solution of the problem of drawing of level for identical scale for different screens. For a solution of this problem coordinates of figures at so-called reference permission were determined. And display in the necessary scale is performed due to determination of coefficient of the attitude of reference permission towards the current permission and multiplication of coordinates of all figures by the calculated coefficient.

Database structure
public class LevelsDatabaseHelper extends SQLiteOpenHelper {

    private static final String DB_NAME = "levels.sqlite";
    private static final int VERSION = 1;

    private static final String TABLE_LEVELS = "levels";
    private static final String COLUMN_LEVELS_STARS = "stars";
    private static final String COLUMN_LEVELS_TIME = "time";
    private static final String COLUMN_LEVELS_NUMBER = "number";

    private static final String TABLE_FIGURE = "figure";
    private static final String COLUMN_FIGURE_TYPE = "type";
    private static final String COLUMN_FIGURE_NUMBER = "number";
    private static final String COLUMN_FIGURE_CENTER_X = "center_x";
    private static final String COLUMN_FIGURE_CENTER_Y = "center_y";
    private static final String COLUMN_FIGURE_FINISH_X = "finish_x";
    private static final String COLUMN_FIGURE_FINISH_Y = "finish_y";
    private static final String COLUMN_FIGURE_COLOR = "color";
    private static final String COLUMN_FIGURE_LAYER_INDEX = "layer_index";

    private Context mContext;

    private SQLiteDatabase mDataBase;

    public LevelsDatabaseHelper(Context context) {
        super(context, DB_NAME, null, 2);
    }


    @Override
    public void onCreate(SQLiteDatabase db) {
        mDataBase = db;
        db.execSQL("create table " + TABLE_LEVELS + " (_id integer primary key autoincrement, " +
                COLUMN_LEVELS_NUMBER + " integer, " + COLUMN_LEVELS_STARS + " integer, " + " string, " + COLUMN_LEVELS_TIME + " integer)");
        db.execSQL("create table " + TABLE_FIGURE + " (_id integer primary key autoincrement, " + COLUMN_FIGURE_NUMBER +
                " integer, " + COLUMN_FIGURE_TYPE + " string, " + COLUMN_FIGURE_CENTER_X + " real, "
                + COLUMN_FIGURE_CENTER_Y + " real, " + COLUMN_FIGURE_FINISH_X + " real, " + COLUMN_FIGURE_FINISH_Y
                + " real, " + COLUMN_FIGURE_COLOR + " integer, " + COLUMN_FIGURE_LAYER_INDEX + " string)");
    }
}


Fraud


When testing levels was the opening when passing levels in which there are crossed figures is found. The opening was that when checking level the layer on which there is a figure is not considered. As a result it is possible to draw at first an upper figure, and then lower and level all the same would be considered as passed. For elimination of this opening in a class of a figure was the variable defining number of a layer on which it is located is added. Also the following conditions were considered:

  • The figure is not crossed with others, i.e. layer level to it is not important, in this case the entered variable accepts-1 value;
  • On one layer more than one figure are located, in this case the variable accepts numbers of layers through "/" on which it can be located ("2/3/4").

More visually the solution is provided on the image:
As I got the first experience of development of game on Android

When checking level at first check of the figures independent of a layer was executed, and numbers of layers of the remained figures as it should be in which they were drawn were compared later.

Custom View


Considering nontrivial design, the majority of View were necessary to implement independently, for example progress of bars in a window of result of the passable level. Progress of bars has non-standard appearance, two headings at the left (a name of parameter on which the assessment is made) and on the right (the received result on this parameter). Also one of conditions, was a possibility of support of the animated filling.

Appearance of Progress Bar
As I got the first experience of development of game on Android

ProgressBarSuccessView code
public class ProgressBarSuccessView extends View {

    float mPercentValue = 0;
    String mLeftTitleText = "title";
    int mWidth, mHeight;
    final float mLeftTitleSize = 0.3f, mProgressBarSize = 0.5f, mRightTitleSize = 0.2f;
    float mBorderMargin, mProgressMargin;
    RectF mProgressBarBorder = new RectF(), mProgressBarRectangle = new RectF();
    int mLeftTitleX, mLeftTitleY, mRightTitleX, mRightTitleY;
    Paint mTitlePaint, mProgressPaint, mBorderProgressBarPaint;

    private Rect mTitleTextBounds = new Rect();

    public ProgressBarSuccessView(Context context) {
        super(context);
        init(null);
    }

    public ProgressBarSuccessView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs);
    }

    public ProgressBarSuccessView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(attrs);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;

        mTitlePaint.getTextBounds(mLeftTitleText, 0, mLeftTitleText.length(), mTitleTextBounds);
        mLeftTitleX = (int) ((w * mLeftTitleSize - mTitleTextBounds.width()) / 2 - mTitleTextBounds.left);
        mLeftTitleY = (h - mTitleTextBounds.height()) / 2 - mTitleTextBounds.top;
        mProgressBarBorder.set(w * mLeftTitleSize, 0 + mBorderMargin, w * (mLeftTitleSize + mProgressBarSize), h - mBorderMargin);
    }

    private void init(AttributeSet attrs) {
        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ProgressBarTitle);
        mLeftTitleText = a.getString(R.styleable.ProgressBarTitle_progress_bar_title);

        mBorderMargin = getResources().getDimension(R.dimen.border_progress_bar_margin);
        mProgressMargin = getResources().getDimension(R.dimen.progress_bar_margin);

        mTitlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mTitlePaint.setColor(getResources().getColor(R.color.title_level_success_view));
        mTitlePaint.setStyle(Paint.Style.STROKE);
        mTitlePaint.setTextSize(getResources().getDimension(R.dimen.progress_bar_title_text_size));
        mTitlePaint.setTypeface(FontManager.BEBAS_REGULAR);

        mBorderProgressBarPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mBorderProgressBarPaint.setColor(getResources().getColor(R.color.title_level_success_view));
        mBorderProgressBarPaint.setStyle(Paint.Style.STROKE);
        mBorderProgressBarPaint.setStrokeWidth(2);

        mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mProgressPaint.setColor(getResources().getColor(R.color.progress_bar_level_success_view));
        mProgressPaint.setStyle(Paint.Style.FILL);

        setWillNotDraw(false);
    }

    public void setPercent(float percent) {
        mPercentValue = Math.round(percent * 100f) / 100f;
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        float left, top, right, bottom;
        left = mWidth * mLeftTitleSize + mProgressMargin - mBorderMargin;
        top = 0 + mProgressMargin;
        right = mPercentValue > 0 ? left + mWidth * mProgressBarSize * mPercentValue - mProgressMargin - mBorderMargin : left;
        bottom = mHeight - mProgressMargin;
        mProgressBarRectangle.set(left, top, right, bottom);
        canvas.drawText(mLeftTitleText, mLeftTitleX, mLeftTitleY, mTitlePaint);
        canvas.drawRect(mProgressBarBorder, mBorderProgressBarPaint);
        canvas.drawRect(mProgressBarRectangle, mProgressPaint);
        mTitlePaint.getTextBounds(String.valueOf((int) (mPercentValue * 100)), 0, String.valueOf((int) (mPercentValue * 100)).length(), mTitleTextBounds);
        mRightTitleX = (int) (mWidth - mTitleTextBounds.width() / 2 - mWidth * mRightTitleSize / 2);
        mRightTitleY = (mHeight - mTitleTextBounds.height()) / 2 - mTitleTextBounds.top;
        canvas.drawText(String.valueOf((int) (mPercentValue * 100)), mRightTitleX, mRightTitleY, mTitlePaint);
    }
}


Filling animation progress of bar
public static AnimatorSet createProgressAnimationSet(ArrayList<View> views, float[] percentValues)
{
    AnimatorSet animatorSet = new AnimatorSet();
    List<Animator> animatorList = new ArrayList<>(views.size());
    for (int i = 0; i < views.size(); i++){
        float value = percentValues[i];
        View view = views.get(i);
        animatorList.add(createProgressAnimation(view, value));
    }
    animatorSet.playTogether(animatorList);
    return animatorSet;
}

private static ObjectAnimator createProgressAnimation(View view, float progressValue){
    ObjectAnimator shapeProgressAnimator = ObjectAnimator.ofFloat(view, "percent", 0, progressValue);
    shapeProgressAnimator.setDuration((long) (1000 * progressValue));
    shapeProgressAnimator.setInterpolator(new DecelerateInterpolator());
    return shapeProgressAnimator;
}


Hints

To facilitate life to the player, hints which can be received for points were added. Points, in turn, are added for passing of levels, the quantity of points depends the stars received for level. The player can spend the received points for any of 3 available hints: to show a figure, its center or a circle of radius of a figure.

Results of work
As I got the first experience of development of game on Android
As I got the first experience of development of game on Android
As I got the first experience of development of game on Android
As I got the first experience of development of game on Android

Result


To draw conclusions about popularity of the application still early as it was laid out quite recently. For some reasons for me was to be engaged in game promotion once closely. The only thing that was made – are published links to game in social nets.
On mastering of Java I spent not enough time. And it is reading literatures better not to be spent since there are no new books, and implementations which have relevance in modern development no mobile applications any more often are given in the existing books.

It is unknown whether this game will become a hit or it will be a failure. Having made this game, I got good experience of applications programming for Android-devices which is useful to me in the future, and also at last was engaged in what I wanted long ago. Now I develop the following of the application, approaching process more thoroughly taking into account the previous errors. Besides I have a lot more ideas of mobile applications which will be shortly seen by the world.

This article is a translation of the original post at habrahabr.ru/post/274277/
If you have any questions regarding the material covered in the article above, please, contact the original author of the post.
If you have any complaints about this article or you want this article to be deleted, please, drop an email here: sysmagazine.com@gmail.com.

We believe that the knowledge, which is available at the most popular Russian IT blog habrahabr.ru, should be accessed by everyone, even though it is poorly translated.
Shared knowledge makes the world better.
Best wishes.

comments powered by Disqus