【问题标题】:Cocos2d: Is it ok to have a pointer to a parent's class pointer?Cocos2d:有一个指向父类指针的指针可以吗?
【发布时间】:2012-09-17 16:59:01
【问题描述】:

愚蠢的问题。 Cocos2d 是围绕父子层次结构构建的。我想知道是否可以拥有一个父类(例如 GameScene)并使用指向父类成员(例如 CCSpriteBatchNode*)的指针初始化一个子类(例如 SpriteHandler)。

我正在尝试这样做以优化 CCSpriteBatchNode 的数量。这是我的主类(GameScene 风格)的代码 sn-p。

#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];
}

【问题讨论】:

    标签: pointers opengl-es cocos2d-iphone ccsprite


    【解决方案1】:

    你可以这样做,但如果你使用 ARC,你应该让你的 sharedSpriteBatchNode 成为一个弱指针。如果不这样做,您最终可能会得到一个循环引用。

    循环引用将发生的情况是,当 Director 在完成运行游戏场景后释放您的游戏场景时,您的孩子仍将保留它,而您的游戏场景仍将保留该孩子。这个圈子会飘起来,永远无法释放,因为它被遗弃了。

    【讨论】:

    • 谢谢本,有趣的回答。我将来会考虑这个。我赞成它,但接受了 LearnCocos2D 一个,因为它为我的问题提供了一个“创新”的解决方案,但公平地说,你的答案也应该被接受,并且两者都应该被考虑。
    • 您应该始终接受最有帮助的答案。我同意,Steffen 的回答比我的要好,这是他应得的!
    • 我非常感谢能够向像 SO 这样广泛而乐于助人的社区提出问题。感谢您分享您的知识。我希望,一旦我发布了我的第一款游戏,能够像你们一样了解足够多的知识来提供帮助:)。
    【解决方案2】:

    [Ben] 说了什么。它不应该是保留或强引用,后者是 ARC 中实例变量的默认值。

    有一种方法可以确保在 ARC 下,即使您使用强引用,您也可以保持循环安全。覆盖 cleanup 方法并将引用设为 nil(在 MRC 下,如果您保留了引用,您还应该在此处调用 release):

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

    在 dealloc 中这样做是行不通的。只要子节点对父节点有强引用,它就不会被释放。因此,您需要在清理中执行此操作,并确保可以设置清理标志的所有方法调用都将该参数设置为 YES。

    但还有其他的,我认为比在初始化程序中传递父节点更好的解决方案。例如,您可以通过父节点的公共标记获取共享批处理节点,并且每次需要时都这样做(将其包装成一个小函数)或将其存储在弱(非保留)实例变量中:

    // 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;
    }
    

    特别推荐后一种解决方案。即使您经常需要这样做,它也很快。铸造是免费的,财产访问不超过一个消息发送。只需确保父级实际上是您将其投射到的类的对象。

    【讨论】:

    • 谢谢你,Steffen,回答很好,很清楚。很多我没有考虑过的想法。
    猜你喜欢
    • 2015-02-16
    • 2010-09-22
    • 2016-08-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-01
    相关资源
    最近更新 更多