AI - Steering behaviors(转向系统)

游戏AI角色的转向系统(Steering behaviors)实现

一些向量的接口是cocos2dx的。但从名字上应该能理解做了什么向量操作

Seek:

获取当前位置指向目标点的向量,转化为单位向量后再乘以速度值,即为所需速度desired velocity,所需速度减去当前速度current velocity,即为seek的转向力,将角色推向目标
在这里插入图片描述

//追逐转向力
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;
    Vec2 steering;
    if (MoveSmooth) steering = desiredVelocity - _velocity;
    else steering = desiredVelocity;
    return steering;
}

在这里插入图片描述

物体在追逐目标时,会有一个逐渐转弯的过程
请添加图片描述

如果,没有转向力的话,物体就是直接切换方向
请添加图片描述

Flee

与seek类似,只是所需速度desired velocity的方向,是由目标点指向当前位置。
在这里插入图片描述
这里在逃离目标点加了个检测半径,在这个范围里的物体将会受到逃离力。

//躲避转向力
Vec2 MoveNode::flee() {
    Vec2 steering = Vec2::ZERO;
    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;
}

在这里插入图片描述
逃离时的转向效果
请添加图片描述
与没有转向力直接逃离的效果对比
请添加图片描述

Arrive

因为seek力的存在,所以在到达目标点时,会在目标点附近来回弹跳
请添加图片描述
为此在目标点周围添加一个减速场slowing area。在目标点周围一定范围内,当物体越靠近目标时,seek力会越小。seek力大小在减速带范围内线性减小,与目标点的距离成反比。而在范围外,则是正常的seek力大小
在这里插入图片描述

//追逐转向力
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;
}

请添加图片描述

Wander

漫游:巡逻,在物体前进方向的前方一定范围_circleDistance内,画个圆,用来计算受力行为。位移力 以圆心为原点,受半径_circleRadius约束。半径越大以及角色到圆圈的距离越大,角色在每个游戏帧中受到的“推力”就越强。

在这里插入图片描述
初始会给一个方向wander angle,之后每帧会在一定的转向范围_changeAngle内随机一个转角方向。改变物体巡逻的方向wander angle在这里插入图片描述在这里插入图片描述在这里插入图片描述
我在这里加了一个巡逻范围_wanderPullBackSteering,当偏离一定范围时,会受到回拉的力

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;
}

请添加图片描述

Pursuit

追捕某个物体,实际上就是seek到某个位置,只不过这个位置需要预测追捕目标将来的位置target in the future来seek,而不能以追捕目标当前位置来seek在这里插入图片描述
预测的位置即目标当前位置+目标当前速度加一定帧数T。如果T的值是个常数时会出现一个问题:当目标很近时,追击精度往往会变差。这是因为当目标接近时,追击者会继续寻找目标位置的进行预测,也就是“远离”T帧后的位置。因此可以用动态T 值代替常量T 值
新的T值为目标从当前位置移动到追击者位置还需要更新多少次

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);
}

请添加图片描述
没有预测的追捕的话,就会一直沿着追捕目标的路径一直跟在后面
请添加图片描述

Evading

躲避,与追捕类似,只不过将预测目标的位置用来做flee,而不是做seek

Combining Steering Forces

多种力是可以相互结合的,以向量的形式相加,得到物体最终受到的力
在这里插入图片描述

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

    _velocity += wallAvoid();

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

下面是一个物体,受到多个正在漫游目标产生的逃离力的影响,同时还有寻求到目标的力 的例子
请添加图片描述

源码

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);

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

	Vec2 wallAvoid();

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

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

	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 _dtSpeed; //每帧速度值
	Vec2 _velocity; //速度
	float _wanderAngle; //巡逻角度
	Vec2 _wanderPos; //巡逻范围中心点
	Vec2 _tarPos; //目标点
	Vec2 _fleePos; //逃离点

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

	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)
{
    _dtSpeed = _speed * dt;
    if (MoveSmooth) {
        Vec2 steering = Vec2::ZERO;
        steering += seek(_tarPos);
        steering += flee();
        steering += wander();
        steering += pursuit();
        steering = turncate(steering, _maxForce);
        steering *= ( 1 / (float)_mass );
        _velocity += steering;
    }
    else {
        _velocity += seek(_tarPos);
        _velocity += flee();
        _velocity += wander();
        _velocity += pursuit();
    }

    _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);
}

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);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值