45度地图 寻路主要参考了某位博客文章,链接如下:
cocos2dx 45度Staggered格式A*寻路 曼哈顿算法(待优化)
另外也参考了某位作者的正方形A* 寻路,所以这套A* 算法是可以兼正方形寻路也可以45度菱形寻路的。
现贴下代码:
地图类:
#ifndef _MAP_LAYER_H_
#define _MAP_LAYER_H_
#include <cocos2d.h>
#include "Role.h"
#include "Astar.h"
#include <iostream>
#include "common/BaseInfo.h"
#include "common/Global.h"
#include "Role.h"
USING_NS_CC;
class Role;
class MapLayer : public Layer,public BaseInfo,public Global
{
public:
MapLayer();
~MapLayer();
int _screenWidth, _screenHeight; // 屏幕宽度和高度
virtual bool init();
void update(float dt);
void setViewpointCenter(Point pos);
void initMapWithFile(const char * path);
static cocos2d::Scene* createScene();
CREATE_FUNC(MapLayer);
private:
Role *CRole; //玩家角色
};
#endif
#include "MapLayer.h"
MapLayer::MapLayer()
{
}
MapLayer::~MapLayer()
{
}
Scene* MapLayer::createScene()
{
// 'scene' is an autorelease object
auto scene = Scene::create();
// 'layer' is an autorelease object
auto layer = MapLayer::create();
// add layer as a child to scene
scene->addChild(layer);
// return the scene
return scene;
}
bool MapLayer::init()
{
if (!Layer::init())
{
return false;
}
this->initMapWithFile("chi.tmx");//地图初始化
astar.InitAstar(_grid, MapWidth, MapHeight);
auto gameListener = EventListenerTouchOneByOne::create();
// 响应触摸事件函数
gameListener->onTouchBegan = [](Touch* touch, Event* event){return true; };
gameListener->onTouchEnded = [=](Touch *touch, Event *event)
{
// OpenGL坐标
Vec2 touchLocation = touch->getLocation();
// 将触摸点坐标转换成相对的Node坐标
Vec2 nodeLocation = this->convertToNodeSpace(touchLocation);
// 用玩家位置作为起点,触摸点作为终点,转换为网格坐标,在地图上查找最佳到达路径
Vec2 from = tileCoordForPosition(CRole->getPosition());
Vec2 to = tileCoordForPosition(nodeLocation);
// 如果终点是不可通过(即有障碍物)的位置,则直接return
int tileGid = _collidable->getTileGIDAt(to);
if (tileGid)
{
// 使用GID来查找指定tile的属性,返回一个Value
Value properties = _tileMap->getPropertiesForGID(tileGid);
// 返回的Value实际是一个ValueMap
ValueMap map = properties.asValueMap();
// 查找ValueMap,判断是否有”可碰撞的“物体,如果有,直接返回
std::string value = map.at("collidable").asString();
if (value.compare("true") == 0)
{
return;
}
}
CRole->path.clear();
int fromx = (int)from.x;
int fromy = (int)from.y;
int tox = (int)to.x;
int toy = (int)to.y;
APoint start(fromx, fromy);
APoint end(tox, toy);
//A*算法找寻路径
auto t1 = ::clock();
CRole->path = astar.GetPath(start, end, false);
auto t2 = ::clock();
CRole->PathCurrentStep = 1;
CRole->PathSteps = CRole->path.size() - 1;
CRole->bMoving = true;
log("path size===>%d, time=====>%d ms", CRole->path.size(),t2-t1);
for (auto &p : CRole->path)
{
log("point===============>%d,%d",p->x,p->y);
}
if (CRole->path.size()>0)
{
CRole->RoleMove();
}
};
// 添加场景优先事件监听器
Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(gameListener, this);
//设置起始和结束点
this->scheduleUpdate();
return true;
}
void MapLayer::initMapWithFile(const char * path)
{
_tileMap = TMXTiledMap::create(path);
//_tileMap = cocos2d::experimental::TMXTiledMap::create(path);
_collidable = _tileMap->getLayer("collidable");
_collidable->setVisible(true);
auto bg= _tileMap->getLayer("back");
bg->setVisible(true);
_tileMap->setPosition(Vec2(0, 0));
MapWidth = _tileMap->getMapSize().width;
MapHeight = _tileMap->getMapSize().height;
this->initGrid();
this->addChild(_tileMap);
CRole = Role::create();
CRole->setPosition(positionForTileCoord(Point(8,286)));
CRole->setSumLifeValue(200);
CRole->setCurtLifeValue(CRole->getSumLifeValue());
CRole->_tileMap = _tileMap;
CRole->MapHeight = MapHeight;
CRole->MapWidth = MapWidth;
CRole->SetState(Role::ActionState::IDLE_STATE_UP);
this->addChild(CRole,2);
}
void MapLayer::setViewpointCenter(Point pos) //这个是移动地图,同时跟踪X,Y轴标准算法
{
Size winSize = Director::getInstance()->getWinSize();
//如果主角坐标小于屏幕的一半,则取屏幕中点坐标,否则取对象的坐标
int x = MAX(pos.x, winSize.width / 2);
int y = MAX(pos.y, winSize.height / 2);
//如果X、Y的坐标大于右上角的极限值,则取极限值的坐标(极限值是指不让地图超出屏幕造成出现黑边的极限坐标 )
x = MIN(x, (_tileMap->getMapSize().width *_tileMap->getTileSize().width) - winSize.width / 2);
y = MIN(y, (_tileMap->getMapSize().height * _tileMap->getTileSize().height) - winSize.height / 2);
//对象当前所在坐标
Point actualPosition = Vec2(x, y);
//计算屏幕中点和所要移动的目的点之间的距离
Point centerOfView = Vec2(winSize.width / 2, winSize.height / 2);
Point viewPoint = centerOfView - actualPosition;
this->setPosition(viewPoint);
}
void MapLayer::update(float delta)
{
this->setViewpointCenter(CRole->getPosition());
}
角色类,这里直接用了3d模型做主角:
#ifndef _ROLE_H_
#define _ROLE_H_
#include "cocos2d.h"
#include "Astar.h"
#include "common/BaseInfo.h"
USING_NS_CC;
//基础角色类,主角和NPC都需要继承它
class BaseInfo;
class Role :public Node,public BaseInfo
{
public:
Role(void);
~Role(void);
/* 角色状态设定,初始化角色状态等 */
CC_SYNTHESIZE(std::string, name, name); //角色名称
CC_SYNTHESIZE(float, curtLifevalue, CurtLifeValue); //角色当前生命值
CC_SYNTHESIZE(float, sumLifevalue, SumLifeValue); //角色总体生命值
public:
enum class ActionState {
ACTION_STATE_IDLE = 0,
RUN_STATE_UP,
RUN_STATE_DOWN,
RUN_STATE_LEFT,
RUN_STATE_RIGHT,
RUN_STATE_LEFTUP,
RUN_STATE_RIGHTUP,
RUN_STATE_LEFTDOWN,
RUN_STATE_RIGHTDOWN,
IDLE_STATE_UP,
IDLE_STATE_DOWN,
IDLE_STATE_LEFT,
IDLE_STATE_RIGHT,
IDLE_STATE_LEFTUP,
IDLE_STATE_RIGHTUP,
IDLE_STATE_LEFTDOWN,
IDLE_STATE_RIGHTDOWN,
};
public:
Action* m_down_idle;
Action* m_right_idle;
Action* m_up_idle;
Action* m_left_idle;
Action* m_leftdown_idle;
Action* m_rightdown_idle;
Action* m_leftup_idle;
Action* m_rightup_idle;
Action* m_down_walk;
Action* m_right_walk;
Action* m_up_walk;
Action* m_left_walk;
Action* m_leftdown_walk;
Action* m_rightdown_walk;
Action* m_leftup_walk;
Action* m_rightup_walk;
//角色初始化
virtual bool init();
//初始化创建角色动作
void CreateActions();
//角色移动
//virtual void RoleMove();
CREATE_FUNC(Role);
void SetState(ActionState actionState);
bool bMoving;
unsigned int PathCurrentStep;
unsigned int PathSteps;
Vector<APoint *> path;
ActionState currActionState;
Sprite3D *_player;
void RoleMove();
};
#endif
#include "Role.h"
Role::Role(void) :
m_down_idle(NULL),
m_up_idle(NULL),
m_left_idle(NULL),
m_right_idle(NULL),
m_leftdown_idle(NULL),
m_rightdown_idle(NULL),
m_leftup_idle(NULL),
m_rightup_idle(NULL),
m_down_walk(NULL),
m_up_walk(NULL),
m_left_walk(NULL),
m_right_walk(NULL),
m_leftdown_walk(NULL),
m_rightdown_walk(NULL),
m_leftup_walk(NULL),
m_rightup_walk(NULL),
currActionState(ActionState::ACTION_STATE_IDLE)
{
//this->setRoleDirection(RolelTurnRight);//设定初始朝向
}
Role::~Role(void)
{
}
bool Role::init(){
bool ret = false;
do
{
//加载模型文件
std::string fileName = "orc.c3b"; //"orc.c3b";
_player = Sprite3D::create(fileName);
_player->setScale(3.0f);
_player->setPosition(Vec2(0, 0));
_player->setGlobalZOrder(1);
_player->setRotation3D(Vec3(0, 180, 0));
addChild(_player);
//加载武器
auto sp = Sprite3D::create("axe.c3b");
sp->setGlobalZOrder(1);
//将武器放到玩家手上
_player->getAttachNode("Bip001 R Hand")->addChild(sp);
//获取骨骼动画信息
auto animation = Animation3D::create(fileName);
if (animation)
{
auto _idle = Animate3D::create(animation);
_idle->retain();
//让精灵循环播放动作
Sequence* pSequence = Sequence::create(_idle, NULL);
_player->runAction(RepeatForever::create(pSequence));
}
this->addChild(_player);
bMoving = false;
CreateActions();
PathCurrentStep = 0;
PathSteps = 0;
ret = true;
} while (0);
return ret;
}
void Role::CreateActions()
{
//初始化用户的动作
}
void Role::SetState(ActionState actionState)
{
this->stopAllActions();
switch (actionState)
{
case ActionState::IDLE_STATE_UP:
_player->setRotation3D(Vec3(0, 0, 0));
break;
case ActionState::IDLE_STATE_DOWN:
_player->setRotation3D(Vec3(0, 180, 0));
break;
case ActionState::IDLE_STATE_LEFT:
_player->setRotation3D(Vec3(0, 90, 0));
break;
case ActionState::IDLE_STATE_RIGHT:
_player->setRotation3D(Vec3(0, -90, 0));
break;
case ActionState::IDLE_STATE_LEFTUP:
_player->setRotation3D(Vec3(0, -135, 0));
break;
case ActionState::IDLE_STATE_RIGHTUP:
_player->setRotation3D(Vec3(0, 45, 0));
break;
case ActionState::IDLE_STATE_LEFTDOWN:
_player->setRotation3D(Vec3(0, -45, 0));
break;
case ActionState::IDLE_STATE_RIGHTDOWN:
_player->setRotation3D(Vec3(0, 135, 0));
break;
case ActionState::RUN_STATE_UP:
_player->setRotation3D(Vec3(0, 0, 0));
break;
case ActionState::RUN_STATE_DOWN:
_player->setRotation3D(Vec3(0, 180, 0));
break;
case ActionState::RUN_STATE_LEFT:
_player->setRotation3D(Vec3(0, 90, 0));
break;
case ActionState::RUN_STATE_RIGHT:
_player->setRotation3D(Vec3(0, -90, 0));
break;
case ActionState::RUN_STATE_LEFTUP:
_player->setRotation3D(Vec3(0, -135, 0));
break;
case ActionState::RUN_STATE_RIGHTUP:
_player->setRotation3D(Vec3(0, -45, 0));
break;
case ActionState::RUN_STATE_LEFTDOWN:
_player->setRotation3D(Vec3(0, 45, 0));
break;
case ActionState::RUN_STATE_RIGHTDOWN:
_player->setRotation3D(Vec3(0, 135, 0));
break;
default:
break;
}
currActionState = actionState;
}
void Role::RoleMove()
{
Vector<FiniteTimeAction* > Actions;
if (bMoving)
{
//角色当前所在格子坐标
auto currpos = tileCoordForPosition(this->getPosition());
if (currpos.x==this->path.at(PathSteps)->x && currpos.y == this->path.at(PathSteps)->y)
{
//已到达目的地
this->SetState(ActionState::IDLE_STATE_UP);
}
else
{
for (int i = 0; i < PathSteps; i++)
{
auto netStep = Point(this->path.at(i + 1)->x, this->path.at(i + 1)->y);
Point v = Point(this->path.at(i)->x, this->path.at(i)->y)-netStep;
float len = v.getLength();// 计算每一段的距离
float duration = len / 10 * 1.2; //计算每段行走的时间
float rad = v.getAngle(Point(1, 1));
float Degree = CC_RADIANS_TO_DEGREES(rad);
log("the degree=========> %f",Degree);
auto fnSetState = [](Role *pRole, ActionState state)
{
pRole->SetState(state);
};
if (Degree >= 0 && Degree < 45)
{
// Actions.pushBack(CallFunc::create(bind(fnSetState, this, ActionState::RUN_STATE_RIGHT)));
SetState(ActionState::RUN_STATE_LEFT);
}
if (Degree >= 45 && Degree < 90)
{
// Actions.pushBack(CallFunc::create(bind(fnSetState, this, ActionState::RUN_STATE_RIGHTUP)));
SetState(ActionState::RUN_STATE_LEFTDOWN);
}
if (Degree >= 90 && Degree < 135)
{
// Actions.pushBack(CallFunc::create(bind(fnSetState, this, ActionState::RUN_STATE_UP)));
SetState(ActionState::RUN_STATE_RIGHTDOWN);
}
if (Degree >= 135 && Degree < 180)
{
// Actions.pushBack(CallFunc::create(bind(fnSetState, this, ActionState::RUN_STATE_LEFTUP)));
SetState(ActionState::RUN_STATE_RIGHT);
}
if (Degree >-135 && Degree <= -180)
{
// Actions.pushBack(CallFunc::create(bind(fnSetState, this, ActionState::RUN_STATE_LEFT)));
SetState(ActionState::RUN_STATE_RIGHTUP);
}
FiniteTimeAction *pAct = MoveTo::create(duration, positionForTileCoord(netStep));
Actions.pushBack(pAct);
}
auto pSeq = Sequence::create(Actions);
pSeq->setTag(1);
this->runAction(pSeq);
}
bMoving = false;
}
else {
SetState(ActionState::IDLE_STATE_DOWN);
}
}
A* 寻路类:
#pragma once
/* //A*算法对象类 */
#include <vector>
#include <list>
#include <unordered_map>
#include "cocos2d.h"
USING_NS_CC;
using namespace std;
//横向移动一格的路径评分
static const int COST_HORIZONTAL = 20;
//竖向移动一格的路径评分
static const int COST_VERTICAL = 5;
//斜向移动一格的路径评分
static const int COST_DIAGONAL = 12;
const int kCost1 = 10; //直移一格消耗
const int kCost2 = 14; //斜移一格消耗
struct APoint :public Ref
{
int x, y; //点坐标,这里为了方便按照C++的数组来计算,x代表横排,y代表竖列
int F, G, H; //F=G+H
APoint *parent; //parent的坐标,这里没有用指针,从而简化代码
APoint(int _x, int _y) :x(_x), y(_y), F(0), G(0), H(0), parent(NULL) //变量初始化
{
}
};
class Astar
{
public:
void InitAstar(std::vector<bool> &_maze,int mapwidth, int mapheight);
Vector<APoint *> GetPath(APoint &startPoint, APoint &endPoint, bool isIgnoreCorner);
private:
APoint *findPath(APoint &startPoint, APoint &endPoint, bool isIgnoreCorner);
Vector<APoint *> getSurroundPoints(const APoint *point, bool isIgnoreCorner) const;
bool isCanreach(const APoint *point, const APoint *target, bool isIgnoreCorner) const; //判断某点是否可以用于下一步判断
APoint *isInList(const Vector<APoint *> &list, const APoint *point) const; //判断开启/关闭列表中是否包含某点
APoint *getLeastFpoint(); //从开启列表中返回F值最小的节点
//计算FGH值
int calcG(APoint *temp_start, APoint *point);
int calcH(APoint *point, APoint *end);
int calcF(APoint *point);
private:
std::vector<bool> maze;
Vector<APoint *> openList; //开启列表
Vector<APoint *> closeList; //关闭列表
int width;
int height;
};
#include <math.h>
#include "Astar.h"
void Astar::InitAstar(std::vector<bool> &_maze, int mapwidth, int mapheight)
{
maze = _maze;
width = mapwidth;
height = mapheight;
}
int Astar::calcG(APoint *temp_start, APoint *point)
{
/* 原正方形 */
//int extraG = (abs(point->x - temp_start->x) + abs(point->y - temp_start->y)) == 1 ? kCost1 : kCost2;
//int parentG = point->parent == NULL ? 0 : point->parent->G; //如果是初始节点,则其父节点是空
//return parentG + extraG;
//45度菱形
int g = 0;
if (temp_start->y == point->y) // 横向 左右
{
g = temp_start->G + COST_HORIZONTAL;
}
else if (temp_start->y + 2 == point->y || temp_start->y - 2 == point->y) // 竖向 上下
{
g = temp_start->G + COST_VERTICAL * 2;
}
else // 斜向 左上 左下 右上 右下
{
g = temp_start->G + COST_DIAGONAL;
}
return g;
}
int Astar::calcH(APoint *point, APoint *end)
{
//原正方形 用简单的欧几里得距离计算H
// return sqrt((double)(end->x - point->x)*(double)(end->x - point->x) + (double)(end->y - point->y)*(double)(end->y - point->y))*kCost1;
//45度菱形 曼哈顿算法
int to0 = point->x * COST_HORIZONTAL + ((int)point->y & 1) * COST_HORIZONTAL / 2;
int endTo0 = end->x * COST_HORIZONTAL + ((int)end->y & 1) * COST_HORIZONTAL / 2;
return abs((float)endTo0 - (float)to0) + abs((float)end->y - (float)point->y) * COST_VERTICAL;
}
int Astar::calcF(APoint *point)
{
return point->G + point->H;
}
APoint *Astar::getLeastFpoint()
{
if (!openList.empty())
{
auto resPoint = openList.front();
for (auto point : openList)
if (point->F<resPoint->F)
resPoint = point;
return resPoint;
}
return NULL;
}
APoint *Astar::findPath(APoint &startPoint, APoint &endPoint, bool isIgnoreCorner)
{
openList.clear();
closeList.clear();
//CCLOG("%d,%d", startPoint.x,startPoint.y);
//CCLOG("%d,%d", endPoint.x,endPoint.y);
openList.pushBack(new APoint(startPoint.x, startPoint.y)); //置入起点,拷贝开辟一个节点,内外隔离
while (!openList.empty())
{
auto curPoint = getLeastFpoint(); //找到F值最小的点
openList.eraseObject(curPoint); //从开启列表中删除
closeList.pushBack(curPoint); //放到关闭列表
//1,找到当前周围八个格中可以通过的格子
auto surroundPoints = getSurroundPoints(curPoint, isIgnoreCorner);
for (auto &target : surroundPoints)
{
//2,对某一个格子,如果它不在开启列表中,加入到开启列表,设置当前格为其父节点,计算F G H
if (!isInList(openList, target))
{
target->parent = curPoint;
target->G = calcG(curPoint, target);
target->H = calcH(target, &endPoint);
target->F = calcF(target);
openList.pushBack(target);
}
//3,对某一个格子,它在开启列表中,计算G值, 如果比原来的大, 就什么都不做, 否则设置它的父节点为当前点,并更新G和F
else
{
int tempG = calcG(curPoint, target);
if (tempG<target->G)
{
target->parent = curPoint;
target->G = tempG;
target->F = calcF(target);
//CCLOG("%d,%d", target.x, target.y);
}
}
APoint *resPoint = isInList(openList, &endPoint);
if (resPoint)
return resPoint; //返回列表里的节点指针,不要用原来传入的endpoint指针,因为发生了深拷贝
}
}
return NULL;
}
Vector<APoint *> Astar::GetPath(APoint &startPoint, APoint &endPoint, bool isIgnoreCorner)
{
log("from point===============>%d,%d", startPoint.x, startPoint.y);
log("to point===============>%d,%d", endPoint.x, endPoint.y);
APoint *result = findPath(startPoint, endPoint, isIgnoreCorner);
Vector<APoint *> path;
//返回路径,如果没找到路径,返回空链表
int s = 1;
while (result->parent)
{
// result->y = height - result->y - 1;
path.insert(0,result);
result = result->parent;
s++;
}
log("s===>%d",s);
return path;
}
APoint *Astar::isInList(const Vector<APoint *> &list, const APoint *point) const
{
//判断某个节点是否在列表中,这里不能比较指针,因为每次加入列表是新开辟的节点,只能比较坐标
for (auto p : list)
if (p->x == point->x&&p->y == point->y)
return p;
return NULL;
}
bool Astar::isCanreach(const APoint *point, const APoint *target, bool isIgnoreCorner) const
{
if (target->x<0 || target->x>width-1
|| target->y<0 || target->y>height-1
|| maze[width*target->y + target->x] == false
|| (target->x == point->x&&target->y == point->y)
|| isInList(closeList, target)) //如果点与当前节点重合、超出地图、是障碍物、或者在关闭列表中,返回false
return false;
else
{
if (abs(point->x - target->x) + abs(point->y - target->y) == 1) //非斜角可以
return true;
else
{
//斜对角要判断是否绊住
if (maze[width*target->y + point->x] == true && maze[width*point->y + target->x] == true)
return true;
else
return isIgnoreCorner;
}
}
}
Vector<APoint *> Astar::getSurroundPoints(const APoint *point, bool isIgnoreCorner) const
{
Vector<APoint *> surroundPoints;
//原正方形
/*for (int x = point->x - 1; x <= point->x + 1; x++) for (int y = point->y - 1; y <= point->y + 1; y++) if (isCanreach(point, new APoint(x, y), isIgnoreCorner)) surroundPoints.pushBack(new APoint(x, y));*/
//45度菱形 菱形组合的地图八方向与正常不同
// 左
auto p = new APoint(point->x - 1, point->y);
if (isCanreach(point, p, isIgnoreCorner)) // 可走并且不在关闭列表
{
surroundPoints.pushBack(p);
}
// 右
p = new APoint(point->x + 1, point->y);
if (isCanreach(point, p, isIgnoreCorner))
{
surroundPoints.pushBack(p);
}
// 上
p = new APoint(point->x, point->y-2);
if (isCanreach(point, p, isIgnoreCorner))
{
surroundPoints.pushBack(p);
}
// 下
p = new APoint(point->x , point->y+2);
if (isCanreach(point, p, isIgnoreCorner))
{
surroundPoints.pushBack(p);
}
// 左上
p = new APoint(point->x - 1+ ((int)point->y & 1), point->y-1);
if (isCanreach(point, p, isIgnoreCorner))
{
surroundPoints.pushBack(p);
}
// 左下
p = new APoint(point->x - 1 + ((int)point->y & 1), point->y + 1);
if (isCanreach(point, p, isIgnoreCorner))
{
surroundPoints.pushBack(p);
}
//右上
p = new APoint(point->x + ((int)point->y & 1), point->y - 1);
if (isCanreach(point, p, isIgnoreCorner))
{
surroundPoints.pushBack(p);
}
//右下
p = new APoint(point->x + ((int)point->y & 1), point->y +1);
if (isCanreach(point, p, isIgnoreCorner))
{
surroundPoints.pushBack(p);
}
return surroundPoints;
}
主要代码已经贴上了:下面是本demo图片,随便做的,看看就行
本文demo代码请到如下地址下载,图片资源来自网络,切勿商用。
下载资源