AI - Steering behaviors (steering system)

Implementation of game AI character’s steering system (Steering behaviors)

Some vector interfaces are cocos2dx. But from the name, you should be able to understand what vector operations are done.

Seek:

Get the vector from the current position pointing to the target point, convert it into a unit vector and then multiply it by the velocity value, which is the desired velocity. The desired velocity minus the current velocity is the steering force of seek, which pushes the character towards the target.
Insert image description here

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

Insert image description here

When the object is chasing the target, there will be a gradual turning process.
Please add image description

If there is no steering force, the object will directly switch directions.
Please add image description

Flee

is similar to seek, except that the direction of desired velocity is from the target point to the current position.
Insert image description here
Here, a detection radius is added to the escape target point, and objects within this range will receive the escape force.

//躲避转向力
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;
}

Insert image description here
The steering effect when escaping
Please add image description
Compared with the effect of escaping directly without steering force
Please add image description

arrive

Because of the seek force, when it reaches the target point, it will bounce back and forth near the target point
Please add image description
For this purpose, add a slowing area around the target point. Within a certain range around the target point, the seek force will become smaller as the object gets closer to the target. The size of the seek force decreases linearly within the speed bump range and is inversely proportional to the distance from the target point. Outside the range, it is the normal seek force size
Insert image description here

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

Please add image description

Wander

Roaming: Patrol, draw a circle within a certain range _circleDistance in front of the object's forward direction to calculate the force behavior. The displacement force takes the center of the circle as the origin and is constrained by the radius _circleRadius. The larger the radius and the further the character is from the circle, the stronger the "push" the character receives each game frame.

Insert image description here
will initially give a direction wander angle, and then each frame will randomly turn a corner direction within a certain turning range _changeAngle. Change the direction of the object patrol wander angleInsert image description hereInsert image description hereInsert image description here
I added a patrol range_wanderPullBackSteering here. When it deviates from a certain range, it will receive a pullback force

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

Please add image description

Pursuit

Chasing an object is actually seeking to a certain location, but this location needs to predict the future location of the target in the future to seek, instead of seeking based on the current location of the target Insert image description here
The predicted position is the current position of the target + the current speed of the target plus a certain number of frames T. If the value of T is a constant, there will be a problem: when the target is very close, the pursuit accuracy will often become worse. This is because when the target approaches, the pursuer will continue to look for the target's position to predict, that is, "away" from the position after the T frame. Therefore, the constant T value can be replaced by the dynamic T value
The new T value is how many times the target needs to be updated to move from the current position to the pursuer's position

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

Please add image description
If there is no predicted pursuit, it will always follow the path of the pursuit target.
Please add image description

Evading

Avoidance is similar to pursuit, except that the predicted target position is used to flee instead of seeking.

Combining Steering Forces

Multiple forces can be combined with each other and added in the form of vectors to obtain the final force exerted on the object.
Insert image description here

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

Below is an example of an object affected by escape forces from multiple roaming targets, as well as forces that seek out the targets.
Please add image description

Source code

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

Guess you like

Origin blog.csdn.net/Mhypnos/article/details/134656350