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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/177623.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C语言】结构体

目录 1. 前言2. 结构体类型的声明2.1 结构体的概念2.2 结构的创建2.3 特殊的声明2.4 结构的自引用 3. 结构成员访问操作符4. 结构体内存对齐4.1 对齐规则4.2 为什么存在内存对齐&#xff1f;4.3 修改默认对齐数 5. 结构体传参6. 结构体实现位段6.1 什么是位段6.2 位段的内存分配…

[ACTF2020 新生赛]BackupFile

打开题目就一句话&#xff1a;尝试找到源文件 和上一题一样&#xff0c;用dirsearch扫描网站找到了一下内容 flag.php&#xff0c;0B&#xff0c;虚假flag 瞅一眼index.php.bak是啥 下载了一个文件&#xff0c;把bak后缀删掉&#xff0c;打开了index.php源码 is_numeric()&am…

成为AI产品经理——模型评估(混淆矩阵)

一、混淆矩阵 1.混淆矩阵的介绍 混淆矩阵有两个定义positive&#xff08;正例&#xff09;和negative&#xff08;反例&#xff09;。分别代表模型结果的好和坏。 下图就是一个分类问题的混淆矩阵。横行代表真实的情况&#xff0c;而竖行代表预测的结果。 为了便于理解&…

新疆大学与优艾智合机器人成立联合创新实验室

11月22日至24日&#xff0c;第五届中国工业互联网大赛新疆赛站决赛在新疆维吾尔自治区昌吉回族自治州昌吉市举行。在大赛中崭露头角的优秀解决方案&#xff0c;将为绿色工厂、绿色园区、绿色供应链等建设提供新的动能&#xff0c;促进工业绿色发展。 作为大赛的成果延伸&#…

面试常问-如何判断链表有环、?

如何判断链表有环 题目&#xff1a;解决方案一&#xff1a;解决方案二&#xff1a;解决方案三&#xff1a; 题目&#xff1a; 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;…

MSB3541 Files 的值“<<<<<<< HEAD”无效。路径中具有非法字符。

MSB3541 Files 的值“<<<<<<< HEAD”无效。路径中具有非法字符。 一般来说出现这个问题是因为使用git版本控制工具合并代码出现了问题&#xff0c;想要解决也很简单。 如图点击错误后定位到文件&#xff0c;发现也没有什么问题。 根据错误后边的提示&a…

JVM内存模型和结构详解(五大模型图解)

目录 方法区&#xff08;Method Area&#xff09;: 堆&#xff08;Heap&#xff09;: 栈&#xff08;Stack&#xff09;: 本地方法栈&#xff08;Native Method Stack&#xff09;: 程序计数器&#xff08;Program Counter Register&#xff09;: Java Virtual Machine (J…

SpringCloudAlibaba之sentinel 流量卫兵(流控,熔断降级) ——详细讲解

目录 一、什么是sentinel 二、sentinel使用 1. sentinel dashboard的安装 2.启动 3.访问web界面 ​编辑 4.登录 三、sentinel 实时监控服务 1.创建项目引入依赖 2.配置 3.启动服务 4.访问dashboard界面查看服务监控 5.开发服务 6.启动进行调用 7.查看监控界面 四、senti…

肠道菌群16s检测粪便采样工具包 粪便采样套装

肠道菌群16s检测是一种常见的分子生物学技术&#xff0c;用于研究人体肠道中的微生物群落。该技术通过分析16s rRNA基因序列&#xff0c;可以快速、准确地鉴定并定量不同种类的肠道微生物。 肠道菌群16s检测通常通过采集粪便样本进行分析。在实验室中&#xff0c;通过提取微生物…

leetcode面试经典150题——33 最小覆盖子串(滑动窗口)

题目&#xff1a; 最小覆盖子串 描述&#xff1a; 给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串&#xff0c;则返回空字符串 “” 。 注意&#xff1a; 对于 t 中重复字符&#xff0c;我们寻找的子字符串中…

【三维重建】摄像机标定(张正友相机标定法)

摄像机标定的目的是为了求解摄像机的内、外参数 求解投影矩阵M 通过建立特殊的场景&#xff0c;我们能过得到多对世界坐标和对应图像坐标 根据摄像机几何可知 &#xff1a; &#xff0c;M是一个3*4的矩阵&#xff0c;令 通过一对点可以得到两个方程组&#xff0c;M中一共有11个…

SpringBoot : ch09 整合Redis

前言 当你的应用程序需要一个快速、可扩展的内存数据库时&#xff0c;Redis是一个非常流行的选择。通过将Redis与Spring Boot集成&#xff0c;你可以轻松地利用Redis的功能&#xff0c;例如缓存、会话存储和消息队列等&#xff0c;从而提升应用程序的性能和可伸缩性。 在本教…

mongodb查询数据库集合的基础命令

基础命令 启动mongo服务 mongod -f /usr/local/mongodb/mongod.conf //注意配置文件路径停止mongo服务 关闭mongodb有三种方式&#xff1a; 一种是进入mongo后通过mongo的函数关闭&#xff1b; use admin db.shutdownServer()一种是通过mongod关闭&#xff1b; mongod --s…

Selenium 学习(0.14)——软件测试之测试用例设计方法——因果图法2【基本步骤及案例】

1、因果图法的基本步骤 2、案例分析 1&#xff09;分析原因和结果 2&#xff09;关联原因和结果 投入1元5角或2元&#xff0c;按下“可乐”&#xff0c;送出“可乐”【暂时忽略找零】 投入2元&#xff0c;按下“可乐”或“雪碧”。找零5角&#xff0c;送出“可乐”或“雪…

软件测试测试文档编写

在软件测试中的流程中&#xff0c;测试文档也是一个重要的流程&#xff0c;所以测试人员也需要学习测试文档的编写和阅读。 一、定义&#xff1a;   测试文档&#xff08;Testing Documentation&#xff09;记录和描述了整个测试流程&#xff0c;它是整个测试活动中非常重要…

vscode注释插件「koroFileHeader」

前言 在vscode上进行前端开发&#xff0c;有几个流行的注释插件&#xff1a; Better CommentsTodo TreekoroFileHeaderDocument ThisAuto Comment Blocks 在上面的插件中我选择 koroFileHeader 做推荐&#xff0c;原因一是使用人数比较多&#xff08;最多的是 Better Commen…

NAS-DIP: Learning Deep Image Prior with Neural Architecture Search

NAS-DIP: 用神经结构搜索学习深度图像先验 论文链接&#xff1a;https://arxiv.org/abs/2008.11713 项目链接&#xff1a;https://github.com/YunChunChen/NAS-DIP-pytorch Abstract 最近的研究表明&#xff0c;深度卷积神经网络的结构可以用作解决各种逆图像恢复任务的结构…

使用vue脚手架创建vue项目

Vue是一个流行的前端框架&#xff0c;可以用简洁的语法和组件化的思想开发单页面应用。Vue脚手架是一个官方提供的命令行工具&#xff0c;它可以帮你快速搭建和配置vue项目的基本结构和依赖。 本文介绍如何使用vue脚手架创建一个vue2项目&#xff0c;并选择一些常用的功能和插件…

Java开源ETL工具-Kettle

一、背景 公司有个基于Kettle二次开发产品主要定位是做一些数据ETL的工作, 所谓的ETL就是针对数据进行抽取、转换以及加载的过程&#xff0c;说白了就是怎么对原始数据进行清洗&#xff0c;最后拿到我们需要的、符合规范的、有价值的数据进行存储或者分析的过程。 一般处理ETL的…

【从浅识到熟知Linux】基本指令之man、uname和bc

&#x1f388;归属专栏&#xff1a;从浅学到熟知Linux &#x1f697;个人主页&#xff1a;Jammingpro &#x1f41f;每日一句&#xff1a;干完饭写篇博客放松一下。 文章前言&#xff1a;本文介绍man、uname和bc指令用法并给出示例和截图。 文章目录 man基本语法功能选项无选项…