您的位置:首页 > 移动开发 > Cocos引擎

iphone游戏开发-Collisions and Collectables: How To Make a Tile Based Game with Cocos2D Part 2

2011-06-22 18:27 811 查看
源自:http://www.raywenderlich.com/1186/collisions-and-collectables-how-to-make-a-tile-based-game-with-cocos2d-part-2




Mmm, that was tasty!

This is the second part of a 2-part tutorial where we cover how to make a tile based game with Cocos2D and the Tiled map editor. We are creating a simple tile-based game where a ninja explores a desert in search of tasty watermelon-things!

In the first part of the tutorial, we covered how to create a map with Tiled, how to add the map to the game, how to scroll the map to follow the player, and how to use object layers.

In the this part of the tutorial, we’ll cover how to make collidable areas in the map, how to use tile properties, how to make collectable items and modify the map dynamically, and how to make sure your ninja doesn’t overeat.

So let’s pick up where we left off last time and make our map a bit more game-like!

Tiled Maps and Collisions

You may have noticed that currently the ninja can move right through walls and obstacles with no problem at all. He is a ninja, but even ninjas aren’t that good!

So we need to figure out a way to mark some tiles as “collidable” so we can prevent the player from moving into those positions. There are many possible solutions to this (including using object layers), but I’m going to show you a new technique that I think is effective and also a good learning exercise – using a meta layer and layer properties.

Let’s dive right in! Load up Tiled again, click “Layer/Add Tile Layer…”, name the Layer “Meta”, and click OK. This is the layer we will be putting a few fake tiles in to indicate “special tiles”.

So now we need to add our special tiles. Click “Map/New Tileset…”, browse to meta_tiles.jpg in your Resources folder, and click Open. Set the Margin and Spacing to 1 and click OK.

You should see a new tab in your Tilesets area for meta_tiles. Open it up and you’ll see two tiles: red and green.





There is nothing at all special about these tiles – I just made a simple image with two red and green tiles with partial transparency. However we’re going to decide that red means “collidable” (we’ll use green later on), and paint our scene appropriately.

So make sure the Meta layer is selected, choose the stamp tool, choose the red tile, and paint over any object that you want the ninja to collide with. When you’re done it might look like the following:





Next, we need to set a property on the tile to flag it so we can recognize in code that this is the tile that is “collidable.” Right click on the red tile in the Tilesets section, and click “Properties…”. Add a new property for “Collidable” set to “True” like the following:





Save the map and return to XCode. Make the following changes to HelloWorldScene.h:

// Inside the HelloWorld class declaration
CCTMXLayer *_meta;

// After the class declaration
@property (nonatomic, retain) CCTMXLayer *meta;

Then make the following changes to HelloWorldScene.m:

// Right after the implementation section
@synthesize meta = _meta;

// In dealloc
self.meta = nil;

// In init, right after loading background
self.meta = [_tileMap layerNamed:@"Meta"];
_meta.visible = NO;

// Add new method
- (CGPoint)tileCoordForPosition:(CGPoint)position {
int x = position.x / _tileMap.tileSize.width;
int y = ((_tileMap.mapSize.height * _tileMap.tileSize.height) - position.y) / _tileMap.tileSize.height;
return ccp(x, y);
}

Ok let’s stop here a second. We declare a member/variable for the meta layer as usual, and load a reference from the tile map. Note that we mark the layer as invisible since we don’t want to see these objects, they are for annotating what is collidable only.

Next we add a new helper method that helps us convert x,y coordinates to “tile coordinates”. Each of the tiles has a coordinate, starting with (0,0) for the upper left and (49,49) for the bottom right (in our case).





The above screenshot is from the Java version of Tiled, btw. Showing the coordinates for tiles is a feature I don’t think they’ve ported to the Qt version yet.

Anyway, some of the functions we’re about to use require tile coordinates rather than x,y coordintes, so we need a way to convert the x,y coordinates into tile coordinates. This is exactly what this function does!

Getting the x coordinate is easy – we just divide it by the width of a tile. To get the y coordinate, we have to flip things around because in Cocos2D (0,0) is at the bottom left, not the top left.

Next replace the contents of setPlayerPosition with the following:

CGPoint tileCoord = [self tileCoordForPosition:position];
int tileGid = [_meta tileGIDAt:tileCoord];
if (tileGid) {
NSDictionary *properties = [_tileMap propertiesForGID:tileGid];
if (properties) {
NSString *collision = [properties valueForKey:@"Collidable"];
if (collision && [collision compare:@"True"] == NSOrderedSame) {
return;
}
}
}
_player.position = position;

Here we convert the x,y coordinates for the player to tile coordinates. Then we use the tileGIDAt function in the meta layer to get the GID at the specified tile coordinate.

Huh, what’s a GID? GID stands for “globally unique identifier” (I think). But in this case I like to think of it as the id for the tile we’re using, which would be the red square if that’s where we’re trying to move.

We then use the GID to look up properties for that tile. It returns a dictionary of properties, so we look through to see if “Collidable” is set to true, and if it is we return immediately, hence not setting the player position and making the move invalid.

And that’s it! Compile and run the project, and you should now no longer be able to walk through any tiles you painted red:





Modifying the Tiled Map Dynamically

So far our ninja is having a fine time exploring, but this world is a little dull. There’s simply nothing to do!

Plus our ninja looks a bit hungry. So let’s spice things up by giving our ninja something to eat.

For this to work, we’re going to have to create a foreground layer for any objects we want the user to collect. That way, we can simply delete the tile from the foreground layer when the ninja picks it up, and the background will show through.

So open up Tiled, go to “Layer/Add Tile Layer…”, name the layer “Foreground”, and click OK. Make sure the Foreground layer is selected, and add a couple collectibles to your map. I liked to use the tile that looks like a Watermelon or something to me.





Now, we need to mark those tiles as collectible, similarly to how we marked some of the tiles as collidable. Select the meta layer, switch over to the meta_tiles, and paint a green tile over each of your collectables. You’ll have to click “Layer/Move Layer Up” to make sure the meta layer is on top so that the green is visible.





Next, we need to add the property to the tile to mark it as collectable. Right click on the green tile in the Tilesets section, click “Properties…” and add a new property with name “Collectable”, value “True”.

Save the map and go back to XCode. Make the following changes to HelloWorldScene.h:

// Inside the HelloWorld class declaration
CCTMXLayer *_foreground;

// After the class declaration
@property (nonatomic, retain) CCTMXLayer *foreground;

Then make the following changes to HelloWorldScene.m:

// Right after the implementation section
@synthesize foreground = _foreground;

// In dealloc
self.foreground = nil;

// In init, right after loading background
self.foreground = [_tileMap layerNamed:@"Foreground"];

// Add to setPlayerPosition, right after the if clause with the return in it
NSString *collectable = [properties valueForKey:@"Collectable"];
if (collectable && [collectable compare:@"True"] == NSOrderedSame) {
[_meta removeTileAt:tileCoord];
[_foreground removeTileAt:tileCoord];
}

Here is standard stuff to keep a reference to the foreground layer. The new thing is we check to see if the tile the player is moving to has the “Collectable” property. If it does, we use the removeTileAt method to remove the tile from both the meta layer and the foreground layer.

Compile and run the project, and now your ninja will be able to dine on tasty-melon-thingie!





Creating a Score Counter

Our ninja is happy and fed, but as players we’d like to know how many melons he’s eaten. You know, we don’t want him getting fat on us.

Usually we’d just add a label to our layer and be done with it. But wait a minute – we’re moving the entire layer all the time, that will screw us up, oh noes!

This is a good opportunity to show how to use multiple layers in a scene – this is the type of situation they are built for. We’ll keep our HelloWorld layer as we’ve been doing, but we’ll make an additional Layer called HelloWorldHud to display our label. (Hud means heads up display).

Of course, our two layers need some method of communicating – the Hud layer will want to know when the ninja snacks on a melon. There are many many ways of getting the two layers to communicate, but we’ll go with the most simple way possible – we’ll hand the HelloWorld layer a pointer to the HelloWorldHud layer, and it can call a method to notify it when the ninja snacks.

So add the following to HelloWorldScene.h:

// Before HelloWorld class declaration
@interface HelloWorldHud : CCLayer
{
CCLabel *label;
}

- (void)numCollectedChanged:(int)numCollected;
@end

// Inside HelloWorld class declaration
int _numCollected;
HelloWorldHud *_hud;

// After the class declaration
@property (nonatomic, assign) int numCollected;
@property (nonatomic, retain) HelloWorldHud *hud;

And make the following changes to HelloWorldScene.m:

// At top of file
@implementation HelloWorldHud

-(id) init
{
if ((self = [super init])) {
CGSize winSize = [[CCDirector sharedDirector] winSize];
label = [CCLabel labelWithString:@"0" dimensions:CGSizeMake(50, 20)
alignment:UITextAlignmentRight fontName:@"Verdana-Bold"
fontSize:18.0];
label.color = ccc3(0,0,0);
int margin = 10;
label.position = ccp(winSize.width - (label.contentSize.width/2)
- margin, label.contentSize.height/2 + margin);
[self addChild:label];
}
return self;
}

- (void)numCollectedChanged:(int)numCollected {
[label setString:[NSString stringWithFormat:@"%d", numCollected]];
}

@end

// Right after the HelloWorld implementation section
@synthesize numCollected = _numCollected;
@synthesize hud = _hud;

// In dealloc
self.hud = nil;

// Add to the +(id) scene method, right before the return
HelloWorldHud *hud = [HelloWorldHud node];
[scene addChild: hud];

layer.hud = hud;

// Add inside setPlayerPosition, in the case where a tile is collectable
self.numCollected++;
[_hud numCollectedChanged:_numCollected];

Nothing too fancy here. Our second layer derives from CCLayer and just adds a simple label to the bottom right corner. We modify the scene to add the second layer to the scene as well, and pass the HelloWorld layer a reference to the Hud. Then we modify the HelloWorldLayer to call a method on the Hud when the count changes, so it can update the label accordingly.

Compile and run the project, and if all goes well you should see a melon counter in the bottom right!





Gratuituous Sound Effects and Music

You know this wouldn’t be a game tutorial from this site without completely unnecessary but fun sound effects and music :]

Simply make the following changes to HelloWorldScene.m:

// At top of file
#import "SimpleAudioEngine.h"

// At top of init for HelloWorld layer
[[SimpleAudioEngine sharedEngine] preloadEffect:@"pickup.caf"];
[[SimpleAudioEngine sharedEngine] preloadEffect:@"hit.caf"];
[[SimpleAudioEngine sharedEngine] preloadEffect:@"move.caf"];
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"TileMap.caf"];

// In case for collidable tile
[[SimpleAudioEngine sharedEngine] playEffect:@"hit.caf"];

// In case of collectable tile
[[SimpleAudioEngine sharedEngine] playEffect:@"pickup.caf"];

// Right before setting player position
[[SimpleAudioEngine sharedEngine] playEffect:@"move.caf"];

Now our ninja can groove happy as he eats!

Where To Go From Here?

That’s it for this tutorial series, at least for now. You should have a good grasp on the most important concepts related to using tile maps in Cocos2D at this point.

Here is a copy of the Tile-Based Cocos2D game that we’ve developed so far.

If you enjoyed this series, our good friends from Geek and Dad have developed a follow-up to the series: Enemies and Combat: How To Make a Tile-Based Game with Cocos2D Part 3! Check it out to see how you can extend the game to add enemies, projectiles, and a win/lose scene!

If you have any additional tips or suggestions for how to effectively use Tiled or tile-based maps in Cocos2D effectively, or if you’ve used or are planning to use tile-based maps in your projects, please share below!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: