Cocos2d: 6 сомнений в использовании CCSpriteBatchNode

Мне интересно, как оптимизировать использование CCSpriteBatchNode. Другими словами, я понимаю, что:

  • 1) Каждый экземпляр CCSpriteBatchNode выполняет один вызов метода рисования, что приводит к сокращению вызовов OpenGL и, следовательно, к значительному повышению производительности.
  • 2) Каждый CCSpriteBatchNode может ссылаться на один и только текстурный атлас.

В чем я не уверен на 100% и хотел бы получить ваш ответ:

  • 3) Если у меня есть один атлас текстур, например. game-art-hd.png и создайте несколько CCSpriteBatchNode в разных классах, получу ли я несколько вызовов отрисовки? Другими словами, я предполагаю, что каждый экземпляр CCSpriteBatchNode будет вызывать свой собственный метод отрисовки, что приведет к множественным вызовам отрисовки GL и снижению производительности по сравнению с одним общим пакетным узлом. Я прав?

    – 4) Если я использую анимацию, состоящую из нескольких кадров спрайта, думаю, мне следует добавить кадры анимации в пакетный узел спрайта. Как мне это сделать?

    Ниже приведен фрагмент кода о том, как я обычно анимирую спрайт. Как можно заметить, кадры спрайтов не добавляются в узел пакета спрайтов. Для повышения производительности мне, вероятно, следует сделать это во время инициализации. Это правильно?

        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 учитывать только спрайты, у которых для параметра visible установлено значение true, или спрайты, положение которых фактически находится за пределами области экрана?


Другие соображения по поводу 3

Чтобы учесть мое предположение в 3. и уменьшить количество экземпляров CCSpriteBatchNode, мое решение будет следовать тому, что предложено @Suboptimus в этом ответ. Мне нравится предложенный подход инициализации классов, которые хотят использовать один и тот же пакетный узел с классом MainScene, а не предоставлять им доступ к MainScene через self.parent.parent.(...).parent.finallysharedbatchNode.

Вместо этого другие люди предложили бы обратиться к MainScene, используя self.parent.....parent и правильно приведя его.

Является ли это лучшим подходом с точки зрения разработки программного обеспечения?

Я предпочитаю указывать, куда добавляются спрайты, используя явную ссылку на класс MainScene. Это должно помочь, если я работаю в команде или меняю иерархию классов. Но недостатком этого является то, что мне «нужно» хранить ссылку на него, если я хочу впоследствии добавить спрайты в пакетный узел, что приводит к необходимости поддерживать больше кода.

Причина, по которой я задаю этот вопрос, заключается в том, что если я найду небольшое противоречие между моим традиционным мышлением «Разработка программного обеспечения» и подходом к иерархии «родительский узел Cocos2d». Я новичок в программировании игр и хотел бы понять, какой подход используют опытные разработчики игр, работающие в больших командах :). ЧАС


person mm24    schedule 17.09.2012    source источник


Ответы (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» становится временным классом 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;
}

Отличие от настоящего синглтона в том, что он не инициализирует экземпляр, если он еще не существует. Следовательно, NSAssert в файле sharedMainScene. Вы должны обращаться к экземпляру сцены только тогда, когда он уже запущен, т.е. он может использоваться только дочерними узлами этой конкретной сцены.

Этот полусинглтон позволяет получить доступ ко всем свойствам сцены. Вы также можете отправлять сообщения, которые сцена может ретранслировать другим узлам. Или поставьте в очередь физические объекты сцены, которые необходимо удалить, но которые нельзя удалить во время самой обработки столкновений.

И если вас смущает этот синглтон, всегда есть возможность получить бегущую сцену от режиссера:

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

Вы должны быть осторожны при приведении к MainScene, только если runningScene действительно является объектом MainScene. isKindOfClass: проверка или утверждение в порядке.

person LearnCocos2D    schedule 18.09.2012
comment
предложенный вами полуодиночный подход в основном такой же, как в примере ShootEmUp в LearnCocos2D. Мне это нравится, но у меня была только одна проблема, и поэтому я начал переосмысливать структуру кода. Всякий раз, когда я был в GameScene и решал вернуться к сцене главного меню (через SharedDirector.replaceScene), экземпляр GameScene все еще оставался в памяти до тех пор, пока из основной сцены я не выбирал еще раз GameScene (например, [[CCDirector sharedDirector] replaceScene :[сцена GameSceneWithId:1]). Не могли бы вы помочь прояснить это? В противном случае большое спасибо за исчерпывающий пост - person mm24; 21.09.2012