Как оптимизировать рисование из больших файлов SKTextureAtlas с предварительной загрузкой?
У меня есть игра с 4 текстурными атласами. Каждый атлас имеет размер 4096 x 4096 и содержит около 300 спрайтов, все ~ 15kb - 50kb. Неоптимизированные в Texture Packer, они занимают около 2 - 4 МБ каждый, и примерно вдвое меньше, чем при оптимизации.
Они содержат ресурсы для рисования символов, но я нахожу, что рисовать один символ (около 24 узлов) очень медленно ~ 0,5 секунды. Это из-за нескольких звонков [atlas textureNamed:textureName]
Чтобы ускорить процесс, я хочу предварительно загрузить атласы. В идеале я бы держал их в памяти, так как они мне всегда нужны. Итак, мой первый вопрос: возможно ли это с атласом такого размера? Я пробовал звонить [SKTextureAtlas preloadTextureAtlases:@[MaleFeatureAtlas] withCompletionHandler...]
но я получаю сбой без трассировки стека, просто потерянное соединение с устройством.
В настоящее время у меня есть класс AtlasManager, который имеет статические переменные, которые инициализируются для каждого атласа текстуры:
static SKTextureAtlas *MaleFeatureAtlas;
static SKTextureAtlas *MaleItemAtlas;
static SKTextureAtlas *FemaleFeatureAtlas;
static SKTextureAtlas *FemaleItemAtlas;
@implementation AtlasManager
{
}
#pragma mark - Initialisation Methods
+ (void)initialize
{
MaleFeatureAtlas = [SKTextureAtlas atlasNamed:MaleFeatures];
MaleItemAtlas = [SKTextureAtlas atlasNamed:MaleItems];
FemaleFeatureAtlas = [SKTextureAtlas atlasNamed:FemaleFeatures];
FemaleItemAtlas = [SKTextureAtlas atlasNamed:FemaleItems];
}
У каждого символьного спрайта есть экземпляр AtlasManager, но поскольку SKTextureAtlases являются статическими переменными, я подумал, что их можно будет быстро нарисовать. Но постоянные призывы к [atlas textureNamed:textureName]
действительно замедляет рисование. Я сохраняю NSDictionary узлов после отрисовки, поэтому перерисовка выполняется очень быстро, но начальная отрисовка занимает слишком много времени. Рендеринг 8 символов с общим количеством узлов более 100 занимает около 5 секунд.
Так что же, одноэлементный подход лучше, чем использование статических переменных? И разумно ли предварительно загружать атласы такого размера?
1 ответ
Есть много места для разных мнений относительно ваших вопросов. Имея это в виду, я предлагаю следующее:
При создании текстурного атласа вы всегда должны заранее планировать то, что действительно необходимо. Например, у вас есть 5 различных типов врагов, но первый уровень вашей игры представляет только врагов № 1 и № 2. В этом случае вы должны создать текстурный атлас, который содержит только необходимые ресурсы для вашего первого уровня (враг #1 и #2).
Синглтон Плюсы: централизация всего вашего кода в одном классе. Вам нужно иметь только 1 экземпляр класса. Предотвращает необходимость удвоения загрузки некоторых активов.
Минусы Singleton: Количество кода может быть огромным, если есть много ресурсов для управления.
Плюсы подклассов: обработка всего вашего кода и ресурсов в одном классе, соответствующем потребностям спрайта.
Подклассы Минусы: В некоторых случаях у вас может быть одна и та же анимация или изображение, загруженные несколько раз разными классами. Например, определенный вид взрыва может быть использован одним или несколькими подклассами.
Я предпочитаю использовать одноэлементный подход, потому что мне нравится централизовать мой код. Ниже приведен упрощенный пример того, как я это делаю. Я также использую TexturePacker и использую опцию заголовочного файла при создании текстурного атласа. Мой синглтон-класс называется "Анимации".
В заголовочном файле анимации у меня есть это:
-(void)loadPlayer0Atlas;
@property (strong) SKTexture *player0_startLeft;
@property (strong) SKAction *player0_idleLeft;
@property (strong) SKAction *player0_idleRight;
@property (strong) SKAction *player0_walkLeft;
@property (strong) SKAction *player0_walkRight;
В файле реализации анимации:
-(void)loadPlayer0Atlas {
if(self.player0Atlas == nil) {
self.player0Atlas = [SKTextureAtlas atlasNamed:PLAYER0ATLAS_ATLAS_NAME];
[SKTextureAtlas preloadTextureAtlases:[NSArray arrayWithObject:self.player0Atlas] withCompletionHandler:^{
[self loadPlayer0Assets];
}];
} else {
[[NSNotificationCenter defaultCenter]postNotificationName:@"player0AtlasLoaded" object:self];
}
}
-(void)loadPlayer0Assets {
self.player0_startLeft = PLAYER0ATLAS_TEX_PLAYERIDLEL__000;
self.player0_idleLeft = [SKAction repeatActionForever:[SKAction animateWithTextures:PLAYER0ATLAS_ANIM_PLAYERIDLEL timePerFrame:0.2]];
self.player0_idleRight = [SKAction repeatActionForever:[SKAction animateWithTextures:PLAYER0ATLAS_ANIM_PLAYERIDLE timePerFrame:0.2]];
self.player0_walkLeft = [SKAction repeatActionForever:[SKAction animateWithTextures:PLAYER0ATLAS_ANIM_PLAYERWALKL timePerFrame:0.1]];
self.player0_walkRight = [SKAction repeatActionForever:[SKAction animateWithTextures:PLAYER0ATLAS_ANIM_PLAYERWALK timePerFrame:0.1]];
[[NSNotificationCenter defaultCenter]postNotificationName:@"player0AtlasLoaded" object:self];
}
Приведенный выше код позволяет мне загружать активы игрока, вызывая метод loadPlayer0Atlas
, Этот метод проверяет, был ли атлас уже создан. Если да, он публикует NSNotification с указанием этого. Если нет, он загружает атлас, назначает активы свойствам класса и затем отправляет NSNotification.
Вернуться в класс, который называется loadPlayer0Atlas
Вам необходимо зарегистрироваться для NSNotification в методах init или didMoveToView.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(myMethod)
name:@"player0AtlasLoaded"
object:nil];
Как только уведомление получено, myMethod
можно продолжить с кодом, зная, что атлас игрока теперь загружен.
Для хорошего обслуживания не забудьте удалить вызывающий класс из NSNotifications следующим образом:
-(void)willMoveFromView:(SKView *)view {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
Чтобы ответить на ваш последний вопрос о предварительной загрузке атласа. Да, всегда полезно предварительно загрузить свой атлас, потому что он делает игру более плавной. Необходимость загрузки активов в середине игры может привести к задержкам. Просто не забудьте загружать только те ресурсы, которые вам нужны для сцены, в которой вы находитесь. Хорошее планирование даст вам хорошие результаты.