- Простейшая 3D игра на libGDX под Android в 200 строк кода
- Дополнение: модель истребителя вместо пирамидки
- Build and run your app
- Change the run/debug configuration
- Change the build variant
- Build your project
- Monitor the build process
- Apply Changes
- Requirements
- Use Apply Changes
- Enable Run fallback for Apply Changes
- Platform-dependent changes
- Limitations of Apply Changes
- Code changes that require app restart
- Libraries and plugins
- Code that directly references content in an installed APK
Простейшая 3D игра на libGDX под Android в 200 строк кода
Я преподаю в IT школе Samsung программирование под Android для школьников. Программа обучения охватывает множество разнообразных тем. В числе прочих предусмотрен один урок, знакомящий учеников с основами 3D-графики под Android. Стандартный учебный материал этого урока показался мне очень бесполезным по нескольким причинам:
- Используется голый OpenGL, а поскольку на практике в программировании игр чаще всего используются готовые движки, то это мало полезно для школьников в контексте их собственных проектов. Кто-то может возразить, что увидеть в деле чистый OpenGL полезно для понимания основ, но здесь вступает в дело 2-й недостаток.
- Урок очень непонятный. У типичного школьника, пусть и разбирающегося в программировании, нет достаточной базы, чтобы понимать многое из того, что описано в уроке (например матрицы многие пройдут только уже в ВУЗе).
- В конце урока мы приходим к результату — отрисовка 3-х треугольников средствами OpenGL. Это настолько далеко от реальной 3D-игры, что легко может отбить интерес у школьника.
Поэтому я решил подготовить свой урок, описывающий основы использования libGDX под Android, а раз я все равно готовлю этот материал, заодно разместить его здесь — на хабре. В этом уроке мы сделаем наипростейшую 3D игру под Android, скриншот которой вы можете видеть во вступлении к статье. Итак, интересующиеся, добро пожаловать под кат.
Почему именно libGDX? Во-первых код должен быть на Java, потому-что мы обучаем студентов именно Java-программированию. Это сужает выбор. Во-вторых libGDX оказался очень прост в освоении. В моих условиях это большое достоинство, перевешивающее прочие недостатки.
Идея игры очень проста: вы второй пилот истребителя, отвечающий за системы вооружения. Вам нужно успеть вовремя выстрелить лазерным оружием когда двигатель вражеского корабля находится в перекрестье прицела, пока ваш первый пилот старается не дать сбросить себя с хвоста. То есть по сути геймплей описывается фразой «кликни вовремя».
В ходе этого урока нам понадобится только Android Studio 1.5 (версия может отличаться, здесь я привел ту, с которой у меня все точно получилось).
Для начала нам нужно скачать мастер создания проекта от libGDX, который значительно облегчает задачу первоначальной настройки проекта (скачать можно по ссылке в инструкции на wiki проекта libGDX). Вот, что за настройки я туда вбил:
Импортируем получившийся проект в Android Studio и начинаем собственно работу с кодом. Основной код игры находится в файле MyGdxGame.java (если вы назвали этот класс так же, как и я). Удалим шаблонный код и начнем писать свое:
Здесь мы создаем новую камеру с углом обзора в 67 градусов (что является довольно часто используемым значением) и задаем соотношением сторон ширину и высоту экрана. Затем мы устанавливаем позицию камеры в точку (150, -9, 0) и указываем, что она будет смотреть на центр координат (поскольку именно там мы планируем разместить пирамидку, вокруг которой будет строиться геймплей). И наконец мы вызываем служебный метод update(), чтобы все наши изменения применились к камере.
Теперь можно изобразить что-то, на что мы будем смотреть. Мы, конечно, могли бы использовать какую-то 3D модель, но сейчас, в целях упрощения урока, мы будем рисовать только простую пирамиду:
Тут мы создаем экземпляр ModelBuilder, который предназначен для создания моделей в коде. Затем мы создаем простую модель конуса с размерами 20x120x20 и количество граней 3 (что в итоге и дает пирамиду) и задаем ему материал зеленого цвета. Когда мы создаем модель, необходимо задать как минимум Usage.Position. Usage.Normal добавляет к модели нормали, так что освещение сможет работать правильно.
Модель содержит все необходимое для отрисовки и управления собственными ресурсами. Однако она не содержит информации, где именно отрисовываться. Так что нам нужно создать ModelInstance. Он содержит данные о расположении, параметрах вращения и масштаба для отрисовки модели. По умолчанию рисуется в (0, 0, 0) так что мы просто создаем ModelInstance, который отрисуется в (0, 0, 0). Но кроме того, мы еще вызовом метода transform.setToRotation() поворачиваем нашу пирамидку на 120 градусов по оси Z (так ее лучше видно с позиции камеры).
Модель необходимо освобождать после использования, так что мы добавляем немного кода в наш метод Dispose().
Теперь давайте отрисуем наш экземпляр модели:
Здесь мы добавляем в метод create ModelBatch, который отвечает за отрисовку и инициализацию модели. В методе render мы очищаем экран, вызываем modelBatch.begin(cam), рисуем наш ModelInstance и затем вызываем modelBatch.end() чтобы завершить процесс отрисовки. Наконец, нам нужно освободить modelBatch чтобы удостовериться, что все ресурсы (например шейдеры, которые он использует) корректно освобождены.
Выглядит довольно неплохо, но немного освещения могло бы улучшить ситуацию, так что давайте его добавим:
Здесь мы добавляем экземпляр Environment. Мы создаем его и задаем окружающий (рассеянный) свет (0.4, 0.4, 0.4) (обратите внимание, что значение прозрачности игнорируется). Затем мы создаем DirectionalLight (направленный свет) с цветом (0.8, 0.8, 0.8) и направлением (10, 10, 20). Я предполагаю, что вы уже знакомы с источниками освещения в общем хотя здесь все итак довольно очевидно. Наконец, во время отрисовки мы передаем созданный environment (окружение) в обработчик моделей.
Поскольку мы все-таки пишем игру, не мешало бы добавить немного динамики в статичную картинку. Сделаем, чтобы камера немножко сдвигалась при каждой прорисовке. Здесь уместно сказать о жизненном цикле libGDX-приложения. При старте вызывается метод create(), в котором уместно размещать всю инициализацию. Затем N раз в секунду вызывается метод render(), где N — ваш FPS. Этот метод отрисовывает текущий кадр. Поэтому чтобы добавить в приложение динамичности, нам просто нужно в render() как-то менять параметры наших игровых объектов.
Здесь мы создаем иллюзию того, что наша пирамидка двигается, хотя на самом деле движется камера, посредством которой мы смотрим на нее. В начале игры в методе create() случайным образом выбирается значение приращения Vpos[i] для каждой координаты (скорость). На каждой перерисовке сцены в методе render() к координатам прибавляется значение шага изменения. Если мы вышли за установленные границы изменения координат, то возвращаем координаты в эти границы и генерируем новые скорости, чтобы камера начала двигаться в другую сторону. cam.position.set() собственно и устанавливает камеру в новые координаты, рассчитанные по описанному выше закону, а cam.update() завершает процесс изменения параметров камеры.
Можно отметить, что на разных устройствах скорость движения пирамидки будет разной из-за разницы в FPS и, соответственно, количестве вызовов render() в секунду. По хорошему здесь бы добавить зависимость приращения координат от времени между кадрами, тогда скорости были бы везде одинаковыми. Но мы этим заниматься не будем, чтобы не усложнять проект.
А теперь сделаем игровой HUD:
Обратите внимание, что параметры вращения и сдвига (метод translate(x, y, z))пирамидки изменены так, чтобы она находилась в центре экрана и направлена туда же, куда смотрит наша камера. То есть на старте игры мы находимся с противником на одном курсе и смотрим ему прямо в двигатели.
Здесь мы создаем 2 текстовых метки. Метка label предназначена для отображения внутриигровой информации (FPS, время игры и статистика попаданий). Метка crosshair рисуется красным цветом и содержит в себе только один символ — «+». Это показывает игроку середину экрана — его прицел. Для каждой из них в конструкторе new Label( , new Label.LabelStyle(font, )) задается стиль, включающий в себя шрифт и цвет надписи. Метки передаются в объект Stage методом addActor(), и, соответственно, отрисовываются автоматически когда отрисовывается Stage.
Кроме того для метки crosshair методом setPosition() задается позиция — середина экрана. Здесь мы используем размеры экрана (Gdx.graphics.getWidth(), . getHeight()) чтобы рассчитать, куда нужно поместить наш плюсик, чтобы он оказался в середине. Еще имеет место небольшой грязный хак: setPosition() задает координаты левого нижнего угла надписи. Чтобы в центре экрана оказался именно центр плюсика, я вычитаю из полученного значения эмпирически (то есть наугад) полученные константы 3 и 9. Не используйте такой подход в полноценных играх. Просто плюсик в середине экрана — это несерьезно. Если вам нужно перекрестье прицела, вы можете использовать спрайты.
При каждой отрисовке мы создаем текст через StringBuilder, куда кладем все то, что хотим вывести внизу экрана: FPS, время в игре, количество попаданий и рейтинг. Метод setText() позволяет задать метке текст, что мы и делаем в render() раз за разом.
Правда стрелять то мы пока не можем. Самое время исправить этот недостаток.
Обратите внимание, что описание класса MyGdxGame теперь изменилось. Здесь мы наследуемся от InputAdapter и реализуем интерфейс ApplicationListener. Такая структура позволит нам сохранить наш код в неизменном виде, но дополнить его возможностью обработки пользовательского ввода. В методе create() добавляется строка, регистрирующая наш класс как обработчик ввода. Методы pause() и resume() мы просто обязаны реализовать, поскольку у InputAdapter он абстрактные.
Вся математика расчета попадания находится в render(). Мы проверяем, находятся ли координаты камеры в той зоне, чтобы наш противник был в центре экрана на одном с нами курсе (находятся ли координаты Y и Z в пределах start ± zone). Если мы на одном курсе, значит стрелять можно: устанавливаем isUnder = true и делаем прицел более яркого красного цвета. Опять-таки эта простота определения попадания — это хитрость, основанная на тупости простоте, некоторой условности игрового процесса. Вообще же в libGDX есть средства для определения, какие 3D-модели попали в область касания в общем случае.
Методы обработки касаний называются touchDown (палец коснулся экрана) и touchUp (палец убрали с экрана). Эти методы принимают координаты касания, но мы их здесь использовать не будем. На самом деле нам достаточно определить, находится ли камера сейчас в той позиции, чтобы смотреть на пирамидку прямо. Если это так (пользователь нажал вовремя), то в touchDown мы начинаем подсчет времени, сколько лазер жарил враждебную пирамидку. Если нет, то сокращаем очки пользователя делением надвое (штраф за промах). Когда пользователь отпускает палец, проверяем не отпустил ли он его слишком поздно. Если отпустил поздно, то штрафуем, если вовремя (лазер еще жарил цель), то добавляем очки.
Дополнение: модель истребителя вместо пирамидки
Вобщем-то игра готова, но хочется чтобы выглядела она как-то поприличнее, а пирамидка — это довольно скучно. Так что в качестве опционального дополнения к уроку можно еще реализовать нормальную 3D-модель летательного аппарата вместо пирамидки. Возьмем эту модель и попробуем вставить ее в нашу игру.
Модель поставляется в 4-х форматах разных 3D-редакторов. Однако libGDX использует свой бинарный формат моделей, в который их нужно конвертировать, чтобы использовать в игре. Для этого предусмотрена специальная утилита — fbx-conv. Скачиваем собранные бинарники и распаковываем в какую-нибудь папку. Там есть версия под Windows, Linux и MacOS. Windows-версия запустится без лишних телодвижений, а для Linux и MacOS нужно предварительно выполнить команду
Тем самым мы указываем утилите, где искать свою разделяемую библиотеку libfbxsdk.so, которую она требует для работы. Запускаем утилиту:
Конечно, вам надо указать свой путь до модели и использовать бинарник для вашей ОС. В итоге у вас получится файл space_frigate_6.g3db , который надо положить в папку проект android/assets (папка с ресурсами приложения для платформы Android).
Ну а теперь подключим это в игре:
Здесь мы создаем экземпляр класса AssetManager, который отвечает за загрузку игровых ресурсов и указываем ему загрузить нашу модельку. На каждой отрисовке мы проверяем, не загрузил ли еще AssetManager модель (метод update(), возвращающий boolean). Если загрузил, то пихаем в instance вместо набившей оскомину пирамидки наш няшный самолетик и устанавливаем loading = false, чтобы это создание inctance не повторялось на каждом кадре, а то ведь assets.update() будет возвращать true дальше на протяжении всего времени работы приложения.
При запуске получим исключение java.io.FileNotFoundException: SPACE_FR.PNG . Значит файл модели не включает текстуры, их нужно засовывать отдельно. Берем из 4-х представленных понравившуюся текстуру, переименовываем в SPACE_FR.PNG , кладем в assets , запускаем. В итоге получаем то, что во вступительной картинке. Ну а на закуску — гифка с игровым процессом:
Итог: мы написали очень простую, но почти полноценную с точки зрения использованных средств (освещение, движение, HUD, касания, модели) игру, уложившись всего в 200 строк кода. Конечно, тут есть многое, что можно улучшить: нормальный прицел, skybox (небо или космос вокруг), звуки выстрелов и полета, игровое меню, нормальное определение попадания и т. п. Тем не менее игра уже содержит самую базу игрового процесса и наглядно показывает самые основные моменты разработки игр на libGDX. Надеюсь этот урок будет способствовать появлению многих новых интересных игр на Android как от моих студентов, так и от аудитории хабра.
Источник
Build and run your app
Android Studio sets up new projects to deploy to the Android Emulator or a connected device with just a few clicks. Once your app is installed, you can use Apply Changes to deploy certain code and resource changes without building a new APK.
To build and run your app, follow these steps:
- In the toolbar, select your app from the run configurations drop-down menu.
From the target device drop-down menu, select the device that you want to run your app on.
If you don’t have any devices configured, then you need to either connect a device via USB or create an AVD to use the Android Emulator.
Click Run .
Change the run/debug configuration
When you run your app for the first time, Android Studio uses a default run configuration. The run configuration specifies whether to deploy your app from an APK or an Android App Bundle, the module to run, package to deploy, activity to start, target device, emulator settings, logcat options, and more.
The default run/debug configuration builds an APK, launches the default project activity, and uses the Select Deployment Target dialog for target device selection. If the default settings don’t suit your project or module, you can customize the run/debug configuration, or even create a new one, at the project, default, and module levels. To edit a run/debug configuration, select Run > Edit Configurations. For more information, see Create and Edit Run/Debug Configurations.
Change the build variant
By default, Android Studio builds the debug version of your app, which is intended for use only during development, when you click Run.
To change the build variant Android Studio uses, select Build > Select Build Variant in the menu bar.
For projects without native/C++ code, the Build Variants panel has two columns: Module and Active Build Variant. The Active Build Variant value for the module determines which build variant the IDE deploys to your connected device and is visible in the editor.
Figure 1. The Build Variants panel has two columns for projects that do not have native/C++ code
To switch between variants, click the Active Build Variant cell for a module and choose the desired variant from the list field.
For projects with native/C++ code, the Build Variants panel has three columns: Module, Active Build Variant, and Active ABI. The Active Build Variant value for the module determines the build variant that the IDE deploys to your device and is visible in the editor. For native modules, the Active ABI value determines the ABI that the editor uses, but does not impact what is deployed.
Figure 2. The Build Variants panel adds the Active ABI column for projects with native/C++ code
To change the build variant or ABI, click the cell for the Active Build Variant or Active ABI column and choose the desired variant or ABI from the list. After you change the selection, the IDE syncs your project automatically. Changing either column for an app or library module will apply the change to all dependent rows.
By default, new projects are set up with two build variants: a debug and release variant. You need to build the release variant to prepare your app for public release.
To build other variations of your app, each with different features or device requirements, you can define additional build variants.
Conflicts in Android Studio’s Build Variants dialog
In Android Studio’s Build Variants dialog, you might see error messages indicating conflicts between build variants, such as the following:
This error does not indicate a build issue with Gradle – it is only indicating that the Android Studio IDE itself cannot resolve symbols between the variants of the selected modules.
For example, if you have a module M1 that depends on variant v1 of module M2 , but M2 has variant v2 selected in the IDE, you have unresolved symbols in the IDE. Let’s say that M1 depends on a class Foo which is only available in v1 . When v2 is selected, that class is not known by the IDE and it will fail to resolve it and show errors in the code of M1 .
These error messages appear because the IDE cannot load code for multiple variants simultaneously. In terms of your app’s build, however, the variant selected in this dialog will have no effect because Gradle builds your app with the source code specified in your Gradle build recipes, not based on what’s currently loaded in the IDE.
Build your project
The Run button builds and deploys your app to a device. However, to build your app to share or upload to Google Play, you’ll need to use one of the options in the Build menu to compile parts or all of your project. Before you select any of the build options listed in table 1, make sure you first select the build variant you want to use.
Table 1. Build options in the Build menu.
Menu Item | Description |
---|---|
Make Module | Compiles all source files in the selected module that have been modified since the last build, and all modules the selected module depends on recursively. The compilation includes dependent source files and any associated build tasks. You can select the module to build by selecting either the module name or one of its files in the Project window. |
Make Project | Makes all modules. |
Clean Project | Deletes all intermediate/cached build files. |
Rebuild Project | Runs Clean Project for the selected build variant and produces an APK. |
Build Bundle(s) / APK(s) > Build APK(s) | |
Build Bundle(s) / APK(s) > Build Bundle(s) | |
Brings up a dialog with a wizard to set up a new signing configuration, and build either a signed app bundle or APK. You need to sign your app with a release key before you can upload it to the Play Console. For more information about app signing, see Sign your app. |
Note: The Run button builds an APK with testOnly=»true» , which means the APK can only be installed via adb (which Android Studio uses). If you want a debuggable APK that people can install without adb, select your debug variant and click Build Bundle(s) / APK(s) > Build APK(s).
For details about the tasks that Gradle executes for each command, open the Build window as described in the next section. For more information about Gradle and the build process, see Configure Your Build.
Monitor the build process
You can view details about the build process by clicking View > Tool Windows > Build (or by clicking Build in the tool window bar). The window displays the tasks that Gradle executes in order to build your app, as shown in figure 3.
Figure 3. The Build output window in Android Studio
- Build tab: Displays the tasks Gradle executes as a tree, where each node represents either a build phase or a group of task dependencies. If you receive build-time or compile-time errors, inspect the tree and select an element to read the error output, as shown in figure 4.
Figure 4. Inspect the Build output window for error messages
If your build variants use product flavors, Gradle also invokes tasks to build those product flavors. To view the list of all available build tasks, click View > Tool Windows > Gradle (or click Gradle in the tool window bar).
If an error occurs during the build process, Gradle may recommend some command-line options to help you resolve the issue, such as —stacktrace or —debug . To use command-line options with your build process:
- Open the Settings or Preferences dialog:
- On Windows or Linux, select File >Settings from the menu bar.
- On Mac OSX, select Android Studio >Preferences from the menu bar.
- Navigate to Build, Execution, Deployment >Compiler.
- In the text field next to Command-line Options, enter your command-line options.
- Click OK to save and exit.
Gradle applies these command-line options the next time you try building your app.
Apply Changes
In Android Studio 3.5 and higher, Apply Changes lets you push code and resource changes to your running app without restarting your app—and, in some cases, without restarting the current activity. This flexibility helps you control how much of your app is restarted when you want to deploy and test small, incremental changes while preserving your device’s current state. Apply Changes uses capabilities in the Android JVMTI implementation that are supported on devices running Android 8.0 (API level 26) or higher. To learn more about how Apply Changes works, see Android Studio Project Marble: Apply Changes.
Requirements
Apply Changes actions are only available when you meet the following conditions:
- You build the APK of your app using a debug build variant.
- You deploy your app to a target device or emulator that runs Android 8.0 (API level 26) or higher.
Use Apply Changes
Use the following options when you want to deploy your changes to a compatible device:
Apply Changes and Restart Activity
Attempts to apply both your resource and code changes by restarting your activity but without restarting your app. Generally, you can use this option when you’ve modified code in the body of a method or modified an existing resource.
You can also perform this action by pressing Ctrl+Alt+F10 (or Control+Shift+Command+R on macOS).
Apply Code Changes
Attempts to apply only your code changes without restarting anything. Generally, you can use this option when you’ve modified code in the body of a method but you have not modified any resources. If you’ve modified both code and resources, use Apply Changes and Restart Activity instead.
You can also perform this action by pressing Ctrl+F10 (or Control+Command+R on macOS).
Run
Deploys all changes and restarts the app. Use this option when the changes that you have made cannot be applied using either of the Apply Changes options. To learn more about the types of changes that require an app restart, see Limitations of Apply Changes.
Enable Run fallback for Apply Changes
After you’ve clicked either Apply Changes and Restart Activity or Apply Code Changes, Android Studio builds a new APK and determines whether the changes can be applied. If the changes can’t be applied and would cause Apply Changes to fail, Android Studio prompts you to Run your app again instead. However, if you don’t want to be prompted every time this occurs, you can configure Android Studio to automatically rerun your app when changes can’t be applied.
To enable this behavior, follow these steps:
Open the Settings or Preferences dialog:
- On Windows or Linux, select File > Settings from the menu bar.
- On macOS, select Android Studio > Preferences from the menu bar.
Navigate to Build, Execution, Deployment > Deployment.
Select the checkboxes to enable automatic Run fallback for either of the Apply Changes actions.
Click OK.
Platform-dependent changes
Some features of Apply Changes depend on specific versions of the Android platform. To apply these kinds of changes, your app must be deployed to a device running that version of Android (or higher).
Type of change | Minimum platform version |
---|---|
Adding a method | Android 11 |
Limitations of Apply Changes
Apply Changes is designed to speed up the app deployment process. However, there are some limitations for when it can be used. If you encounter any issues while using Apply Changes, file a bug.
Code changes that require app restart
Some code and resource changes cannot be applied until the app is restarted, including the following:
- Adding or removing a field
- Removing a method
- Changing method signatures
- Changing modifiers of methods or classes
- Changing class inheritance
- Changing values in enums
- Adding or removing a resource
- Changing the app manifest
- Changing native libraries (SO files)
Libraries and plugins
Some libraries and plugins automatically make changes to your app’s manifest files or to resources that are referenced in the manifest. These automatic updates can interfere with Apply Changes in the following ways:
- If a library or plugin makes changes to your app’s manifest, you can’t use either Apply Code Changes or Apply Changes and Restart Activity and have to restart your app before you can see your changes.
- If a library or plugin makes changes to your app’s resource files, you can’t use Apply Code Changes , and you must use Apply Changes and Restart Activity to see your changes.
You can avoid these limitations by disabling all automatic updates for your debug build variants.
For example, Crashlytics updates app resources with a unique build ID during every build, which prevents you from using Apply Code Changes and requires you to restart your app’s activity to see your changes. You can disable this behavior so that you can use Apply Code Changes alongside Crashlytics with your debug builds.
Code that directly references content in an installed APK
If your code directly references content from your app’s APK that’s installed on the device, that code can cause crashes or misbehave after clicking Apply Code Changes . This behavior occurs because when you click Apply Code Changes, the underlying APK on the device is replaced during installation. In these cases, you can click Apply Changes and Restart Activity or Run , instead.
Content and code samples on this page are subject to the licenses described in the Content License. Java is a registered trademark of Oracle and/or its affiliates.
Источник