Pull to refresh

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

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

Простая gif анимация загрузчика для привлечения внимания

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

Часть 1. Загрузчик


Схема работы загрузчика проста и прозрачна:
  1. Добавляем скрин, которому необходимы некие ресурсы
  2. Если ресурсы не загружены, добавляем поверх него скрин загрузчика
  3. После загрузки ресурсов убираем загрузчик

При реализации данного алгоритма появляются сложности. Рассмотрим ниже их решение.

Обработка нажатий, когда ресурсы еще не загружены.
Эту проблему решает менеджер скринов, и каждый экран имеет внутри себя метод 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
}

Улучшенная логика загрузчика выглядит так (пример с заменой загруженного скрина на новый):
  1. Добавляем второй скрин, которому необходимы некие ресурсы
  2. Если ресурсы не загружены, добавляем поверх него скрин загрузчика
  3. Отображается анимация показа загрузчика, одновременно начинается загрузка ресурсов
  4. После загрузки ресурсов, мы хотим отображать уже второй скрин, потому удаляем первый скрин
  5. Отображается анимация сокрытия загрузчика
  6. Скрываем загрузчик и меняем слушателя нажатий на второй скрин

При удалении загруженных ресурсов из памяти удаляются ресурсы, которые используются другими скринами.
При удалении скрина, нужно выгрузить из памяти ресурсы, которые больше не используются. Для данной цели служит данный метод (он выгрузит только те ресурсы, которые не востребованы другими скринами)
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<AssetDescriptor> getDependencies(String fileName, FileHandle atlasFile, TextureAtlasParameter parameter) {
		Array<AssetDescriptor> dependencies = super.getDependencies(fileName, atlasFile, parameter);
		for (AssetDescriptor descriptor : dependencies) {
			if (!(descriptor.file instanceof CryptFileHandle)) {
				descriptor.file = new CryptFileHandle(descriptor.file);
			}
		}
		return dependencies;
	}
}


Заключение


Для демонстрации работоспособности записано видео:


Разумеется, весь этот текст был бы бесполезен без исходников.
Доступны по ссылке GitHub.
Tags:
Hubs:
Total votes 15: ↑15 and ↓0 +15
Views 14K
Comments 7
Comments Comments 7