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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Create Your Own Level Editor: Part 2/3
65 mins
- Getting Started
- Creating The LevelEditor Class
- Adding the Editor Menu
- Drawing Ropes On-screen
- Drawing the Game Objects On-screen
- Detecting User Inputs: Touch, Movement and Long Press
- Adding the Popup Editor Menu
- Positioning the Menu
- Implementing Dynamic Popup Menu Positioning
- Adding New Game Objects - Pineapples
- Adding New Game Objects - Ropes
- Where To Go From Here?
Drawing Ropes On-screen
You now need to implement the code that draws the level elements on the screen. For the pineapple, this is relatively easy. However, the ropes are slightly more complex.
You might as well tackle the harder problem first — start with the ropes! :]
The ropes can have variable length and orientation. The easiest way to draw them would be to use one sprite and scale and rotate it accordingly. However, this can end up looking rather ugly, as shown in the example below:
What you want is to have the ropes sized consistently. The easiest way to do this is to use a small rope segment graphic that is chained or tiled so as to create a rope of any desired length.
In order to encapsulate this drawing code, create a new Objective-C class in the LevelEditor group. Name the class RopeSprite and make it a sub-class of NSObject.
Switch to RopeSprite.h and replace its contents with the following:
#import "cocos2d.h"
#import "Constants.h"
#import "RopeModel.h"
@interface RopeSprite : NSObject
@property (readonly, getter = getID) int id;
-(id)initWithParent:(CCNode*)parent andRopeModel:(RopeModel*)aRopeModel;
-(int)getID;
-(CGPoint)getAnchorA;
-(CGPoint)getAnchorB;
-(int)getBodyAID;
-(int)getBodyBID;
-(void)setAnchorA:(CGPoint)anchorA;
-(void)setAnchorB:(CGPoint)anchorB;
@end
This defines some essential methods and properties. Most of the methods are simple getters and setters.
You might ask, “Why aren’t we simply using properties?”. Properties are easy ways to implement getters and setters; however, in this case you want to add custom code to the setter so that it redraws the rope when a property is changed.
Note: You could have used properties and then overridden the getter and setter in your implementation. However in this tutorial the getter and setter approach used above does a better job of explaining each implementation step in detail.
Note: You could have used properties and then overridden the getter and setter in your implementation. However in this tutorial the getter and setter approach used above does a better job of explaining each implementation step in detail.
Switch to RopeSprite.m and replace its contents with the following code:
#import "RopeSprite.h"
#import "CoordinateHelper.h"
@interface RopeSprite () {
CCSprite* ropeSprite;
RopeModel* ropeModel;
}
@end
@implementation RopeSprite
@end
The above adds some private variables to RopeSprite
. The CCSprite
is required to draw the rope. Additionally, you need a RopeModel
, which gives you all the placement information for the rope.
Now add the following to RopeSprite.m:
-(id)initWithParent:(CCNode*)parent andRopeModel:(RopeModel*)aRopeModel {
self = [super init];
if (self) {
ropeModel = aRopeModel;
ropeSprite = [CCSprite spriteWithFile:@"rope_texture.png"];
ccTexParams params = {GL_LINEAR,GL_LINEAR,GL_REPEAT,GL_CLAMP_TO_EDGE};
[ropeSprite.texture setTexParameters:¶ms];
[self updateRope];
[parent addChild:ropeSprite];
}
return self;
}
The above method takes two parameters. The parent parameter is the reference to the node on which the rope will be drawn. This parameter is followed by the model, which contains all information required to draw the rope. The code then stores the rope model in an instance variable.
Next, the code creates a CCSprite
from the file “rope_texture.png”. This image file contains only one small segment of the rope. The whole rope is then drawn by repeating this sprite over and over.
You could accomplish the same thing by drawing the same sprite several times, but this would require a large amount of code. Instead, it’s much more efficient to set up a rope texture to handle the drawing.
Textures in OpenGL have a few parameters that you might not be familiar with. The ccTexParams
struct has the following fields:
minFilter
is used when the surface the texture is drawn upon is smaller than the texture itself, whereas magFilter
is used when the texture is smaller than the surface.
The level editor uses GL_LINEAR
for both minFilter
and magFilter
. GL_LINEAR
returns the weighted average of the four texture elements that are closest to the center of the pixel being textured. In other words, with this setting OpenGL uses linear interpolation to calculate the values of the pixels.
The wrapS
and wrapT
parameters let you set the wrapping behavior of the texture in the s
and t
coordinate.
Note: If you haven’t worked with OpenGL directly, you’re probably wondering what s
and t
stand for. In OpenGL the x, y, and z coordinate are used to define the position of an object in 3D space. As it would be confusing to reuse the x
and y
coordinate names for the texture coordinates, they’re simply re-labeled s
and t
.
Note: If you haven’t worked with OpenGL directly, you’re probably wondering what s
and t
stand for. In OpenGL the x, y, and z coordinate are used to define the position of an object in 3D space. As it would be confusing to reuse the x
and y
coordinate names for the texture coordinates, they’re simply re-labeled s
and t
.
You want the rope to repeat itself along the s-axis, which is done using the GL_REPEAT
value. Along the t-axis, you want it to display just once without any scaling. For this, you use the GL_CLAMP_TO_EDGE
value.
Note: There are several other filter values for texture parameters. You can look them up in the OpenGL Documentation.
Note: There are several other filter values for texture parameters. You can look them up in the OpenGL Documentation.
You now have a CCSprite
with a texture which will repeat along one axis while it stays unchanged along the other axis. That’s pretty neat! :]
The only thing you need to do now in order to display the rope properly on the screen is to update its length and rotation. This magic takes place in updateRope
, which you’ll implement below.
Add the updateRope
implementation to RopeSprite.m as shown below:
-(void)updateRope {
CGPoint anchorA = [CoordinateHelper levelPositionToScreenPosition:ropeModel.anchorA];
CGPoint anchorB = [CoordinateHelper levelPositionToScreenPosition:ropeModel.anchorB];
float distance = ccpDistance(anchorA, anchorB);
CGPoint stickVector = ccpSub(ccp(anchorA.x,anchorA.y),ccp(anchorB.x,anchorB.y));
float angle = ccpToAngle(stickVector);
ropeSprite.textureRect = CGRectMake(0, 0, distance, ropeSprite.texture.pixelsHigh);
ropeSprite.position = ccpMidpoint(anchorA, anchorB);
ropeSprite.rotation = -1 * CC_RADIANS_TO_DEGREES(angle);
}
The code above uses ccpDistance
to calculate the distance between the two anchor points, which equals the length of the rope. It then calculates the rotation angle of the rope with ccpToAngle
, which takes a vector and translates it into an angle in radians.
Next, the code changes the rope texture’s rectangle using the rope length calculated above. Then it updates the rope’s position. Keep in mind that the anchor point is at the middle of the ropeSprite
, so its position is exactly in the middle of the two anchor points.
Finally, the code sets the angle of the rope sprite. As the angle is currently in radians, you need to transform it into degree notation with CC_RADIANS_TO_DEGREES
.
This is how you draw a rope having arbitrary length without using a texture that is stretched in ugly ways. While it requires more code than simple scaling, it looks far, far better. As an added bonus, you’ve probably learned something along the way about OpenGL that you can use in your other projects!
The RopeSprite
class is almost done. All that’s left is to add the getter and setter calls.
Add the following code to RopeSprite.m:
-(void)setAnchorA:(CGPoint)theAnchorA {
ropeModel.anchorA = [CoordinateHelper screenPositionToLevelPosition:theAnchorA];
[self updateRope];
}
-(void)setAnchorB:(CGPoint)theAnchorB {
ropeModel.anchorB = [CoordinateHelper screenPositionToLevelPosition:theAnchorB];
[self updateRope];
}
-(int)getID {
return ropeModel.id;
}
-(CGPoint)getAnchorA {
return [CoordinateHelper levelPositionToScreenPosition:ropeModel.anchorA];
}
-(CGPoint)getAnchorB {
return [CoordinateHelper levelPositionToScreenPosition:ropeModel.anchorB];
}
-(int)getBodyAID {
return ropeModel.bodyAID;
}
-(int)getBodyBID {
return ropeModel.bodyBID;
}
The getters and setters simply call updateRope
whenever the position of one of the rope anchors is changed. This will redraw the rope to reflect the changes.
Build and run your app! You should now see…oh, wait. That’s just the same empty screen as before, isn’t it?
Why don’t you see anything new?
You’ve implemented the RopeSprite
class — but you’re not using it yet to draw anything on screen! That comes next. :]