Experience in developing games for Android. From idea to implementation

Funglish Logo

In this article I want to introduce you the experience of developing an English simulator game for the Android platform, tell me what difficulties arose during the development process, and also what mistakes were made.

Background


Since 2011, I worked as an Android developer in various companies, including freelance. For all this time he participated in the development of various projects, but did not find the time to write his own application. It turns out a “shoemaker without boots” - a programmer who has an account on Google Play and experience in various projects, but has no own applications.

Idea


In May 2015, he decided to make a simulator game for memorizing words in English. There are a huge number of such games and applications on Google Play, but I wanted to do something my own, not like the others. To do this, it was necessary to add some “highlight” that would distinguish my game from others. This key difference was the time limit for which the user must have time to translate the words. The presence of a timer should add an element of tension to the game.

The basic concept of the game is similar to other games - the user translates the words, receives “stars” for this, which allow you to discover new levels of difficulty. For further development, it was necessary to carry out a deeper detail of the project.

First of all, I decided to divide the words according to the classic for games 3 difficulty levels: easy, medium and heavy, where in the light - the most simple and often used words in English, and in the difficult level - rarely used words, or rare translation options.
After determining the level of difficulty, it was necessary to understand what the game level would be. It was decided that the level will contain 5 words, the translation of which is given a limited time. Each word is given 4 translation options. To complicate the task, the words for translation will be selected either in tune with the Russian translation, or translations of words in tune with the original English word. All this is done so that the user is fully focused on the application and carefully (and at the same time quickly) selects the answers.
If the time allotted for the level has expired, or the user has made a mistake with the translation, the level must begin anew, and the user can view the correct answer.

If you answer correctly to all 5 words, a rating is set (from 1 to 3 stars, depending on the time spent). When typing a certain number of words, a new level of complexity should open up, in which there will be less time for answers.

As a result, we have 3 difficulty levels, each of which has 36 cards of 5 words each. It turns out that with the full passage of the game, the user will learn 540 English words of various difficulty levels, if the game completely passes. Pretty good to start.

Naming


The name of the application itself came to mind - a combination of the English words “Fun” and “English”, which describe the whole essence of the game - a boring study of English words.

Design


For starters, sketches of future application screens were drawn on plain paper, indicating the sequence of transitions between screens and comments for the designer. It turned out only 10 screens:
  • saver
  • Main menu
  • difficulty level selection
  • game level selection
  • the game
  • victory window
  • defeat window
  • settings
  • information
  • training

This material was transferred to a familiar designer who set to work. He developed a logo, an icon, chose the main colors, fonts, button sizes, etc. Then he presented a ready-made interface solution, drawn in a vector, and sliced. Prepared materials for release (“previews”, advertising images, etc.).

You can see the result of work on screenshots
Splash-screen

Main menu

Level selection

The game

Victory window

Defeat window

Training window


Development


Highlights

The minimum supported version of Android was API 15 (Android 4.0.3).
The application does not need additional permissions (Android Permissions), because the application has neither advertising nor statistics collection.

There are only 2 Activities in the project: SplashActivity and GameActivity, in the latter screens are changed by changing various fragments, of which there are only 7:
  • InfoFragment - Developer Information
  • MainFragment - main menu
  • PlayFragment - game
  • SelectDifficultFragment - select difficulty level
  • SelectLevelFragment - game level selection
  • SettingsFragment - settings
  • TutorialFragment - training

The database in its structure is very simple, therefore it is made in the traditional way for Android, without the use of any auxiliary libraries (greenDao or others).
Database structure
public static abstract class BaseTable {
        public static final String _ID = BaseColumns._ID;
    }
    public static class LevelResultTable extends BaseTable {
        private static final String TABLE_NAME = "LevelResult";
        public static final String DIFFICULT = "difficult";
        public static final String LEVEL_NUMBER = "level_number";
        public static final String STARS = "stars";
        private static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + _ID + " INTEGER PRIMARY KEY UNIQUE, " +
                DIFFICULT + " INTEGER NOT NULL, " +
                LEVEL_NUMBER + " INTEGER NOT NULL, " +
                STARS + " INTEGER NOT NULL, " +
                " UNIQUE (" + DIFFICULT + ", " + LEVEL_NUMBER + ")" +
                ") ;";
    }
    public static class WordTable extends BaseTable {
        private static final String TABLE_NAME = "Word";
        public static final String ORIGINAL = "original";
        public static final String DIFFICULT = "difficult";
        public static final String LEVEL = "level";
        public static final String ANSWER = "answer";
        public static final String TRANSLATE1 = "translate1";
        public static final String TRANSLATE2 = "translate2";
        public static final String TRANSLATE3 = "translate3";
        public static final String TRANSLATE4 = "translate4";
        public static final String TRANSLATE5 = "translate5";
        private static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + _ID + " INTEGER PRIMARY KEY UNIQUE, " +
                DIFFICULT + " INTEGER NOT NULL, " +
                LEVEL + " INTEGER NOT NULL, " +
                ORIGINAL + " TEXT UNIQUE NOT NULL, " +
                ANSWER + " TEXT NOT NULL, " +
                TRANSLATE1 + " TEXT NOT NULL, " +
                TRANSLATE2 + " TEXT NOT NULL, " +
                TRANSLATE3 + " TEXT NOT NULL, " +
                TRANSLATE4 + " TEXT NOT NULL, " +
                TRANSLATE5 + " TEXT NOT NULL " +
                ");";
    }

You can notice that the WordTable table contains the answer field and the fields translate1, translate2, translate3, translate4, translate5, which in total gives 6 answer options, although only 4 are displayed on the screen. The additional 2 answer options are used so that the user re-passing the level saw partially different answer options.


When developing the first version, they focused only on phones, so the line was added in AndroidManifest
to limit the list of supported devices.

Content

The popularity of any application (if you do not take into account any utilities or highly specialized professional applications) directly depends on the quality of its content.

As mentioned above, there are 3 difficulty levels in the app. Words for each level were selected manually, and complexity was assessed subjectively. To further complicate the game, when searching for words, I tried to use words that are consonant and similar in spelling or meaning, for example, if the original word is “Something,” then the translation options will be:
  • Something
  • Somewhere
  • Somewhere
  • Someone
  • For some reason
  • For some reason


Bluer

When at the end of the level a result window appears (“defeat” or “success”), the background becomes “blurred”. The blues effect is implemented in the classical way: save the Bitmap of the game Layout, process it and put the background in the result window.

Image processing was implemented using the Mario Klingemann code (mario@quasimondo.com), which was found at the link . Image processing is a very long process, therefore, to speed up processing, the image is preliminarily reduced. As often happens, losing in quality we win in time. In this case, loss of quality is not critical.

Screenshot Method Code
public class ScreenShot {
    public static Bitmap getScaledScreenshot(View v, float scaleFactor) {
        Bitmap b = Bitmap.createBitmap((int) (v.getWidth() / scaleFactor), (int) (v.getHeight() / scaleFactor), Bitmap.Config.RGB_565);
        Canvas c = new Canvas(b);
        c.scale(1.f / scaleFactor, 1.f / scaleFactor);
        v.draw(c);
        return b;
    }
}


Class code for image reduction
public class Resize {
    private static Paint sPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
    public static Bitmap scale(Bitmap bmp, float scaleFactor, boolean recycleOriginalBmp) {
        Bitmap overlay = Bitmap.createBitmap((int) (bmp.getWidth()/scaleFactor),
                (int) (bmp.getHeight()/scaleFactor), Bitmap.Config.RGB_565);
        Canvas canvas = new Canvas(overlay);
        canvas.scale(1 / scaleFactor, 1 / scaleFactor);
        Paint paint = new Paint();
        paint.setFlags(Paint.FILTER_BITMAP_FLAG);
        canvas.drawBitmap(bmp, 0, 0, paint);
        if(recycleOriginalBmp) {
            bmp.recycle();
            bmp = null;
        }
        return overlay;
    }
}


Customview

To improve performance and to improve the display of some screens, several native View and ViewGroup were written, for example, a button for answering a question.

The text of buttons with words should be as large as possible, but at the same time, the number of letters in a word may differ from word to word. Therefore, an automatic adjustment of the text size was made if it exceeds a certain width (the width of the View minus the indent).

Button Code
public class AnswerButton extends View {
    //private final static String TAG = AnswerButton.class.getSimpleName();
    public final static int STATE_NORMAL = 0;
    public final static int STATE_SUCCESS = 1;
    public final static int STATE_FAILED = 2;
    public final static int STATE_PRESSED = 3;
    private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private static Bitmap sBackgroundBitmap, sPressedBitmap, sSuccessBitmap, sFailedBitmap;
    private int mState = STATE_NORMAL;
    private int mWidth;
    private int mHeight;
    private float mTextLeftX, mTextTopY;
    private float mLeftRightPadding;
    private Rect mBackgroundRect = new Rect(), mTextBounds = new Rect();
    private float mTextSize;
    private String mText;
    public AnswerButton(Context context) {
        super(context);
        init();
    }
    public AnswerButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }
    public AnswerButton(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }
    private void init() {
        if(sBackgroundBitmap == null) {
            sBackgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.btn_answer_normal);
        }
        if(sSuccessBitmap == null) {
            sSuccessBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.btn_answer_success);
        }
        if(sFailedBitmap == null) {
            sFailedBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.btn_answer_failed);
        }
        if(sPressedBitmap == null) {
            sPressedBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.btn_answer_pressed);
        }
        setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                int action = event.getAction() & MotionEvent.ACTION_MASK;
                if(action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_MOVE) {
                    if(mState == STATE_PRESSED) {
                        return false;
                    }
                    mState = STATE_PRESSED;
                    invalidate();
                } else {
                    if(mState != STATE_PRESSED) {
                        return false;
                    }
                    mState = STATE_NORMAL;
                    invalidate();
                }
                return false;
            }
        });
        mLeftRightPadding = getResources().getDimension(R.dimen.view_answer_button_left_right_padding);
        mTextSize = getResources().getDimension(R.dimen.answer_button_text_size);
        mTextPaint.setTextSize(mTextSize);
        mTextPaint.setColor(getResources().getColor(R.color.answer_button_text_color));
        mTextPaint.setTypeface(FontManager.VDS_COMPENSATED_LIGHT);
    }
    public void setText(String text) {
        setState(STATE_NORMAL);
        mText = text;
        recalculate();
        invalidate();
    }
    public String getText() {
        return mText;
    }
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
        mBackgroundRect.left = 0;
        mBackgroundRect.top = 0;
        mBackgroundRect.right = w;
        mBackgroundRect.bottom = h;
        recalculate();
        invalidate();
    }
    public void setState(int state) {
        mState = state;
        invalidate();
    }
    private void recalculate() {
        mTextPaint.setTextSize(mTextSize);
        mTextPaint.getTextBounds(mText, 0, mText.length(), mTextBounds);
        if(mWidth != 0) {
            while (mTextBounds.width() >= mWidth - mLeftRightPadding * 2) {
                mTextPaint.setTextSize(mTextPaint.getTextSize() - 2);
                mTextPaint.getTextBounds(mText, 0, mText.length(), mTextBounds);
            }
        }
        mTextLeftX = (mWidth - mTextBounds.width()) / 2 - mTextBounds.left;
        mTextTopY = (mHeight - mTextBounds.height()) / 2 - mTextBounds.top;
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if(mState == STATE_NORMAL) {
            canvas.drawBitmap(sBackgroundBitmap, null, mBackgroundRect, mBackgroundPaint);
        } else if(mState == STATE_PRESSED) {
            canvas.drawBitmap(sPressedBitmap, null, mBackgroundRect, mBackgroundPaint);
        } else if(mState == STATE_SUCCESS) {
            canvas.drawBitmap(sSuccessBitmap, null, mBackgroundRect, mBackgroundPaint);
        } else {
            canvas.drawBitmap(sFailedBitmap, null, mBackgroundRect, mBackgroundPaint);
        }
        canvas.drawText(mText, mTextLeftX, mTextTopY, mTextPaint);
    }
}


Animation

The static splash-screen looked boring. Therefore, we decided to add animation to it, namely a rotating British flag in the letter g of the logo. It was possible to make the usual gif-animation, but we are not looking for easy ways, so everything is implemented in the code.

The animation was implemented quite simply: in the markup, the first layer is the image of the British flag, centered on the container, the next layer is the image of the word funglish, also centered. When the Activity opens, an animation starts that performs 2 turns of the British flag image. The standard animation interpolator has also been changed to AccelerateDecelerateInterpolator so that the rotation non-linearly accelerates and slows down.

The animation turned out to be quite pretty and memorable and liked all the people surveyed.

Tutorial

Most articles and books about the development and promotion of applications unanimously say that one of the most important points is training the user to use the product. For training, a Tutorial screen was created, in which on 3 pages the user is briefly told the key points of the game. This screen appears once before the first start of the game.

Screenshot
Tutorial

Publication


The publication of the application was made on August 7. I think it makes no sense to describe the process of obtaining the key and putting the application on the Play Market.

The application is available for download only in a few countries where you can meet a large number of Russian-speaking population, this, of course, Russia and Belarus, Bulgaria, Kazakhstan and Ukraine. As you already understood, the application has support for only the Russian language.

Testing


When most of the game was done, the content was found, and the game was already preparing for release, testing was conducted. For this, volunteers were found who agreed to play the alpha version of the game.

Expectations were justified - users liked the concept of guessing words for a while. According to them, a constantly updated timer keeps you in suspense, and similar word translations are confusing.

At the same time, the “Flappy Bird” effect was detected when the user does not have time to answer the words before the timer ends or at the last moment answers incorrectly - he is very angry. Emotions, although in this case they are negative, have a positive effect. The user cannot calm down until he passes the level, which means that he will learn an unfamiliar word.

Spread


For some reason, I did not have the opportunity to work closely on the distribution of the application. The only thing that was done on the first day - putting the link in the social. VKontakte network, and a story to friends with a request to download the application. The first day brought 17 downloads. Then the number of downloads dropped to 1 per day.

On August 20, a topic was created on the forum site w3bsit3-dns.com, where a description of the application and a link to the Play Market were posted. To date, more than 700 people have watched this topic. A little later, the site r-android.ru pleasantly surprised and wrote a review of the game on his site. These days, the number of downloads has risen to 30.
Download statistics
Download statistics

It should be noted that w3bsit3-dns.com has brought a large audience of users. And, most likely, the authors of the site r-android.ru learned from there exactly about the game and wrote a review on it.

Conclusion


If you look back, you can understand that much has been done wrong or at the wrong time. For example, you could better think out the concept of the application so that in the middle of design development you do not have to redo half the work of the designer.

Testing UX could and should have been done first of all on a quickly assembled concept to test the idea. But this was done just before the release.

Due to lack of time, I did not add Flurry statistics, although now it would give a lot of useful and necessary information about using the application.

I also really wanted to add AdMob advertising to see what income it could bring and whether it would bring. But again there was not enough time.
I was not ready for the release either: neither press releases, nor articles were written, nothing. If you would better approach this issue, on the day of putting the application you could distribute messages on thematic forums, make the application page in social networks, send articles to various sites. All this could in theory bring a large number of downloads, which would positively affect the position of the application in the Play Market rating.

The topic of learning English is very relevant, so now I have a choice: make a small update (update content), make a conceptual update (add new types of levels, for example, audio translation), or make an application package for learning English (tenses, irregular verbs and etc.).

They say, "the first pancake is lumpy." But I think that this does not apply to Funglish. Developing this game is an invaluable experience. The game has good ratings, people write reviews, asking to add some new features. It’s very nice to know that someone really likes your application. It is encouraging to make new games and applications.

Also popular now: