Всем доброго времени суток!


Возникла потребность встроить рекламу в свою игру на Love2D. Решил показывать банер после выигрыша игрока и тут появились сложности: банер вызывается Java кодом приложения, а выигрыш определяется в Lua коде. Связывает их код на Си, туториалов, как добавлять свои методы в движок не было, и пришлось копаться в коде самостоятельно. Репозиторий Love2D для Android лежит вот тут.


С чего начать?


Начать я решил с изучения метода love.system.vibrate() – метод, который появляется при использовании Love2D на андроид, а значит его где-то добавляют также, как я хочу добавить рекламу.
Если открыть класс GameActivity, то там можно найти метод vibrate, который мы ищем, и вот тут стоит объяснить, как происходит вызов Java кода из Lua.


Когда приложение только запускается, для каждого модуля Love2D создается экземпляр класса модуля и экземляр класса связки (объект, который привязывает Сишный объект к переменной Lua). У объекта связки для каждого метода, который нужно интегрировать в Lua есть свой маленький метод и список, в котором указано какой метод в Lua сопостовляется сишной связке. Выглядит это так:


// Список связок
static const luaL_Reg functions[] =
{
    { "getOS", w_getOS },
    { "getProcessorCount", w_getProcessorCount },
    { "setClipboardText", w_setClipboardText },
    { "getClipboardText", w_getClipboardText },
    { "getPowerInfo", w_getPowerInfo },
    { "openURL", w_openURL },
    { "vibrate", w_vibrate },
    { 0, 0 }
};

extern "C" int luaopen_love_system(lua_State *L)
{
    // Эземпляр модуля
    System *instance = instance();
    if (instance == nullptr)
    {
        instance = new love::system::sdl::System();
    }
    else
        instance->retain();

    // Итоговая связка
    WrappedModule w;
    w.module = instance;
    w.name = "system";
    w.type = MODULE_ID;
    w.functions = functions;
    w.types = nullptr;

    return luax_register_module(L, w);
}

Так же замечу, что дополнительные методы, которые добавлены для андроида, хранятся в отдельном классе, который лежит в папке ./jni/love/src/common/ и называется android.


Добавляем свой метод


Сначала создадим статичный метод в классе GameActivity:


private static GameActivity instance;

@Override
protected void onCreate(Bundle savedInstanceState) {
    instance = this;
    // ...
}

// ...

// Важно, чтобы метод был статичным, потому что доступ к
// экземпляру класса получить будет сложнее
public static void showAd() {
    Toast.makeText(instance, "Ad example", Toast.LENGTH_LONG).show();
}

Теперь создадим метод в модуле love.system. Я выбрал этот модуль, потому что внедряю рекламу, но использовать именно system не обязательно. Можно даже создать свой модуль, в зависимости от того, что вам нужно.


Сначала нужно написать главную часть метода в классе android. Объявляем:


// ./jni/love/src/common/android.h

bool openURL(const std::string &url);

void showAd();

void vibrate(double seconds);

И создаем:


// ./jni/love/src/common/android.cpp

void showAd()
{
    // Получаем среду исполнения Java
    JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();
    // И класс, в котором мы создали статичный метод
    jclass activity = env->FindClass("org/love2d/android/GameActivity");

    // Теперь сам метод
    jmethodID show_ad_method = env->GetStaticMethodID(activity, "showAd", "()V");
    // И вызываем его
    env->CallStaticVoidMethod(activity, show_ad_method);

    env->DeleteLocalRef(activity);
}

При получении идентификатора метода третьем параметром мы указываем, что возвращает метод и какие параметры принимаем. Если ваш метод будет принимать аргументы, то в скобках их нужно перечислить. Если ваш метод возвращает значение, то также нужно будет изменить V на соответствующую букву. Так, чтобы описать вот такой метод:


bool isGreater(double a, double b) { return a > b; }

Будет использоваться следующая строка: (DD)Z. Подробнее можно прочитать вот тут.


Добавляем метод showAd в сам модуль:


// ./jni/love/src/modules/system/System.h

/**
 * Shows ad
 */
virtual void showAd() const;

Ну и сам код:


// ./jni/love/src/modules/system/System.cpp
void System::showAd() const {
#ifdef LOVE_ANDROID
    love::android::showAd();
#endif
}

Немного клея


Теперь осталось все это соеденить с помощью связки. Добавляем соответсвующий метод в класс связки:


int w_showAd(lua_State *L)
{
    instance()->showAd();
    return 0;
}

// Ну и список связок конечно же
static const luaL_Reg functions[] =
{
    { "getOS", w_getOS },
    { "getProcessorCount", w_getProcessorCount },
    { "setClipboardText", w_setClipboardText },
    { "getClipboardText", w_getClipboardText },
    { "getPowerInfo", w_getPowerInfo },
    { "openURL", w_openURL },
    { "vibrate", w_vibrate },
    { "showAd", w_showAd },
    { 0, 0 }
};

Собираем все это по туториалу с самого начала.


Добавляем код в луа (love.system.showAd()) и проверяем:


Вуаля.


Заключение


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


Спасибо за прочтение (:

Поделиться с друзьями
-->

Комментарии (11)


  1. Radegast
    22.05.2016 14:38

    А какая мотивация была для выбора этого движка?


    1. yegorf1
      22.05.2016 15:00
      +1

      Он простой, легкий и удобный, но в тоже время мощный. Ко всему прочему я больше люблю программировать, чем в редакторе расcтавлять спрайты, поэтому это тоже плюс, но уже лично мой. Если нужно написать что-то простое, то этот движок подходит идеально.


      Если вам интересно, у меня есть пара статей-туториалов, можете посмотреть в моих публикациях.


  1. MaxAkaAltmer
    22.05.2016 17:35

    Буквально на днях в своем приложении на маркете тоже сделал вызовы Java из C++, но по другой причине (не хотел лишние библиотеки полключать для работы со шрифтами и картинками), проверил все на своих устройствах, на куче виртуальных андроидов всех версий.
    Но в итоге сложилось все плохо — этот механизм нестабильный, у приложения рухнул рейтинг поскольку нашлось немало пользователей у которых все это дело падало, причем на ровном месте судя по краш-репортам.

    Имхо — плохая идея, на своей шкуре убедился =)


    1. yegorf1
      22.05.2016 19:03

      УМВР (:
      Видимо, косяк был у вас, потому что весь андроид написан на подобных схемах.


      1. MaxAkaAltmer
        22.05.2016 23:03
        +2

        Может конечно и косяк, но уж точно не у меня, ибо в активити черным по белому метод прописан, на 99% устройств прекрасно находится и вызывается, а 1% устройств метод не находит )))

        Андроид написан на Си, как и любой другой линукс. А Java там лишь для снижения порога вхождения программистов приложений. И надо отметить, что такими схемами при написании приложений пользуются относительно редко. Поэтому они не достаточно надежны.

        Все что связанно с Java+C в андроиде мягко говоря недоработано, вот на вскидку:

        — Новичок делает проект Hello, World! С использованием JNI. Новичок долбится головой об стену не понимая почему проект не компилируется. А не компилируется он потому, что одного файла С/С++ почему-то мало, надо хотя бы 2! О.о
        — Упаси бог вас использовать в имени своего проекта тире или подчерки — до JNI вы не достучитесь и никто вам не скажет почему.
        — Даже не вздумайте использовать код с long double — получите падение без предупреждения, это конечно логично, ведь NEON такого типа не знает, но зачем тогда код компилируется без ошибок?! Это для мня загадка.


        1. yegorf1
          22.05.2016 23:23

          С одной стороны смешивать два серьезных самостоятельных языка вообще плохая идея, с другой стороны первый написан на втором и почему подобные вещи за столько лет не алы учили — загадка. Мир не идеален и с этим придется жить в нем (:


          1. MaxAkaAltmer
            22.05.2016 23:53

            Я вовсе не против таких вызовов, ведь 99% это довольно неплохо — когда проект стартует — можно просто забить на 1% — они просто не купят и пройдут мимо. Но если начинать такое делать на популярном проекте и вдруг у кого-то перестает работать купленное приложение — это я скажу совсем нехорошо.


  1. lxsmkv
    22.05.2016 19:03
    -1

    T.e. вы расширили API фреймворка одной функцией… ну… круто, авось пригодится.
    Стоило бы переести на английский виде how to и предложить автору фреймворка опубликовать в документации.
    Странно, что фреймворк не использует LuaJ.
    А love.system.openURL она на андроиде не работает? (А то реклама то сама по себе грубо говоря прямоугольник со ссылкой.)


    1. yegorf1
      22.05.2016 19:07

      Фреймворк написан на Си и это не плохо и не хорошо. Это выбор автора и рядовому пользователю это не важно. Ко всему прочему работает прямо из коробкеэи без установок Java машины.


      Не проверял, но я вызываю рекламу Java методом, который предоставляет мне SDK.


    1. playermet
      23.05.2016 23:02
      +1

      А почему он должен использовать LuaJ? Сам он написан на С, поддержка Android (и надобность использовать Java соответственно) появилась относительно недавно, и сейчас он использует luajit которому LuaJ не ровня.


      1. lxsmkv
        23.05.2016 23:27

        я так понял что смысл задачи в использовании ява функции, вот и подумал что луаджей, она же рефлексией все как бы может достать.
        Может задачу не доконца понял. Я смотрю с прагматичной стороны. У нас вот на embedded системе для тестирования самописный интерпретатор синтаксиса питона используют (а приложение на яве) так я тоже сперва спросил, а что не jython. Сказали что много памяти жрет. Ну мне все сразу и ясно стало. Я же никого задеть не хочу, просто пытаюсь усвоить чужой опыт.