1

在我的 cocos2d 游戏中,我需要共享一个通用的页眉和页脚,它将出现在每个游戏层上。

页眉包含现金和精灵之类的东西——而页脚包含用于在不同游戏场景之间移动的 CCMenuItem 按钮。

页眉/页脚出现在比当前场景/图层略高的 Z-index 上,因此它出现在显示的所有内容之上。

例如,标头可能如下所示:

现金:5,000 美元

例如,页脚可能看起来像带有精灵按钮的 CCMenu,如下所示:

主页 | 市场

这将被视为一个平显系统。

现在,我尝试以类似的方式将 HUD 添加到每个场景中;

HudLayer *hud = [HudLayer node]
[self addChild:hud z:1];

但是,它会导致断言错误。

* -[CCMenuItemSprite addChild:z:tag:] 中的断言失败 ...

这是因为在不同的场景中无法再次添加 HUD。这是我对 cocos2d 的真正挫败感。

到目前为止,我能够解决这个问题的唯一方法是BaseScene拥有一个HUD作为 CCLayer 的,然后我使用 NSNotifications 加载所有后续的 CCLayer。

代码如下;

#define HudBase 1
#define kHUDTag 9000

#import "cocos2d.h"

@interface BaseScene : CCScene

@end

@class HeaderHUDLayer;
@interface BaseLayer : CCLayer
{
    CCNode *currentNode;
    CCLayer *thisLayer;
    HeaderHUDLayer *hud;
}

@property (nonatomic, retain) CCNode *currentNode;
@property (nonatomic, retain) CCLayer *thisLayer;
@property (nonatomic, retain) HeaderHUDLayer *hud;

-(void) changeNodeTo:(CCNode *)thisNode;
-(void) changeHUDSceneObserver:(NSNotification *)notification;

@end

@implementation BaseScene

- (id)init
{
    self = [super init];
    if (self) {
        [self addChild:[BaseLayer node] z:0 tag:800];
    }
    return self;
}
@end

@implementation BaseLayer
@synthesize currentNode, thisLayer;
@synthesize hud;

-(id) init
{
    if ((self =[super init]))
    {
        NSLog(@"HUD Scene");

        self.hud = [HeaderHUDLayer node];

        if (self.currentNode == nil)
        {
            self.currentNode = [MyDashboardScene node];
            [self changeNodeTo:self.currentNode];
        }

        [self addChild:self.hud z:TopZLayer];

        [[NSNotificationCenter defaultCenter] addObserver:self selector: @selector(changeHUDSceneObserver:) name: @"changeHUDScene" object: nil];
    } // end if
    return self;
}


- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [super dealloc];
}


-(void) changeNodeTo:(CCNode *)thisNode
{
    NSLog(@"changeNodeTo");
    [self removeChild:self.currentNode cleanup:YES];
    [self addChild:thisNode z:0];
    self.currentNode = thisNode;
}

-(void) changeHUDSceneObserver:(NSNotification *)notification
{
    NSLog(@"changeHUDSceneObserver = %@", notification.name);

    if ([notification.name isEqualToString:@"changeHUDScene"]==YES) {
        if ([notification object]!=nil) {
            [self changeNodeTo:[notification object]];
        }
    }
}

@end

标头本身非常复杂,但简单来说我有一个 CCLabel,如下所示:

NSString *txtCash = [NSString stringWithFormat:@"Cash: %d", [player.cash intValue]];
            self.lblCash = [CCLabelBMFont labelWithString:txtCash fntFile:@"font_minipixi_16.fnt"];
            [lblCash setTag:kCashLabelTag];
            [lblCash setAnchorPoint:CGPointMake(1, 0)];
            [lblCash setPosition:CGPointMake(100, 8)];
[self addChild:lblCash];

这很好用,我可以在游戏的所有不同部分之间移动,并且页眉/页脚在所有场景中都是通用的。

但是,使用 NSNotifications 更改节点让我非常头疼,即我似乎无法更改 HUD 内的标签 (lblCash),而且我不知道如何解决这个问题。

例如,如果我点击减少我的现金的 CCMenuItem,HUD 标签永远不会更新。

为了解决这个问题,我使用另一个 NSNotification 来解决这个问题。除了有时我需要多个 NSNotifications(更改标题、刷新页面或其他)并且我最终会出现断言错误,因为我无法重新发送内容。

有时我有一个 NSNotification 来更新标头,然后是另一个 NSNotification 直接将节点重定向到我要呈现的另一个节点;这又会导致断言错误。

我觉得整个事情正在变成一团糟的 NSNotifications,这让我对 cosos2d 感到非常沮丧。

我试过了:

使基础场景移除 HUD(和她所有的孩子)并重新添加它。这会导致断言错误,并且似乎不可能做到。我可能编码错了

尝试投射 BaseScene/Layer 并使用标签抓取 HUD 并以这种方式更新标签。

IE,

    BaseLayer *baseLayer = (BaseLayer *) [self.parent getChildByTag:800];
    [baseLayer.hud updateCashAmount];

什么都不做。(基础层/场景和 hud 层都有一个标签)。

也不行,

    HeaderHUDLayer *hud = (HeaderHUDLayer *)[base getChildByTag:kHUDTag];
    [hud updateCashAmount];

我认为我不能进行强制转换的原因是因为 NSNotifications 改变了上下文。

这整件事让我对 cosos2d 感到非常沮丧。

我想要的只是很多场景中的通用页眉/页脚,我希望能够更新页眉的标签而不会大惊小怪。

鉴于此,我如何共享一个通用的页眉/页脚 CCLayer 并使页眉/页脚内的元素可更新?

有没有更简单的方法可以做到这一点,而无需到处都有大量的 NSNotifications?

感谢。

4

3 回答 3

1

在具有公共菜单层的项目中,我使用此方法将层从一个场景转移到另一个场景(在 Kobold2D 中,此方法已经可用):

-(void) transferToNode:(CCNode*)targetNode
{
    NSAssert(self.parent != nil, @"self hasn't been added as child. Use addChild in this case, transferToNode is only for reassigning child nodes to another node");
    CCNode* selfNode = [self retain];
    [self removeFromParentAndCleanup:NO];
    [targetNode addChild:selfNode z:selfNode.zOrder tag:selfNode.tag];
    [selfNode release];
}

它是这样工作的:

CCScene* newScene = [MyNewScene node];
[menuLayer transferToNode:newScene];
[[CCDirector sharedDirector] replaceScene:newScene];

现在菜单层继续存在于下一个场景中。就是这样。

PS:如果您使用 ARC,请省略保留和释放行。

于 2012-10-16T12:48:59.873 回答
0

我相信我已经解决了这个问题。

我解决这个问题的方法是:

  1. 使用 NSNotifications 在场景/层之间移动,这样 cocos2d 就不会抱怨已经添加了节点或其他资产。
  2. 在 NSNotification 观察者中,我更新标题,或者我需要更新的 HUD 层上的其他项目。
  3. 使用在整个游戏中共享的单用户数据(字典),因此我可以通过 NSNotification 将数据传递到/从标题。

这几乎是一个黑客,但我认为它现在似乎工作正常。

例如,在我的 HUD 场景/图层中,我创建了一个 NSNotification

[[NSNotificationCenter defaultCenter] addObserver:self selector: @selector(changeHUDObserver:) name:@"hudStatus" object:nil];

我还创建了一个观察者

-(void) changeHUDObserver:(NSNotification *)notification
{
    NSLog(@"changeHUD.status = %@", notification.name);

// 从状态单例中获取数据 // 还有其他方法可以做到这一点,但这只是一个示例 Player *player = [state.userData objectForKey:@"player"];

// Update header cash label
    // Cash formatter
    NSLocale *usLocale = [[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"] autorelease];
    NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
    [formatter setLocale:usLocale];
    [formatter setNumberStyle:NSNumberFormatterCurrencyStyle];
    [formatter setMaximumFractionDigits:0];
    NSString *txtCash = [formatter stringFromNumber:player.cash];
    [formatter release];

    // Cash Label
    self.lblCash.string = txtCash;
} // end method

这只是一个示例,但我现在可以发送和更新标题中的内容。

虽然我的答案是一个 hack,但我觉得 @LearnCocos2d 有一个答案,应该将其视为可能的替代方案。

谢谢

于 2012-10-17T14:18:01.673 回答
0

我想我有另一个解决这个问题的方法。

回顾一下,

我想在多个场景/图层中拥有或共享一个通用的页眉/页脚。

我这样做的方式是使用作为我的 HD 的单个孩子的 BaseScene (CCScene),然后我使用 NSNotification 观察者来更改底层孩子 (CCLayer)。

这变得非常混乱,HUD之类的东西与任何模式对话框重叠。

我还不能解决模态对话框的问题,但是在共享一个通用的页眉/页脚方面,我想我可能有一个解决方案。

在 Steffen Itterheim 所著的“Learn cocos2d game development with iOS5”一书中第 5 章左右,它谈到了使用“半单例”

以下是引文;

static MultiLayerScene* multiLayerSceneInstance;
+(MultiLayerScene*) sharedLayer {
NSAssert(multiLayerSceneInstance != nil, @"MultiLayerScene not available!");
return multiLayerSceneInstance; }

-(void) dealloc {
// MultiLayerScene will be deallocated now, you must set it to nil multiLayerSceneInstance = nil;
// don't forget to call "super dealloc"
[super dealloc]; }

简单地说,multiLayerSceneInstance 是一个静态全局变量,它将在其生命周期内保存当前的 MultiLayerScene 对象。static 关键字表示 multiLayerSceneInstance 变量只能在定义它的实现文件中访问。同时,它不是实例变量;它存在于任何类的范围之外。这就是为什么它定义在任何方法之外,并且可以在类方法中访问,例如 sharedLayer。

通过属性获取方法授予对 GameLayer 和 UserInterfaceLayer 的访问权限,以便于使用。

这使得从 MultiLayerScene 的任何节点访问各个层变得容易。

在开源游戏中,作者CastleHassle使用了一种类似的技术,我已经能够查看和“复制”一种我可以从一个场景移动到另一个场景(它们实际上是 CCLayers)并在没有这些的情况下共享相同的公共标题烦人的“......孩子已经添加”错误。

我正在进一步调查它,但 CastleHassle 在这个问题上帮了我很多,如果我尝试使用本书的代码,我可能会有进一步的解决方案。

总之,我这样做的方式是按照 CastleHassle 使用实例概念;但我会看看这本书是如何使用共享实例的概念来完成的。

于 2012-10-22T18:42:27.057 回答