Search code examples
iphoneobjective-ccocos2d-iphonecocos2d-iphone-3

Adding a Hud Layer to Scene Cocos2d-3


To keep it simple, what is the easiest way to make the default [ Menu ] in default HelloWorld Scene (for example) as it's own layer. Issue I'm having now is that the scene is completely black, with nothing showing up!

GameLayer node:

- (id)init
{
    // Enable touch handling on scene node
    self.userInteractionEnabled = YES;
    self.theMap     =   [CCTiledMap tiledMapWithFile:@"AftermathRpg.tmx"];

    self.contentSize = theMap.contentSize;

    self.metaLayer = [theMap layerNamed:@"Meta"];
    metaLayer.visible = NO;

    CCTiledMapObjectGroup *objects  =    [theMap objectGroupNamed:@"mainChar"];
    NSMutableDictionary *startPoint =    [objects objectNamed:@"startPosition"];
    int x = [[startPoint valueForKey:@"x"] intValue];
    int y = [[startPoint valueForKey:@"y"] intValue];

    self.mainChar     = [CCSprite spriteWithImageNamed:@"mainChar.png"];
    mainChar.position = ccp(x,y);
    [self addChild:mainChar];

    [self addChild:theMap z:-1];
    [self setCenterOfScreen: mainChar.position];

    return self;
}

HudLayer node

-(id)init
{
    CGSize winSize = [[CCDirector sharedDirector] viewSize];


    CCButton *backButton = [CCButton buttonWithTitle:@"[ Menu ]" fontName:@"Verdana-Bold" fontSize:18.0f];
    backButton.position = ccp(0.85f * winSize.width, 0.95f * winSize.height);
    [backButton setTarget:self selector:@selector(onBackClicked:)];
    [self addChild:backButton];

    return self;
    }

Scene

+ (GameScene *)scene
{

    return [[self alloc] init];
}    
- (id)init
{
    // Apple recommend assigning self with supers return value
    self = [super init];
    if (!self) return(nil);

    CGSize winSize = [CCDirector sharedDirector].viewSize;

    self.gameLayer = [GameLayer node];
    [self addChild:gameLayer z:-1];

    //self.contentSize = self.gameLayer.contentSize;
    hudLayer = [HudLayer node];
    hudLayer.position = ccp(winSize.width * 0.9, winSize.height * 0.9);

    [self addChild:hudLayer z:1];


    return self;
}

Solution

  • From the OP I take that you have two issues with one being that the HUD is not static (i.e. it is moving as your map moves which you don't want) and that it is not positioning at the top of the screen.

    Looking at the position issue first, your position is set to normalized. Since the scene's content size has been made to be the size of your map, which I take is larger than your screen, then this is why it is showing up at the top right of the map and not the screen. To fix this don't do normalized positioning. If you want to be able to still express the position in the 0 to 1 range, use (remove the line that sets the position type to normalized also):

    CGSize winSize = [[CCDirector sharedDirector] viewSize];
    backButton.position = ccp(0.85f * winSize.width, 0.95f * winSize.height);
    

    If your map is 10,000 x 10,000 then using the normalized positioning like you are will set the button to (8,500, 9,500) rather than the top of the screen.

    Looking at the static issue next, from the looks of it you have the Hello World scene that you are adding everything to right? It also looks like you are moving the Hello World scene with a call to:

    [self setCenterOfScreen: player.position];
    

    What you want to do instead is this, you first have a scene:

    HelloWorldScene* scene;
    

    And to this scene you are adding two "main" layers with one being your gameplay layers as children of a main gameplay layer and the other being your HUD layer, which for example could look something like:

    GameplayLayer* gameLayer;
    HudLayer* hudLayer;
    
    [scene addChild:gameLayer];
    [scene addChild:hudLayer];
    

    When the player moves (or camera or whatever), what should be moving is the game's layer, not the root Hello World scene. Moving the root scene will move all of its children, which includes the hud. That is not what you want.

    When I worked on, for example, the Goldfish Mysteries app (https://itunes.apple.com/us/app/finn-friends-mysteries/id740040227?mt=8) I had essentially layers for:

    • Story (comprised of multiple sub-layers like bg, characters, etc)
    • Text (highlighted text that plays along with the narration audio)
    • HUD (comprised of multiple sub-layers)

    Whenever there is movement on the story level it occurs on the story layer. When the HUD appears then the text and story layers are paused recursively (i.e. them and all of their children) along with the narration audio if any is playing but the HUD layer remains untouched. Resuming consists of resuming story, text, and any playing narration. I don't remember off the top if dropping the hud moved down the story and text layers in this specific app since I don't have my iPad in front of me, but i have done apps in the past where dropping the hud shifted all other layers. In that situation and for a simple app it would be fine to move the scene since the scene would only be shifted enough to show the TOC (in this type of app for example). What you look to be doing is moving the entire scene with the player's movement, which is not what you really wanted to do by the looks of it.

    Either way you want a clear separation between layers and operations that are meant to only happen on specific layers should be directed only towards those layers.

    Hope this helped.

    UPDATE (Edited):

    Based on your new edited OP, you have a new problem that you've introduced. For the hud you shouldn't have to set the layer's position since inside the hud layer everything is already being laid out relative to the screen anyways. So what you should have instead is:

    hudLayer = [HudLayer node];
    [self addChild:hudLayer z:1];
    

    The second issue is that you are not properly writing your init methods for the game and hud layer classes. The init should look like:

    - (instanceType)init
    {
        self = [super init];
    
        if (self)
        {
            // Do your init stuff...
        }
    
        return self;
    }
    

    You are never calling:

    self = [super init];