Sunday, 24 July 2011

How to easily (in comparison!) set up a looping menu for level select screens

So, I have tried a lot of times to find good material about this. There are so many threads asking for help about this, and a lot of them have solutions but when you go to put it together or open their projects its all so outdated and filled with warnings and errors.

 They are also really complicated, so someone who's new like me doesn't understand it at all and so it sort of becomes a black box. So I thought I'd come up with my own solution. All the code is fully commented and explained.

// test
// Created by petemyster on 21/07/2011.
// Copyright petemyster. All rights reserved.

// Import the interfaces
#import "easyLevelSelect.h"
#import "difficultySelectLayer.h"

// easyLevelSelect implementation
@implementation easyLevelSelect

@synthesize beginTouchPoint, movingTouchPoint, originPoint;
@synthesize arrayOfLevels, numOfLevels, differencePoint, retardationNum, spacingBetweenLevels;

+(CCScene *) scene
// 'scene' is an autorelease object.
CCScene *scene = [CCScene node];
// 'layer' is an autorelease object.
easyLevelSelect *layer = [easyLevelSelect node];
// add layer as a child to scene
[scene addChild: layer];
// return the scene
return scene;

// on "init" you need to initialize your instance
-(id) init
if( (self=[super init])) {
self.isTouchEnabled = YES;
//add the images for the looping menu
//set the number of levels
numOfLevels = 7;
//the space between the images (image size + spacing you want)
spacingBetweenLevels = 150;
//allocate memory for the mutable array, do initwithcapacity because just init has problems sometimes
arrayOfLevels = [[NSMutableArray alloc] initWithCapacity:1];
for(int i = 0; i < numOfLevels; i++)
//loop through all the levels, creating a sprite and the specified image
//uses stringwithformat, the %d is replaced with the number of the loop +1, because my image numbers start at number 1
CCSprite * item = [CCSprite spriteWithFile:[NSString stringWithFormat:@"testPicPreview-%d.png", i+1] ];
//set the position of the sprite. mine start off on the far right of the screen (320) then are spaced according to my spacing and the number of sprites that have appeared before it
item.position = ccp(320 +((i-1)*spacingBetweenLevels), 150);
//set a tag for the sprite so we can identify it later
item.tag = i+1;
//add the sprite to the view
[self addChild:item];
//add sprite to array so we can cycle through all sprites later
[arrayOfLevels addObject:item];
//this is the slowing amount for when scrolling through the menu, see later
retardationNum = 1.0;
//this constantly checks for movement and moves the menu
[self schedule:@selector(accelerateLabel) interval:0.05];

return self;

-(void) accelerateLabel{
//if is it moving ridicously slowly, stop it
if (fabs(differencePoint.x) < 1) {
differencePoint.x = 0;
//if the menu is moving
if (differencePoint.x !=0) {
//if its moving ridiclously fast, slow it down
if (fabs(differencePoint.x) > 30) {
differencePoint.x = differencePoint.x * 0.75;

//if it is moving to the right, slow it down
if (differencePoint.x >0) differencePoint.x -=retardationNum;
//if its moving to the left, slow it down
if (differencePoint.x <0) differencePoint.x +=retardationNum;
//move the menu by the "strength" of the swipe
[self moveMenuItem:differencePoint.x];
//reset variables
differencePoint = ccp(0, 0);

-(void) callMe:(CCSprite*) sender{
//takes the tag of the sprite that was selected, and checks for an appropiate action
switch (sender.tag) {
case 1:NSLog(@"lol");
case 2: NSLog(@"lol 2");
case 3:[[CCDirector sharedDirector] replaceScene:[CCTransitionFade transitionWithDuration:0.5 scene:[difficultySelectLayer scene]]];
//etc etc etc

-(void) registerWithTouchDispatcher {
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES];

-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event{

//gets the touch location on the screen
beginTouchPoint= [touch locationInView: [touch view]];
beginTouchPoint = [[CCDirector sharedDirector] convertToGL:beginTouchPoint];
beginTouchPoint = [self convertToNodeSpace:beginTouchPoint];
//store the very first starting point, we will need this later as beginTouchPoint is changed throughout
originPoint = beginTouchPoint;
return true;

-(void) ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event{
//get the point that the player is currently pressing as they move
movingTouchPoint = [self convertTouchToNodeSpace:touch];
//get the difference of positions between the last touch point, and the current move point
differencePoint = ccpSub(beginTouchPoint, movingTouchPoint);
//set the last touch point to equal the current, so the line of code above is always subtracting the latest points next time it is called
beginTouchPoint = movingTouchPoint;

-(void) ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event{
//get the position of the final touch
CGPoint locationPressed = [touch locationInView: [touch view]];
locationPressed = [[CCDirector sharedDirector] convertToGL:locationPressed];
//subtract the final touch location from the very first touch location
CGPoint difference = ccpSub(originPoint, locationPressed);

//if the distance between the points was very small, the player is wanting to press the button instead of finishing their swipe
if (fabs(difference.x) <15) {
//cycle through our array of sprites
for (int i = 0; i < numOfLevels; i++) {
CCSprite * item = [arrayOfLevels objectAtIndex:i];
// if the location that the player touched is within the sprites picture...
if (CGRectContainsPoint(item.boundingBox, locationPressed)) {
// call the function that will find the specific action for that sprite
[self callMe:item];

-(void) moveMenuItem:(float) velocity{
//defining the first sprite in the array and the last sprite of the array
CCSprite * testBoundItemMax = [arrayOfLevels objectAtIndex:numOfLevels-1];
CCSprite * testBoundItemMin = [arrayOfLevels objectAtIndex:0];

//allow movement of the sprites as long as they don't go off screen
if (((velocity>0) && (testBoundItemMax.position.x> 225)) || ((velocity<0) && (testBoundItemMin.position.x< 225))) {

for(int i = 0; i < numOfLevels; i++)
//move the sprites one by one
CCSprite * item = [arrayOfLevels objectAtIndex:i];
item.position = ccp(item.position.x + (-velocity), item.position.y);



// on "dealloc" you need to release all your retained objects
- (void) dealloc
// in case you have something to dealloc, do it in this method
// in this particular example nothing needs to be released.
// cocos2d will automatically release all the children (Label)
// don't forget to call "super dealloc"
//loop menu
[arrayOfLevels release];
[super dealloc];


// test
// Created by petemyster on 21/07/2011.
// Copyright petemyster. All rights reserved.

// When you import this file, you import all the cocos2d classes
#import "cocos2d.h"

@interface easyLevelSelect : CCLayer
//set up the looping menu
CGPoint beginTouchPoint;
CGPoint movingTouchPoint;
CGPoint originPoint;
CGPoint differencePoint;
NSMutableArray * arrayOfLevels;


// returns a CCScene that contains the HelloWorldLayer as the only child
+(CCScene *) scene;
-(void) callMe:(CCSprite*) sender;
-(void) moveMenuItem:(float) velocity;
-(void) accelerateLabel;

//set up looping menu
@property (nonatomic) CGPoint beginTouchPoint;
@property (nonatomic) CGPoint movingTouchPoint;
@property (nonatomic) CGPoint originPoint;
@property CGPoint differencePoint;
@property (nonatomic, retain) NSMutableArray * arrayOfLevels;
@property int numOfLevels;
@property int spacingBetweenLevels;
@property float retardationNum;


1 comment:

  1. Thank's for your info. I encorage you to put a video of the code, in order to help people understand what are you doing. ;)