LÖVE + Android + AdMob = дружба

Однажды возникло желание изучить для своих скромных нужд 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.
  • +10
  • 10.1k
  • 5
Share post

Comments 5

    0
    Есть идея и даже зачаточный код для портирования игр на Corona SDK под Love, чтобы можно было выпускать игры на десктопах. Но это задача оказалась очень сложной в реализации. Возможно грядущая поддержка HTML5 сможет помочь этому делу.

    Грубо говоря Love более низкоуровневый и в плане графики позволяет делать больше, чем Corona.
    Однако бесплатная версия Starter умеет очень многое, чем именно она не подошла? Разноцветные линии она умеет рисовать, и рекламу выводить тоже.
      0
      Разноцветные линии для примера, чтобы не просто черное окно показать. По большей части смущает пустой пункт Offline builds.
        0
        Все почему-то боятся этого пункта, расскажите подробнее чем вас это смущает?
        По мне так online build это очень удобно, особенно новичкам — не надо устанавливать все компиляторы, SDK и прочее. На сервера исходные коды не утекают, только скомпелированный байт код, все картинки и другие assets тоже не покидают вашего компьютера.
          +3
          В общем случае мне как раз не нравится отсутствие локального окружения. Сторонний сервис, есть сторонний сервис. Для новичка может быть трудно разобраться, зато это можно сделать один раз и не зависеть ни от кого. В последствие, мы получаем доступ не только к функциям фреймворка, но и к функциям нативного апи, по тем же принципам, что и в реализации порта love. Тут каждый волен выбрать то, что ему нужно.
      0
      Отличная статья (:

      Only users with full accounts can post comments. Log in, please.