Cocos2d:对CCSpriteBatchNode的使用有6个疑问

时间:2012-09-17 13:42:51

标签: cocos2d-iphone ccsprite atlassprites

我想知道如何优化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.您确认是否愿意避免 在运行时向sprite批处理节点添加和删除sprite?

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


关于3的其他注意事项

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

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

这也是软件工程术语中最好的方法吗?

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

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

1 个答案:

答案 0 :(得分:1)

  1. 正确,但“绘制调用”并不等同于执行draw方法。绘制调用是OpenGL状态的变化,需要执行昂贵的操作来重置状态机。绑定新纹理或更改变换符合要求。
  2. 正确。
  3. 正确。
  4. 无需这样做。动画在精灵上运行。因此只需要对精灵进行批处理。如果一个动画帧不是来自相同的纹理图集,当动画尝试使用这样的帧时,CCSpriteBatchNode会抱怨。
  5. 最好。在CCSpriteBatchNode中添加/删除精灵比添加/删除任何其他节点更昂贵。因为精灵批处理节点的四边形需要更新。虽然如果您有许多子节点并经常添加/删除它可能只会有所不同。
  6. 不,不。见my answer here
  7. 关于3的其他注意事项:

    如果你的场景层次结构不是太深,你可以偶尔使用self.parent或self.parent.parent,但只能在父 - 父关系实际修复的地方(即绕过精灵批处理的精灵批量精灵)节点以获取底层的“真实”父节点。但我不建议更深入。有关self.parent和避免保留周期的技术,请参阅my answer here

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

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

    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:检查或断言是有序的。