0

我有一个有 4 个纹理图集的游戏。每个图集大小为 4096 x 4096,包含大约 300 个精灵,全部约为 15kb - 50kb。在 Texture Packer 中未优化,它们每个大约 2 - 4MB,优化后大约是一半。

它们包含用于绘制字符的资产,但我发现绘制单个字符(大约 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 秒。

那么,单例方法比使用静态变量更好吗?预加载这种大小的图集是否明智?

4

1 回答 1

0

对于您的问题,有很多不同意见的空间。牢记这一点,我建议如下:

创建纹理图集时,您应该始终提前计划真正需要的内容。例如,您有 5 种不同类型的敌人,但您的游戏的第一关仅显示敌人 #1 和 #2。在这种情况下,您应该创建一个纹理图集,其中仅包含第一级所需的资产(敌人 #1 和 #2)。

Singleton Pros: 将所有代码集中到一个类中。您只需要拥有该类的 1 个实例。防止必须双重加载某些资产。

单例缺点: 如果要管理大量资产,代码量可能会变得不堪重负。

子类化优点: 将所有代码和资产放在一个特定于 sprite 需求的类中处理。

子类化缺点: 在某些情况下,不同的类可能会多次加载相同的动画或图像。例如,某种爆炸可能会被一个或多个子类利用。

我更喜欢使用单例方法,因为我喜欢集中我的代码。下面是一个关于我如何做到这一点的简化示例。在创建纹理图集时,我还使用 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,您需要在 init 或 didMoveToView 方法中注册 NSNotification。

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(myMethod)
                                             name:@"player0AtlasLoaded"
                                           object:nil];

收到通知后,myMethod可以继续执行知道播放器图集已加载的代码。

为了保持良好的内务管理,请记住从 NSNotifications 中删除调用类,如下所示:

-(void)willMoveFromView:(SKView *)view {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

回答关于预加载地图集的最后一个问题。是的,预加载你的地图集总是明智的,因为它可以让游戏更流畅。必须在游戏中期加载资产可能会导致延迟。请记住只加载您当前所在场景所需的资产。良好的计划将为您带来良好的结果。

于 2015-05-22T13:24:07.210 回答