0. in the previous article, the characters can be correctly oriented and moved. This time, we will add several stones to achieve the following effects:
1) the characters and stones can be properly blocked Based on the station position.
2) The character cannot pass through the stone because it is blocked by the stone.
3) when the mouse clicks the back of the stone, the character can automatically walk around the stone
Effect:
You can directly run the Version Download:> click to enter the download page <
1. Object Manager and occlusion
We only have one player before, so we call player directly when drawing an object. draw () is enough, but when there are many objects, we need a manager to automatically help us draw all objects in the current window. Therefore, we can use the inheritance feature of C ++ to define a base class for all objects. This base class is responsible for defining the unified draw () method, the object manager can ignore the specific types of these objects and call draw.
The following code defines the basic classes of all objects:
Class gameobj <br/>{< br/> Public: <br/> virtual int Init () = 0; <br/> virtual int logic () = 0; <br/> virtual int draw () = 0; <br/> virtual void release () = 0; </P> <p> int MapX; <br/> int mapy; <br/> };
For the object manager, it is very simple to initialize, draw, and clean all objects in this manager, so its definition is also four methods. In addition, for simplicity, we didn't use the method of reading object data from files, so players and stones are hardcoded directly in the object manager. The following is the header file:
Class objmanager <br/>{< br/> Public: <br/> int Init (); <br/> int logic (); <br/> int draw (); <br/> void release (); </P> <p> STD: List <gameobj *> listobj; <br/> PRIVATE: <br/> STD :: list <stone *> liststone; <br/> player; <br/> };
Among them, listobj stores the base type pointer and is used to execute the init, logic, draw, and release methods they all share. Something in private is the hard-coded object I mentioned above.
Let's take a look at the implementation of objmanager, which is very simple:
# Include "stdafx. H "</P> <p> # include <list> <br/> # include" gamescreen. H "<br/> # include" gameobj. H "<br/> # include" player. H "<br/> # include" Stone. H "<br/> # include" inputmanager. H "<br/> # include" objmanager. H "</P> <p> int objmanager: Init () <br/> {<br/> // first load the Environmental Object <br/> for (INT I = 0; I <10; ++ I) <br/>{< br/> stone * stone = new stone (); <br/> liststone. push_back (Stone); <br/> stone-> Init (); <br/> stone-> Mapx + = I * stone-> width; <br/> stone-> setblock (); <br/> listobj. push_back (gameobj *) Stone); <br/>}</P> <p> // finally load the player, because it is necessary to determine whether the landing point needs to be corrected according to the Environment <br/> player. init (); <br/> listobj. push_back (gameobj *) & player); </P> <p> return 1; <br/>}</P> <p> int comparegameobj (gameobj * obj1, gameobj * obj2) <br/>{< br/> If (obj1-> mapy> obj2-> mapy) <br/>{< br/> return 0; <br/>}< br/> else <br/> {<br/> If (obj1-> mapy = obj2-> mapy) & <Br/> (obj1-> MapX> obj2-> MapX) <br/>{< br/> return 0; <br/>}</P> <p> return 1; <br/>}</P> <p> int objmanager: logic () <br/> {<br/> listobj. sort (comparegameobj); </P> <p> for (STD: List <gameobj *>: iterator iter = listobj. begin (); iter! = Listobj. end (); ++ ITER) <br/>{< br/> (* ITER)-> logic (); <br/>}</P> <p> return 1; <br/>}</P> <p> int objmanager: Draw () <br/>{< br/> for (STD: List <gameobj * >:: iterator iter = listobj. begin (); iter! = Listobj. end (); ++ ITER) <br/>{< br/> (* ITER)-> draw (); <br/>}< br/> return 1; <br/>}</P> <p> void objmanager: release () <br/> {<br/> for (STD: List <gameobj *> :: iterator iter = listobj. begin (); iter! = Listobj. end (); ++ ITER) <br/>{< br/> (* ITER)-> release (); <br/>}< br/> for (STD:: List <stone * >:: iterator iter = liststone. begin (); iter! = Liststone. end (); ++ ITER) <br/>{< br/> Delete * ITER; <br/>}</P> <p>
One by one function, the first is objmanager: Init (). He first loads all the stones into the unified object list, then initializes the players and puts them into the list. There is a small detail here, that is, the first initialization of the stone, then many places on the map can not be placed on the player, so you need to put the stone first and then put the player, otherwise, the player's current coordinates may be inside the stone and it will get stuck. How to determine the landing point is described later.
Then we will find that there is a function of int comparegameobj (gameobj * obj1, gameobj * obj2). What is this function. I don't know whether you have heard of the painter algorithm. The principle is very simple. It is to first draw a background and then draw a foreground, which will produce a layered effect. Because our game is inclined to 45 degrees, it is equivalent to the Y coordinate (that is, the screen is down) that is close to everyone, and the Y small is far away from everyone, therefore, we can determine who to draw first based on the value of Y. This is the principle of occlusion. Therefore, this function is self-evident and is provided for the sort method of the list, so that they can be sorted by Y. :
The following functions will be available at a Glance. In addition to sorting the listobj order by the logic method, other methods directly call the gameobj-related methods in listobj cyclically. There is nothing to say.
2. Road Manager waymanager and blocking
Speaking of pathfinding, I guess everyone has heard of a * pathfinding algorithm. So the first thing I feel is to learn this algorithm and then let's see how to use it. After learning it, I found that it was actually available. I will not talk about a * pathfinding algorithm here. I will talk about a lot of articles on casual search. Here I will briefly talk about what he is doing, for example:
A * finding the path is doing this: divide the map into a pile of grids, so we know the starting point grid (the player's current grid) and the ending point grid (the mouse clicks) and whether the grid is an obstacle (Map Information), from the start point to the end point, through the grid queue, that is, the path.
Therefore, in the game, we are equivalent to attaching a large grid to our map to form a waytable array. In fact, this grid has nothing to do with the map, only the pixels they occupy are correlated. Then we will notice a problem: the point where a player is located is the coordinate point of a pixel, and a grid occupies a lot of pixels. How can this be converted. This method is used here. Because we regard this grid as an array waytable, we can use the player's coordinates and the length and width of each grid to determine how many pixels are occupied, obtain the index of the waytable in which the current player is located. On the contrary, we can use a waytable [N] to obtain the coordinates of the pixel center of the grid where N is located. Because the array is one-dimensional, it is not convenient to represent the position of the grid, so I often think of an index in this form: N = x + y * tablewidth, where X is the number of columns in the grid, Y indicates the number of rows in the grid and tablewidth indicates the number of grids in each row. In addition, a set of X and Y can be regarded as a point type. Another problem is that when we specify any point, we need to find the grid closest to the point, which is not an obstacle. In this way, when the player points to the stone, the objmanager init method mentioned above can still be used to calculate the player's landing point.
To sum up, this waymanager is defined as follows:
# Pragma once </P> <p> # include <list> <br/> # include <stack> <br/> # include "pathpoint. H "</P> <p> class waymanager <br/>{< br/> Public: <br/> int Init (); <br/> void release (); <br/> int mappointtowaytableindex (point mappoint); // convert the map coordinate to the index of the path. <br/> point waytableindextomappoint (INT tableindex ); // obtain the coordinates of the central map of a certain path. <br/> point waytableindextopoint (INT tableindex ); // convert an index in the path table to the expression of a vertex <br/> STD: Stack <int> findway (INT startindex, int endindex); // seek the path, put the ordered path index into the queue and return it. Assume that both startindex and endindex are accessible Points <br/> int getnearestindex (point mappoint); // For a point on the map, calculate the nearest unblocked path index </P> <p> int unitwidth; // The width of the path element <br/> int unitheight; // path element height <br/> int tablewidth; // The number of elements in the horizontal direction of the path table <br/> int tableheight; // number of units in the vertical direction of the path table <br/> int * waytable; // path Table Array pointer <br/> int waytablesize; // path Table Array length </P> <p> PRIVATE: <br/> int startindex; // path retrieval <br/> int endindex; // target path <br/> STD: List <pathpoint *> openlist; // enable the list and use the path search function <br/> STD: List <pathpoint *> closelist; // close the list and use <br/> pathpoint * Find (); // Recursive Method Used for path searching <br/>}; <br/>
There is a pathpoint structure, which is used to implement a * pathfinding algorithm. When you look at the * pathfinding algorithm, you can naturally understand its usage. Similarly, variables in private are also intermediate variables used for path searching.
The helper functions of the preceding conversions are simple and implemented as follows:
Int waymanager: mappointtowaytableindex (point mappoint) <br/>{< br/> int IX, Iy; <br/> IX = mappoint. x/unitwidth; <br/> Iy = mappoint. y/unitheight; <br/> If (IX <0) <br/> IX = 0; <br/> If (IX> = tablewidth) <br/> IX = tablewidth-1; <br/> If (Iy <0) <br/> Iy = 0; <br/> If (Iy> = tableheight) <br/> Iy = tableheight-1; </P> <p> return (IX + Iy * tablewidth ); <br/>}</P> <p> point waymanager: waytableindextomappoint (INT tableindex) <br/>{< br/> point tablepoint = waytableindextopoint (tableindex ); </P> <p> point result; <br/> result. X = tablepoint. x * unitwidth + unitwidth/2; <br/> result. y = tablepoint. y * unitheight + unitheight/2; </P> <p> return result; <br/>}</P> <p> point waymanager: waytableindextopoint (INT tableindex) <br/>{< br/> int IX, Iy; <br/> Iy = tableindex/tablewidth; <br/> IX = tableindex % tablewidth; </P> <p> point result; <br/> result. X = IX; <br/> result. y = Iy; <br/> return result; <br/>}
A * The implementation code of the pathfinding algorithm is also posted, with comments in it. My function will return a series of indexes of the computed waytable. After direct calls, you can use these indexes, which is very convenient:
STD: Stack <int> waymanager: findway (INT startindex, int endindex) <br/>{< br/> STD: Stack <int> result; </P> <p> // Delete Obsolete path <br/> for (list <pathpoint *>: iterator iter = openlist. begin (); iter! = Openlist. end (); ++ ITER) <br/>{< br/> Delete * ITER; <br/>}< br/> for (list <pathpoint *> :: iterator iter = closelist. begin (); iter! = Closelist. end (); ++ ITER) <br/>{< br/> Delete * ITER; <br/>}< br/> openlist. clear (); <br/> closelist. clear (); </P> <p> This-> startindex = startindex; <br/> This-> endindex = endindex; </P> <p> pathpoint * startpoint = new pathpoint (); <br/> startpoint-> G = 0; <br/> startpoint-> H = 0; <br/> startpoint-> isstart = true; <br/> startpoint-> parent = NULL; <br/> startpoint-> Index = startindex; </P> <p> openlist. push_back (STA Rtpoint); </P> <p> pathpoint * temp; // loop temporary variable, get the result of each path check <br/> while (true) <br/>{< br/> temp = find (); <br/> If (temp = NULL) <br/>{< br/> If (openlist. empty () <br/>{< br/> return result; <br/> break; <br/>}< br/> else <br/> {<br/> while (true) <br/>{< br/> If (temp-> Index = startindex) <br/>{< br/> break; <br/>}< br/> else <br/>{< br/> result. push (temp-> index); <br/> temp = temp-> parent; <br/>}< br/>} <Br/> break; <br/>}</P> <p> return result; <br/>}</P> <p> int comparepathpoint (pathpoint * point1, pathpoint * point2) <br/>{< br/> int F1 = point1-> G + point1-> H; <br/> int F2 = point2-> G + point2-> h; <br/> If (F1> F2) <br/>{< br/> return 1; <br/>}< br/> else <br/>{< br/> return 0; <br/>}</P> <p> pathpoint * waymanager: Find () <br/>{< br/> pathpoint * result; <br/> If (openlist. empty () <br/> {<B R/> return NULL; <br/>}< br/> else <br/>{< br/> openlist. sort (comparepathpoint); <br/> pathpoint * point = openlist. back (); <br/> openlist. pop_back (); <br/> closelist. push_back (point); </P> <p> int Sx, Sy, ex, ey, dx, Dy, DG, Ti; <br/> point spoint = waytableindextopoint (point-> index); <br/> point epoint = waytableindextopoint (endindex); <br/> SX = spoint. x; <br/> Sy = spoint. y; <br/> ex = epoint. x; <br/> ey = E Point. y; </P> <p> pathpoint ** roundpoints = new pathpoint * [8]; <br/> for (INT I = 0; I <8; ++ I) <br/>{< br/> switch (I) <br/>{< br/> case 0: <br/>{< br/> dx = SX; <br/> DY = SY + 1; <br/> DG = 10; <br/> break; <br/>}< br/> case 1: <br/>{< br/> dx = SX-1; <br/> DY = SY + 1; <br/> DG = 14; <br/> break; <br/>}< br/> case 2: <br/>{< br/> dx = SX-1; <br/> DY = sy; <br/> DG = 10; <br/> break; <br/>}< br/> case 3: <br/> {<Br/> dx = SX-1; <br/> DY = sy-1; <br/> DG = 14; <br/> break; <br/>}< br/> case 4: <br/>{< br/> dx = SX; <br/> DY = sy-1; <br/> DG = 10; <br/> break; <br/>}< br/> case 5: <br/>{< br/> dx = SX + 1; <br/> DY = sy-1; <br/> DG = 14; <br/> break; <br/>}< br/> case 6: <br/>{< br/> dx = SX + 1; <br/> DY = sy; <br/> DG = 10; <br/> break; <br/>}< br/> case 7: <br/>{< br/> dx = SX + 1; <br/> DY = SY + 1; <br/> DG = 14; <Br/> break; <br/>}< br/> Ti = dx + tablewidth * (dy ); <br/> // whether the path is available <br/> If (dx> = 0 & DX <tablewidth & dy> = 0 & dy <tableheight & waytable [Ti] = 0) <br/> {<br/> roundpoints [I] = new pathpoint (); <br/> roundpoints [I]-> isstart = false; <br/> roundpoints [I]-> parent = point; <br/> roundpoints [I]-> Index = Ti; <br/> roundpoints [I]-> G = roundpoints [I]-> parent-> G + DG; <br/> roundpoints [I]-> H = (ABS (ey-dy) + ABS (ex-dx) * 10; </P> <p> If (roundpoints [I]-> Index = endindex) <br/> {<br/> result = roundpoints [I]; <br/> Delete [] roundpoints; <br/> return result; <br/>}</P> <p> // whether it is disabled <br/> bool hasclose = false; <br/> for (STD :: list <pathpoint *>: iterator iter = closelist. begin (); iter! = Closelist. end (); ++ ITER) <br/>{< br/> If (* ITER)-> Index = roundpoints [I]-> index) <br/>{< br/> hasclose = true; <br/> break; <br/>}< br/> If (! Hasclose) // not closed <br/>{< br/> // determine whether the path is enabled <br/> pathpoint * hasopen = NULL; <br/> for (STD :: list <pathpoint *>: iterator iter = openlist. begin (); iter! = Openlist. end (); ++ ITER) <br/>{< br/> If (* ITER)-> Index = roundpoints [I]-> index) <br/>{< br/> hasopen = * ITER; <br/> break; <br/>}< br/> If (hasopen! = NULL) // enabled <br/>{< br/> // whose G is left and who <br/> If (hasopen-> G) <(roundpoints [I]-> G) // enable the small value in the list and discard the current value <br/> {<br/> Delete roundpoints [I]; <br/>}< br/> else // The current value is small. Replace the value in the enabled list, clear the current <br/>{< br/> hasopen-> G = roundpoints [I]-> G; <br/> hasopen-> H = roundpoints [I]-> H; <br/> hasopen-> parent = roundpoints [I]-> parent; <br/> Delete roundpoints [I]; <br/>}< br/> else // disabled <br/>{< br/> openlist. push_back (roundpoints [I]); <br/>}< br/> else // disabled <br/>{< br/> Delete roundpoints [I]; <br/>}< br/>}// end for <br/> Delete [] roundpoints; <br/>}< br/> return NULL; <br/>}
So there is only one small problem. I drew a picture of the computing implementation point. The idea is very simple:
That is, if the search start point is unavailable, for example, the stone has occupied this path (GRID), then I will find an available grid in a radius R, we can find a feature through the image. All the red implementation lengths of L are equal to L = 2R. You can try a situation with a larger radius. Therefore, the search function is implemented as follows:
Int waymanager: getnearestindex (point mappoint) <br/>{< br/> int im = mappointtowaytableindex (mappoint); <br/> If (waytable [im] = 0) <br/>{< br/> return im; <br/>}< br/> point pi = waytableindextopoint (IM); <br/> int MX = pi. x; <br/> int my = pi. y; </P> <p> int IX, Iy; <br/> for (INT r = 1; r <= 5; ++ R) // R indicates the search radius <br/>{< br/> IX = Mx-R; <br/> Iy = My + R; <br/> int I; <br/> for (I = 0; I <2 * r; ++ I) <br/>{< br/> -- Iy; <br/> If (IX> = 0 & Iy> = 0 & IX <tablewidth & Iy <tableheight & waytable [ix + Iy * tablewidth] = 0) <br/>{< br/> return IX + Iy * tablewidth; <br/>}< br/> for (I = 0; I <2 * r; ++ I) <br/>{< br/> ++ IX; <br/> If (IX> = 0 & Iy> = 0 & IX <tablewidth & Iy <tableheight & waytable [ix + Iy * tablewidth] = 0) <br/>{< br/> return IX + Iy * tablewidth; <br/>}< br/> for (I = 0; I <2 * r; ++ I) <br/>{< br/> ++ Iy; <br/> If (IX> = 0 & Iy> = 0 & IX <tablewidth & Iy <tableheight & waytable [ix + Iy * tablewidth] = 0) <br/>{< br/> return IX + Iy * tablewidth; <br/>}< br/> for (I = 0; I <2 * r; ++ I) <br/>{< br/> -- IX; <br/> If (IX> = 0 & Iy> = 0 & IX <tablewidth & Iy <tableheight & waytable [ix + Iy * tablewidth] = 0) <br/>{< br/> return IX + Iy * tablewidth; <br/>}</P> <p> return-1; <br/>}
So far, the game has achieved the expected results at the beginning of the article.
In fact, there are still many ways to optimize on this basis, and there are many things I can think:
1) maintain the order in which the list is enabled during a * search algorithm so that it does not need to be sorted every time it is found.
2) When a small map is added later, when a player clicks a position on a small map, a large grid can be used to calculate a rough value, reducing performance overhead.
3) if there are islands on the map in the future, so as to facilitate all the points, you should put the indexes of these islands in a list for management, to prevent a large number of Traversal
4) before a map is found, it should be impossible to find the path. For example, if a small map is black, the map should not automatically find the best route. This is too false.
5) In fact, the biggest problem now is that we calculate a path in the frame of each mouse. In fact, it is not necessary. There is no obstacle between straight lines, we should let him go directly without looking for a path. For this discovery, I accidentally played diablo2. Please refer to the following two figures:
In the absence of obstacles, the characters will move smoothly, while in the case of obstacles, even if the characters walk straight after the obstacles, there is still slight jitter.
I finally finished writing the complete project code, just like the previous article, because I have already finished writing it.
> Click to enter the download page <