Cocos2d: можно ли иметь указатель на указатель родительского класса?

Глупый вопрос. Cocos2d строится вокруг иерархии родитель-потомок. Мне было интересно, можно ли иметь родительский класс (например, GameScene) и инициализировать дочерний класс (например, SpriteHandler) указателем на член родительского класса (например, CCSpriteBatchNode*).

Я пытаюсь сделать это, чтобы оптимизировать количество CCSpriteBatchNodes. Вот фрагмент кода моего основного класса (в стиле GameScene).

#import <Foundation/Foundation.h>
#import "cocos2d.h"

enum ShooterSceneLayerTags {
    HudLayerTag = 0,
    };

@interface ShooterScene : CCLayer {
    CCSpriteBatchNode* sharedSpriteBatchNode;
}


-(id) initWithSharedBatchNodeReference:( CCSpriteBatchNode*) sharedSpriteBatchNode;
+ (id) sceneWithId:(int)sceneId;
 @end


#import "ShooterScene.h"
#import "MainMenuScene.h"

//Layers
#import "LevelSpritesLayer.h"
#import "HudLayer.h"



@interface ShooterScene (PrivateMethods)
-(void) addLayers:(int)sceneId;
-(void) loadGameArtFile;
-(BOOL) verifyAndHandlePause;
@end

@implementation ShooterScene

+ (id) sceneWithId:(int)sceneId
{
    CCScene *scene = [CCScene node];

    ShooterScene * shooterLayer = [[self alloc] initWithId:sceneId];
    [scene addChild:shooterLayer];

    return scene;    
}

-(id) initWithId:(int)sceneId 
{
    if ((self = [super init]))
    {
        //Load game art before adding layers - This will initialize the batch node
        [self loadGameArtFile:sceneId]; 

        //Will add sprites to shared batch node for performance
        [self addLayers:sceneId];
        [self addChild:sharedSpriteBatchNode];

        //Do other stuff..
        [self scheduleUpdate];

    }
    return self;

}

-(void) addLayers:(int)sceneId
{
    LevelSpritesLayer * levelData = [LevelSpritesLayer node];
    [levelData initWithSharedBatchNodeReference:sharedSpriteBatchNode];

    [self addChild:levelData];

    switch (sceneId) {
        case 1:
            [levelData loadLevelOneSprites];
            break;
        case 2:
            [levelData loadLevelTwoSprites];
            break;            
        default:
            break;
    }

    HudLayer * hud = [HudLayer node];
    [hud setUpPauseMenu];
    [self addChild:hud z:1 tag:HudLayerTag];
}

-(BOOL) verifyAndHandlePause
{
    HudLayer * hud = [self getChildByTag:HudLayerTag];
    if(hud.pauseRequested){
         [[CCDirector sharedDirector] replaceScene:[MainMenuScene scene]];

        return true;
    }
    else {
        return false;
    }

}
-(void) update:(ccTime)delta
{
    if([self verifyAndHandlePause]==false)
    {
        //Continue with animation etc.. 


    }
}

/**
 This is tricky. Could have loaded this in LevelData but as I am expecting to use the same SpriteSheet for HudLayer as well then 
 I prefer to have the control here of this. Also, the same sheet could be used for more level, hence specific function is not bad 
 **/
-(void) loadGameArtFile:(int) sceneId
{
    CCSpriteFrameCache* frameCache = [CCSpriteFrameCache sharedSpriteFrameCache];
    [frameCache addSpriteFramesWithFile:@"game-art-hd.plist"];

    sharedSpriteBatchNode = [CCSpriteBatchNode batchNodeWithFile:@"game-art-hd.png"];
}

//As dealloc is deprecated, I prefer to remove unused sprites and texture on cleanup
-(void) cleanup
{
    [[CCSpriteFrameCache sharedSpriteFrameCache] removeUnusedSpriteFrames];     
}

А вот один из методов loadLevelData, он использует ссылку sharedSpriteBAtchNodeReference

-(void) loadLevelOneSprites
{
    //Just a proof of concept example
    testSprite = [CCSprite spriteWithSpriteFrameName:@"File0.png"];
    testSprite.anchorPoint = CGPointMake(0.5f, 0.5f);
    testSprite.position = CGPointMake(160.0f, 240.0f);
    [sharedSpriteBatchNodeReference addChild:testSprite];
}

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


Ответы (2)


Что [Бен] сказал. Это не должна быть сохраняющая или сильная ссылка, последняя используется по умолчанию в ARC для переменных экземпляра.

Есть один способ гарантировать, что в рамках ARC вы защищены от цикла сохранения, даже если используете сильную ссылку. Переопределите метод очистки и обнулите ссылку (в MRC вы также должны вызвать здесь релиз, если вы сохранили ссылку):

-(void) cleanup
{
    sharedSpriteBatchNode = nil;
    [super cleanup];
}

Выполнение этого в Dealloc не будет работать. Пока дочерний узел имеет сильную ссылку на родителя, он не будет освобожден. Поэтому вам нужно сделать это в очистке и убедиться, что все вызовы методов, в которых вы можете установить флаг очистки, имеют этот параметр, установленный в YES.

Но есть и другие, и я думаю, лучшие решения, чем передача родительского узла в инициализаторе. Например, вы можете получить общий пакетный узел через общий тег от родителя и либо делать это каждый раз, когда вам это нужно (обернуть его в небольшую функцию), либо сохранить его в слабом (не сохраняющемся) экземпляре var:

// onEnter is typically called right after init (during addChild)
// parent is already set here
-(void) onEnter
{
    [super onEnter];

    CCSpriteBatchNode* sharedBatchNode = [parent getChildByTag:kSharedBatchNodeTag];
}

Или получите родителя и приведите его, предполагая, что sharedBatchNode является свойством родительского класса:

-(void) whereEver
{
    ShooterScene* scene = (ShooterScene*)parent;
    CCSpriteBatchNode* sharedBatchNode = scene.sharedSpriteBatchNode;
    …

    // you can also reduce the above to a single line:
    CCSpriteBatchNode* batch = ((ShooterScene*)parent).sharedSpriteBatchNode;
}

Особенно рекомендуется последнее решение. Даже если вам нужно делать это часто, это быстро. Кастинг бесплатный, доступ к собственности не более, чем отправка сообщения. Просто убедитесь, что родитель на самом деле является объектом класса, к которому вы его приводите.

person LearnCocos2D    schedule 18.09.2012
comment
Спасибо, Штеффен, хороший и ясный ответ. Богат идеями, которые я не рассматривал. - person mm24; 20.09.2012

Вы можете сделать это, но если вы используете ARC, вы должны сделать свой sharedSpriteBatchNode слабым указателем. В противном случае вы можете получить циклическую ссылку.

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

person Ben Trengrove    schedule 17.09.2012
comment
Спасибо, Бен, интересный ответ. Я рассмотрю это в будущем. Я проголосовал за него, но принял вариант LearnCocos2D, поскольку он предложил инновационное решение моей проблемы, но, честно говоря, ваш ответ также должен быть принят, и оба должны быть рассмотрены. - person mm24; 20.09.2012
comment
Вы всегда должны принимать тот ответ, который наиболее полезен. Я согласен, ответ Штеффена лучше моего, и он этого заслуживает! - person Ben Trengrove; 21.09.2012
comment
Я так благодарен за возможность задавать вопросы такому широкому и полезному сообществу, как в SO. Спасибо, что поделились своими знаниями. Я надеюсь, что когда я опубликую свою первую игру, у меня будет достаточно знаний, чтобы помочь в ответ так же сильно, как и вы, ребята :). - person mm24; 21.09.2012