FlowField流场寻路,利用网格存储每个点对目标点的推力,网格上的单位根据对于推力进行移动。用于大量单位进行寻路对于同一目的地的寻路,常用于rts游戏等。
计算所有网格对于目标点(图中红点)网格的路径距离。(每个格子的移动距离算作1)。
通过dijkstra算法遍历出每个格子的路径距离.(a *算法启发函数结果为0就是dijkstra算法。之前NavMesh寻路有说明过a *算法)
void FlowFieldScene::createHeadMap() {
unordered_map<int, float> openList;
unordered_map<int, float> closeList;
_distNode->removeAllChildren();
_dist.clear();
//FlowFieldMathHelper::mapHeight 地图高度,即地图在y轴上的格子数
for (int i = 0; i <= FlowFieldMathHelper::mapHeight; i++) {
vector<float> d;
d.resize(FlowFieldMathHelper::mapWidth + 1, 0);
_dist.push_back(d);
}
//转换实际位置到网格坐标的位置
Vec2 gridPos = FlowFieldMathHelper::getGridPos(_touchBeganPosition);
//每个网格都有个唯一id
int gridId = FlowFieldMathHelper::getGridIdByGridPos(gridPos.x, gridPos.y);
openList.emplace(gridId, 0);
while (!openList.empty()) {
pair<int, float> node = *openList.begin();
for (auto n : openList) {
if (node.second > n.second) node = n;
}
openList.erase(node.first);
closeList.insert(node);
Vec2 gridPos = FlowFieldMathHelper::getGridPosById(node.first);
_dist[gridPos.y][gridPos.x] = node.second;
//FlowFieldMathHelper::getNeighbor获取周边的格子,计算热度图四向就够
auto neighbors = FlowFieldMathHelper::getNeighbor(node.first);
for (auto neighbor : neighbors) {
//isBlock 判断是否是阻挡格
if (isBlock(neighbor.first)) continue;
if (closeList.find(neighbor.first) != closeList.end()) continue;
if (openList.find(neighbor.first) == openList.end()) {
openList.emplace(neighbor.first, neighbor.second + node.second);
}
else {
if (openList[neighbor.first] > neighbor.second + node.second) {
openList[neighbor.first] = neighbor.second + node.second;
}
}
}
}
}
生成热度图之后,遍历每个网格,查找他所有相邻的网格(8向),选择到目标点路径距离最小的网格(阻挡网格的路径距离无穷大),把当前网格的向量指向对应网格,最终生成矢量图
(注意,当周围有阻挡网格时,要判断不能斜向穿过阻挡格)
当矢量是左上(-1,1)左下(-1,-1)右上(1,1)右下(1,-1),判断目标格子周围的阻挡格
//静止倾斜穿过障碍物
bool FlowFieldScene::checkObliqueAngleBlock(int gridX, int gridY, int offsetX, int offsetY) {
if (offsetX * offsetY == 0) return false;
if (isBlock(gridX + offsetX, gridY) || isBlock(gridX, gridY + offsetY)) return true;
return false;
}
生成向量图
void FlowFieldScene::createVectorMap() {
_vectorNode->clear();
_vectorMap.clear();
for (int y = 0; y <= FlowFieldMathHelper::mapHeight; y++) {
for (int x = 0; x <= FlowFieldMathHelper::mapWidth; x++) {
if (_dist[y][x] == 0) continue;
Vec2 direct;
float neighborDist = -1;
for (int offsetX = -1; offsetX <= 1; offsetX++) {
for (int offsetY = -1; offsetY <= 1; offsetY++) {
int toX = x + offsetX;
int toY = y + offsetY;
if (isBlock(toX, toY)) continue;
else if (x == toX && y == toY) continue;
else if (toX < 0 || toX > FlowFieldMathHelper::mapWidth) continue;
else if (toY < 0 || toY > FlowFieldMathHelper::mapHeight) continue;
if ( neighborDist == -1 || neighborDist > _dist[toY][toX] ) {
if (checkObliqueAngleBlock(x, y, offsetX, offsetY)) continue;
neighborDist = _dist[toY][toX];
direct = Vec2(offsetX, offsetY);
}
}
}
_vectorMap.emplace(FlowFieldMathHelper::getGridIdByGridPos(x, y), direct);
}
}
}
根据设置的流场向量获得转向力
//_flowFieldDirect为设置的当前格子流场转向力方向
Vec2 MoveNode::flowField() {
if (_flowFieldDirect == Vec2::ZERO) return Vec2::ZERO;
Vec2 desiredVelocity = _flowFieldDirect * _dtSpeed;
Vec2 steering;
if (MoveSmooth) steering = desiredVelocity - _velocity;
else steering = desiredVelocity;
return steering;
}
加入之前的steering系统,转向系统,集群模拟
void MoveNode::update(float dt)
{
findNeighbourObjs();
_dtSpeed = _speed * dt;
Vec2 steering = Vec2::ZERO;
steering += seek(_tarPos);
steering += flee();
steering += wander();
steering += pursuit();
steering += cohesion();
steering += separation();
steering += alignment();
steering += flowField();
steering = turncate(steering, _maxForce);
steering *= ( 1 / (float)_mass );
_velocity += steering;
_velocity += wallAvoid();
_velocity = turncate(_velocity, _maxSpeed * dt);
updatePos();
}
此时如果直接取当前所在网格的向量作流场力的方向,会出现两个问题
void FlowFieldScene::update(float dt) {
for (auto node : _moveNodes) {
int gridId = FlowFieldMathHelper::getGridId(node->getPosition());
Vec2 direct = _vectorMap[gridId];
node->setFlowFieldDirect(direct);
}
}
1.如果转向力所占的权重不够大,会导致物体转向不及时,并且可能插入阻挡格的情况
2.如果增大转向力所占权重,又容易导致物体直接贴着网格边缘行走,而非沿着中线行走
为了改善这种情况,通常会根据所处当前格的不同位置,选取不同方向的三个相邻网格,加上自身网格的4个向量进行线插值
进行双线性插值
获取4个向量后,先把网格y值相同的向量,进行两两线性插值,再把求出的两个新向量进行线性插值即可
注意,双线性插值的话,如果目标点再阻挡格旁边,而阻挡格又没有向量(取0)的话,目标会直接穿过阻挡格
因此如果获取的网格是阻挡格,则直接取阻挡网格指向当前格的方向做插值。
这个是只有流场力的单独优化,实际项目中,不同力的权重可能不同,真正避免与阻挡物碰撞。还是要加上专门的碰撞避免处理(如阻挡物周围加力场,ORCA等)
Vec2 FlowFieldScene::bilinearInterpolation(Vec2 curPosition) {
Vec2 gridPos = FlowFieldMathHelper::getGridPos(curPosition);
int offsetX, offsetY;
if (curPosition.x < gridPos.x * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen / 2) offsetX = -1;
else if (curPosition.x == gridPos.x * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen / 2) offsetX = 0;
else offsetX = 1;
if (curPosition.y < gridPos.y * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen / 2) offsetY = -1;
else if (curPosition.y == gridPos.y * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen / 2) offsetY = 0;
else offsetY = 1;
Vec2 v1, v2, v3, v4;
v1 = getVectorByGridPos(gridPos);
bool noX = gridPos.x == FlowFieldMathHelper::mapWidth || isBlock(gridPos.x + offsetX, gridPos.y);
//if (noX) v2 = getVectorByGridPos(gridPos);
if (noX) v2 = Vec2(-offsetX, 0);
else v2 = getVectorByGridPos(gridPos + Vec2(offsetX, 0));
bool noY = gridPos.y == FlowFieldMathHelper::mapHeight || isBlock(gridPos.x, gridPos.y + offsetY);
//if (noY) v3 = getVectorByGridPos(gridPos);
if (noY) v3 = Vec2(0, -offsetY);
else v3 = getVectorByGridPos(gridPos + Vec2(0, offsetY));
//if (noX || noY) v4 = getVectorByGridPos(gridPos);
if (noX || noY) Vec2(-offsetX, -offsetY);
else v4 = getVectorByGridPos(gridPos + Vec2(offsetX, offsetY));;
float xWeight = abs(curPosition.x - gridPos.x * FlowFieldMathHelper::gridLen - FlowFieldMathHelper::gridLen / 2) / FlowFieldMathHelper::gridLen;
v1 = v1.lerp(v2, xWeight);
v3 = v3.lerp(v4, xWeight);
float yWeight = abs(curPosition.y - gridPos.y * FlowFieldMathHelper::gridLen - FlowFieldMathHelper::gridLen / 2) / FlowFieldMathHelper::gridLen;
Vec2 direct = v1.lerp(v3, yWeight);
direct.normalize();
return direct;
}
void FlowFieldScene::update(float dt) {
for (auto node : _moveNodes) {
int gridId = FlowFieldMathHelper::getGridId(node->getPosition());
Vec2 direct = bilinearInterpolation(node->getPosition());
// Vec2 direct = _vectorMap[gridId];
node->setFlowFieldDirect(direct);
}
}
通过双线性插值的方式,物体的行动轨迹会更加靠近与正中路线。
FlowField.h
#ifndef __FLOW_FIELD_SCENE_H__
#define __FLOW_FIELD_SCENE_H__
#include "cocos2d.h"
#include "CrowdSimulation/MoveNodeManager.h"
USING_NS_CC;
using namespace std;
class FlowFieldScene : public Scene
{
public:
static Scene* createScene();
virtual bool init();
virtual bool onTouchBegan(Touch* touch, Event* unused_event);
// implement the "static create()" method manually
CREATE_FUNC(FlowFieldScene);
void createHeadMap();
void showHeatInfo();
void createVectorMap();
bool checkObliqueAngleBlock(int gridX, int gridY, int offsetX, int offsetY);
bool isBlock(int gridX, int gridY);
bool isBlock(int gridId);
void resetMoveNode();
Vec2 getVectorByGridPos(Vec2 gridPos);
Vec2 bilinearInterpolation(Vec2 curPosition);
void update(float dt);
protected:
EventListenerTouchOneByOne* _touchListener;
Vec2 _touchBeganPosition;
DrawNode* _mapDrawNode;
Node* _distNode;
DrawNode* _vectorNode;
DrawNode* _tarDot;
unordered_map<int, bool> _blockGridIdMap;
vector<vector<float>> _dist;
unordered_map<int, Vec2> _vectorMap;
MoveNodeManager* _manager;
vector<MoveNode*> _moveNodes;
};
#endif
FlowField.cpp
#include "FlowFieldScene.h"
#include "FlowFieldMathHelper.h"
Scene* FlowFieldScene::createScene()
{
return FlowFieldScene::create();
}
vector<pair<Vec2, Vec2>> blockLine = {
make_pair(Vec2(200,400), Vec2(200,300)),
make_pair(Vec2(220,400), Vec2(220,300)),
make_pair(Vec2(240,400), Vec2(240,300)),
make_pair(Vec2(260,550), Vec2(260,250)),
make_pair(Vec2(280,350), Vec2(600,350)),
make_pair(Vec2(700, 280), Vec2(1100, 280)),
make_pair(Vec2(750, 330), Vec2(1150, 330)),
make_pair(Vec2(115, 115), Vec2(115, 150)),
make_pair(Vec2(115, 115), Vec2(215, 115)),
make_pair(Vec2(270, 87), Vec2(666, 87)),
make_pair(Vec2(777, 575), Vec2(1233, 575)),
make_pair(Vec2(500, 400), Vec2(500, 466)),
make_pair(Vec2(370, 222), Vec2(577, 222)),
make_pair(Vec2(888, 124), Vec2(1277, 124)),
};
static void problemLoading(const char* filename)
{
printf("Error while loading: %s\n", filename);
printf("Depending on how you compiled you might have to add 'Resources/' in front of filenames in FlowFieldScene.cpp\n");
}
// on "init" you need to initialize your instance
bool FlowFieldScene::init()
{
//
// 1. super init first
if (!Scene::init())
{
return false;
}
auto visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
auto layer = LayerColor::create(Color4B(255, 255, 255, 255));
layer:setContentSize(visibleSize);
this->addChild(layer);
auto node = DrawNode::create();
this->addChild(node);
// for (int i = FlowFieldMathHelper::gridLen; i < visibleSize.width; i += FlowFieldMathHelper::gridLen) {
// node->drawSegment(Vec2(i, 0), Vec2(i, 640), 1, Color4F(0, 0, 0, 0.3));
// }
// for (int i = FlowFieldMathHelper::gridLen; i < visibleSize.height; i += FlowFieldMathHelper::gridLen) {
// node->drawSegment(Vec2(0, i), Vec2(1400, i), 1, Color4F(0, 0, 0, 0.3));
// }
for (auto line : blockLine) {
if (line.first.x == line.second.x) {
auto minY = min(line.first.y, line.second.y);
auto maxY = max(line.first.y, line.second.y);
int gridMinY = minY / FlowFieldMathHelper::gridLen;
int gridMaxY = maxY / FlowFieldMathHelper::gridLen + 1;
int gridX = line.first.x / FlowFieldMathHelper::gridLen;
for (auto y = gridMinY; y <= gridMaxY; y ++) {
int id = FlowFieldMathHelper::getGridIdByGridPos(gridX, y);
_blockGridIdMap.emplace(id, true);
}
Vec2 v1 = Vec2(gridX, gridMinY);
Vec2 v2 = Vec2(gridX, gridMaxY);
vector<Vec2> v = {
Vec2(v1.x * FlowFieldMathHelper::gridLen, v1.y * FlowFieldMathHelper::gridLen),
Vec2(v1.x * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen, v1.y * FlowFieldMathHelper::gridLen),
Vec2(v1.x * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen, v2.y * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen),
Vec2(v1.x * FlowFieldMathHelper::gridLen, v2.y * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen),
};
node->drawPolygon(reinterpret_cast<Vec2*>(v.data()), v.size(), Color4F(0, 0, 0, 1), 0, Color4F(0, 0, 0, 0));
}
else if (line.first.y == line.second.y) {
auto minX = min(line.first.x, line.second.x);
auto maxX = max(line.first.x, line.second.x);
int gridMinX = minX / FlowFieldMathHelper::gridLen;
int gridMaxX = maxX / FlowFieldMathHelper::gridLen + 1;
int gridY = line.first.y / FlowFieldMathHelper::gridLen;
for (auto x = gridMinX; x <= gridMaxX; x++) {
int id = FlowFieldMathHelper::getGridIdByGridPos(x, gridY);
_blockGridIdMap.emplace(id, true);
}
Vec2 v1 = Vec2(gridMinX, gridY);
Vec2 v2 = Vec2(gridMaxX, gridY);
vector<Vec2> v = {
Vec2(v1.x * FlowFieldMathHelper::gridLen, v1.y * FlowFieldMathHelper::gridLen),
Vec2(v2.x * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen, v1.y * FlowFieldMathHelper::gridLen),
Vec2(v2.x * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen, v1.y * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen),
Vec2(v1.x * FlowFieldMathHelper::gridLen, v1.y * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen),
};
node->drawPolygon(reinterpret_cast<Vec2*>(v.data()), v.size(), Color4F(0, 0, 0, 1), 0, Color4F(0, 0, 0, 0));
}
}
_mapDrawNode = DrawNode::create();
this->addChild(_mapDrawNode);
_distNode = Node::create();
this->addChild(_distNode);
_vectorNode = DrawNode::create();
this->addChild(_vectorNode);
_tarDot = DrawNode::create();
this->addChild(_tarDot);
_tarDot->drawDot(Vec2::ZERO, 5, Color4F(1, 0, 0, 1));
_tarDot->setPosition(Vec2(777, 444));
_touchListener = EventListenerTouchOneByOne::create();
_touchListener->setSwallowTouches(true);
_touchListener->onTouchBegan = CC_CALLBACK_2(FlowFieldScene::onTouchBegan, this);
this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(_touchListener, layer);
_manager = new MoveNodeManager();
this->scheduleUpdate();
return true;
}
bool FlowFieldScene::onTouchBegan(Touch* touch, Event* event)
{
_touchBeganPosition = touch->getLocation();
CCLOG("==========》 %f, %f", _touchBeganPosition.x, _touchBeganPosition.y);
if (FlowFieldMathHelper::getGridId(_touchBeganPosition) == -1) {
resetMoveNode();
return true;
}
_tarDot->setPosition(_touchBeganPosition);
createHeadMap();
createVectorMap();
// resetMoveNode();
return true;
}
void FlowFieldScene::update(float dt) {
for (auto node : _moveNodes) {
int gridId = FlowFieldMathHelper::getGridId(node->getPosition());
Vec2 direct = bilinearInterpolation(node->getPosition());
// Vec2 direct = _vectorMap[gridId];
node->setFlowFieldDirect(direct);
}
}
Vec2 FlowFieldScene::getVectorByGridPos(Vec2 gridPos) {
return _vectorMap[FlowFieldMathHelper::getGridIdByGridPos(gridPos.x, gridPos.y)];
}
Vec2 FlowFieldScene::bilinearInterpolation(Vec2 curPosition) {
Vec2 gridPos = FlowFieldMathHelper::getGridPos(curPosition);
int offsetX, offsetY;
if (curPosition.x < gridPos.x * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen / 2) offsetX = -1;
else if (curPosition.x == gridPos.x * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen / 2) offsetX = 0;
else offsetX = 1;
if (curPosition.y < gridPos.y * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen / 2) offsetY = -1;
else if (curPosition.y == gridPos.y * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen / 2) offsetY = 0;
else offsetY = 1;
Vec2 v1, v2, v3, v4;
v1 = getVectorByGridPos(gridPos);
bool noX = gridPos.x == FlowFieldMathHelper::mapWidth || isBlock(gridPos.x + offsetX, gridPos.y);
//if (noX) v2 = getVectorByGridPos(gridPos);
if (noX) v2 = Vec2(-offsetX, 0);
else v2 = getVectorByGridPos(gridPos + Vec2(offsetX, 0));
bool noY = gridPos.y == FlowFieldMathHelper::mapHeight || isBlock(gridPos.x, gridPos.y + offsetY);
//if (noY) v3 = getVectorByGridPos(gridPos);
if (noY) v3 = Vec2(0, -offsetY);
else v3 = getVectorByGridPos(gridPos + Vec2(0, offsetY));
//if (noX || noY) v4 = getVectorByGridPos(gridPos);
if (noX || noY) Vec2(-offsetX, -offsetY);
else v4 = getVectorByGridPos(gridPos + Vec2(offsetX, offsetY));;
float xWeight = abs(curPosition.x - gridPos.x * FlowFieldMathHelper::gridLen - FlowFieldMathHelper::gridLen / 2) / FlowFieldMathHelper::gridLen;
v1 = v1.lerp(v2, xWeight);
v3 = v3.lerp(v4, xWeight);
float yWeight = abs(curPosition.y - gridPos.y * FlowFieldMathHelper::gridLen - FlowFieldMathHelper::gridLen / 2) / FlowFieldMathHelper::gridLen;
Vec2 direct = v1.lerp(v3, yWeight);
direct.normalize();
return direct;
}
void FlowFieldScene::createHeadMap() {
unordered_map<int, float> openList;
unordered_map<int, float> closeList;
_distNode->removeAllChildren();
_dist.clear();
for (int i = 0; i <= FlowFieldMathHelper::mapHeight; i++) {
vector<float> d;
d.resize(FlowFieldMathHelper::mapWidth + 1, 0);
_dist.push_back(d);
}
Vec2 gridPos = FlowFieldMathHelper::getGridPos(_touchBeganPosition);
int gridId = FlowFieldMathHelper::getGridIdByGridPos(gridPos.x, gridPos.y);
openList.emplace(gridId, 0);
while (!openList.empty()) {
pair<int, float> node = *openList.begin();
for (auto n : openList) {
if (node.second > n.second) node = n;
}
openList.erase(node.first);
closeList.insert(node);
Vec2 gridPos = FlowFieldMathHelper::getGridPosById(node.first);
_dist[gridPos.y][gridPos.x] = node.second;
auto neighbors = FlowFieldMathHelper::getNeighbor(node.first);
for (auto neighbor : neighbors) {
if (isBlock(neighbor.first)) continue;
if (closeList.find(neighbor.first) != closeList.end()) continue;
if (openList.find(neighbor.first) == openList.end()) {
openList.emplace(neighbor.first, neighbor.second + node.second);
}
else {
if (openList[neighbor.first] > neighbor.second + node.second) {
openList[neighbor.first] = neighbor.second + node.second;
}
}
}
}
// showHeatInfo();
}
void FlowFieldScene::showHeatInfo() {
_distNode->removeAllChildren();
for (int y = 0; y <= FlowFieldMathHelper::mapHeight; y++) {
for (int x = 0; x <= FlowFieldMathHelper::mapWidth; x++) {
if (!isBlock(x, y)) {
auto num = Label::create();
num->setColor(Color3B(0, 255, 0));
//num->setSystemFontSize(20);
num->setSystemFontSize(20);
_distNode->addChild(num);
auto s = to_string(_dist[y][x]);
//num->setString(s.substr(0, s.find(".") + 2));
num->setString(s.substr(0, s.find(".")));
num->setPosition(Vec2(x * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen / 2, y * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen / 2));
}
}
}
}
bool FlowFieldScene::isBlock(int gridX, int gridY) {
return isBlock(FlowFieldMathHelper::getGridIdByGridPos(gridX, gridY));
}
bool FlowFieldScene::isBlock(int gridId) {
return _blockGridIdMap.find(gridId) != _blockGridIdMap.end();
}
//静止倾斜穿过障碍物
bool FlowFieldScene::checkObliqueAngleBlock(int gridX, int gridY, int offsetX, int offsetY) {
if (offsetX * offsetY == 0) return false;
if (isBlock(gridX + offsetX, gridY) || isBlock(gridX, gridY + offsetY)) return true;
return false;
}
void FlowFieldScene::createVectorMap() {
_vectorNode->clear();
_vectorMap.clear();
for (int y = 0; y <= FlowFieldMathHelper::mapHeight; y++) {
for (int x = 0; x <= FlowFieldMathHelper::mapWidth; x++) {
if (_dist[y][x] == 0) continue;
Vec2 direct;
float neighborDist = -1;
for (int offsetX = -1; offsetX <= 1; offsetX++) {
for (int offsetY = -1; offsetY <= 1; offsetY++) {
int toX = x + offsetX;
int toY = y + offsetY;
if (isBlock(toX, toY)) continue;
else if (x == toX && y == toY) continue;
else if (toX < 0 || toX > FlowFieldMathHelper::mapWidth) continue;
else if (toY < 0 || toY > FlowFieldMathHelper::mapHeight) continue;
if ( neighborDist == -1 || neighborDist > _dist[toY][toX] ) {
if (checkObliqueAngleBlock(x, y, offsetX, offsetY)) continue;
neighborDist = _dist[toY][toX];
direct = Vec2(offsetX, offsetY);
}
}
}
// Vec2 v1 = Vec2(x * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen / 2, y * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen / 2);
// Vec2 v2 = v1 + (direct * FlowFieldMathHelper::gridLen / 2);
// _vectorNode->drawSegment(v1, v2, 1, Color4F(0, 0, 1, 1));
_vectorMap.emplace(FlowFieldMathHelper::getGridIdByGridPos(x, y), direct);
}
}
}
void FlowFieldScene::resetMoveNode() {
if (_moveNodes.empty()) {
for (int i = 0; i < 100; i++) {
auto moveNode = _manager->getFlowFieldNode();
this->addChild(moveNode);
_moveNodes.push_back(moveNode);
}
}
float width = FlowFieldMathHelper::mapWidth * FlowFieldMathHelper::gridLen;
float height = FlowFieldMathHelper::mapHeight * FlowFieldMathHelper::gridLen;
for (auto node : _moveNodes) {
Vec2 v;
do {
v.x = RandomHelper::random_real<float>(10, width-10);
v.y = RandomHelper::random_real<float>(10, height-10);
} while (isBlock(FlowFieldMathHelper::getGridId(v)));
node->setPos(v);
//Vec2 vg = FlowFieldMathHelper::getGridPos(v);
//node->setPos(Vec2(vg.x * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen / 2, vg.y * FlowFieldMathHelper::gridLen + FlowFieldMathHelper::gridLen / 2));
}
}
FlowFieldMathHelper.h
#pragma once
#ifndef __FLOWFIELD_MATH_H__
#define __FLOWFIELD_MATH_H__
#include "cocos2d.h"
USING_NS_CC;
using namespace std;
class FlowFieldMathHelper
{
public:
static Vec2 getGridPos(Vec2 pos);
static int getGridId(Vec2 pos);
static int getGridIdByGridPos(int gridX, int gridY);
static vector<pair<int, float>> getNeighbor(int gridId);
static Vec2 getGridPosById(int gridId);
static const int gridLen = 24;
static const int mapWidth = 1400 / gridLen - 1;
static const int mapHeight = 640 / gridLen - 1;
};
#endif
FlowFieldMathHelper.cpp
#include "FlowFieldMathHelper.h"
Vec2 FlowFieldMathHelper::getGridPos(Vec2 pos) {
return Vec2(int(pos.x / gridLen), int(pos.y / gridLen));
}
int FlowFieldMathHelper::getGridId(Vec2 pos) {
Vec2 v = getGridPos(pos);
return getGridIdByGridPos(v.x, v.y);
}
int FlowFieldMathHelper::getGridIdByGridPos(int gridX, int gridY) {
if (gridX < 0 || gridX > mapWidth) return -1;
if (gridY < 0 || gridY > mapHeight) return -1;
return gridX + gridY * (mapWidth + 1);
}
Vec2 FlowFieldMathHelper::getGridPosById(int gridId) {
int x = gridId % (mapWidth + 1);
int y = (gridId - x) / (mapWidth + 1);
return Vec2(x, y);
}
vector<pair<int, float>> FlowFieldMathHelper::getNeighbor(int gridId) {
Vec2 pos = getGridPosById(gridId);
vector<pair<int, float>> ret;
/*for (int offsetX = -1; offsetX <= 1; offsetX++) {
for (int offsetY = -1; offsetY <= 1; offsetY++) {
int x = pos.x + offsetX;
int y = pos.y + offsetY;
if (x == pos.x && y == pos.y) continue;
else if (x < 0 || x > mapWidth) continue;
else if (y < 0 || y > mapHeight) continue;
else if (x == pos.x || y == pos.y) ret.push_back(make_pair(getGridIdByGridPos(x, y), 1));
else ret.push_back(make_pair(getGridIdByGridPos(x, y), 1.5));
}
}*/
for (int offsetX = -1; offsetX <= 1; offsetX++) {
for (int offsetY = -1; offsetY <= 1; offsetY++) {
int x = pos.x + offsetX;
int y = pos.y + offsetY;
if (x == pos.x && y == pos.y) continue;
else if (x < 0 || x > mapWidth) continue;
else if (y < 0 || y > mapHeight) continue;
else if (x == pos.x || y == pos.y) ret.push_back(make_pair(getGridIdByGridPos(x, y), 1));
}
}
return ret;
}