Chapter 8 Map Design and Implementation
This is the tenth chapter. There are two chapters in the above plan. One is to complete an asteroid game as a summary, just like the fourth chapter, and summarize the usage of Sprite, demonstrate how to handle the NPC and bullets. However, when I wrote the last example in chapter 7, I expanded the simple Collision Detection example and added the NPC and bullets, which is basically similar to the asteroid function, so we cut down the original asteroid. The other chapter explains the life cycle of a program. The content is relatively simple, but considering that tiledlayer has been introduced in Sprite in the previous chapter, the map should be more coherent, therefore, the lifecycle is moved to the back end.
So let's take a look at the map design and implementation.
If the map we need is small and small, we can draw the whole map on one image. However, if there are a lot of maps, it will be very difficult to draw and manage maps. In this case, we need to use another technology-the tile ). The so-called tile is to extract the public elements in the map, and then combine these elements to form a complete map. This is the main content to be introduced in this chapter.
All elements (16x16 pixels) that constitute a tank war map ):
Next let's look at a scenario in the game:
As you can see, the entire game scenario is composed of the above tile. In this way, the task in front of us is very simple: divide the map into several grids according to the size of the tile, and fill the corresponding tile in the grid.
When talking about Sprite, we know that we can combine the key serial numbers in an image, and then we can use the serial numbers (refer to the content of the animation in the previous chapter ), this method also applies to tile. The 2D map makes it easy for us to think of 2D arrays. That is to say, we can put the tile number into a two-dimensional array, so that we can display the entire map by going through the array elements like Sprite.
The above game is used as an example. There are 13 rows and 13 columns in total. we can define a two-dimensional array of 13x13 (because there is a blank area on the map, we start the tile number from 1, 0 ).
Let's find gameview_old.java and add the member variable map [] []:
Int[] [] Map = {
{0, 0, 0, 0, 0, 0, 0, 0, 0 },
{, 0 },
{, 0 },
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{, 2 },
{, 0 },
},
{, 0 },
{, 0 },
{, 0 },
{, 0, 0 },
{, 0 },
},
};
Add tile.png to the Resource
Initialize a bitmap object in the constructor.
Res = context. getresources ();
BMP = bitmapfactory.Decoderesource(Res, R. drawable.Tile);
Drawing in ondraw
// Used to display the image blockRectObject
Rect src =NewRect (0, 0, 0, 16 );
Rect DST =NewRect ();
For(IntI = 0; I
For(IntJ = 0; j
// Obtain the corresponding position based on the tile number.
SRC. Left = (Map [I] [J]-1) * 16;
SRC. Right = SRC. Left + 16;
// Calculate the corresponding screen position based on the number in the graph
DST. Left = J * 16;
DST. Right = DST. Left + 16;
DST. Top = I * 16;
DST. Bottom = DST. Top + 16;
Canvas. drawbitmap (BMP, SRC, DST, paint );
}
}
Do not forget to modify setcontentview in Main. java.
Gameview_old gameview;
/** Called when the activity is first created .*/
@ Override
Public VoidOncreate (bundle savedinstancestate ){
Super. Oncreate (savedinstancestate );
Gameview =NewGameview_old (This);
Setcontentview (gameview );
}
Okay. Run the program to see the result:
Comparing the source image, except for the bricks around the base, there is no difference between the two. We can say that our tile map practices are basically successful.
Now we know how to use tile to display maps. In fact, we don't need to write so much code every time. Do you still remember the tiledlayer mentioned above? These operations have already been encapsulated. In addition, tiledlayer can display Dynamic Maps. Next let's take a look at the basic usage of tiledlayer. In fact, it is very similar to sprite usage (we need to set tiledlayer. to add Java to the project, use the tiledlayer of the program included in this chapter. java file, the tiledlayer in the previous chapter does not work properly ):
This time, we will use gameview to copy the array map to gameview and declare a tiledlayer variable.
// Background
Tiledlayer background;
Initialize tiledlayer In the constructor
// Background image
Background =NewTiledlayer (13, 13, bitmapfactory.Decoderesource(Res,
R. drawable.Tile), 16, 16 );
The tiledlayer constructor has five parameters, namely, the number of rows and columns of a map (in tile units), including the bitmap object of tile, the width and height of tile.
Use the setcell method to pass the tile number defined in the array to the tiledlayer.
For(IntI = 0; I
For(IntJ = 0; j
Background. setcell (I, j, map [I] [J]);
}
}
Finally, you only need to call the paint method in the run function to display the tiledlayer. Of course, you can control the position displayed by the tiledlayer as you control Sprite.
Background. Paint (C );
Let's take a look at the running effect.
Next let's learn how to implement dynamic maps. The so-called dynamic map is the same as the ghost animation mentioned above, that is, several key animations are displayed cyclically. Let's take a look at the image above tiles.
We will find that there are two pictures of the waters, which are prepared for the dynamic map. After the combination, we should have the following effects:
So how can we implement dynamic maps in tiledlayer? Tiledlayer has prepared the following functions for us:
Createanimatedtile: create a dynamic graph block. Many people will be confused by the name of this function, that is, creating a dynamic graph block, but where is it created? How can I create and use it? Only the day knows. In fact, the main function of this function is to allocate a storage structure to the dynamic graph block. If you do not call it, an error is returned, Which is useless. Createanimatedtile returns the number of a dynamic graph block, which decreases sequentially from-1. The first call returns-1, and a dynamic graph block numbered-1 is allocated. The second call will return-2, and so on. This return value is generally useless, because the position of the Dynamic Graph block must have been determined when we made the map. This number has long been written into the array. In the future, you can use this number to control the corresponding graph blocks. The function has a parameter that specifies the ID of a tile. This tile is initially displayed in the dynamic graph block. It is through changing the tile to implement animation. See the following code:
Background. createanimatedtile (4 );
We initialized a dynamic graph block with the ID-1. Parameter 4 indicates the fourth tile in the currently displayed tile sequence diagram, which is the image of the first waters.
Setanimatedtile: the content of the Dynamic Graph block is changed using this function. The first parameter of the function is the number of the Dynamic Graph block, such as-1. The second parameter is the tile number.
Here, we should be clear about the usage of Dynamic Maps:
First, determine the position of the Dynamic Graph block to be displayed in the map array and fill in the corresponding number. For example, we add three waters to the third line of the previous map.
Int[] [] Map = {
{0, 0, 0, 0, 0, 0, 0, 0, 0 },
{, 0 },
{,-1,-1,-, 0 },
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{, 2 },
{, 0 },
},
{, 0 },
{, 0 },
{, 0 },
{, 0, 0 },
{, 0 },
},
};
Then initialize tiledlayer in the gameview constructor. There is no difference except that createanimatedtile is called before setcell.
The last step is to call setanimatedtile in the run function and constantly change the graph block.
If(Background. getanimatedtile (-1) = 4 ){
Background. setanimatedtile (-1, 5 );
}Else{
Background. setanimatedtile (-1, 4 );
}
Background. Paint (C );
Let's take a look at the running effect.
In fact, I think tiledlayer can use the sequence and nextframe as sprite to achieve dynamic effects. It seems easier to use. Interested readers can modify the tiledlayer to implement this function.
At this point, we have mastered the map display method, but this map cannot be used in our game. Readers certainly see that in the first example of tiledlayer, our tanks can pass through the wall. Obviously, this map still lacks the most basic function-blocking.
There is a simple solution to implement blocking. Let's take a look at the sprite class, which has a method:
Public Final BooleanCollideswith (tiledlayer t,BooleanPixellevel)
Detects the collision between Sprite and tiledlayer. This check is in the unit of tile. If the tiles number that overlaps with Sprite is not 0, the function returns true; otherwise, false.
Let's test the following example:
Open gameview_old and add a Sprite member variable.
// Main character
Sprite player;
Initialize player in the constructor
// Initialize the main character
Player =NewSprite (bitmapfactory.Decoderesource(Res,
R. drawable.Player1), 16, 16 );
Player. setframesequence (New Int[] {0, 1 });
Draw player in ondraw
Player. Paint (C );
Background. Paint (C );
Control sprite motion in onkeydown
@ Override
Public BooleanOnkeydown (IntKeycode, keyevent event ){
//TodoAuto-generated method stub
Switch(Keycode ){
CaseKeyevent.Keycode_dpad_up:
X = player. getx ();
Y = player. Gety ();
Player. Move (0,-16 );
If(! Player. collideswith (background,False)){
Y-= 16;
}
Player. settransform (sprite.Trans_none);
Player. setposition (x, y );
Break;
CaseKeyevent.Keycode_dpad_down:
X = player. getx ();
Y = player. Gety ();
Player. Move (0, 16 );
If(! Player. collideswith (background,False)){
Y + = 16;
}
Player. settransform (sprite.Trans_rot180);
Player. setposition (x, y );
Break;
CaseKeyevent.Keycode_dpad_left:
X = player. getx ();
Y = player. Gety ();
Player. Move (-16, 0 );
If(! Player. collideswith (background,False)){
X-= 16;
}
Player. settransform (sprite.Trans_rot270);
Player. setposition (x, y );
Break;
CaseKeyevent.Keycode_dpad_right:
X = player. getx ();
Y = player. Gety ();
Player. Move (16, 0 );
If(! Player. collideswith (background,False)){
X + = 16;
}
Player. settransform (sprite.Trans_rot90);
Player. setposition (x, y );
Break;
}
Postinvalidate (); // notifies the system to refresh the screen
Return Super. Onkeydown (keycode, event );
}
As you can see, the tank can only be moved in the blank area. This time it cannot be mounted to the wall.
However, this method is still rough and often cannot achieve our goal. For example, in the tank war, water and bricks cannot pass through tanks, but the bunker can pass, there are also bullets that can pass through the waters. At this time, the tile number is more accurate. That is to say, we determine in advance which tile numbers can pass and which ones cannot. The collideswith method is used to obtain the ID of the tile to be reached based on the position of the tank and determine whether the tank is blocked.
Let's use this solution to rewrite the onkeydown method (to simplify the tutorial, we assume that each player and tile are completely coincident ):
First, we define a function to determine whether tank can pass
// Determine whether the tank can pass
Private BooleanTankpass (IntX,IntY ){
// Do not exceed the MAP range
If(X 12*16 | y 12*16 ){
Return False;
}
IntTid = map [Y/16] [x/16];
If(TID = 1 | tid = 2 | tid =-1)
Return False;
Return True;
}
Then use the new scheme in onkeydown.
@ Override
Public BooleanOnkeydown (IntKeycode, keyevent event ){
//TodoAuto-generated method stub
Switch(Keycode ){
CaseKeyevent.Keycode_dpad_up:
X = player. getx ();
Y = player. Gety ();
Player. Move (0,-16 );
If(Tankpass (player. getx (), player. Gety ())){
Y-= 16;
}
Player. settransform (sprite.Trans_none);
Player. setposition (x, y );
Break;
CaseKeyevent.Keycode_dpad_down:
X = player. getx ();
Y = player. Gety ();
Player. Move (0, 16 );
If(Tankpass (player. getx (), player. Gety ())){
Y + = 16;
}
Player. settransform (sprite.Trans_rot180);
Player. setposition (x, y );
Break;
CaseKeyevent.Keycode_dpad_left:
X = player. getx ();
Y = player. Gety ();
Player. Move (-16, 0 );
If(Tankpass (player. getx (), player. Gety ())){
X-= 16;
}
Player. settransform (sprite.Trans_rot270);
Player. setposition (x, y );
Break;
CaseKeyevent.Keycode_dpad_right:
X = player. getx ();
Y = player. Gety ();
Player. Move (16, 0 );
If(Tankpass (player. getx (), player. Gety ())){
X + = 16;
}
Player. settransform (sprite.Trans_rot90);
Player. setposition (x, y );
Break;
}
Postinvalidate (); // notifies the system to refresh the screen
Return Super. Onkeydown (keycode, event );
}
Now let's take a look. This time we finally achieved what we wanted. Our tank is hiding under the bunker and it cannot pass through the walls and waters.
So far, the map content has been explained. These contents are not complex. First, we introduced the principle of using tile to form a map, then introduced the dynamic map of tiledlayer, and finally demonstrated the implementation method of blocking. Most examples in this chapter use gameview_old, and do not use game loops or smooth scrolling of maps. This knowledge will be added as needed in the future.
This chapter sample program http://u.115.com/file/f12827d8db