Libgdx: экран загрузки и загрузка шифрованных ресурсов

Очень часто мобильным играм требуется экран загрузки, так как подгрузка ресурсов может длиться долго на различных устройствах. В этой статье я покажу свою реализацию загрузчика и шифрования файлов, используя движок Libgdx.Простая gif анимация загрузчика для привлечения внимания

Во многих приложениях экран загрузки используется только при старте (или не используется совсем), при этом загружаются сразу все изображения, которые могут понадобиться. Это удобно, если ваша игра «Flappy Bird» и имеет при себе несколько десятков картинок, упакованных в атлас размером 1024×1024. Если же приложение крупное, такое как «Cut The Rope», то загрузить все ресурсы разом не получится, да и неободимости загружать «лишние» ресурсы нету, так как пользователь их может даже не увидеть.

Часть 1. ЗагрузчикСхема работы загрузчика проста и прозрачна: Добавляем скрин, которому необходимы некие ресурсы Если ресурсы не загружены, добавляем поверх него скрин загрузчика После загрузки ресурсов убираем загрузчик При реализации данного алгоритма появляются сложности. Рассмотрим ниже их решение.Обработка нажатий, когда ресурсы еще не загружены.Эту проблему решает менеджер скринов, и каждый экран имеет внутри себя метод isActive (), который вернет false, если скрин не готов.

public boolean isTouched (Rectangle rect, int pointer) { if (isActive ()) { return Gdx.input.isTouched (pointer) && rect.contains (Gdx.input.getX (pointer), Gdx.input.getY (pointer)); } return false; }

public boolean isPressed (int key) { if (isActive ()) { return Gdx.input.isKeyPressed (key); } return false; } Загрузчик имеет при себе красивую анимацию, и убирать его нужно после ее выполнения.Вся логика анимированного загрузчика будет построена на методах, которые будут возвращать true, если они завершились и можно переходить к след. этапу. @Override public void assetsReady () { super.assetsReady (); // стартуется анимация } @Override protected boolean isShowedScreen () { // возвращается true, если анимация старта завершена } @Override protected void updateProgress () { // анимируем проценты загрузки } @Override protected void onAssetsLoaded () { // стартуем анимацию закрытия скрина } @Override protected boolean isHidedScreen () { // если анимация завершена, возвращаем true } Улучшенная логика загрузчика выглядит так (пример с заменой загруженного скрина на новый): Добавляем второй скрин, которому необходимы некие ресурсы Если ресурсы не загружены, добавляем поверх него скрин загрузчика Отображается анимация показа загрузчика, одновременно начинается загрузка ресурсов После загрузки ресурсов, мы хотим отображать уже второй скрин, потому удаляем первый скрин Отображается анимация сокрытия загрузчика Скрываем загрузчик и меняем слушателя нажатий на второй скрин При удалении загруженных ресурсов из памяти удаляются ресурсы, которые используются другими скринами.При удалении скрина, нужно выгрузить из памяти ресурсы, которые больше не используются. Для данной цели служит данный метод (он выгрузит только те ресурсы, которые не востребованы другими скринами) public void unload (boolean checkDependencies) { loaded = false; if (checkDependencies) { for (CoreScreen screen: coreManager.screens) { BaseAsset[] screenAssets = screen.getAssets (); for (BaseAsset screenAsset: screenAssets) { if (screenAsset!= this && name.equals (screenAsset.name)) { return; // neededByOtherAsset } } } } coreManager.resources.unload (name); } Данный загрузчик позволяет приложению расходовать меньше ресурсов, и сокращает ожидание пользователя, тем самым делая приложение более удобным.Часть 2. Шифрование Иногда появляется необходимость шифровать ресурсы. Разумеется, если приложение умеет их расшифровывать, то и пользователь сможет, но это будет уже не так просто, особенно если вы не забыли использовать ProGuard при релизе приложения (для защиты хранящихся в нем данных). В Libgdx при работе с файловой системой необходима абстракция, чтобы на различных операционных системах было схожее поведение. При работе с внутренними ресурсами в конструктор AssetManager можно передать свою реализацию FileResolver, который в свою очередь вернет свою реализацию FileHandle, который вернет свою реализацию InputStream.В моем случае логика работы проста, и wrapper для InputStream замещает главный метод read () @Override public int read () throws IOException { int one; if ((one = inputStream.read ()) != -1) { one = CryptUtil.proceedByte (one, position); ++position; } return one; }

// CryptUtil.java

private static final int[] CRYPT_VALS = { 13, 177, 24, 36, 222, 89, 85, 56 }; static int proceedByte (int data, long position) { return (data ^ CRYPT_VALS[(int) (position % CRYPT_VALS.length)]); } При чтении из файла мы лишь проводим операцию XOR над полученным байтом со значением из массива. Конечно, данный метод можно усложнить, забивая числами массива начало файла, и при первом чтении этот массив инициализировать.К сожалению, это не все места, где нужно оборачивать FileHandle. При загрузке атласов, подгружаемые зависимости получаются иным путем, потому для зашифрованных файлов добавим новый тип, с разрешением ».atlascrypt», а также сообщим AssetManager, что у этого типа новый загрузчик:

public class CryptTextureAtlasLoader extends TextureAtlasLoader { public CryptTextureAtlasLoader (FileHandleResolver resolver) { super (resolver); } @SuppressWarnings («rawtypes») @Override public Array getDependencies (String fileName, FileHandle atlasFile, TextureAtlasParameter parameter) { Array dependencies = super.getDependencies (fileName, atlasFile, parameter); for (AssetDescriptor descriptor: dependencies) { if (!(descriptor.file instanceof CryptFileHandle)) { descriptor.file = new CryptFileHandle (descriptor.file); } } return dependencies; } } Заключение Для демонстрации работоспособности записано видео:[embedded content]Разумеется, весь этот текст был бы бесполезен без исходников.Доступны по ссылке GitHub.

© Habrahabr.ru