Однажды возникло желание изучить для своих скромных нужд Lua. Но просто прочесть спецификации и примеры не интересно, и давно была мысль написать свою логическую игру под Android, начались поиски подходящего фреймворка для комфортной работы.

После недолгого просмотра гугла выделились 2 основных кандидата: Corona SDK и LÖVE. Corona SDK обладает, с моей точки зрения, несомненными преимуществами: поддержка, хорошая документация, легкая интеграция разнообразных магазинов/реклам/социальных сетей (нужное подчеркнуть). Несмотря на это, сам SDK платный, нет версии под Linux. Версия Starter обладает рядом ограничений.

После Corona SDK я взялся за внимательное рассмотрение LÖVE. Первым делом подкупила надпись на стартовой странице:
It's free, open-source, and works on Windows, Mac OS X and Linux.

LÖVE is licensed under the liberal zlib/libpng license. That means you can use it freely for any purpose — including commercial ones.


Есть довольно хорошая документация даже на русском языке. Фреймфорк легок в установке. Как написать игру, используя LÖVE пользователь yegorf1 писал здесь и продолжил здесь. Так, неспешно изучая возможности фреймворка, я написал прототип своего приложения. И здесь началось самое интересное.

Вопрос №1

Как запустить приложение под андроид, чтобы наконец проверить свое «чудо» на любимом смартфоне?
Поиск ответа на этот вопрос навел меня на проект love-android-sdl2, разработчиком которого является Martin Felis. Как пишет автор, проект является портом LÖVE под андроид. По сути это порт Lua на SDL 2.0 под андроид.
  1. Пакуем свое приложение в zip-архив и переименовываем его в game.love
  2. Коприуем архив в папку assets проекта.
  3. Собираем .apk-файл согласно инструкциям для Linux, Windows, Mac OS X. Процесс подробно описан у автора, если кратко его описать, то это несколько шагов:
    1. Установка Android SDK, Android NDK
    2. Утановка Android SDK Platoform-tools, Android SDK Built-tools, Android 4.4.2 (API 19) and the Android Support Library через SDK Manager
    3. Запуск сборки в директории с проектом. Сначала ndk-build, потом ant debug.

На выходе мы имеем .apk-файл, который же можно залить в телефон и наконец потестировать! Как собрать релиз и подписать его, можно подробно почитать здесь.
Итак, ответ на первый вопрос получен. Конечно, сделать быстрое или очень экономное приложение вряд ли получится, но для быстрого протот��пирования на Lua или легковесной игры самое то. Почти пустой .apk-файл сразу весит около 5 мб, а основное приложение в ассетах может повергнуть эстетов Java или С++ в легкий шок, хотя насчет последних я не уверен. Можно попробовать убрать ненужные библиотеки, но это уже тема других экспериментов.

Когда приложение стало приобретать все более законченные черты, пришла пора задуматься о возможной монетизации. Конечно, есть вариант совсем для ленивых. Выставить приложение за фиксированную цену и ждать у моря на Мальдивах легкого бриза. Но я себе такого пока позволить не могу, а учитывая таргетирование на андроид, я подумал, что надо так или иначе встроить рекламу. Интеграцией с рекламным сервисом AdMob я никогда не занимался. Учитывая специфику получившегося «бутерброда» из библиотек, мне стало еще интереснее.

Вопрос №2

Как интегрировать AdMob в свое приложение?
Поскольку официальная справка (en и рус) предлагает использовать в этом процесс Eclipse, стало очевидно, что:
  1. Необходимо собрать приложение в Eclipse. Эта часть в основном полезна для новичков. Для этого скачиваем чистый проект love-android-sdl2.
    git clone git@bitbucket.org:MartinFelis/love-android-sdl2.git
    
  2. Открываем Eclipse (я использую из ADT Bundle). Создаем новый workspace и добавляем наш проект File → New → Project… → Android → Android Project from Existing Code.

    Далее указываем путь до папки с проектом. Должно определиться 2 проекта: love-android-sdl2 и SDLActivity. Жмем Finish.
  3. Заходим в свойства проекта SDLActivity. Левой кнопкой мыши жмем на проект, выбираем Properties. Далее Android и ставим галочку Is Library. Жмем OK.

  4. Заходим в свойства проекта love-android-sdl2. Java Build Path → вкладка Libraries → Add JARs… → находим SDLActivity/bin/sdlactivity.jar.

  5. Затем идем во вкладку Builders и снимаем галочку напротив [Löve] Generate Internal Scripts.
  6. Нажимаем кнопку Run As… → Android Application .
  7. Ждем пока соберется проект, и Eclipse попросит нас подключить устройство или запустить love-android-sdl2.apk на эмуляторе.


Первая серия экспериментов с использованием официальной справки по интеграции AdMob не привела к успеху. Я обратился за помощью к разработчику проекта, но он ответил, что еще не пробовал добавлять AdMob в свой проект, и что ему бы было тоже интересно, как это сделать.
  1. Следуем инструкциям стандартной справки начиная с раздела 1. Внедрение библиотеки сервисов Google Play в проект. Единственное оговорюсь, что проект google-play-services_lib необходимо добавить сначала из sdk/extras/google/google_play_services/libproject/google-play-services_lib.
  2. Дальше можно сделать Project → Clean… → Clean all project → OK и перезапустить сборку.
  3. Открываем файл love-android-sdl2/src/org.love2d.android/GameActivity.java
  4. Дописываем необходимый код.
    Листинг GameActivity.java
    package org.love2d.android;
    
    import com.google.android.gms.ads.*; // импортируем библиотеки для рекламы
    
    import org.libsdl.app.SDLActivity;
    
    import java.io.BufferedInputStream;
    import java.io.BufferedOutputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.HttpURLConnection;
    import java.net.URL;
    
    import android.app.DownloadManager;
    import android.app.ProgressDialog;
    import android.content.Context;
    import android.content.DialogInterface;
    import android.content.Intent;
    import android.net.Uri;
    import android.os.AsyncTask;
    import android.os.Build;
    import android.os.Bundle;
    import android.os.Environment;
    import android.os.Handler;
    import android.os.PowerManager;
    import android.os.ResultReceiver;
    import android.util.Log;
    import android.util.DisplayMetrics;
    import android.widget.RelativeLayout;
    import android.widget.Toast;
    
    public class GameActivity extends SDLActivity {
        private static DisplayMetrics metrics = new DisplayMetrics();
        private static String gamePath = "";
        private static Context context;
        private AdView adView;
      
        @Override
        protected void onCreate(Bundle savedInstanceState) {
          Log.d("GameActivity", "started");
    
          context = this.getApplicationContext();
    
          Uri game = this.getIntent().getData();
          if (game != null) {
            if (game.getScheme().equals ("file")) {
              gamePath = game.getPath();
            } else {
              copyGameToCache (game);
            }
            Log.d("GameActivity", "Selected the file: " + getGamePath());
          }
    
          super.onCreate(savedInstanceState);
          getWindowManager().getDefaultDisplay().getMetrics(metrics);
          
          // Создаем экземпляр AdView
          adView = new AdView(mSingleton);
          adView.setAdSize(AdSize.BANNER); // задаем размер
          adView.setAdUnitId("ca-app-pub-1209995634500922/8885931497"); // прописывам id приложения
       
          // Задаем параметры отображения
          RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
    		   RelativeLayout.LayoutParams.WRAP_CONTENT,
    		   RelativeLayout.LayoutParams.WRAP_CONTENT);
       
          params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
          params.addRule(RelativeLayout.CENTER_HORIZONTAL);
          
          AdRequest adRequest = new AdRequest.Builder().build();
       	  adView.loadAd(adRequest);
          mLayout.addView(adView, params);
        }
    
        public static String getGamePath() {
          Log.d ("GameActivity", "called getGamePath(), game path = " + gamePath);
            return gamePath;
        }
    
        public static DisplayMetrics getMetrics() {
            return metrics;
        }
    
        public static void openURL (String url) {
          Log.d ("GameActivity", "opening url = " + url);
          Intent i = new Intent(Intent.ACTION_VIEW);
          i.setData(Uri.parse(url));
          i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
          context.startActivity(i);
        }
      
        void copyGameToCache (Uri sourceuri)
        {
          String destinationFilename = this.getCacheDir().getPath()+"/downloaded.love";
          gamePath = destinationFilename;
    
          BufferedOutputStream bos = null;
          try {
            bos = new BufferedOutputStream(new FileOutputStream(destinationFilename, false));
          } catch (IOException e) {
            Log.d ("GameActivity", "Could not open destination file:" + e.getMessage());
          }
    
          int chunk_read = 0;
          int bytes_written = 0;
    
          BufferedInputStream bis = null;
          if (sourceuri.getScheme().equals("content")) {
            try {
              bis = new BufferedInputStream(getContentResolver().openInputStream(sourceuri));
            } catch (IOException e) {
              Log.d ("GameActivity", "Could not open game file:" + e.getMessage());
            }
          } else {
            Log.d ("GameActivity", "Unsupported scheme: " + sourceuri.getScheme());
          }
    
          if (bis != null) {
            // actual copying
            try {
              byte[] buf = new byte[1024];
              chunk_read = bis.read(buf);
              do {
                bos.write(buf, 0, chunk_read);
                bytes_written += chunk_read;
                chunk_read = bis.read(buf);        
              } while(chunk_read != -1);
            } catch (IOException e) {
              Log.d ("GameActivity", "Copying failed:" + e.getMessage());
            } 
          }
    
          // close streams
          try {
            if (bis != null) bis.close();
            if (bos != null) bos.close();
          } catch (IOException e) {
            Log.d ("GameActivity", "Copying failed: " + e.getMessage());
          }
    
          Log.d("GameActivity", "Copied " + bytes_written + " bytes");
        }
    }
    


  5. Пересобираем наше приложение, запускаем, немного ждем и видим сверху рекламу!



Ответ на второй и больше всего волнующий меня вопрос был найден. Таким способом мне удалось «подружить» LÖVE, Android и AdMob. Моя игра почти закончена, я надеюсь на скорый релиз. Happy end.