cocos2d: Displaying a grid of items in a scrollview, clipping node

In a cocos2d iOS game I am currently writing, one of the things I wanted was to display a menu of items using a grid style, display the said menu in a scrollview; so I would have multiple items per page, and then finally — put the items in a clipping node.

Here is a visual representation from my game,

grid

This is a menu of 12 items displayed in a grid of 3×2, that is 6 items per page.

To make this work, I would need to use a CCScrollLayer to paginate between each page, each having 3×2 items on them.

Furthermore, I need to put them in a clipping node. By a clipping node I mean something which clips the viewport in which the grid appears in.

Imagine we have a modal pop-up window, or something where we do not want the visuals to “spill over” a certain boundary; this is what we use the clipping node for.

For all of this to work, you will need the following:

+ cocos2d-iphone v2.x (I did not test for 1.x) (cocos2d website)
+ CCScrollLayer (part of the cocos2d-iphone-extensions in github)
+ CCMenu+Layout.h (Tony Ngo)
+ Viewport (https://gist.github.com/agasiev/3908876) or ClippingNode (via cocos2d website)
+ (Optional) CCMenuAdvanced (See: cocos2d-iphone-extensions)

I used Viewport rather than ClippingNode because I couldn’t quite get it to work, whereas I found Viewport used similar code and seemed to work a bit more straightforwardly.

Anyway, my code now follows. It may not necessarily work for you, but should give you some indications of what I did.

Code:

#import "Viewport.h"
#import "CCMenu+Layout.h"
#import "CCMenuAdvanced.h"
#import "CCScrollLayer.h"
// Notes
// Apple is a custom item.
// List would be your array of custom objects ie: Apples or whatever

// Display the items in a grid            
NSMutableArray *pageArray = [NSMutableArray array];
CCLayer *page = nil;
CCMenu *itemMenu = nil;

int tag = 0;
int count = 0;

// We want a grid of 3x2, this means count must 
// reach 6 before making a new page.
for (Apple *apple in list) {
    if (count == 0) {
        page = [CCLayer node];
        itemMenu=[CCMenu menuWithItems:nil];
        [itemMenu setContentSize:CGSizeMake(300, 100)];
        [itemMenu setAnchorPoint:CGPointMake(0, 0.5)];
        [itemMenu setPosition:CGPointMake(80, 90)];
        [page addChild:itemMenu];
        [pageArray addObject:page];
    }
        
    // Avatar Button
    CCSprite *avtButton = nil;
    
    // Get the image from the custom object
    NSString *fileName = [NSString stringWithFormat:@"%@.png", apple.filename];

    avtButton = [AvatarButton spriteWithSpriteFrameName:fileName];
    [avtButton setScale:0.75];
    
    // Create menu item
    CCMenuItemSprite *btnWG = [CCMenuItemSprite itemFromNormalSprite:avtButton selectedSprite:nil target:self selector:@selector(menuButtonTapped:)];
    [btnWG setTag:tag];
    [itemMenu addChild:btnWG];
    
    count++;
    
    if (count == 6) {
        [itemMenu alignItemsInGridWithPadding:CGPointMake(15, 2) columns:3];
        count=0;
    }
    
    tag++;
} // next

// Now create the scroller and pass-in the pages (set widthOffset to 0 for fullscreen pages)
self.scroller = [[[CCScrollLayer alloc] initWithLayers:pageArray widthOffset:250] autorelease];
[self.scroller setShowPagesIndicator:YES];
[self.scroller setPagesIndicatorPosition:CGPointMake(335, 100)];            

Viewport *cn = [[Viewport alloc] initWithRect:CGRectMake(75,75, 300, 180)];
[cn setAnchorPoint:CGPointMake(0, 0.5)];
[cn addChild:scroller];
[self addChild:cn z:8];
[cn release];

Because we need a grid of 6 items, the `count` variable must reach 6 before “creating” a new page. Each page has it menu aligned to 3 columns.

The page gets added to the pageArray (a list of pages) which are used by the scroller object.

Finally, we create a clipping node and add the scroller as a child of the clipping node’s object before we add the clipping node to self (or it could be a layer).

I’ve been able to test it for 12 items, 6 per page but if you want a custom amount (say 2-4 per page) you must adjust the numbers in the code above to match your expectations.

I hope this helps in your code development.

Recursive disabling of CCNodes

This is an update of my previous post where I was attempting to lock/disable a CCScrollLayer when I launch a modal pop-up dialog.

The previous code doesn’t always work, and I’ve changed it a bit to the below;

It isn’t 100% perfect but it is a bit more recursive than the last version.

Basically the idea is:

  1. I want to disable/enable every node except my CoverLayer and her children, this is implicit because all the pop-up dialog’s appear as a child of CoverLayer
  2. I want to stop all interaction on CCScrollLayers
  3. I want to stop the user from clicking on a MenuItem within the CCScrollLayer multiple times

Again, it isn’t perfect; but its a start.

Put this in where you need to disable stuff; I put it in a singleton or a single controller instance and launch it there.

Code:

// Disabled/Enable layers
-(void) MenuStatus:(BOOL)_enable Node:(id)_node
{
    BOOL showLogs = YES;
    
    for (id result in ((CCNode *)_node).children)
    {        
        if (showLogs == YES) NSLog(@"Node result = %@", [result class]);
        
        if ([result isKindOfClass:[CoverLayer class]])
        {
            // Do nothing
            if (showLogs == YES) NSLog(@" -- Do nothing --");
            
        } else {
            
                
            // Scrolllayer
            if ([result isKindOfClass:[CCScrollLayer class]]) {
                if (showLogs == YES) NSLog(@"A. Found CCScrollLayer...");
                ((CCScrollLayer *)result).isTouchEnabled = _enable;
                [self MenuStatus:_enable Node:result];

            } // end if
            
            // Layers
            if ([result isKindOfClass:[CCLayer class]]) {
                if (showLogs == YES) NSLog(@"B.    Found CCLayer -- %@", [CCLayer class]);
                
                // Disable CCLayer and any children?
                ((CCLayer *)result).isTouchEnabled = _enable;
                
                for (id result2 in ((CCLayer *)result).children)
                {
                    if (showLogs==YES) NSLog(@"  1. child found: %@", [result2 class]);
                    [self MenuStatus:_enable Node:result2];
                } // next
            } // end if
            
            // Menus
            if ([result isKindOfClass:[CCMenu class]]) {
                ((CCMenu *)result).isTouchEnabled = _enable;
            } // end if
            
            
        } // end if
        
	} // next
    
    NSLog(@"-------------");
}