Совместное использование общих верхних и нижних колонтитулов CCLayers между несколькими CCScenes и делает его обновляемым
В моей игре cocos2d мне нужно использовать общий верхний и нижний колонтитулы, которые будут отображаться на каждом игровом слое.
Заголовок содержит такие вещи, как деньги и спрайты, тогда как нижний колонтитул содержит кнопки CCMenuItem для перемещения между различными игровыми сценами.
Верхний / нижний колонтитулы отображаются в Z-индексе немного выше, чем текущая сцена / слой, так что он появляется поверх всего отображаемого.
Заголовок может, например, выглядеть так:
Наличные деньги: 5000 долларов
Нижний колонтитул может, например, выглядеть как CCMenu со спрайтовыми кнопками, например так:
Главная | базарная площадь
Это будет считаться системой HUD.
Теперь я попытался добавить HUD к каждой сцене способом, подобным этому;
HudLayer *hud = [HudLayer node]
[self addChild:hud z:1];
Но это вызывает ошибку утверждения.
* Ошибка подтверждения в -[CCMenuItemSprite addChild:z:tag:] ...
Это вызвано тем, что HUD не может быть добавлен снова в разных сценах. Это настоящее разочарование у меня с cocos2d.
Единственный способ решить эту проблему до сих пор - это иметь BaseScene
который имеет HUD
как CCLayer, и я загружаю все последующие CCLayers, используя NSNotifications.
Код теперь следует;
#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 для изменения узла вызывает у меня настоящую головную боль, а именно, я не могу изменить метки (lblCash) внутри HUD, и я не знаю, как решить эту проблему.
Например, если я нажимаю на CCMenuItem, который уменьшает мои деньги, метка HUD никогда не обновляется.
Чтобы обойти эту проблему, я использую другое NSNotification для решения этой проблемы. За исключением случаев, когда мне нужно иметь несколько NSNotifications (чтобы изменить заголовок, обновить страницу или что-то еще), и я в конечном итоге с ошибками утверждения, потому что я не могу повторно отправить вещи.
Бывают случаи, когда у меня есть NSNotification для обновления заголовка, а затем сразу же другой NSNotification, который перенаправляет узел на другой узел, который я хочу представить; опять это вызывает ошибку утверждения.
Я чувствую, что все это превращается в беспорядок NSNotifications повсюду и делает меня очень разочарованным cosos2d.
Я пытался:
Создание базовой сцены удаляет HUD (и всех ее детей) и повторно добавляет его. Это вызывает ошибку утверждения и не представляется возможным сделать. Я, вероятно, кодирую это неправильно, хотя
Попытка разыграть BaseScene/Layer и захватить HUD с помощью тега и таким образом обновить метку.
т.е.
BaseLayer *baseLayer = (BaseLayer *) [self.parent getChildByTag:800];
[baseLayer.hud updateCashAmount];
Ничего не делает (Базовые слои / сцены и слой HUD имеют теги).
Ни один не делает,
HeaderHUDLayer *hud = (HeaderHUDLayer *)[base getChildByTag:kHUDTag];
[hud updateCashAmount];
Я думаю, что причина, по которой я не могу выполнить кастинг, заключается в том, что NSNotifications изменили контекст.
Все это сделало меня очень расстроенным из-за cosos2d.
Все, что я хочу, это общий верхний / нижний колонтитул во многих сценах, я хочу иметь возможность обновлять метки заголовка без лишней суеты.
Учитывая это, как мне использовать общий CCLayer верхнего / нижнего колонтитула и сделать элементы внутри верхнего / нижнего колонтитула обновляемыми?
Есть ли более простой способ сделать это без необходимости загружать NSNotifications повсюду?
С благодарностью.
3 ответа
Я думаю, что у меня есть другое решение этой проблемы.
Напомним,
Я хочу иметь или совместно использовать общий верхний / нижний колонтитул для нескольких сцен / слоев.
Я использовал это как BaseScene (CCScene) с одним ребенком, который был моим HD, а затем я использовал наблюдателя NSNotification, чтобы изменить нижележащего ребенка (CCLayer).
Это стало очень грязным, и такие вещи, как HUD, перекрывали любые модальные диалоги.
Я пока не могу решить проблему модального диалога, но с точки зрения совместного использования общего колонтитула, я думаю, у меня может быть решение.
В книге Штеффена Иттерхейма "Изучите разработку игр cocos2d с 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; но я посмотрю как книга справилась с ее концепцией общего экземпляра.
В проекте с общим слоем меню я использую этот метод для переноса слоя из одной сцены в другую (в 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, пропустите строки сохранения и выпуска.
Я считаю, что я решил проблему.
Я решил это следующим образом:
- Используйте NSNotifications для перемещения между сценами / слоями, чтобы cocos2d не жаловался на уже добавленные узлы или другие ресурсы.
- В обозревателе NSNotification я обновляю заголовки или другие элементы на уровне HUD, которые мне нужно обновить.
- Используйте единый userData (словарь), который является общим для всей игры, и поэтому я могу передавать данные в / из заголовка через 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
Это всего лишь пример, но теперь я могу отправлять и обновлять содержимое в шапке.
Хотя мой ответ взломан, я чувствую, что у @LearnCocos2d есть ответ, который следует рассматривать как возможную альтернативу.
Спасибо