AI - Crowd Simulation(集群模拟)

类似鱼群,鸟群这种群体运动模拟。
是Microscopic Models 微观模型,定义每一个个体的行为,然后合在一起。
主要是根据一定范围内族群其他对象的运动状态决定自己的运动状态

Cohesion

保证个体不会脱离群体
求物体一定半径范围内的其他临近物体的所有位置,相加取平均位置,用这个位置进行一个追寻力seek

//求物体一定半径范围内的其他临近物体的所有位置,用这个位置进行一个追寻力seek
Vec2 MoveNode::cohesion() {
    Vec2 averagePos = Vec2::ZERO;
    int count = 0;
    for (auto obj : _cohesionObj) {
        if (obj->getId() != _id) {
            averagePos += obj->getPosition();
            count++;
        }
    }
    if (count > 0) { 
        averagePos *= (1 / (float)count); 
        return seek(averagePos) * _cohesionWeight;
    }
    return Vec2::ZERO;
}

一定范围内的个体会自发的聚集在一起
请添加图片描述

separation

保证个体不会聚集太密
求物体一定半径范围内的其他临近物体的位置,用当前物体位置分别减去临近物体位置,获取单位方向向量,乘以根据距离远近算出来的权重
越近权重越大。在把所有向量相加取平均值

Vec2 MoveNode::separation() {
    Vec2 steering = Vec2::ZERO;
    int count = 0;
    for (auto obj : _separationObj) {
        if (obj->getId() != _id) {
            float dist = this->getPosition().getDistance(obj->getPosition());

            Vec2 normalVector = (this->getPosition() - obj->getPosition()).getNormalized();
            Vec2 desiredVelocity = normalVector;

            desiredVelocity *= (1 / dist);
            steering += desiredVelocity;
            count++;
        }
    }
    if (count > 0) steering *= (1 / (float)count);
    return steering * _dtSpeed * _separationWeight;
}

一定范围内的个体会逐渐分散开来
请添加图片描述

alignment

保证个体的运动方向是跟随群体的
求物体一定半径范围内的其他临近物体的所有速度向量,相加取平均值

Vec2 MoveNode::alignment() {
    Vec2 steering = Vec2::ZERO;
    int count = 0;
    for (auto obj : _alignmentObj) {
        if (obj->getId() != _id) {
            steering += obj->getVelocity();
            count++;
        }
    }
    if (count > 0) steering *= (1 / (float)count);
    return steering * _alignmentWeight;
}

可以看到一开始各自不同移动方向的个体,在靠近群体的时候,逐渐跟随上群体的方向
请添加图片描述

合并效果

给三种力分别设置不同的权重,组合在一起可以对比群体运动的效果

node->setCohesionWeight(0.5);
node->setSeparationWeight(30);
node->setAlignmentWeight(0);

对齐力权重为0,即只有聚集力和分散力
集群只是聚成一团,但并没有一个整体的运动方向
请添加图片描述

node->setCohesionWeight(0.5);
node->setSeparationWeight(0);
node->setAlignmentWeight(1);

分散力权重为0,即只有聚集力和对齐力
集群几乎直接聚集成同一个点,进行移动
请添加图片描述

node->setCohesionWeight(0);
node->setSeparationWeight(30);
node->setAlignmentWeight(1);

聚集力权重为0,即只有分散力和对齐力
个体会随着周围的群体方向行进,但是容易散开来
请添加图片描述

node->setCohesionWeight(0.5);
node->setSeparationWeight(30);
node->setAlignmentWeight(1);

三种力都有的
可以通过对三种力设置不同的权重来控制集群的密集程度运动轨迹
请添加图片描述

我这里是简单粗暴的把所有物体加入遍历来筛选周围物体,实际项目中需要各种优化如AOI等来减少遍历的个数

源码

CrowdSimulation.h

#ifndef __CROWD_SIMULATION_SCENE_H__
#define __CROWD_SIMULATION_SCENE_H__

#include "cocos2d.h"
#include "MoveNodeManager.h"
USING_NS_CC;
using namespace std;

class CrowdSimulationScene : public Scene
{
public:
	static Scene* createScene();

    virtual bool init();

    virtual bool onTouchBegan(Touch* touch, Event* unused_event);
    void setSeekPos(Vec2 seekPos);
    void setFleePos(Vec2 fleePos);
    void setWanderPos(Vec2 wanderPos);
    void showPursuitModel(Vec2 tarPos);
    void showCombineModel(Vec2 tarPos);
    void showCohsionModel();
    void showSeparationModel();
    void showAlignmentModel();
    void showCrowdModel();

    // implement the "static create()" method manually
    CREATE_FUNC(CrowdSimulationScene);

    void update(float dt);

protected:
    EventListenerTouchOneByOne* _touchListener;
    Vec2 _touchBeganPosition;
    DrawNode* _mapDrawNode;
    DrawNode* _mapDrawNode1;

    MoveNodeManager* _manager;
    MoveNode* _moveNode;
    MoveNode* _moveNode1;
    vector<MoveNode*> _fleeNodes;
    bool _isDrawMoveLine;
};

#endif

CrowdSimulation.cpp

#include "CrowdSimulationScene.h"

Scene* CrowdSimulationScene::createScene()
{
    return CrowdSimulationScene::create();
}

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 CrowdSimulationScene.cpp\n");
}

// on "init" you need to initialize your instance
bool CrowdSimulationScene::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);

    _mapDrawNode = DrawNode::create();
    this->addChild(_mapDrawNode);

    _mapDrawNode1 = DrawNode::create();
    this->addChild(_mapDrawNode1);

    _touchListener = EventListenerTouchOneByOne::create();
    _touchListener->setSwallowTouches(true);
    _touchListener->onTouchBegan = CC_CALLBACK_2(CrowdSimulationScene::onTouchBegan, this);
    this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(_touchListener, layer);

    _manager = new MoveNodeManager();

    this->scheduleUpdate();
    return true;
}

bool CrowdSimulationScene::onTouchBegan(Touch* touch, Event* event)
{
    _touchBeganPosition = touch->getLocation();

    CCLOG("==========°∑ %f£¨ %f", _touchBeganPosition.x, _touchBeganPosition.y);

//    setSeekPos(_touchBeganPosition);
    //setFleePos(_touchBeganPosition);
//    setWanderPos(_touchBeganPosition);
//    showPursuitModel(_touchBeganPosition);
//    showCombineModel(_touchBeganPosition);
//    showCohsionModel();
//    showSeparationModel();
//    showAlignmentModel();
    showCrowdModel();
    return true;
}

void CrowdSimulationScene::update(float dt) {
    if (_isDrawMoveLine && _moveNode->getVelocity() != Vec2::ZERO) _mapDrawNode->drawDot(_moveNode->getPosition(), 3, Color4F(0, 0, 0, 1));
    _mapDrawNode1->clear();
    for (auto e : _fleeNodes) {
        _mapDrawNode1->drawDot(e->getPosition(), 100, Color4F(1, 1, 0, 0.3));
    }
}

void CrowdSimulationScene::setSeekPos(Vec2 seekPos) {
    if (_moveNode == nullptr) {
        _moveNode = _manager->getPlayer();
        _moveNode->setPos(Vec2(100, 100));
        this->addChild(_moveNode);
        _isDrawMoveLine = true;
    }

    _moveNode->setTarPos(seekPos);
    _mapDrawNode->clear();
    _mapDrawNode->drawDot(seekPos, 150, Color4F(0, 1, 1, 0.3));
    _mapDrawNode->drawDot(seekPos, 10, Color4F(0, 1, 1, 1));
}

void CrowdSimulationScene::setFleePos(Vec2 fleePos) {
    if (_moveNode == nullptr) {
        _moveNode = _manager->getPlayer();
        _moveNode->setPos(Vec2(100, 100));
        this->addChild(_moveNode);
        _isDrawMoveLine = true;
    }
    _moveNode->setFleePos(_touchBeganPosition);
    _mapDrawNode->clear();
    _mapDrawNode->drawDot(_touchBeganPosition, 100, Color4F(0, 0, 1, 0.3));
    _mapDrawNode->drawDot(_touchBeganPosition, 10, Color4F(0, 0, 1, 1));
}

void CrowdSimulationScene::setWanderPos(Vec2 wanderPos) {
    if (_moveNode == nullptr) {
        _moveNode = _manager->getWanderNode();
        this->addChild(_moveNode);
    }
    _moveNode->setWanderPos(wanderPos);
    _mapDrawNode->clear();
    _mapDrawNode->drawDot(wanderPos, 200, Color4F(1, 1, 0, 0.3));
}

void CrowdSimulationScene::showPursuitModel(Vec2 tarPos){
    if (_moveNode == nullptr) {
        _moveNode = _manager->getPlayer();
        this->addChild(_moveNode);
        _moveNode1 = _manager->getPursuitNode();
        this->addChild(_moveNode1);
    }
    _moveNode->setPos(Vec2(100, 100));
    _moveNode1->setPos(Vec2(100, 500));
    _moveNode1->switchPursuitObj(_moveNode);
    setSeekPos(tarPos);
}

void CrowdSimulationScene::showCombineModel(Vec2 tarPos) {
    if (_moveNode == nullptr) {
        _moveNode = _manager->getPlayer();
        _moveNode->setPos(Vec2(100, 100));
        this->addChild(_moveNode);
        _isDrawMoveLine = true;
        vector<Vec2> wanderPos = { Vec2(300, 300), Vec2(300, 600), Vec2(450,450),Vec2(600,640),Vec2(500,200),Vec2(650,400),Vec2(850,550) };
        for (auto v : wanderPos) {
            auto fleeNode = _manager->getFleeNode();
            this->addChild(fleeNode);
            fleeNode->setWanderPos(v);
            _fleeNodes.push_back(fleeNode);
            _mapDrawNode1->drawDot(v, 100, Color4F(1, 1, 0, 0.3));
        }
        _moveNode->setFleeObjs(_fleeNodes);
    }
    setSeekPos(tarPos);
}

void CrowdSimulationScene::showCohsionModel() {
    if (_manager->getFlockObjs().empty()) {
        for (int i = 0; i < 30; i++) {
            auto cohesionObj = _manager->getCohesionNode();
            this->addChild(cohesionObj);
            /*float x = RandomHelper::random_real<float>(200, 1200);
            float y = RandomHelper::random_real<float>(200, 500);
            cohesionObj->setPos(Vec2(x, y));*/
        }
    }
    auto objs = _manager->getFlockObjs();
    for (auto obj : objs) {
        float x = RandomHelper::random_real<float>(200, 1200);
        float y = RandomHelper::random_real<float>(200, 500);
        obj->setPos(Vec2(x, y));
    }
}

void CrowdSimulationScene::showSeparationModel() {
    if (_manager->getFlockObjs().empty()) {
        for (int i = 0; i < 30; i++) {
            auto separationObj = _manager->getSeparationNode();
            this->addChild(separationObj);
            /*float x = RandomHelper::random_real<float>(650, 700);
            float y = RandomHelper::random_real<float>(250, 300);
            separationObj->setPos(Vec2(x, y));*/
        }
    }
    auto objs = _manager->getFlockObjs();
    for (auto obj : objs) {
        float x = RandomHelper::random_real<float>(650, 700);
        float y = RandomHelper::random_real<float>(250, 300);
        obj->setPos(Vec2(x, y));
    }
}

void CrowdSimulationScene::showAlignmentModel() {
    if (_manager->getFlockObjs().empty()) {
        for (int i = 0; i < 30; i++) {
            auto separationObj = _manager->getAlignmentNode();
            this->addChild(separationObj);
            /*float x = RandomHelper::random_real<float>(400, 800);
            float y = RandomHelper::random_real<float>(200, 400);
            separationObj->setPos(Vec2(x, y));

            auto angle = RandomHelper::random_real<float>(0, 360);
            float rad = angle * M_PI / 180;
            float len = 1;
            Vec2 v;
            v.x = len * cos(rad);
            v.y = len * sin(rad);
            separationObj->setVelocity(v);*/
        }
    }
    auto objs = _manager->getFlockObjs();
    for (auto obj : objs) {
        float x = RandomHelper::random_real<float>(100, 1300);
        float y = RandomHelper::random_real<float>(100, 540);
        obj->setPos(Vec2(x, y));

        auto angle = RandomHelper::random_real<float>(0, 360);
        float rad = angle * M_PI / 180;
        float len = 1;
        Vec2 v;
        v.x = len * cos(rad);
        v.y = len * sin(rad);
        obj->setVelocity(v);
    }
}

void CrowdSimulationScene::showCrowdModel() {
    if (_manager->getFlockObjs().empty()) {
        for (int i = 0; i < 30; i++) {
            auto flockNode = _manager->getFlockNode();
            this->addChild(flockNode);
            /*float x = RandomHelper::random_real<float>(100, 1300);
            float y = RandomHelper::random_real<float>(100, 540);
            flockNode->setPos(Vec2(x, y));

            auto angle = RandomHelper::random_real<float>(0, 360);
            float rad = angle * M_PI / 180;
            float len = 1;
            Vec2 v;
            v.x = len * cos(rad);
            v.y = len * sin(rad);
            flockNode->setVelocity(v);*/
        }
    }
   
    auto objs = _manager->getFlockObjs();
    for (auto obj : objs) {
        float x = RandomHelper::random_real<float>(100, 1300);
        float y = RandomHelper::random_real<float>(100, 540);
        obj->setPos(Vec2(x, y));

        auto angle = RandomHelper::random_real<float>(0, 360);
        float rad = angle * M_PI / 180;
        float len = 1;
        Vec2 v;
        v.x = len * cos(rad);
        v.y = len * sin(rad);
        obj->setVelocity(v);
    }
}

MoveNodeManager.h

#ifndef __MOVE_NODE_MANAGER_H__
#define __MOVE_NODE_MANAGER_H__

#include "cocos2d.h"
#include "MoveNode.h"
USING_NS_CC;
using namespace std;

class MoveNodeManager
{
public:
	MoveNode* getPlayer();
	MoveNode* getWanderNode();
	MoveNode* getPursuitNode();
	MoveNode* getFleeNode();
	MoveNode* getCohesionNode();
	MoveNode* getSeparationNode();
	MoveNode* getAlignmentNode();
	MoveNode* getFlockNode();
	vector<MoveNode*> getFlockObjs() { return _flockObjs; };

protected:
	int _id = 0;
	vector<MoveNode*> _flockObjs;
};

#endif

MoveNodeManager.cpp

#include "MoveNodeManager.h"

MoveNode* MoveNodeManager::getPlayer() {
	auto node = MoveNode::create();
	node->setId(_id);
	_id++;
	auto body = DrawNode::create();
	node->addChild(body);
	body->drawDot(Vec2(0, 0), 10, Color4F(0, 0, 0, 1));
	auto direct = DrawNode::create();
	node->addChild(direct);
	node->setDirect(direct);
	direct->drawDot(Vec2(0, 0), 3, Color4F(1, 1, 1, 1));
	node->setSpeed(400);
	node->setMaxForce(20);
	node->setMass(10);
	node->setMaxSpeed(400);
	node->setTarSlowRadius(150);
	node->setFleeRadius(100);
	return node;
}

MoveNode* MoveNodeManager::getWanderNode() {
	auto node = MoveNode::create();
	node->setId(_id);
	_id++;
	auto body = DrawNode::create();
	node->addChild(body);
	body->drawDot(Vec2(0, 0), 10, Color4F(0, 1, 0, 1));
	auto direct = DrawNode::create();
	node->addChild(direct);
	node->setDirect(direct);
	direct->drawDot(Vec2(0, 0), 3, Color4F(1, 0, 1, 1));
	node->setSpeed(400);
	node->setMaxForce(20);
	node->setMass(20);
	node->setMaxSpeed(100);
	node->setCircleDistance(30);
	node->setCircleRadius(15);
	node->setChangeAngle(180);
	node->setWanderRadius(200);
	node->setWanderPullBackSteering(50);
	return node;
}

MoveNode* MoveNodeManager::getPursuitNode() {
	auto node = MoveNode::create();
	node->setId(_id);
	_id++;
	auto body = DrawNode::create();
	node->addChild(body);
	body->drawDot(Vec2(0, 0), 10, Color4F(0, 1, 0, 1));
	auto direct = DrawNode::create();
	node->addChild(direct);
	node->setDirect(direct);
	direct->drawDot(Vec2(0, 0), 3, Color4F(1, 0, 1, 1));
	node->setSpeed(400);
	node->setMaxForce(20);
	node->setMass(10);
	node->setMaxSpeed(400);
	return node;
}

MoveNode* MoveNodeManager::getFleeNode() {
	auto node = MoveNode::create();
	node->setId(_id);
	_id++;
	auto body = DrawNode::create();
	node->addChild(body);
	body->drawDot(Vec2(0, 0), 10, Color4F(0, 1, 0, 1));
	auto direct = DrawNode::create();
	node->addChild(direct);
	node->setDirect(direct);
	direct->drawDot(Vec2(0, 0), 3, Color4F(1, 0, 1, 1));
	node->setSpeed(400);
	node->setMaxForce(20);
	node->setMass(10);
	node->setMaxSpeed(50);
	node->setCircleDistance(30);
	node->setCircleRadius(15);
	node->setChangeAngle(180);
	node->setWanderRadius(200);
	node->setWanderPullBackSteering(50);
	return node;
}

MoveNode* MoveNodeManager::getCohesionNode() {
	auto node = MoveNode::create();
	_flockObjs.push_back(node);
	node->setId(_id);
	_id++;
	auto body = DrawNode::create();
	node->addChild(body);
	body->drawDot(Vec2(0, 0), 10, Color4F(0, 0, 0, 1));
	auto direct = DrawNode::create();
	node->addChild(direct);
	node->setDirect(direct);
	direct->drawDot(Vec2(0, 0), 3, Color4F(1, 1, 1, 1));
	node->setSpeed(300);
	node->setMaxForce(20);
	node->setMass(20);
	node->setMaxSpeed(50);
	node->setFleeRadius(50);
	node->setAllObj(&_flockObjs);
	node->setCohesionRadius(100);
	return node;
}

MoveNode* MoveNodeManager::getSeparationNode() {
	auto node = MoveNode::create();
	_flockObjs.push_back(node);
	node->setId(_id);
	_id++;
	auto body = DrawNode::create();
	node->addChild(body);
	body->drawDot(Vec2(0, 0), 10, Color4F(0, 0, 0, 1));
	auto direct = DrawNode::create();
	node->addChild(direct);
	node->setDirect(direct);
	direct->drawDot(Vec2(0, 0), 3, Color4F(1, 1, 1, 1));
	node->setSpeed(300);
	node->setMaxForce(20);
	node->setMass(20);
	node->setMaxSpeed(50);
	node->setFleeRadius(50);
	node->setAllObj(&_flockObjs);
	node->setSeparationRadius(30);
	return node;
}

MoveNode* MoveNodeManager::getAlignmentNode() {
	auto node = MoveNode::create();
	_flockObjs.push_back(node);
	node->setId(_id);
	_id++;
	auto body = DrawNode::create();
	node->addChild(body);
	body->drawDot(Vec2(0, 0), 10, Color4F(0, 0, 0, 1));
	auto direct = DrawNode::create();
	node->addChild(direct);
	node->setDirect(direct);
	direct->drawDot(Vec2(0, 0), 3, Color4F(1, 1, 1, 1));
	node->setSpeed(300);
	node->setMaxForce(20);
	node->setMass(20);
	node->setMaxSpeed(150);
	node->setFleeRadius(50);
	node->setAllObj(&_flockObjs);
	node->setAlignmentRadius(150);
	return node;
}

MoveNode* MoveNodeManager::getFlockNode() {
	auto node = MoveNode::create();
	_flockObjs.push_back(node);
	node->setId(_id);
	_id++;
	auto body = DrawNode::create();
	node->addChild(body);
	body->drawDot(Vec2(0, 0), 10, Color4F(0, 0, 0, 1));
	auto direct = DrawNode::create();
	node->addChild(direct);
	node->setDirect(direct);
	direct->drawDot(Vec2(0, 0), 3, Color4F(1, 1, 1, 1));
	node->setSpeed(300);
	node->setMaxForce(20);
	node->setMass(20);
	node->setMaxSpeed(100);
	node->setFleeRadius(50);
	node->setAllObj(&_flockObjs);
	node->setCohesionRadius(100);
	node->setSeparationRadius(40);
	node->setAlignmentRadius(50);
	node->setCohesionWeight(0.5);
	node->setSeparationWeight(30);
	node->setAlignmentWeight(1);
	return node;
}

MoveNode.h

#ifndef __MOVE_NODE_H__
#define __MOVE_NODE_H__

#include "cocos2d.h"
USING_NS_CC;
using namespace std;

class MoveNode : public Node
{
public:
	static MoveNode* create();

CC_CONSTRUCTOR_ACCESS:
	virtual bool init() override;

	void setId(int id) { _id = id; };
	void setDirect(DrawNode* direct) { _direct = direct; };
	void setSpeed(float speed) { _speed = speed; };
	void setMaxForce(float maxForce) { _maxForce = maxForce; };
	void setMass(float mass) { _mass = mass; };
	void setMaxSpeed(float maxSpeed) { _maxSpeed = maxSpeed; };
	void setTarSlowRadius(float tarSlowRadius) { _tarSlowRadius = tarSlowRadius; };
	void setFleeRadius(float fleeRadius) { _fleeRadius = fleeRadius; };
	void setCircleDistance(float circleDistance) { _circleDistance = circleDistance; };
	void setCircleRadius(float circleRadius) { _circleRadius = circleRadius; };
	void setChangeAngle(float changeAngle) { _changeAngle = changeAngle; };
	void setWanderRadius(float wanderRadius) { _wanderRadius = wanderRadius; };
	void setWanderPullBackSteering(float wanderPullBackSteering) { _wanderPullBackSteering = wanderPullBackSteering; };
	void setPos(Vec2 pos);
	void setTarPos(Vec2 tarPos) { _tarPos = tarPos; };
	void setFleePos(Vec2 fleePos) { _fleePos = fleePos; };
	void setFleeObjs(vector<MoveNode*> fleeObjs) { _fleeObjs = fleeObjs; };
	void setWanderPos(Vec2 wanderPos);
	void switchPursuitObj(MoveNode* pursuitObj);
	void setAllObj(vector<MoveNode*>* allObj) { _allObj = allObj; };
	void setCohesionRadius(float cohesionRadius) { _cohesionRadius = cohesionRadius; };
	void setSeparationRadius(float separationRadius) { _separationRadius = separationRadius; };
	void setAlignmentRadius(float alignmentRadius) { _alignmentRadius = alignmentRadius; };
	void setCohesionWeight(float cohesionWeight) { _cohesionWeight = cohesionWeight; };
	void setSeparationWeight(float separationWeight) { _separationWeight = separationWeight; };
	void setAlignmentWeight(float alignmentWeight) { _alignmentWeight = alignmentWeight; };

	Vec2 seek(Vec2 seekPos);
	Vec2 flee();
	Vec2 wander();
	Vec2 pursuit();

	Vec2 cohesion();
	Vec2 separation();
	Vec2 alignment();

	Vec2 wallAvoid();

	Vec2 turncate(Vec2 vector, float maxNumber);
	Vec2 changeAngle(Vec2 vector, float angle);

	void updatePos();
	void update(float dt);
	void findNeighbourObjs();

	int getId() { return _id; };
	Vec2 getVelocity(){ return _velocity; };
	void setVelocity(Vec2 velocity) { _velocity = velocity; };
protected:
	DrawNode* _direct;

	int _id;
	float _speed; //速度
	float _maxForce; //最大转向力,即最大加速度
	float _mass; //质量
	float _maxSpeed; //最大速度
	float _tarSlowRadius; //抵达目标减速半径
	float _fleeRadius; //逃离目标范围半径
	float _circleDistance; //巡逻前方圆点距离
	float _circleRadius; //巡逻前方圆半径
	float _changeAngle; //巡逻转向最大角度
	float _wanderRadius; //巡逻点范围半径
	float _wanderPullBackSteering; //超出巡逻范围拉回力

	float _alignmentRadius; //方向对齐判断的范围半径
	float _cohesionRadius; //聚集判断的范围半径
	float _separationRadius; //分离判断得范围半径

	float _alignmentWeight = 1.0f; //方向对齐力权重
	float _cohesionWeight = 1.0f; //聚集力权重
	float _separationWeight = 1.0f; //分离力权重

	float _dtSpeed; //每帧速度值
	Vec2 _velocity; //速度
	float _wanderAngle; //巡逻角度
	Vec2 _wanderPos; //巡逻范围中心点
	Vec2 _tarPos; //目标点
	Vec2 _fleePos; //逃离点

	MoveNode* _pursuitObj; //追逐目标
	vector<MoveNode*> _fleeObjs; //逃离目标

	vector<MoveNode*>* _allObj; //所有对象
	vector<MoveNode*> _alignmentObj; //方向对齐目标
	vector<MoveNode*> _cohesionObj; //聚集目标
	vector<MoveNode*> _separationObj; //分离目标

	float wallAvoidRadius = 50.0f; //墙壁碰撞检测半径
};

#endif

MoveNode.cpp

#include "MoveNode.h"

bool MoveSmooth = true;

MoveNode* MoveNode::create() {
    MoveNode* moveNode = new(nothrow) MoveNode();
    if (moveNode && moveNode->init()) {
        moveNode->autorelease();
        return moveNode;
    }
    CC_SAFE_DELETE(moveNode);
    return nullptr;
}

bool MoveNode::init()
{
    _tarPos = Vec2(-1, -1);
    _wanderPos = Vec2(-1, -1);
    _velocity.setZero();
    _pursuitObj = nullptr;
    this->scheduleUpdate();
    return true;
}

void MoveNode::update(float dt)
{
    findNeighbourObjs();
    _dtSpeed = _speed * dt;
    if (MoveSmooth) {
        Vec2 steering = Vec2::ZERO;
        steering += seek(_tarPos);
        steering += flee();
        steering += wander();
        steering += pursuit();
        steering += cohesion();
        steering += separation();
        steering += alignment();
        steering = turncate(steering, _maxForce);
        steering *= ( 1 / (float)_mass );
        _velocity += steering;
    }
    else {
        _velocity += seek(_tarPos);
        _velocity += flee();
        _velocity += wander();
        _velocity += pursuit();
        _velocity += cohesion();
        _velocity += separation();
        _velocity += alignment();
    }

    _velocity += wallAvoid();

    _velocity = turncate(_velocity, _maxSpeed * dt);
    updatePos();
}

Vec2 MoveNode::wallAvoid() {
    Vec2 temp = _velocity.getNormalized();
    temp *= wallAvoidRadius;
    Vec2 tarPos = this->getPosition() + temp;
    if (!Rect(Vec2::ZERO, Director::getInstance()->getVisibleSize()).containsPoint(tarPos)) {
        Vec2 steering = Vec2::ZERO;
        if (tarPos.y >= Director::getInstance()->getVisibleSize().height) steering += Vec2(0, -1);
        if (tarPos.y <= 0) steering += Vec2(0, 1);
        if (tarPos.x >= Director::getInstance()->getVisibleSize().width) steering += Vec2(-1, 0);
        if (tarPos.x <= 0) steering += Vec2(1, 0);
        return steering * _dtSpeed;
    }
    return Vec2::ZERO;
}

void MoveNode::updatePos() {
    Vec2 tarPos = this->getPosition() + _velocity;

    if (!Rect(Vec2::ZERO, Director::getInstance()->getVisibleSize()).containsPoint(tarPos)) {
        _velocity = _velocity *= -100;
    }
    Vec2 directPos = _velocity.getNormalized() *= 5;
    _direct->setPosition(directPos);
    this->setPosition(tarPos);
    if (_velocity == Vec2::ZERO) _tarPos = Vec2(-1, -1);
}

Vec2 MoveNode::turncate(Vec2 vector, float maxNumber) {
    if (vector.getLength() > maxNumber) { 
        vector.normalize();
        vector *= maxNumber;
    }
    return vector;
}

//追逐转向力
Vec2 MoveNode::seek(Vec2 seekPos){
    if (seekPos == Vec2(-1, -1)) return Vec2::ZERO;
    Vec2 normalVector = (seekPos - this->getPosition()).getNormalized();
    float dist = this->getPosition().getDistance(seekPos);
    Vec2 desiredVelocity = normalVector * _dtSpeed;

    //靠近目标减速带
    if (dist < _tarSlowRadius) desiredVelocity *= (dist / _tarSlowRadius);

    Vec2 steering;
    if (MoveSmooth) steering = desiredVelocity - _velocity;
    else steering = desiredVelocity;
    return steering;
}

//躲避转向力
Vec2 MoveNode::flee() {
    Vec2 steering = Vec2::ZERO;
    if (!_fleeObjs.empty()) {
        for (auto eludeObj : _fleeObjs) {
            auto fleePos = eludeObj->getPosition();
            if (fleePos.getDistance(this->getPosition()) < _fleeRadius) {
                Vec2 normalVector = (this->getPosition() - fleePos).getNormalized();
                Vec2 desiredVelocity = normalVector * _dtSpeed;
                Vec2 steeringChild;
                if (MoveSmooth) steeringChild = desiredVelocity - _velocity;
                else steeringChild = desiredVelocity;
                steering += steeringChild;
            }
        }
        return steering;
    }
    if(_fleePos == Vec2::ZERO) return steering;
    if (this->getPosition().getDistance(_fleePos) < _fleeRadius) {
        Vec2 normalVector = (this->getPosition() - _fleePos).getNormalized();
        Vec2 desiredVelocity = normalVector * _dtSpeed;
        if (MoveSmooth) steering = desiredVelocity - _velocity;
        else steering = desiredVelocity;
    }
    return steering;
}

Vec2 MoveNode::changeAngle(Vec2 vector, float angle) {
    float rad = angle * M_PI / 180;
    float len = vector.getLength();
    Vec2 v;
    v.x = len * cos(rad);
    v.y = len * sin(rad);
    return v;
}

Vec2 MoveNode::wander() {
    if (_wanderPos == Vec2(-1, -1)) return Vec2::ZERO;
    Vec2 circleCenter = _velocity.getNormalized();
    circleCenter *= _circleDistance;

    Vec2 displacement = Vec2(0, -1);
    displacement *= _circleRadius;
    displacement = changeAngle(displacement, _wanderAngle);

    float randomValue = RandomHelper::random_real<float>(-0.5f, 0.5f);
    _wanderAngle = _wanderAngle + randomValue * _changeAngle;

    Vec2 wanderForce = circleCenter - displacement;

    float dist = this->getPosition().getDistance(_wanderPos);
    if (dist > _wanderRadius) {
        // 偏离漫游点一定范围的话,给个回头力
        Vec2 desiredVelocity = (_wanderPos - this->getPosition()).getNormalized() * _wanderPullBackSteering;
        desiredVelocity -= _velocity;
        wanderForce += desiredVelocity;
    }
    return wanderForce;
}

Vec2 MoveNode::pursuit() {
    if (_pursuitObj == nullptr) return Vec2::ZERO;
    Vec2 pursuitPos = _pursuitObj->getPosition();
    float t = this->getPosition().getDistance(pursuitPos) / _dtSpeed;
    //float t = 3;
//    Vec2 tarPos = pursuitPos + _pursuitObj->getVelocity() * t;
    Vec2 tarPos = pursuitPos;
    return seek(tarPos);
}

//求物体一定半径范围内的其他临近物体的所有位置,用这个位置进行一个追寻力seek
Vec2 MoveNode::cohesion() {
    Vec2 averagePos = Vec2::ZERO;
    int count = 0;
    for (auto obj : _cohesionObj) {
        if (obj->getId() != _id) {
            averagePos += obj->getPosition();
            count++;
        }
    }
    if (count > 0) { 
        averagePos *= (1 / (float)count); 
        return seek(averagePos) * _cohesionWeight;
    }
    return Vec2::ZERO;
}

//求物体一定半径范围内的其他临近物体的位置,用当前物体位置分别减去临近物体位置,获取单位方向向量,乘以根据距离远近算出来得权重
//越近权重越大。在把所有向量相加取平均值
Vec2 MoveNode::separation() {
    Vec2 steering = Vec2::ZERO;
    int count = 0;
    for (auto obj : _separationObj) {
        if (obj->getId() != _id) {
            float dist = this->getPosition().getDistance(obj->getPosition());

            Vec2 normalVector = (this->getPosition() - obj->getPosition()).getNormalized();
            Vec2 desiredVelocity = normalVector;

            desiredVelocity *= (1 / dist);
            steering += desiredVelocity;
            count++;
        }
    }
    if (count > 0) steering *= (1 / (float)count);
    return steering * _dtSpeed * _separationWeight;
}

//求物体一定半径范围内的其他临近物体的所有速度向量,相加取平均值
Vec2 MoveNode::alignment() {
    Vec2 steering = Vec2::ZERO;
    int count = 0;
    for (auto obj : _alignmentObj) {
        if (obj->getId() != _id) {
            steering += obj->getVelocity();
            count++;
        }
    }
    if (count > 0) steering *= (1 / (float)count);
    return steering * _alignmentWeight;
}

void MoveNode::setPos(Vec2 pos) {
    this->setPosition(pos);
    _velocity.setZero();
}

void MoveNode::setWanderPos(Vec2 wanderPos) {
    _wanderPos = wanderPos;
    setPos(wanderPos);
}

void MoveNode::switchPursuitObj(MoveNode* pursuitObj) {
    if (_pursuitObj == nullptr) _pursuitObj = pursuitObj;
    else {
        _pursuitObj = nullptr;
        _velocity = Vec2::ZERO;
        _tarPos = Vec2(-1, -1);
    }
}

void MoveNode::findNeighbourObjs() {
    if (_allObj == nullptr) return;
    _alignmentObj.clear();
    _cohesionObj.clear();
    _separationObj.clear();
    for (auto obj : *_allObj) {
        float dist = this->getPosition().getDistance(obj->getPosition());
        if (dist < _alignmentRadius) {
            _alignmentObj.push_back(obj);
        }
        if (dist < _cohesionRadius) {
            _cohesionObj.push_back(obj);
        }
        if (dist < _separationRadius) {
            _separationObj.push_back(obj);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值