How To Make a Tile Based Game with Cocos2D
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!
이 강좌는 Tiled프로그램으로 맵을 어떻게 만드는지, 맵을 게임에 추가 하는 방법 , 플레이어를 따라서 맵을 스크롤 하는 방업, 오브젝트(장애물, 아이템 줍기)레이어를 사용하는 방법 이다.
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!
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.
[xcode실행, 템플릿 cocos2d application 선택 ]
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 / 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.
메뉴 - File\New 맵만들기
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:
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. / 직교좌면, xy좌표
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:
To get the image, just click Browse and navigate to your TestGame folder, and pick the tmw_desert_spacing.png 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.png, 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.
--- 타일셋 불러온 윈도우
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.
맵그리기
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!
그리기 버튼 / 줌인, 줌아웃
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…”.
저장하기 / .TMX 파일
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!
타일 맵을 cocos2d 화면에 추가하기
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:
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.
오브젝트 속성 설정 - 나중에 게임소스에소 불러 올때 비교
Then right click the gray object you just added, and click “Properties”. Give it a name of “SpawnPoint” and click OK:
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.png"]; _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:
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:
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!
닌자이동 만들기
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.
'온라인게임' 카테고리의 다른 글
Cocos2D에서 사용하는 스프라이트 애니메이션 만들기 2/2 (0) | 2011.04.12 |
---|---|
Cocos2D에서 사용하는 스프라이트 애니메이션 만들기 1/2 (0) | 2011.04.12 |
Boost MemoryPool (0) | 2011.04.05 |
cocos2d-x 2d 그래픽 엔진 / 모바일 / 아이폰 / c++ win32 (0) | 2011.04.01 |
데이타그리드뷰에서 엑셀 저장 c샵 프로그래밍 (0) | 2011.03.29 |