iphone游戏编程-How To Make a Tile Based Game with Cocos2D

源自:http://www.raywenderlich.com/1163/how-to-make-a-tile-based-game-with-cocos2d

 

In this 2-part tutorial, we’re going to cover how to make a tile based game with Cocos2D and the Tiled map editor. We will do so by creating a simple tile-based game where a ninja explores a desert in search of tasty watermelon-things!

In this part of the tutorial, we’ll cover 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 second 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.

If you haven’t already, you may wish to start with the How To Make A Simple iPhone Game with Cocos2D Tutorial Series, since that covers most of the basics that we’ll build upon here.

Ok, so let’s have some fun with tile maps!

Mmm, that was tasty!

Mmm, that was tasty!

Creating a Project Skeleton

We’re going to start by creating a skeleton project to make sure we have all of the files we need for the rest of the project in the right spot.

So load up XCode, pick “File/New Project…” pick the template for a cocos2d Application, and create a project named TileGame.

Next, download this zip file of resources for the game. The zip file contains the following:

  • A sprite we’ll use for our player object. This may look familiar from the How to Make A Simple iPhone Game with Cocos2D Tutorial!
  • Some sound effects we’ll be using that I made with the excellent cxfr utility.
  • Some background music I made with Garage Band (see this post for more info).
  • The tile set we’ll be using – it actually comes with the map editor we’ll be using, but I thought it would be easier to include it with everything else.
  • Some additional “special” tiles we’ll explain a bit later.

Once you have the resources downloaded, unzip it and drag all of the resources to the “Resources” group in your project. Verify “Copy items into destination group’s folder (if needed)” is checked, reference type is “Relative to Project”, and click Add.

If all works well, all of the files should be listed in your project. That’s it for now – time to have some fun and make our map!

Making a Map with Tiled

Cocos2D supports maps created with the open source Tiled Map Editor and saved in TMX format.

If you visit the above link, you’ll see there are two versions of Tiled – one written with the Qt application framework, and one written with Java. There are two versions because Tiled was written first in Java, and they are porting it over to Qt.

Which version you use is largely up to you. In this tutorial we will cover using the Qt version because that is the development mainline for Tiled from now on, but some people like to use the Java version because not all of the old features have been completely ported over yet.

So anyway – if you would like to follow along, download the Qt version and install and run the app. Go to File/New, and fill in the dialog as follows:

New Map Dialog in Tiled Map Editor

In the orientation section, you can choose between Orthogonal (think: the Legend of Zelda) or Isometric (think: Disgaea). We’re going to pick Orthogonal here.

Next you get to set up the map size. Keep in mind that this is in tiles, not pixels. We are going to make a smallish sized map, so choose 50×50 here.

Finally you specify the tile width and height. What you choose here depends on the tile set that your artist will be making. For this tutorial, we are going to use some sample tiles that come with the Tiled editor which are 32×32, so choose that.

Next, we have to add the tile set that we’ll be using to draw our map. Click on “Map” in the menu bar, “New Tileset…”, and fill in the dialog as follows:

New Tileset Dialog in Tiled Map Editor

To get the image, just click Browse and navigate to your TestGame folder, and pick the tmw_desert_spacing.jpg file that you downloaded from the resource zip and added to your project. It will automatically fill out the name based on the filename.

You can leave the width and height as 32×32 since that is the size of the tiles. As for margin and spacing, I couldn’t find any good documentation on the exact meaning of these, but this is what I think they mean:

  • Margin is how many pixels Tiled should skip (for both width and height) for the current tile before it starts looking for actual tile pixels.
  • Spacing is how many pixels Tiled should advance (for both width and height) after it reads the actual tile pixels to get to the next tile data.

If you take a look at tmw_desert_spacing.jpg, you’ll see that each tile has a 1px black border around it, which is would explain the settings of margin and spacing as 1.

Zoomed screenshot of desert tiles

Once you click OK, you will see the tiles show up in the Tilesets window. Now you can start drawing away! Simply click the “Stamp” icon in the toolbar, then click a tile, then click anywhere on the map you’d like to place a tile.

Using the Stamp Tool in Tiled

So go ahead and draw yourself a map – be as creative as you’d like! Make sure to add at least a couple buildings on the map, because we’ll need something to collide into later!

Drawing Our Map in Tiled

Some handy shortcuts to keep in mind:

  • You can drag a box around a series of tiles in the Tileset picker, to put down multiple adjacent tiles at the same time.
  • You can use the paint button in the toolbar to paint the entire background with a base tile.
  • You can zoom in and out with “View/Zoom In…” and “View/Zoom Out…”.

Once you’re done drawing the map, double click on the Layer in Layers (which probably says “Layer 1″ right now), and change the name to “Background”. Then click “File/Save” and save the file to the Resources folder of your TileMap project, and name the file “TileMap.tmx”.

We’re going to do some more stuff with Tiled later, but for now let’s get this map into our game!

Adding the Tiled Map to our Cocos2D Scene

First thing first, right click on Resources, click “Add/Existing Files…” and add the new TileMap.tmx file you just created to your project.

Open up HelloWorldScene.h, and add a couple of member variables/properties we’ll need:

// Inside the HelloWorld class declaration
CCTMXTiledMap *_tileMap;
CCTMXLayer *_background;
 
// After the class declaration
@property (nonatomic, retain) CCTMXTiledMap *tileMap;
@property (nonatomic, retain) CCTMXLayer *background;

Then make the following changes to HelloWorldScene.m:

// Right after the implementation section
@synthesize tileMap = _tileMap;
@synthesize background = _background;
 
// In dealloc
self.tileMap = nil;
self.background = nil;
 
// Replace the init method with the following
-(id) init
{
    if( (self=[super init] )) {
 
        self.tileMap = [CCTMXTiledMap tiledMapWithTMXFile:@"TileMap.tmx"];
        self.background = [_tileMap layerNamed:@"Background"];
 
        [self addChild:_tileMap z:-1];
 
    }
    return self;
}

Here we make a call to the CCTMXTiledMap file, instructing it to create a map from the map file we created with Tiled.

Some quick background on CCTMXTiledMap. It’s a CCNode, so you can set its position, scale, etc. The children of the node are the layers of the map, and there’s a helper function where you can look the up by name – which we do here to get the background. Each layer is a subclass of CCSpriteSheet for performance reasons – but this also means that you can only have one tileset per layer.

So all we do here is save a reference to the tile map and the background layer, then add the tile map to the HelloWorld layer.

And that’s it! Compile and run the code, and you should see the bottom left corner of your map:

Screenshot of the Tile Map Edge on our iPhone

Not bad! But for this to be a game, we need three things: a) a player, b) a starting point to put the player, and c) to move our view so that we are looking at the player.

And this is where it gets tricky. So let’s tackle this next!

Tiled Object Layers and Setting Tile Map Position

Tiled supports two kinds of layers – tile layers (which is what we’ve been working with so far), and object layers.

Object layers allow you to draw boxes around portions of the maps to specify areas where things might happen. For example, you might make an area where monsters spawn, or an area that is deadly to enter. In our case, we’re going to create an area for our “spawn point” for our player.

So go to the menu bar in Tiled and pick “Layer/Add Object Layer…”, name the layer “Objects”, and click OK. If you draw on the map, you’ll notice it doesn’t draw a tile, instead it draws a weird looking gray shape, which you can expand to cover multiple tiles or move around.

We just want to select one tile for the player to start in. So choose somewhere on your map and click the tile. The size of the box doesn’t really matter, since we’ll just be using the x, y coordinates.

Creating an Object Layer in Tiled

Then right click the gray object you just added, and click “Properties”. Give it a name of “SpawnPoint” and click OK:

Object Properties in Tiled

Supposedly, you can get fancy here and set the Type of the object to a Cocos2D class name and it will create an object of that type for you (such as CCSprite), but I couldn’t find where it was doing that in the source code. Update: Tyler from GeekAndDad.com pointed out that the code used to be in a previous version of Cocos2D, but was removed due to issues with it a while back.

Anyway – we’re just going to leave the type blank, which will create an NSMutableDictionary for us where we can access the various aspects of the object, including the x, y coordinates.

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

// Inside the HelloWorld class declaration
CCSprite *_player;
 
// After the class declaration
@property (nonatomic, retain) CCSprite *player;

Then make the following changes to HelloWorldScene.m:

// Right after the implementation section
@synthesize player = _player;
 
// In dealloc
self.player = nil;
 
// Inside the init method, after setting self.background
CCTMXObjectGroup *objects = [_tileMap objectGroupNamed:@"Objects"];
NSAssert(objects != nil, @"'Objects' object group not found");
NSMutableDictionary *spawnPoint = [objects objectNamed:@"SpawnPoint"];        
NSAssert(spawnPoint != nil, @"SpawnPoint object not found");
int x = [[spawnPoint valueForKey:@"x"] intValue];
int y = [[spawnPoint valueForKey:@"y"] intValue];
 
self.player = [CCSprite spriteWithFile:@"Player.jpg"];
_player.position = ccp(x, y);
[self addChild:_player]; 
 
[self setViewpointCenter:_player.position];

Ok let’s stop for a second and explain the bit about the object layer and object groups. First note that you retrieve object layers via the objectGroupNamed method on the CCTMXTiledMap object (rather than layerNamed). It returns a special CCTMXObjectGroup object.

We then call the objectNamed method on the CCTMXObjectGroup to get a NSMutableDictionary containing a bunch of useful info about the object, including x and y coordinates, width, and height. In this case all we care about is the x,y coordinates, so we pull those out and set that as the position of our player sperite.

At the end we want to set the view to focus on where the player is. So now add the following new method to the file:

-(void)setViewpointCenter:(CGPoint) position {
 
    CGSize winSize = [[CCDirector sharedDirector] winSize];
 
    int x = MAX(position.x, winSize.width / 2);
    int y = MAX(position.y, winSize.height / 2);
    x = MIN(x, (_tileMap.mapSize.width * _tileMap.tileSize.width) 
        - winSize.width / 2);
    y = MIN(y, (_tileMap.mapSize.height * _tileMap.tileSize.height) 
        - winSize.height/2);
    CGPoint actualPosition = ccp(x, y);
 
    CGPoint centerOfView = ccp(winSize.width/2, winSize.height/2);
    CGPoint viewPoint = ccpSub(centerOfView, actualPosition);
    self.position = viewPoint;
 
}

Ok, let’s explain this a bit too. Imagine this function is setting the center of a camera. We allow the user to pass in any x,y coordinate in the map here – but if you think about it there are some points that we don’t want to be able to show – for example we don’t want the screen to extend beyond the edges of the map (where it would just be blank space!)

For example, take a look at this diagram:

Diagram of tile map vs. viewport in Cocos2D

See how if the center of the camera is less than winSize.width/2 or winSize.height/2, part of the view would be off the screen? Similarly, we need to check the upper bounds as well, and that’s exactly waht we do here.

Now so far we’ve been treating this function as if it was setting the center of where a camera was looking. However… that isn’t exactly what we’re doing. There is a way in Cocos2D to manipulate the camera of a CCNode, but using that can make things more difficult than the solution we’re going to use: moving the entire layer instead.

Take a look at this diagram:

Diagram of how to move layer to fit within view in Cocos2D

Imagine a big world, and we’re looking at the coordinates from 0 to winSize.height/width. The center of our view is centerOfView, and we know where we want the center to be (actualPosition). So to get the actual position to match up to the center of view, all we do is slide the map down to match!

This is accomplished by subtracting the actual position from the center of view, and then setting the HelloWorld layer to that position.

Phew! Enough theory – let’s see it in action! Compile and run the project, and if all goes well you should see your ninja in the scene, with the view moved to show him strutting his stuff!

Screenshot of our scene centered on our Ninja

Making the Ninja Move

We’re off to a good start, but our ninja is just sitting there! And that’s not very ninja-like.

Let’s make the ninja move simply by moving him in the direction the user taps. Add the following code to HelloWorldScene.m:

// Inside init method
self.isTouchEnabled = YES;
 
-(void) registerWithTouchDispatcher
{
	[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self 
            priority:0 swallowsTouches:YES];
}
 
-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
	return YES;
}
 
-(void)setPlayerPosition:(CGPoint)position {
	_player.position = position;
}
 
-(void) ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event
{
 
    CGPoint touchLocation = [touch locationInView: [touch view]];		
    touchLocation = [[CCDirector sharedDirector] convertToGL: touchLocation];
    touchLocation = [self convertToNodeSpace:touchLocation];
 
    CGPoint playerPos = _player.position;
    CGPoint diff = ccpSub(touchLocation, playerPos);
    if (abs(diff.x) > abs(diff.y)) {
        if (diff.x > 0) {
            playerPos.x += _tileMap.tileSize.width;
        } else {
            playerPos.x -= _tileMap.tileSize.width; 
        }    
    } else {
        if (diff.y > 0) {
            playerPos.y += _tileMap.tileSize.height;
        } else {
            playerPos.y -= _tileMap.tileSize.height;
        }
    }
 
    if (playerPos.x <= (_tileMap.mapSize.width * _tileMap.tileSize.width) &&
        playerPos.y <= (_tileMap.mapSize.height * _tileMap.tileSize.height) &&
        playerPos.y >= 0 &&
        playerPos.x >= 0 ) 
    {
            [self setPlayerPosition:playerPos];
    }
 
    [self setViewpointCenter:_player.position];
 
}

First, we set our layer as touch enabled in the init method. Then we override the registerWithTouchDispatcher method to register ourselves to handle targed touch events. This will result in ccTouchBegan/ccTouchEnded methods being called (singular case), instead of ccTouchesBegan/ccTouchesEnded methods (plural case).

You may wonder why even bother with this, since we used the ccTouchesBegan/ccTouchesEnded method just fine in the How to Make A Simple iPhone Game with Cocos2D Tutorial. It’s true, in this case it doesn’t matter either way. However, I wanted to introduce everyone to this method in case you hadn’t seen it already, because it has two significant advantages (these are listed verbatim from the cocos2D source):

  • “You don’t need to deal with NSSets, the dispatcher does the job of splitting them. You get exactly one UITouch per call.”
  • “You can *claim* a UITouch by returning YES in ccTouchBegan. Updates of claimed touches are sent only to the delegate(s) that claimed them. So if you get a move/ended/cancelled update you’re sure it’s your touch. This frees you from doing a lot of checks when doing multi-touch.”

Anyway, inside our ccTouchEnded location, we convert the location to view coordinates and then to GL coordinates as usual. What is new is we call [self convertToNodeSpace:touchLocation].

This is because the touch location will give us coordinates for where the user tapped inside the viewport (for example 100,100). But we might have scrolled the map a good bit so that it actually matches up to (800,800) for example. So calling this method offsets the touch based on how we have moved the layer.

Next, we figure out the difference between the touch and the player position. We have to choose a direction based on the touch, so first we decide whether to move up/down or side to side based on whichever is the greatest distance away. Then we just see if it’s positive or negative to move up or down.

We adjust the player position accordingly, and then set the viewpoint center to be the player position, which we already wrote in the last section!

Update: Note we have to add a safety check to make sure we’re not moving our player off the map as well! This was pointed out by Geek & Dad from the comments section – thanks!

So compile and run the project and try it out! You should now be able to tap the screen to move the ninja around.

Our Ninja can now move around the map!

Where To Go From Here?

That’s all for this part of the tutorial. At this point you should know the basics about creating maps and importing them into your game.

Here’s a sample project with the code we’ve developed so far.

Next check out part 2 of the tutorial, where we show how to add collision detection into the map, to prevent our ninja from happily walking through the walls!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值