在游戏中,可以控制人物的方法一般有:1.键盘 2.虚拟摇杆 3.鼠标 4.手机触碰。键盘一般是在PC端较为常用,如果在游戏中使用wasd等操作人物的话,那么在移植到安卓端时,就需要使用虚拟摇杆或虚拟按钮来模拟键盘,以实现处理的统一性。鼠标类似于手机的单点触碰,而手机触碰一般分为单点和多点触碰。这里使用触碰操作人物。

既然使用触碰进行操作人物,那么就需要一个从出发点到目的地的路径,这里选用的是A星算法。A星算法较为经典,也较为简单,对于一般的RPG游戏来说,典型的A星算法足以满足我们的需要,这里不赘述A星算法的原理,不了解的可以去看看这:

1.潘长安. 基于改进A星算法的城市交通寻径的研究[D].华侨大学,2015.

https://search.ehn3.com/doc_detail?dbcode=CMFD&filename=1015974456.NH

2.https://blog.csdn.net/mydo/article/details/49975873

第二篇是侯佩大大写的帖子,我就是模仿他的帖子实现的,并根据实际做了一些小小的改变。

先看代码吧。

ShortestPathStep.h

#ifndef __ShortestPathStep_H__
#define __ShortestPathStep_H__
#include<vector>
#include "SDL_Engine/SDL_Engine.h"using namespace std;
using namespace SDL;class ShortestPathStep : public Object
{SDL_SYNTHESIZE(int, m_nGScore, GScore);SDL_SYNTHESIZE(int, m_nHScore, HScore);
public:static Point fromTile;static Point toTile;
private:ShortestPathStep* m_pParent;Point m_tilePos;
public:ShortestPathStep();~ShortestPathStep();static ShortestPathStep* create(const Point& tilePos);bool init(const Point& tilePos);bool equals(const ShortestPathStep& other) const;Point& getTilePos() { return m_tilePos; }void setTilePos(const Point& pos) { m_tilePos = pos;};int getFScore() const;void description();ShortestPathStep* getParent() const;void setParent(ShortestPathStep* other);bool operator>(ShortestPathStep* other);bool operator<(ShortestPathStep* other);bool operator<=(ShortestPathStep* other);bool operator>=(ShortestPathStep* other);
};
#endif

ShortestPathStep.cpp

#include "ShortestPathStep.h"Point ShortestPathStep::fromTile = Point::ZERO;
Point ShortestPathStep::toTile = Point::ZERO;ShortestPathStep::ShortestPathStep():m_nGScore(0),m_nHScore(0),m_pParent(nullptr)
{
}ShortestPathStep::~ShortestPathStep()
{
}
ShortestPathStep* ShortestPathStep::create(const Point& tilePos)
{auto step = new ShortestPathStep();if (step && step->init(tilePos))step->autorelease();elseSDL_SAFE_DELETE(step);return step;
}bool ShortestPathStep::init(const Point& tilePos)
{this->setTilePos(tilePos);return true;
}bool ShortestPathStep::equals(const ShortestPathStep& other) const
{return m_tilePos.equals(other.m_tilePos);
}int ShortestPathStep::getFScore() const
{
/*  auto dx1 = m_tilePos.x - ShortestPathStep::toTile.x;auto dy1 = m_tilePos.y - ShortestPathStep::toTile.y;auto dx2 = fromTile.x - ShortestPathStep::toTile.x;auto dy2 = fromTile.y - ShortestPathStep::toTile.y;auto cross = abs(dx1 * dy2 - dx2 * dy1);if (cross != 1 && cross != 2)cross = 100;*/return m_nGScore + m_nHScore /** (int)cross*/;
}void ShortestPathStep::description()
{printf("tile_pos:%.f,%.f gscore%d,hscore%d\n",m_tilePos.x,m_tilePos.y,m_nGScore,m_nHScore);
}ShortestPathStep* ShortestPathStep::getParent() const
{return m_pParent;
}void ShortestPathStep::setParent(ShortestPathStep* other)
{m_pParent = other;
}bool ShortestPathStep::operator>(ShortestPathStep* other)
{return this->getFScore() > other->getFScore();
}bool ShortestPathStep::operator<(ShortestPathStep* other)
{return this->getFScore() < other->getFScore();
}bool ShortestPathStep::operator<=(ShortestPathStep* other)
{return this->getFScore() <= other->getFScore();
}bool ShortestPathStep::operator>=(ShortestPathStep* other)
{return this->getFScore() >= other->getFScore();
}

ShortestPathStep类作为A星算法的“步”,即角色在根据步数组后到达目的地,它封装了Point,还添加了既定代价G和估算代价H。getFScore函数的数学公式是 F = G + cross *H,这个公式是我在第一篇论文中看到的优化A星算法中启发函数H的比重

(另外一个是使用最小堆来优化open表的排序,这个我尝试过,但是可能是我的最小堆实现有问题,其实际效率不如插入排序)。

经我个人验证,cross会导致求得的路径不是最优解,故目前暂不采用。

然后就是A星算法。

#ifndef __AStar_H__
#define __AStar_H__
#include <cmath>
#include <algorithm>
#include <functional>
#include "SDL_Engine/SDL_Engine.h"using namespace std;
using namespace SDL;enum class Direction;
class ShortestPathStep;class AStar : public Object
{SDL_SYNTHESIZE(Size,m_mapSize,MapSize);
public:std::function<bool (const Point& tilePos)> isPassing;std::function<bool (const Point& tilePos,Direction dir)> isPassing4;
private:vector<ShortestPathStep*> m_openSteps;vector<ShortestPathStep*> m_closeSteps;
public:AStar();~AStar();CREATE_FUNC(AStar);bool init();//不检测toTile是否可通过ShortestPathStep* parse(const Point& fromTile,const Point& toTile);
private:void insertToOpenSteps(ShortestPathStep* step);int computeHScoreFromCoord(const Point& fromTileCoord,const Point& toTileCoord);//根据对应位置获取代价int calculateCost(const Point& tilePos);bool isValid(const Point& tilePos)const;vector<ShortestPathStep*>::const_iterator containsTilePos(const vector<ShortestPathStep*>& vec,const Point& tilePos);
};
#endif

在本游戏中,由于角色只能上下左右四方向行走,故估算函数采用曼哈顿距离。这里的A星算法有isPassing函数和isPassing4函数,isPassing函数是为了判断某一步/点是否可以通过。而isPassing4则是判断某一点的上下左右四个方向是否能通过。如下图:

1,2,3都是特定方向不可通过的,如主角可以站立在1,2,3上,但是对于1,主角向右时无法通过,2,3同理。而对于4而言,本身就无法通过。即只有图块可以通过时,四方向通过才有意义。

其在tiled中的属性如下:

为3图块属性,表示下和右不可通过。为4的属性。

pass_%s 是该图块某方向是否能通过,priority优先级则是人物是否可通过,以及可通过的遮挡关系。

这个在我以前的帖子里(https://blog.csdn.net/bull521/article/details/78935142)有说明,不再赘述。

AStar.cpp

ShortestPathStep* AStar::parse(const Point& fromTile,const Point& toTile)
{bool bPathFound = false;ShortestPathStep* pTail = nullptr;//设置开始和结束位置ShortestPathStep::fromTile = fromTile;ShortestPathStep::toTile = toTile;//方向数组vector<Direction> dirs;dirs.push_back(Direction::Down);dirs.push_back(Direction::Left);dirs.push_back(Direction::Right);dirs.push_back(Direction::Up);//把开始位置插入到开始列表中auto from = ShortestPathStep::create(fromTile);m_openSteps.push_back(from);do{ShortestPathStep* currentStep = m_openSteps.front();m_openSteps.erase(m_openSteps.begin());//添加到封闭列表m_closeSteps.push_back(currentStep);//如果当前路径就是结束路径if (currentStep->getTilePos().equals(toTile)){bPathFound = true;pTail = currentStep;//清除开放列表m_openSteps.clear();m_closeSteps.clear();break;}//对四方向进行遍历for (const auto& dir : dirs){Point tilePos;Direction nextDir;StaticData::getInstance()->direction(dir, nullptr, &tilePos, &nextDir);tilePos += currentStep->getTilePos();//在闭合列表已经存在该位置 直接跳过if (containsTilePos(m_closeSteps,tilePos) != m_closeSteps.end()){continue;}int moveCost = calculateCost(tilePos);//如果该位置不在开放列表中,添加auto it = containsTilePos(m_openSteps, tilePos);if (it == m_openSteps.end()){//目标合法才添加 默认toTile可通过if (isValid(tilePos) && isPassing4(currentStep->getTilePos(),dir)&& (tilePos == toTile || isPassing(tilePos)) && isPassing4(tilePos,nextDir)){ShortestPathStep* step = ShortestPathStep::create(tilePos);step->setParent(currentStep);step->setGScore(currentStep->getGScore() + moveCost);step->setHScore(computeHScoreFromCoord(tilePos, toTile));//插入到开放列表中insertToOpenSteps(step);}}else{auto step = (*it);//当前花费小于原来的花费,覆盖其值if (currentStep->getGScore() + moveCost < step->getGScore()){step->setGScore(currentStep->getGScore() + moveCost);step->setParent(currentStep);//移除后重新添加m_openSteps.erase(it);insertToOpenSteps(step);}}}}while( !m_openSteps.empty());return pTail;
}

注意这里的toTile,在AStart中,对toTile是不进行检测的,这样处理是为了以后便于与NPC的交互,至于toTile是否可达应该交给上层进行判断。

AStar::parse就是获取路径,如果搜索失败,则返回空指针,表示目的地不可达。

流程图如下:

parse中获取相邻的节点后,先判断是否已经访问过了,即是否在close表中,如果不在则判断是否在open表中,如果在,则判断是否应该更新对应的F值,否则对该点的合法性(是否可通过,四方向通过)进行判断。

void AStar::insertToOpenSteps(ShortestPathStep* step)
{int stepFScore = step->getFScore();auto it = m_openSteps.begin();//找打合适的插入位置for (;it != m_openSteps.end();it++){auto temp = *it;if (stepFScore < temp->getFScore()){break;}}//插入m_openSteps.insert(it, step);
}int AStar::computeHScoreFromCoord(const Point& fromTileCoord,const Point& toTileCoord)
{return (int)abs(fromTileCoord.x - toTileCoord.x ) + (int)abs(fromTileCoord.y - toTileCoord.y);
}int AStar::calculateCost(const Point& tilePos)
{return 1;
}bool AStar::isValid(const Point&tilePos) const
{if (tilePos.x < 0 || tilePos.x > m_mapSize.width|| tilePos.y < 0 || tilePos.y > m_mapSize.height)return false;return true;
}vector<ShortestPathStep*>::const_iterator AStar::containsTilePos(const vector<ShortestPathStep*>& vec,const Point& tilePos)
{auto it = find_if(vec.cbegin(), vec.cend(), [tilePos](ShortestPathStep* step){return step->getTilePos().equals(tilePos);});return it;
}

open表的排序使用了插入排序。calculateCost是估算代价,这里统一为1,在以后的开发里如果添加地形可以在这里进行更改代价。至于computeHScoreFromCoord函数则是采用了曼哈顿距离计算H值。

然后就是StaticData增加了以下:

#ifndef __StaticData_H__
#define __StaticData_H__
#include <string>
#include <vector>
#include <functional>
#include "SDL_Engine/SDL_Engine.h"using namespace std;
USING_NS_SDL;
//定义一些常用的宏
#define STATIC_DATA_PATH "data/static_data.plist"
/*简化使用*/
#define STATIC_DATA_STRING(key) (StaticData::getInstance()->getValueForKey(key)->asString())
#define STATIC_DATA_INT(key) (StaticData::getInstance()->getValueForKey(key)->asInt())
#define STATIC_DATA_FLOAT(key) (StaticData::getInstance()->getValueForKey(key)->asFloat())
#define STATIC_DATA_BOOLEAN(key) (StaticData::getInstance()->getValueForKey(key)->asBool())
#define STATIC_DATA_POINT(key) (StaticData::getInstance()->getPointForKey(key))
#define STATIC_DATA_ARRAY(key) (StaticData::getInstance()->getValueForKey(key)->asValueVector())
#define STATIC_DATA_TOSTRING(key) (StaticData::getInstance()->toString(key))/*方向,跟贴图有关*/
enum class Direction
{Down = 0,Left,Right,Up,
};class AStar;class StaticData : public Object
{
private:static StaticData* s_pInstance;
public:static StaticData* getInstance();static void purge();
private://键值对ValueMap m_valueMap;//角色键值对ValueMap m_characterMap;//A*寻路算法AStar* m_pAStar;
private:StaticData();~StaticData();bool init();
public:/**@brief 根据键获取值@key 要查询的键@return 返回的值,如果不存在对应的值,则返回空Value*/Value* getValueForKey(const string& key);//加载角色数据以及加载所需要的图片并解析bool loadCharacterFile(const string& filename);//获取人物行走动画Animation* getWalkingAnimation(const string& chartletName, Direction direction);Animation* getWalkingAnimation(const string& filename, int index, Direction dir, float delay, int loops, bool restoreOriginalFrame);
    //获取A星算法AStar* getAStar() { return m_pAStar; }bool direction(Direction dir,string* sDir,Point* delta,Direction* oppsite);
private://添加角色战斗图并生成16状态动画bool addSVAnimation(const string& filename);//添加角色升级文件bool addLevelUpData(const string& filename);/*在纹理指定区域rect按照宽度切割,并返回*/vector<SpriteFrame*> splitTexture(Texture* texture, const Rect& rect ,float width);};
#endifclass AStar;class StaticData : public Object
{
private:static StaticData* s_pInstance;
public:static StaticData* getInstance();static void purge();
private://键值对ValueMap m_valueMap;//角色键值对ValueMap m_characterMap;//A*寻路算法AStar* m_pAStar;
private:StaticData();~StaticData();bool init();
public:/**@brief 根据键获取值@key 要查询的键@return 返回的值,如果不存在对应的值,则返回空Value*/Value* getValueForKey(const string& key);//加载角色数据以及加载所需要的图片并解析bool loadCharacterFile(const string& filename);//获取人物行走动画Animation* getWalkingAnimation(const string& chartletName, Direction direction);Animation* getWalkingAnimation(const string& filename, int index, Direction dir, float delay, int loops, bool restoreOriginalFrame);
    //获取A星算法AStar* getAStar() { return m_pAStar; }bool direction(Direction dir,string* sDir,Point* delta,Direction* oppsite);
private://添加角色战斗图并生成16状态动画bool addSVAnimation(const string& filename);//添加角色升级文件bool addLevelUpData(const string& filename);/*在纹理指定区域rect按照宽度切割,并返回*/vector<SpriteFrame*> splitTexture(Texture* texture, const Rect& rect ,float width);};
#endif

新增的direction函数的功能是根据当前的方向获取对应的反方向,以及单位向量。

bool StaticData::init()
{//读取文件并保存键值对m_valueMap = FileUtils::getInstance()->getValueMapFromFile(STATIC_DATA_PATH);  m_pAStar = AStar::create();SDL_SAFE_RETAIN(m_pAStar);
return true;
}    m_pAStar = AStar::create();SDL_SAFE_RETAIN(m_pAStar);
return true;
}
bool StaticData::direction(Direction dir,string* sDir,Point* delta,Direction* oppsite)
{if (sDir == nullptr && delta == nullptr && oppsite == nullptr)return false;Point temp;Direction oppsiteDir = dir;string text;switch (dir){case Direction::Down:text = "down";temp.y = 1.f;oppsiteDir = Direction::Up;break;case Direction::Left:text = "left";temp.x = -1.f;oppsiteDir = Direction::Right;break;case Direction::Right:text = "right";temp.x = 1.f;oppsiteDir = Direction::Left;break;case Direction::Up:text = "up";temp.y = -1.f;oppsiteDir = Direction::Down;break;default:break;}if (sDir != nullptr)*sDir = text;if (delta != nullptr)*delta = temp;if (oppsite != nullptr)*oppsite = oppsiteDir;return true;
}

然后就是角色类的更新:

#ifndef __Character_H__
#define __Character_H__
#include <string>
#include "Entity.h"
using namespace std;enum class State
{None,Idle,Walking,
};
enum class Direction;
class ShortestPathStep;
class NonPlayerCharacter;class Character : public Entity
{SDL_SYNTHESIZE_READONLY(string, m_chartletName, ChartletName);//当前贴图名,也可以认为是人物名称,唯一SDL_SYNTHESIZE(float, m_durationPerGrid, DurationPerGrid);//每格的行走时间
private:Direction m_dir;State m_state;bool m_bDirty;Character* m_pFollowCharacter;//运动相关vector<ShortestPathStep*> m_shortestPath;unsigned int m_nStepIndex;ShortestPathStep* m_lastStep;Point m_pendingMove;bool m_bHavePendingMove;bool m_bMoving;//NonPlayerCharacter* m_pTriggerNPC;在地6节添加
public:Character();~Character();static Character* create(const string& chartletName);bool init(const string& chartletName);//跟随某角色void follow(Character* character);//设置npcvoid setTriggerNPC(NonPlayerCharacter* npc);//方向改变Direction getDirection() const;void setDirection(Direction direction);bool isMoving() const { return m_bMoving; }//运动 默认tilePos必能通过,由上层筛选bool moveToward(const Point& tilePos);//移动一步bool moveOneStep(ShortestPathStep* step);
private://切换状态void changeState(State state);//构造路径并运行动画void constructPathAndStartAnimation(ShortestPathStep* pHead);void popStepAndAnimate();//清除行走路径void clearShortestPath();Direction getDirection(const Point& delta) const;
};
#endif

每个角色类都有一个贴图(这点和以后的NPC相同,不过NPC允许没有贴图),贴图表示了当前角色的行走动画,所谓唯一指的是在character.plist文件中键唯一,即一对一映射。

bool Character::moveToward(const Point& tilePos)
{//当前角色正在运动,则更改待到达目的地if (m_bMoving){m_bHavePendingMove = true;m_pendingMove = tilePos;return true;}auto fromTile = GameScene::getInstance()->getMapLayer()->convertToTileCoordinate(this->getPosition());//A*算法解析路径AStar* pAStar = StaticData::getInstance()->getAStar();auto pTail = pAStar->parse(fromTile, tilePos);//目标可达,做运动前准备if (pTail != nullptr){this->constructPathAndStartAnimation(pTail);return true;}return false;
}

Character类中主要添加了一些要实现寻路而必须要有的变量。如m_bHavePendingMove和m_pendingMove是为了保证当前的角色正在运动时,为了保证角色的位置契合图块(所谓契合,表示角色在停止后一定是处理图块的正中间),同时为了响应终点的改变而做的滞后寻路。其他新增的几个私有函数则是为了配合moveToward。节点之间可以认为是链表,寻路完成返回的是pTail即为终点,反过来就是从开始到终点的完整路径。

void Character::constructPathAndStartAnimation(ShortestPathStep* pHead)
{//此时的角色一定不在运动中//构建运动列表while (pHead != nullptr && pHead->getParent() != nullptr){auto it = m_shortestPath.begin();m_shortestPath.insert(it,pHead);SDL_SAFE_RETAIN(pHead);pHead = pHead->getParent();}//此位置为主角当前tile 位置SDL_SAFE_RELEASE(m_lastStep);m_lastStep = pHead;SDL_SAFE_RETAIN(m_lastStep);this->popStepAndAnimate();
}

角色的行走和动画全权交给了popStepAndAnimate进行处理。除了constructPathAndStartAnimation调用popStepAndAnimate函数外,它还会由“自己”调用(内部是MoveTo + Callback,在回调函数Callback中会再次回调popStepAndAnimate函数以保证行走的正确进行和结束)。

void Character::popStepAndAnimate()
{m_bMoving = false;//存在待到达目的点,转入if (m_bHavePendingMove){m_bHavePendingMove = false;this->clearShortestPath();//滞后改变this->moveToward(m_pendingMove);return ;}//运动结束else if (m_nStepIndex >= m_shortestPath.size()){this->clearShortestPath();//站立动画this->changeState(State::Idle);return ;}//点击了NPC,且将要到达
/*  else if (m_pTriggerNPC != nullptr && m_nStepIndex == m_shortestPath.size() - 1){auto delta = m_shortestPath.back()->getTilePos() - m_lastStep->getTilePos();auto newDir = this->getDirection(delta);//改变方向if (newDir != m_dir){m_bDirty = true;m_dir = newDir;}this->clearShortestPath();this->changeState(State::Idle);m_pTriggerNPC->execute(this->getUniqueID());m_pTriggerNPC = nullptr;return ;}*///存在跟随角色,设置跟随if (m_pFollowCharacter != nullptr){m_pFollowCharacter->moveOneStep(m_lastStep);}SDL_SAFE_RELEASE(m_lastStep);m_lastStep = m_shortestPath.at(m_nStepIndex);SDL_SAFE_RETAIN(m_lastStep);auto tileSize = GameScene::getInstance()->getMapLayer()->getTiledMap()->getTileSize();//开始新的运动auto step = m_shortestPath.at(m_nStepIndex++);auto tilePos = step->getTilePos();Point pos = Point((tilePos.x + 0.5f) * tileSize.width,(tilePos.y + 0.5f) * tileSize.height);//开始运动MoveTo* move = MoveTo::create(m_durationPerGrid, pos);CallFunc* moveCallback = CallFunc::create([this](){//发送事件_eventDispatcher->dispatchCustomEvent(GameScene::CHARACTER_MOVE_TO_TILE, this);this->popStepAndAnimate();});//运行动作auto seq = Sequence::createWithTwoActions(move,moveCallback);this->runAction(seq);//引擎原因,需要先调用一次seq->step(1.f/60);//是否改变方向auto delta = pos - this->getPosition();Direction newDir = this->getDirection(delta);if (newDir != m_dir){m_dir = newDir;m_bDirty = true;}//改为运动状态this->changeState(State::Walking);m_bMoving = true;
}

popStepAndAnimate函数的功能就是处理行走和动画,以及跟随者的行走处理。需要注意的是,Callback内部还会分发 名为GameScene::CHARACTER_MOVE_TO_TILE的用户事件,该事件主要是为了方便以后触发NPC(如传送阵)而做的一点准备,目前暂时用不到。而在上面几个函数中,对m_lastStep进行了引用是为了重用,具体用在moveOneStep()函数中。

bool Character::moveOneStep(ShortestPathStep* step)
{//当前角色正在运动.先停止运动if (!m_shortestPath.empty()){this->clearShortestPath();}SDL_SAFE_RETAIN(step);this->m_shortestPath.push_back(step);this->popStepAndAnimate();return true;
}

moveOneStep()主要用在跟随角色,其处理和moveToward大致相同。

还有就是该函数会根据接下来的位置和当前位置进行比较,来判断方向是否改变和是否应该更新行走动画。

void Character::clearShortestPath()
{for (auto it = m_shortestPath.begin();it != m_shortestPath.end();){auto step = *it;SDL_SAFE_RELEASE_NULL(step);it = m_shortestPath.erase(it);}m_nStepIndex = 0;
}
Direction Character::getDirection(const Point& delta) const
{Direction nextDir = Direction::Down;if (delta.x > 0.f){nextDir = Direction::Right;}else if (delta.x < 0.f){nextDir = Direction::Left;}else if (delta.y > 0){nextDir = Direction::Down;}else if (delta.y < 0){nextDir = Direction::Up;}return nextDir;
}

getDirection函数的功能是根据矢量值获取方向。

然后就是isPassing和isPassing4函数。这两个函数由MapLayer提供,并在GameScene中丰富其功能。

bool MapLayer::isPassing(int gid)
{//获取图块优先级//默认为人物优先级最高int priority = 1;//获取对应属性ValueMap* properties = nullptr;//获取失败if ( !m_pTiledMap->getTilePropertiesForGID(gid, &properties))return true;//获取图块优先级ValueMap::iterator it = properties->find("priority");if (it != properties->end()){int value = it->second.asInt();priority = value;}//优先级为0则不可通过return priority != 0;
}bool MapLayer::isPassing(int gid,Direction direction)
{//获取对应属性ValueMap* properties = nullptr;if (!m_pTiledMap->getTilePropertiesForGID(gid, &properties))return true;//获取对应的键string key;switch (direction){case Direction::Down: key = "pass_down"; break;case Direction::Left: key = "pass_left"; break;case Direction::Right: key = "pass_right"; break;case Direction::Up: key = "pass_up"; break;}auto it = properties->find(key);//获取对应值并返回if (it != properties->end()){bool ret = it->second.asBool();return ret;}else//默认为可通过return true;
}

注意:getTilePropertiesForGID(int,ValueMap**)和cocos2d-x不同getTilePropertiesForGID(int, Value**);

这两个函数简单来说就是获取对应的图块,来检测其是否存在对应的属性。使用指针的原因在于指针的效率。

class GameScene : public Scene
{
private:static GameScene* s_pInstance;
public:static GameScene* getInstance();static void purge();
private:EventLayer* m_pEventLayer;MapLayer* m_pMapLayer;PlayerLayer* m_pPlayerLayer;Character* m_pViewpointCharacter;
public:static const int CHARACTER_LOCAL_Z_ORDER = 9999;//需要比tmx地图总图块大static const string CHARACTER_MOVE_TO_TILE;
private:GameScene();~GameScene();bool init();void preloadResources();bool initializeMapAndPlayers();//重写MapLayer方法bool isPassing(const Point& tilePos);bool isPassing4(const Point& tilePos, Direction dir);
public:void update(float dt);//改变场景void changeMap(const string& mapName, const Point& tileCoodinate);//设置视图中心点void setViewpointCenter(const Point& position, float duration = 0.f);//设置视角跟随void setViewpointFollow(Character* character);
public:void clickPath(const Point& location);MapLayer* getMapLayer() const { return m_pMapLayer; }
};EventLayer* m_pEventLayer;MapLayer* m_pMapLayer;PlayerLayer* m_pPlayerLayer;Character* m_pViewpointCharacter;
public:static const int CHARACTER_LOCAL_Z_ORDER = 9999;//需要比tmx地图总图块大static const string CHARACTER_MOVE_TO_TILE;
private:GameScene();~GameScene();bool init();void preloadResources();bool initializeMapAndPlayers();//重写MapLayer方法bool isPassing(const Point& tilePos);bool isPassing4(const Point& tilePos, Direction dir);
public:void update(float dt);//改变场景void changeMap(const string& mapName, const Point& tileCoodinate);//设置视图中心点void setViewpointCenter(const Point& position, float duration = 0.f);//设置视角跟随void setViewpointFollow(Character* character);
public:void clickPath(const Point& location);MapLayer* getMapLayer() const { return m_pMapLayer; }
};

GameScene中新添加了一个事件层,主要是为了过滤以及分发触碰事件。

string const GameScene::CHARACTER_MOVE_TO_TILE = "character move to tile";
bool GameScene::init()
{this->preloadResources();m_pEventLayer = EventLayer::create();this->addChild(m_pEventLayer);//地图层m_pMapLayer = MapLayer::create();this->addChild(m_pMapLayer);//角色层m_pPlayerLayer = PlayerLayer::create();this->addChild(m_pPlayerLayer);//初始化地图和角色this->initializeMapAndPlayers();this->scheduleUpdate();return true;
}
bool GameScene::initializeMapAndPlayers()
{
    //设置A星算法AStar* pAStar = StaticData::getInstance()->getAStar();pAStar->isPassing = SDL_CALLBACK_1(GameScene::isPassing, this);pAStar->isPassing4 = SDL_CALLBACK_2(GameScene::isPassing4, this);//获取地图auto dynamicData = DynamicData::getInstance();//TODO:暂时使用存档1dynamicData->initializeSaveData(1);//获得存档玩家控制的主角队伍的数据auto& valueMap = dynamicData->getTotalValueMapOfCharacter();Character* last = nullptr;//解析数据并生成角色for (auto itMap = valueMap.begin();itMap != valueMap.end();itMap++){auto chartletName = itMap->first;auto& propertiesMap = itMap->second.asValueMap();//创建角色Character* player = Character::create(chartletName);player->setDurationPerGrid(0.25f);//传递给主角层m_pPlayerLayer->addCharacter(player);//TODO:设置属性//DynamicData::getInstance()->//设置跟随if (last != nullptr)player->follow(last);else//设置视角跟随{this->setViewpointFollow(player);}last = player;}auto mapFilePath = dynamicData->getMapFilePath();auto tileCooridinate = dynamicData->getTileCoordinateOfPlayer();//改变地图this->changeMap(mapFilePath, tileCooridinate);return true;
}    //设置A星算法AStar* pAStar = StaticData::getInstance()->getAStar();pAStar->isPassing = SDL_CALLBACK_1(GameScene::isPassing, this);pAStar->isPassing4 = SDL_CALLBACK_2(GameScene::isPassing4, this);//获取地图auto dynamicData = DynamicData::getInstance();//TODO:暂时使用存档1dynamicData->initializeSaveData(1);//获得存档玩家控制的主角队伍的数据auto& valueMap = dynamicData->getTotalValueMapOfCharacter();Character* last = nullptr;//解析数据并生成角色for (auto itMap = valueMap.begin();itMap != valueMap.end();itMap++){auto chartletName = itMap->first;auto& propertiesMap = itMap->second.asValueMap();//创建角色Character* player = Character::create(chartletName);player->setDurationPerGrid(0.25f);//传递给主角层m_pPlayerLayer->addCharacter(player);//TODO:设置属性//DynamicData::getInstance()->//设置跟随if (last != nullptr)player->follow(last);else//设置视角跟随{this->setViewpointFollow(player);}last = player;}auto mapFilePath = dynamicData->getMapFilePath();auto tileCooridinate = dynamicData->getTileCoordinateOfPlayer();//改变地图this->changeMap(mapFilePath, tileCooridinate);return true;
}

默认情况下,设置主角为视角中心点。

bool GameScene::isPassing(const Point& tilePos)
{auto mapSize = m_pMapLayer->getTiledMap()->getMapSize();//不可超出地图if (tilePos.x < 0 || tilePos.x > (mapSize.width - 1)|| tilePos.y > (mapSize.height - 1) || tilePos.y < 0){return false;}auto layer = m_pMapLayer->getCollisionLayer();auto gid = layer->getTileGIDAt(tilePos);return m_pMapLayer->isPassing(gid);
}bool GameScene::isPassing4(const Point& tilePos, Direction dir)
{auto layer = m_pMapLayer->getCollisionLayer();auto gid = layer->getTileGIDAt(tilePos);return m_pMapLayer->isPassing(gid, dir);
}

MapLayer提供的函数是判断对应图块的gid的属性。GameScene则在此的基础上,先获取到对应位置的图块id,然后再进行判断。而使用isPassing和isPassing4的原因是,如果在GameScene都为isPassing的话,编译器会报错,编译器无法知道使用的到底是哪个函数。

void GameScene::update(float dt)
{//视角跟随if (m_pViewpointCharacter != nullptr && m_pViewpointCharacter->isMoving()){this->setViewpointCenter(m_pViewpointCharacter->getPosition());}
}
void GameScene::setViewpointCenter(const Point& position, float duration)
{Size visibleSize = Director::getInstance()->getVisibleSize();const int tag = 10;//地图跟随点移动float x = (float)MAX(position.x, visibleSize.width / 2);float y = (float)MAX(position.y, visibleSize.height / 2);//获取地图层的地图auto tiledMap = m_pMapLayer->getTiledMap();auto tileSize = tiledMap->getTileSize();auto mapSize = tiledMap->getMapSize();auto mapSizePixel = Size(tileSize.width * mapSize.width, tileSize.height * mapSize.height);//不让显示区域超过地图的边界x = (float)MIN(x, (mapSizePixel.width - visibleSize.width / 2.f));y = (float)MIN(y, (mapSizePixel.height - visibleSize.height / 2.f));//实际移动的位置Point actualPosition = Point(x, y);//屏幕中心位置坐标Point centerOfView = Point(visibleSize.width / 2, visibleSize.height / 2);Point delta = centerOfView - actualPosition;Action* action = nullptr;//地图运动if (duration < FLT_EPSILON){action = Place::create(delta);}else{action = MoveTo::create(duration, delta);}action->setTag(tag);if (tiledMap->getActionByTag(tag) != nullptr){tiledMap->stopActionByTag(tag);}tiledMap->runAction(action);
}void GameScene::setViewpointFollow(Character* character)
{m_pViewpointCharacter = character;
}

这两个函数主要是为了实现任意角色的视角跟随,默认情况下是跟随主角。

void GameScene::clickPath(const Point& location)
{auto nodePos = m_pMapLayer->getTiledMap()->convertToNodeSpace(location);auto tilePos = m_pMapLayer->convertToTileCoordinate(nodePos);//目标不可达if (!this->isPassing(tilePos))return false;//主角运动auto player = m_pPlayerLayer->getPlayer();player->moveToward(tilePos);return true;
}

在onTouchBegan函数中对触摸点进行判断,如果合法就传递给主角,使之运动。

#include "EventLayer.h"
#include "GameScene.h"
#include "StaticData.h"EventLayer::EventLayer()
{
}EventLayer::~EventLayer()
{
}bool EventLayer::init()
{auto listener = EventListenerTouchOneByOne::create();listener->onTouchBegan = SDL_CALLBACK_2(EventLayer::onTouchBegan,this);listener->onTouchMoved = SDL_CALLBACK_2(EventLayer::onTouchMoved,this);listener->onTouchEnded = SDL_CALLBACK_2(EventLayer::onTouchEnded,this);_eventDispatcher->addEventListener(listener,this);return true;
}bool EventLayer::onTouchBegan(Touch* touch,SDL_Event* event)
{gameScene->clickPath(location);return true;
}void EventLayer::onTouchMoved(Touch* touch,SDL_Event* event)
{
}void EventLayer::onTouchEnded(Touch* touch,SDL_Event* event)
{
}

目前的事件层主要起到了事件转发功能,以后会在此的基础上有选择的转发事件。

本节运行结果:

本节代码:链接:https://pan.baidu.com/s/1r8gcC7LX9EaeqDK_ukJuOg 密码:6mpf

RPG游戏制作-03-人物行走及A*寻路算法相关推荐

  1. 转载一个手机RPG游戏制作工具,仿造RPGXP写的

    转载一个手机RPG游戏制作工具,仿造RPGXP写的 有朋友问有没有脚本编辑功能,大家注意啦: 有脚本编辑功能,可视化界面编辑脚本,和RMXP一样 经过一年的开发,手机RPG游戏制作工具--MobieG ...

  2. 简单的RPG游戏制作教程

    □企划部份 ◎第一步:决定资料格式 在进入游戏制作的初期,由于有许多和程式有关的资料需要编整,因此担任企划的人员常会忙得乱七八糟.在这个阶段,企划人员必需要和程式商量游戏中资料的格式.举个例子来说,在 ...

  3. Unity学习笔记3 简易2D横版RPG游戏制作(三)

    这一篇本来应该是在上一篇后面直接补进去的.不过因为排版的问题.所以我就另开一篇来整理了,好了,废话不多说,马上整理: 十八.关卡的锁定与解锁 前面我们已经解决了在游戏开始时可以选择关卡的问题,接下来我 ...

  4. Unity学习笔记2 简易2D横版RPG游戏制作(二)

    十二.敌人受攻击时的闪烁和Player的生命值的修正 上一篇中,我们利用Controller2D中的IEnumerator TakenDamage接口,使得我们的Player受到攻击时会进行闪烁,我们 ...

  5. RPG游戏制作-02-游戏世界主角的诞生

    在RPG游戏中,有着各种各样的NPC(Non-Player Control),玩家可以操作主角与NPC进行交互,来获得情报,道具,装备等等.而NPC的概念比较广泛,从各种商人到宝箱再到空气墙,都可以认 ...

  6. 《从零开始的RPG游戏制作教程》第二期:让勇者和怪物登场

    目录 1. 前言 2. 第一期:制作基础场景 在[第一期:制作基础场景]中,我们教学了如何下载魔兽地图编辑器,并制作了一个非常基础的小村子场景. 本期我们会简单地在场景中放入魔兽争霸3自带的英雄,使得 ...

  7. 《从零开始的 RPG 游戏制作教程》第九期:信息反馈(上)

    目录 1. 前言 2. 第一期:制作基础场景 3. 第二期:让勇者和怪物登场 4. 第三期:基础的数值设计 5.第四期:从理论的角度简单聊聊关卡 6.第五期:制作物品和技能 7.第六期:设置怪物掉落物 ...

  8. 《从零开始的RPG游戏制作教程》第三期:基础的数值设计

    目录 1. 前言 2. 第一期:制作基础场景 2. 第二期:让勇者和怪物登场 上期回顾 在[第二期:让勇者和怪物登场]中,我们已经制作了一个自定义的勇者克里夫. 很显然,相比我们先前所放置的怪物,我们 ...

  9. 《从零开始的RPG游戏制作教程》第四期:从理论的角度简单聊聊关卡

    目录 1. 前言 2. 第一期:制作基础场景 3. 第二期:让勇者和怪物登场 4. 第三期:基础的数值设计 到目前为止,我们已经接触了如何制作简单的游戏场景,如何放置装饰物.单位,如何修改单位属性,并 ...

  10. RPG游戏制作-04-接入脚本前的准备

    在RPG的开发中,一般情况下都会使用脚本,脚本在游戏开发中也很重要,在RPG游戏中,脚本就像剧本,来控制整个RPG游戏的流程. 本游戏使用lua脚本语言,版本为5.3,没使用额外的库,这点和cocos ...

最新文章

  1. Ubuntu安装docker-ce,vagrant,virtualbox步骤
  2. 前端小笔记:左定宽,右随意
  3. smartrpc编译构建
  4. QT的QStatusBar类的使用
  5. LoadRunner远程监测Centos服务性能配置过程
  6. 属性的表示方法和对象的枚举
  7. ubuntu14.04matlab2015b 测试caffe的Matlab接口
  8. 图标设计素材|解析UI设计图标
  9. 智能语音升级用户体验,标贝以创新优势布局儿童有声内容市场
  10. android自定义弹出框样式实现
  11. 问题十九:怎么模拟ray tracing中漫射材料球体的颜色(diffuse materials)
  12. 苹果内容拦截器在哪_苹果全家桶,真的有别人说的那么香吗?
  13. java实现水仙花数int_java实现水仙花数的计算|chu
  14. VIM插件管理:管理插件的插件pathogen
  15. 2018 CCPC网络赛1004 HDU6441 Find Integer
  16. 安卓多人聊天室服务端
  17. js手机号码校验,邮箱校验
  18. Linux下的QQ客户端EVA初试以及ibus输入法问题的解决方法
  19. Linux平台设备框架驱动
  20. java-构建乘积数组(剑指offter-数组-简单)

热门文章

  1. Android中的SQLite数据库
  2. windows保护无法启动修复服务器,win10使用命令修复系统时提示Windows 资源保护无法启动修复服务怎么办...
  3. LDD3 sleepy 模块
  4. 计算机串口如何应用程序,串口通信调试软件的功能与使用方法 - 全文
  5. OpenStack实践(十一):Instance Live Migrate and Evacuate
  6. Rdd,DataFrame和DataSet的区别
  7. 轻松查询多个中通速递发出物流中含有某个城市的单号
  8. 宝藏级别的负数取模,让你关于负数取模不在陌生 >o< 进来看看吧
  9. mybatis plus 出现 Invalid bound statement (not found)
  10. UT2011学习笔记