Create Your Own Level Editor: Part 2/3

In this second part of the tutorial, you will implement a portion of the editing capabilities of your level editor. You’ll work through adding popup menus, dynamically positioning and sizing your objects on screen, and much more. By Barbara Reichart.

Leave a rating/review
Save for later
Share
You are currently viewing page 4 of 7 of this article. Click here to view the first page.

Positioning the Menu

Add the following code to PopupMenu.m set the correct position for the menu:

-(void)setPopupPosition:(CGPoint)position {
    tapPosition = position;
    
    // load defaultBackground and use its size to determine whether the popup still fits there
    CCSprite* defaultBackground = [CCSprite spriteWithFile:@"menu.png"];
    CGSize defaultBackgroundSize = defaultBackground.contentSize;
    float contentScaleFactor = [CCDirector sharedDirector].contentScaleFactor;
    float padding = defaultBackgroundSize.width*0.1f*contentScaleFactor;
    [menu alignItemsHorizontallyWithPadding:padding];
    
    CGPoint anchorPoint = CGPointMake(0.5f, 0.0f);
    CGPoint menuPosition = CGPointMake(defaultBackgroundSize.width/2, defaultBackgroundSize.height*0.7f);
    
    // TODO: adjust anchorPoint and orientation of menu, to make it fit the screen
    
    background.anchorPoint = anchorPoint;
    background.position = position;
    background.opacity = menu.opacity;
    
    menu.position = menuPosition;
}

In order to align the menu properly, the above code first loads the background sprite of the menu and then gets its size. This value is used to calculate the padding between the pineapple and rope menu items.

The method then requests the contentScaleFactor from CCDirector. The contentScaleFactor can be used to translate pixel positions to point positions.

On iOS, all coordinates are usually given as points. This has an advantage in that a position in points is the same on both retina and non-retina displays. However, the padding between items in a menu in Cocos2D is for some reason still given in pixels. Therefore, you need to use the contentScaleFactor to translate the padding from points into pixels.

Next, the anchorPoint and menuPosition are set to their default values. The anchor point is set to the tip of the arrow, which is in the bottom center of the graphic.

The menu position is set as follows: the x position is set to the center of the menu’s background graphic. The y position needs to take the arrow into account. It is about as tall as one-third of the background graphic. Placing the menu at about two thirds of the background graphic height will therefore make it appear just in the right spot.

So far, this seems to be going pretty well. There’s only a few nasty warnings about unimplemented methods from Xcode. Good guy, that Xcode. Nice of him to remind you. :]

Keep him happy and add those missing methods now.

Add the following methods to PopupMenu.m:

-(BOOL)isEnabled {
    return isEnabled;
}

-(void)setMenuEnabled:(BOOL)enable {
    for (CCMenuItem* item in menu.children) {
        item.isEnabled = enable;
    }
    isEnabled = enable;
    int opacity;
    if (enable) {
        opacity = 255;
    } else {
        opacity = 0;
    }
    background.opacity = opacity;
    menu.opacity = opacity;
}

-(void)setRopeItemEnabled:(BOOL)enabled {
    ropeItem.isEnabled = enabled;
}

-(void)createPineapple:(id)sender {
    [self.delegate createPineappleAt:tapPosition];
}
 
-(void)createRope:(id)sender {
    [self.delegate createRopeAt:tapPosition];
}

Most of the above methods are fairly straightforward. setMenuEnabled:, as the name implies, allows you to enable or disable the menu. The method simply sets all the menu items to the appropriate state and then adjusts the opacity of the menu, where 255 means completely visible and 0 means invisible.

setRopeItemEnabled: allows the state of the rope menu item to be toggled. This is necessary as it does not always make sense to add a rope in the current context, and you want to prevent your users from creating invalid levels.

The last two methods are called whenever you tap the menu items for a pineapple or a rope. All they do is to forward the signal to the delegate.

Time to put your menu to work! Go to LevelEditor.h and add the following import:

#import "PopupMenu.h"

Next, add PopupMenuDelegate to the @interface line so that it looks like this:

@interface LevelEditor : CCLayer<CCTouchOneByOneDelegate, PopupMenuDelegate>

Your LevelEditor class now implements PopupMenuDelegate. This means that it can listen to orders from the popup menu now.

Switch to LevelEditor.mm and add an instance variable for the popup menu to the @interface block at the top:

    PopupMenu* popupMenu;

Now implement the popup menu delegate methods as follows in LevelEditor.mm:

-(void) createPineappleAt:(CGPoint) position {
    NSLog(@"create pineapple");
    [popupMenu setMenuEnabled:NO];
}

-(void) createRopeAt:(CGPoint) position {
   NSLog(@"create rope");
   [popupMenu setMenuEnabled:NO];
}

For the moment, the delegate methods will simply log that they were called and then close the popup menu.

If you were to build and run the code now, the popup menu would never even show up, since there’s nothing to enable the menu.

Add the following method to LevelEditor.mm:

-(void)togglePopupMenu:(CGPoint)touchLocation {
    if (!popupMenu) {
        popupMenu = [[PopupMenu alloc] initWithParent:self];
        popupMenu.delegate = self;
    }
    if (popupMenu.isEnabled) {
        [popupMenu setMenuEnabled:NO];
    } else {
        [popupMenu setPopupPosition:touchLocation];
        [popupMenu setMenuEnabled:YES];
        if ([[pineapplesSpriteSheet children] count] < 1) {
            [popupMenu setRopeItemEnabled:NO];
        }
    }
}

The code above, as advertised, toggles the state of the menu. It first determines whether the popup menu already exists. If it doesn't, it creates a new instance and registers LevelEditor as the delegate.

If the menu is currently enabled, you disable it. If it is disabled, you enable it, and set its position to the touch location. If there are currently no pineapples in the level, you disable the rope item in the popup menu.

All that is left now is to actually call togglePopupMenu: when the player taps the screen. What method in LevelEditor.mm would you need to change for that?

ccTouchBegan:, of course!

Replace the current code for ccTouchBegan: with the following:

-(BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
    CGPoint touchLocation = [touch locationInView:touch.view];
    touchLocation = [[CCDirector sharedDirector] convertToGL:touchLocation];
    
    [self togglePopupMenu:touchLocation];
    return YES;
}

The first two lines calculate the location where the user tapped the screen. That position is then used to display or hide the popup menu.

Build and run your project and switch to the level editor. You should now be able to show and hide your newly created popup menu, as shown below:

The current look of your popup menu

So what do you think? Did you notice anything awkward about the presentation of the menu as you tapped around the top or edges of the screen?

Depending on how close you click to the edge, there are a few places where the menu is cut off by the edge of the screen! It looks unpolished, to say the least.

To correct this, you'll need to adjust the background graphic and its anchor point so that the tip of arrow always points directly at the tap position and the menu box is always fully visible.

Barbara Reichart

Contributors

Barbara Reichart

Author

Over 300 content creators. Join our team.