2

我想知道如何优化 CCSpriteBatchNode 的使用。换句话说,我理解:

  • 1) 每个 CCSpriteBatchNode 实例对 draw 方法执行一次调用,从而减少 OpenGL 调用,从而显着提高性能
  • 2) 每个 CCSpriteBatchNode 可以引用一个且唯一的纹理图集

我不能 100% 确定,我希望你的回答是:

  • 3) 如果我有一个纹理图集,例如 game-art-hd.png,并在各种类中创建多个 CCSpriteBatchNode,我会得到多个绘制调用吗?换句话说,我假设 CCSpriteBatchNode 的每个实例都会调用自己的 draw 方法,从而导致多个 GL 绘图调用,并且性能低于拥有一个共享批处理节点。我对吗?

    - 4) 如果我在 Sprite 上使用由多个帧组成的动画,我想我应该将动画帧添加到 Sprite 批处理节点。我该怎么做?

    下面是关于我通常如何为精灵设置动画的代码片段。可以注意到,精灵帧没有添加到精灵批处理节点。为了获得更好的性能,我可能应该在初始化时执行此操作。它是否正确?

        NSMutableArray* frames = [[NSMutableArray alloc]initWithCapacity:2];
    
        for (int i = 0; i < 4; i++)
        {
            NSString*bulletFrame = [NSString stringWithFormat:@"animation-%i.png", i];            
            CCSpriteFrame* frame = [[CCSpriteFrameCache sharedSpriteFrameCache]spriteFrameByName:bulletFrame];
            [frames addObject:frame];
        }
        CCAnimation* anim = [CCAnimation animationWithFrames:frames delay:0.1f];
        CCAnimate* animate = [CCAnimate actionWithAnimation:anim];
        CCRepeatForever* repeat = [CCRepeatForever actionWithAction:animate];
        [self runAction:repeat];
    
    } 
    
  • 5) 部分参考 4. 您是否确认更倾向于避免在运行时向精灵批处理节点添加和删除精灵?

  • 6) CCSpriteBatchNode 是否只考虑可见设置为 true 的精灵或位置实际上在屏幕区域之外的精灵?


关于 3 的其他注意事项

为了解决我在 3. 中的假设,并减少 CCSpriteBatchNode 实例的数量,我的解决方案将遵循@Suboptimus 在此答案中建议的内容。我喜欢初始化希望与 MainScene 类共享相同批处理节点的类的建议方法,而不是让它们通过 self.parent.parent.(...).parent.finallysharedbatchNode 访问 MainScene

其他人会建议通过使用 self.parent .....parent 并正确投射来引用 MainScene。

这也是软件工程方面的最佳方法吗?

我更喜欢通过使用对 MainScene 类的显式引用来明确添加精灵的位置。如果我在团队中工作或更改类层次结构,这应该会有所帮助。但是这样做的缺点是,如果我想将后续精灵添加到批处理节点,我“需要”存储对它的引用,从而导致需要维护更多代码。

我问这个问题的原因是,如果发现我传统的“软件工程”思维和“Cocos2d 父节点”层次结构方法之间存在轻微冲突。我是游戏编程的新手,我想了解在大型团队中工作的经验丰富的游戏开发人员使用哪种方法:)。H

4

1 回答 1

1
  1. 正确,但是“绘图调用”不等于执行该draw方法。绘制调用是 OpenGL 状态的更改,需要执行昂贵的操作来重置状态机。绑定新纹理或更改变换符合要求。
  2. 正确的。
  3. 正确的。
  4. 没必要那样做。动画在精灵上运行。所以只需要对精灵进行批处理。如果一个动画帧不是来自同一个纹理图集,CCSpriteBatchNode 会在动画尝试使用这样的帧时报错。
  5. 这是优选的。在 CCSpriteBatchNode 中添加/删除精灵比添加/删除任何其他节点更昂贵。因为精灵批处理节点的四边形需要更新。尽管如果您有许多子节点并经常添加/删除,它可能只会产生任何影响。
  6. 没有也没有。在这里查看我的答案

关于 3 的其他注意事项:

如果您的场景层次不是太深,您可以偶尔使用 self.parent 或 self.parent.parent 但仅在父-父关系实际上固定的情况下(即从一个精灵批处理精灵绕过精灵批处理节点按顺序到达潜在的“真正”父母)。但我不建议更深入。有关self.parent 和避免保留周期的技术,请参见我的答案。

问题self.parent.parent.(…).parent在于,如果您需要更改父子关系,例如通过在层次结构中添加或删除父节点,这将完全中断。然后这将与 EXC_BAD_ACCESS 严重崩溃并且很难调试,因为您必须检查每个父级和父级的父级以查看它到底是什么类型的对象。访问超过 3 个或更多层次结构的父级我不会认为是不好的做法。这是一种可怕的做法

就个人而言,为了访问共享精灵批次等常用节点,我更喜欢“MainScene”在其处于活动状态时成为临时单例类的解决方案。然后您可以从任何子节点执行以下操作:

CCSpriteBatchNode* mainBatch = [MainScene sharedMainScene].spriteBatchNode;

要创建这个临时单例:

static MainScene* instance;
-(id) init
{
    self = [super init];
    if (self)
    {
        instance = self;
    }
    return self;
}
-(void) dealloc
{
    instance = nil;
}
-(MainScene*) sharedMainScene
{
    NSAssert(instance, @"MainScene is not initialized!");
    return instance;
}

与真正的单例的不同之处在于,如果它还不存在,它不会初始化实例。因此 sharedMainScene 中的 NSAssert。您应该只在它已经运行时访问场景实例,即它只能由该特定场景的子节点使用。

这个半单例允许访问场景的所有属性。您还可以发送场景可以中继到其他节点的消息。或者将场景中需要移除但在碰撞处理本身期间无法移除的物理对象排入队列。

如果那个单身人士打扰了你,总有可能从导演那里得到正在运行的场景:

MainScene* mainScene = (MainScene*)[CCDirector sharedDirector].runningScene;

仅当 runningScene 确实是 MainScene 对象时,您才应该小心转换为 MainScene。isKindOfClass:检查或断言是有序的。

于 2012-09-18T09:10:47.630 回答