【unity实战】Unity中使用A*寻路+有限状态机制作一个俯视角敌人AI

最终效果

在这里插入图片描述

文章目录

  • 最终效果
  • 前言
  • A*寻路插件介绍
    • 下载
    • 导入AI插件
    • 生成寻路网格
    • 节点的类型
    • 障碍物
    • 寻路测试
    • A*只打印报错信息
  • 代码控制寻路
  • 动画配置
  • 敌人状态机
  • 各种状态脚本
  • 效果
  • 完结

前言

前面做过有限状态机制作一个敌人AI:【unity实战】在Unity中使用有限状态机制作一个敌人AI

之前做的是2d平台的,但是俯视角怎么做呢?俯视角可能要复杂一些,要考虑4个方向和躲避障碍物,这里我就用A寻路插件来实现,关于A寻路插件,之前我也简单用过一次,感兴趣可以去看看:【推荐100个unity插件之1】2d使用A*Pathfinding插件实现敌人AI,并自动躲避障碍物

A*寻路插件介绍

下载

A*官网下载地址:https://arongranberg.com/astar/download
我们下载免费版即可
在这里插入图片描述

导入AI插件

在这里插入图片描述

生成寻路网格

新建空物体,添加PathFinder组件,用在地图导航
在这里插入图片描述
点击生成寻路网格
在这里插入图片描述

节点的类型

我们可以修改节点的类型,我们选择四个方向就好了,这样算法更高效也能提升游戏性能
在这里插入图片描述

障碍物

同时我们把障碍物全部剔别除出去,这边有指定要剔除的图层,找到我们的围墙和障碍物,比如Wall
在这里插入图片描述
效果
在这里插入图片描述

寻路测试

添加测试敌人,添加对应寻路组件
在这里插入图片描述
然后选择2D游戏常用的Y轴方向,并取消重力改为None,目标设置为玩家
在这里插入图片描述

运行效果,敌人跟随玩家的时候,有一条绿线,那就是自动局路的路线
在这里插入图片描述
我们可以给敌人的速度设置快一点
在这里插入图片描述

A*只打印报错信息

在这里插入图片描述

代码控制寻路

不过实际使用我们只要它的寻路功能来追击玩家,并到达攻击范围后停下来攻击玩家,离开追击范围便放弃追击,这就需要我们通过代码来实现,特定的需求。

我们也不需要在敌人身上挂那么多脚本,我们只需要保留Seeker组件提供的寻路算法
在这里插入图片描述
其实官方文档有个简单的寻路demo供我们参考:
https://arongranberg.com/astar/documentation/4_2_17_c030646a/astaraics.html

注意,调用生成路径的函数是一个相对较耗时的操作,如果每帧都立刻生成路径可能会对性能造成负担,所以这里我们就用到计时器了,每0.5秒调用一次路径生成函数
在这里插入图片描述

动画配置

在这里插入图片描述

敌人状态机

定义状态类型枚举

namespace Enemy
{// 定义状态类型枚举public enum StateType{Idle, //待机Patrol, //巡逻Chase, //追击React, //反应Attack, //攻击Hit, //受击Death //死亡}
}

可序列化的参数类,存储了角色的各种状态参数和配置

namespace Enemy
{[Serializable]public class Parameter{[Header("属性")]public int health;              // TODO:测试 健康值[HideInInspector] public Animator animator;       // 角色动画控制器[HideInInspector] public AnimatorStateInfo animatorStateInfo;    // 动画状态信息[HideInInspector] public Rigidbody2D rb; [HideInInspector] public Collider2D collider; [Header("移动")]public float moveSpeed;         // 移动速度public float chaseSpeed;        // 追击速度[HideInInspector] public float currentSpeed; // 当前速度[Header("巡逻")]public float idleTime;          // 空闲时间public Transform[] patrolPoints;   // 巡逻点数组[Header("追逐")]public LayerMask targetLayer;   // 目标层public int chaseDistance;//追逐的距离[HideInInspector] public Transform target; // 目标对象[Header("A*寻路")][HideInInspector] public Seeker seeker;// 用于处理路径计算的 Seeker 组件。[HideInInspector] public Path path; // Seeker 计算出的路径[HideInInspector] public int currentWaypoint = 0; // 当前路径点的索引。[HideInInspector] public bool reachedEndOfPath; // 标志位,指示 AI 是否到达路径的末尾。[HideInInspector] public float nextWaypointDistance = 3f; // 到达路径点之前的距离。减速距离[HideInInspector] public float repathRate = 0.5f; // 重新计算路径的频率(秒)。[HideInInspector] public float lastRepath = float.NegativeInfinity; // 上次计算路径的时间。[HideInInspector] public bool isPathRefresh;//是否刷新[Header("攻击")]public Transform attackPoint;   // 攻击点的位置public float attackArea;        // 攻击范围[Header("受击")][HideInInspector] public bool isHurt;  // 是否被击中}
}

//抽象基类,定义了所有状态类的基本结构

namespace Enemy
{//抽象基类,定义了所有状态类的基本结构public abstract class IState{protected FSM manager;// 当前状态机protected Parameter parameter;// 参数public abstract void OnEnter();// 进入状态时的方法public abstract void OnUpdate();// 更新方法public abstract void OnFixedUpdate();// 固定更新方法public abstract void OnExit();// 退出状态时的方法}
}

有限状态机类

using System.Collections;
using System.Collections.Generic;
using Pathfinding;
using UnityEngine;namespace Enemy
{// 有限状态机类public class FSM : MonoBehaviour{private IState currentState;        // 当前状态接口protected Dictionary<StateType, IState> states = new Dictionary<StateType, IState>();  // 状态字典,存储各种状态public Parameter parameter;     // 状态机参数protected virtual void Awake() { }protected virtual void OnEnable(){parameter.animator = transform.GetComponent<Animator>();  // 获取角色上的动画控制器组件parameter.seeker = GetComponent<Seeker>();//parameter.rb = GetComponent<Rigidbody2D>();parameter.collider = GetComponent<Collider2D>();TransitionState(StateType.Idle);    // 初始状态为IdlecurrentState.OnEnter();}void Update(){parameter.animatorStateInfo = parameter.animator.GetCurrentAnimatorStateInfo(0);// 获取当前动画状态信息GetPlayerTransform();currentState.OnUpdate();    // 每帧更新当前状态//TODO:用于测试 如果按下回车键,设置被击中状态为trueif (Input.GetKeyDown(KeyCode.Return)){parameter.isHurt = true;}}void FixedUpdate(){currentState.OnFixedUpdate();}// 状态转换方法public void TransitionState(StateType type){if (currentState != null)currentState.OnExit();  // 如果当前状态不为空,调用退出方法currentState = states[type];    // 更新当前状态为指定类型的状态currentState.OnEnter();         // 调用新状态的进入方法}// 翻转角色朝向方法,使其朝向目标public void FlipTo(Transform target){if (target != null){if (transform.position.x > target.position.x){transform.localScale = new Vector3(-1, 1, 1);    // 如果角色在目标左侧,翻转角色朝向为左}else if (transform.position.x < target.position.x){transform.localScale = new Vector3(1, 1, 1);     // 如果角色在目标右侧,翻转角色朝向为右}}}public void Destroy(){Destroy(gameObject, 2f);}// 查找玩家的方法public void GetPlayerTransform(){// 使用Physics2D.OverlapCircleAll获取位于指定距离内的所有Collider2D数组Collider2D[] chaseColliders = Physics2D.OverlapCircleAll(transform.position, parameter.chaseDistance, parameter.targetLayer);// 如果找到了玩家if (chaseColliders.Length > 0){// 将第一个找到的玩家设为追踪目标parameter.target = chaseColliders[0].transform;// 计算与目标的距离// distance = Vector2.Distance(parameter.target.position, transform.position);}else{// 如果没有找到玩家,则目标置空parameter.target = null;}}#region A*寻路方法//每隔一段时间重新计算路径路径public void StartPath(Transform target){if (Time.time > parameter.lastRepath + parameter.repathRate && parameter.seeker.IsDone()){parameter.lastRepath = Time.time;parameter.seeker.StartPath(transform.position, target.position, OnPathComplete);}}//寻路移动public void Move(){// 还没有路径可以跟随,所以不执行任何操作if (parameter.path == null) return;// 循环检查是否已经接近当前路径点,可以切换到下一个点// 使用循环是因为许多路径点可能非常接近,可能在同一帧内到达多个路径点parameter.reachedEndOfPath = false;// 当前路径点到代理的距离float distanceToWaypoint;while (true){// 如果希望最大化性能,可以检查平方距离而不是实际距离,避免使用平方根计算,但这超出了本教程的范围distanceToWaypoint = Vector3.Distance(transform.position, parameter.path.vectorPath[parameter.currentWaypoint]);if (distanceToWaypoint < parameter.nextWaypointDistance){// 检查是否还有下一个路径点,或者是否已经到达路径的末尾if (parameter.currentWaypoint + 1 < parameter.path.vectorPath.Count){parameter.currentWaypoint++;}else{// 设置一个状态变量,表示代理已经到达路径的末尾// 如果你的游戏需要,可以使用这个变量来触发一些特殊代码parameter.reachedEndOfPath = true;break;}}else{break;}}// 在接近路径末尾时平滑减速// 这个值会在代理接近路径的最后一个路径点时,从 1 平滑过渡到 0var speedFactor = parameter.reachedEndOfPath ? Mathf.Sqrt(distanceToWaypoint / parameter.nextWaypointDistance) : 1f;// 到下一个路径点的方向// 归一化,使其长度为1个世界单位Vector3 dir = (parameter.path.vectorPath[parameter.currentWaypoint] - transform.position).normalized;// 将方向乘以我们期望的速度,得到速度向量Vector3 velocity = dir * parameter.currentSpeed * speedFactor;// 移动到目标点// transform.position += velocity * Time.deltaTime;parameter.rb.velocity = velocity;}//路径计算完成时回调方法public void OnPathComplete(Path p){Debug.Log("计算出一条路径。是否出现错误?" + p.error);// 路径池。为了避免不必要的内存分配,路径使用引用计数。// 调用 Claim 方法将引用计数加一,调用 Release 方法将其减一,// 当引用计数为零时,路径将被放入池中,其他脚本可以重用该路径。// ABPath.Construct 和 Seeker.StartPath 方法会尽可能从池中获取路径。详见路径池文档页面。p.Claim(this);if (!p.error){if (parameter.path != null) parameter.path.Release(this);parameter.path = p;// 重置路径点计数器,以便开始移动到路径的第一个点parameter.currentWaypoint = 0;}else{p.Release(this);}}#endregion// 触发器进入事件,检测到玩家时设置目标为玩家private void OnTriggerEnter2D(Collider2D other){if (other.CompareTag("Player")){parameter.target = other.transform;}}// 触发器离开事件,玩家离开时清空目标private void OnTriggerExit2D(Collider2D other){if (other.CompareTag("Player")){parameter.target = null;}}// 开始路径刷新计时public void RefreshTiming(){StartCoroutine(nameof(DodgeOnCooldownCoroutine));}public IEnumerator DodgeOnCooldownCoroutine(){parameter.isPathRefresh = false;yield return new WaitForSeconds(0.5f);parameter.isPathRefresh = true;}// 在Scene视图中绘制攻击范围的辅助图形private void OnDrawGizmos(){//攻击范围Gizmos.DrawWireSphere(parameter.attackPoint.position, parameter.attackArea);//追击范围Gizmos.color = Color.yellow;Gizmos.DrawWireSphere(transform.position, parameter.chaseDistance);}}}

各种状态脚本

待机状态

using UnityEngine;namespace Enemy
{public class IdleState : IState{private float timer;        // 计时器public IdleState(FSM manager){this.manager = manager;this.parameter = manager.parameter;}public override void OnEnter(){parameter.animator.Play("Idle");}public override void OnUpdate(){timer += Time.deltaTime;    // 计时器累加// 如果被击中了,转换到受击状态if (parameter.isHurt){manager.TransitionState(StateType.Hit);}// 如果有目标且目标在追逐范围内,则转换到反应状态if (parameter.target){manager.TransitionState(StateType.React);}// 如果达到空闲时间上限,则转换到巡逻状态if (timer >= parameter.idleTime){manager.TransitionState(StateType.Patrol);}}public override void OnFixedUpdate(){}public override void OnExit(){timer = 0;    // 重置计时器}}
}

巡逻状态

using UnityEngine;//巡逻状态
namespace Enemy
{public class PatrolState : IState{private int patrolPosition;    // 当前巡逻点索引public PatrolState(FSM manager){this.manager = manager;this.parameter = manager.parameter;}public override void OnEnter(){parameter.animator.Play("Walk");parameter.currentSpeed = parameter.moveSpeed;GeneratePatrolPoint();manager.RefreshTiming();}public override void OnUpdate(){// 如果被击中了,转换到受击状态if (parameter.isHurt){manager.TransitionState(StateType.Hit);}// 如果有目标且目标在追逐范围内,则转换到反应状态if (parameter.target){manager.TransitionState(StateType.React);}//如果已经接近当前巡逻点,则转换到空闲状态if (Vector2.Distance(manager.transform.position, parameter.patrolPoints[patrolPosition].position) < .1f){manager.TransitionState(StateType.Idle);}manager.StartPath(parameter.patrolPoints[patrolPosition]);// 如果速度接近静止(每一段时间检测,防止敌人互相卡住)if (parameter.rb.velocity.magnitude < 0.1f && parameter.isPathRefresh){manager.RefreshTiming();GeneratePatrolPoint();}}public override void OnFixedUpdate(){// 朝向当前巡逻点manager.FlipTo(parameter.patrolPoints[patrolPosition]);//移动到当前巡逻点manager.Move();}public override void OnExit(){GeneratePatrolPoint();}//随机选择下一个巡逻点public void GeneratePatrolPoint(){while (true){// 选择一个随机的巡逻点索引int i = Random.Range(0, parameter.patrolPoints.Length);// 确保新选择的巡逻点与当前不同if (patrolPosition != i)// if (parameter.targetPointIndex != i){// parameter.targetPointIndex = i;patrolPosition = i;break; // 退出循环}}}}
}

反应状态


//反应状态
namespace Enemy
{public class ReactState : IState{public ReactState(FSM manager){this.manager = manager;this.parameter = manager.parameter;}public override void OnEnter(){parameter.animator.Play("React");}public override void OnUpdate(){// 如果被击中标志为true,转换到受击状态if (parameter.isHurt){manager.TransitionState(StateType.Hit);}// 如果动画播放进度超过95%,转换到追逐状态if (parameter.animatorStateInfo.normalizedTime >= 0.95f){manager.TransitionState(StateType.Chase);}}public override void OnFixedUpdate() { }public override void OnExit() { }}
}

追击状态

using UnityEngine;
//追击状态
namespace Enemy
{public class ChaseState : IState{// 构造函数public ChaseState(FSM manager){this.manager = manager;this.parameter = manager.parameter;}public override void OnEnter(){parameter.animator.Play("Run");parameter.currentSpeed = parameter.chaseSpeed;}public override void OnUpdate(){// 如果被击中了,转换到受击状态if (parameter.isHurt){manager.TransitionState(StateType.Hit);}// 如果目标不存在或者超出追逐范围,则转换到空闲状态if (parameter.target == null){manager.TransitionState(StateType.Idle);}// 如果检测到攻击范围内有目标,则转换到攻击状态if (Physics2D.OverlapCircle(parameter.attackPoint.position, parameter.attackArea, parameter.targetLayer)){manager.TransitionState(StateType.Attack);}manager.StartPath(parameter.target);}public override void OnFixedUpdate(){manager.FlipTo(parameter.target);    // 面向目标// 向目标位置移动if (parameter.target != null){//移动到当前目标点manager.Move();}}public override void OnExit() { }}
}

攻击状态

namespace Enemy
{public class AttackState : IState{public AttackState(FSM manager){this.manager = manager;this.parameter = manager.parameter;}public override void OnEnter(){parameter.animator.Play("Attack");}public override void OnUpdate(){if (parameter.isHurt){manager.TransitionState(StateType.Hit);}if (parameter.animatorStateInfo.normalizedTime >= .95f){manager.TransitionState(StateType.Chase);}}public override void OnFixedUpdate() { }public override void OnExit() { }}
}

受击状态


using UnityEngine;namespace Enemy
{public class HitState : IState{public HitState(FSM manager){this.manager = manager;this.parameter = manager.parameter;}public override void OnEnter(){parameter.animator.Play("Hit");parameter.health--;    // 减少角色生命值}public override void OnUpdate(){// 如果角色生命值小于等于0,转换到死亡状态if (parameter.health <= 0){manager.TransitionState(StateType.Death);}// 如果动画播放进度超过95%,重新寻找玩家目标并转换到追逐状态if (parameter.animatorStateInfo.normalizedTime >= 0.95f){manager.TransitionState(StateType.Chase);    // 转换到追逐状态}}public override void OnFixedUpdate(){}public override void OnExit(){parameter.isHurt = false;    // 离开状态时重置受击标志}}
}

死亡状态

namespace Enemy
{public class DeathState : IState{public DeathState(FSM manager){this.manager = manager;this.parameter = manager.parameter;}public override void OnEnter(){parameter.animator.Play("Dead");//取消碰撞parameter.collider.enabled = false;}public override void OnUpdate(){// 如果动画播放进度超过95%,销毁敌人if (parameter.animatorStateInfo.normalizedTime >= 0.95f){manager.Destroy();}}public override void OnFixedUpdate() { }public override void OnExit() { }}
}

配置
在这里插入图片描述
可以给敌人刚体加一个2d物理材质,去除摩檫力,防止敌人和碰撞体粘在一起
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

效果

默认巡逻,发现敌人发起追击,靠近时发起攻击
在这里插入图片描述
玩家跑出追击范围,回到巡逻状态
在这里插入图片描述

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,以便我第一时间收到反馈,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,出于兴趣爱好,最近开始自学unity,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!php是工作,unity是生活!如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

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

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

相关文章

vxe-table合并行数据;element-plus的el-table动态合并行

文章目录 一、vxe-table合并行数据1.代码 二、使用element-plus的el-table动态合并行2.代码 注意&#xff1a;const fields 是要合并的字段 一、vxe-table合并行数据 1.代码 <vxe-tableborderresizableheight"500":scroll-y"{enabled: false}":span-m…

信创-办公软件应用工程师认证

随着国家对信息技术自主创新的战略重视程度不断提升&#xff0c;信创产业迎来前所未有的发展机遇。未来几年内&#xff0c;信创产业将呈现市场规模扩大、技术创新加速、产业链完善和国产化替代加速的趋势。信创人才培养对于推动产业发展具有重要意义。应加强高校教育、建立人才…

【信息学奥赛】CSP-J/S初赛07 排序算法及其他算法在初赛中的考察

本专栏&#x1f449;CSP-J/S初赛内容主要讲解信息学奥赛的初赛内容&#xff0c;包含计算机基础、初赛常考的C程序和算法以及数据结构&#xff0c;并收集了近年真题以作参考。 如果你想参加信息学奥赛&#xff0c;但之前没有太多C基础&#xff0c;请点击&#x1f449;专栏&#…

C++|海康摄像头实时预览时设置音量大小

使用海康API设置音量的函数是&#xff1a;NET_DVR_OpenSound。 在实际代码中我遇到了以下问题&#xff1a; 1&#xff1a;调用NET_DVR_OpenSound接口一直返回失败&#xff0c;错误是调用顺序出错。 2&#xff1a;音量设置不成功。 对于以上两种问题&#xff0c;我相信很多人…

FineBI在线学习资源-数据处理

FineBI在线学习资源汇总&#xff1a; 学习资源 视频课程 帮助文档 问答 数据处理学习文档&#xff1a; 相关资料&#xff1a; 故事背景概述-https://help.fanruan.com/finebi6.0/doc-view-1789.html 基础表处理-https://help.fanruan.com/finebi6.0/doc-view-1791.html …

六西格玛绿带培训如何告别“走过场”?落地生根

近年来&#xff0c;六西格玛绿带培训已经成为了众多企业提升管理水平和员工技能的重要途径。然而&#xff0c;不少企业在实施六西格玛绿带培训时&#xff0c;往往陷入形式主义的泥潭&#xff0c;导致培训效果大打折扣。那么&#xff0c;如何避免六西格玛绿带培训变成“走过场”…

【重磅】万能模型-直接能换迪丽热巴的模型

万能模型&#xff0c;顾名思义&#xff0c;不用重新训练src&#xff0c;直接可以用的模型&#xff0c;适应大部分原视频脸 模型用法和正常模型一样&#xff0c;但可以跳过训练阶段&#xff01;直接到合成阶段使用该模型 本模型没有做Xseg&#xff0c;对遮挡过多的画面不会自动适…

【C++】 解决 C++ 语言报错:Double Free or Corruption

文章目录 引言 双重释放或内存破坏&#xff08;Double Free or Corruption&#xff09;是 C 编程中常见且严重的内存管理问题。当程序尝试多次释放同一块内存或对已经释放的内存进行操作时&#xff0c;就会导致双重释放或内存破坏错误。这种错误不仅会导致程序崩溃&#xff0c…

谷粒商城学习-07-虚拟机网络设置

文章目录 一&#xff0c;找到配置文件Vagrantfile二&#xff0c;查询虚拟机网卡地址1&#xff0c;查看虚拟机网络配置2&#xff0c;查看宿主机网络配置 三&#xff0c;修改配置文件下的IP配置四&#xff0c;重新启动虚拟机即可生效五&#xff0c;Vagrantfile 的作用1&#xff0…

Java项目:基于SSM框架实现的校园快递代取管理系统【ssm+B/S架构+源码+数据库+毕业论文】

一、项目简介 本项目是一套基于SSM框架实现的校园快递代取管理系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简单、…

Solo 开发者周刊 (第12期):连接独立开发者,共享开源智慧

这里会整合 Solo 社区每周推广内容、产品模块或活动投稿&#xff0c;每周五发布。在这期周刊中&#xff0c;我们将深入探讨开源软件产品的开发旅程&#xff0c;分享来自一线独立开发者的经验和见解。本杂志开源&#xff0c;欢迎投稿。 产品推荐 1、Soju————一个现代的书签…

【C++】 解决 C++ 语言报错:Undefined Reference

文章目录 引言 未定义引用&#xff08;Undefined Reference&#xff09;是 C 编程中常见的错误之一&#xff0c;通常在链接阶段出现。当编译器无法找到函数或变量的定义时&#xff0c;就会引发未定义引用错误。这种错误会阻止生成可执行文件&#xff0c;影响程序的正常构建。本…

扁鹊三兄弟的启示,探寻系统稳定的秘诀

一、稳定性的重要性 1. 公司收益的角度 从公司收益的视角审视&#xff0c;系统不稳定可能会引发直接损失。例如&#xff0c;当系统突然出现故障导致交易中断时&#xff0c;可能造成交易款项的紊乱、资金的滞留或损失&#xff0c;这不但会阻碍当前交易的顺利完成&#xff0c;还…

长沙(市场调研公司)源点 企业如何决定是否需要开展市场调研?

长沙源点调研咨询认为&#xff1a;对于一个特定问题&#xff0c;管理者在面临几种解决问题的方案时&#xff0c;不应该凭直觉草率开展应用性市场调研。事实上&#xff0c;首先需要做的决策是是否需要开展调研。在下述情况下&#xff0c;最好不要做调研&#xff1a; *缺乏资源。…

【qt】如何获取网卡的信息?

网卡不只一种,有有线的,有无线的等等 我们用QNetworkInterface类的静态函数allInterfaces() 来获取所有的网卡 返回的是一个网卡的容器. 然后我们对每个网卡来获取其设备名称和硬件地址 可以通过静态函数humanReadableName() 来获取设备名称 可以通过静态函数**hardwareAddre…

使用OpenCV对图像进行三角形检测、颜色识别与距离估算【附代码】

文章目录 前言功能概述必要环境一、代码结构1. 参数定义2. 距离估计3. 颜色转换4. 图像处理函数4.1 读取图像和预处理4.2 轮廓检测4.3 过滤面积并检测三角形4.4 提取边框并计算距离 二、效果展示红色三角形绿色三角形蓝色三角形黄色三角形 三、完整代码获取总结 前言 本文将介…

springai+pgvector+ollama实现rag

首先在ollama中安装mofanke/dmeta-embedding-zh:latest。执行ollama run mofanke/dmeta-embedding-zh 。实现将文本转化为向量数据 接着安装pgvector&#xff08;建议使用pgadmin4作为可视化工具&#xff0c;用navicate会出现表不显示的问题&#xff09; 安装好需要的软件后我们…

【Linux进阶】磁盘分区3——目录树,挂载

Linux安装模式下&#xff0c;磁盘分区的选择&#xff08;极重要&#xff09; 在Windows 系统重新安装之前&#xff0c;你可能会事先考虑&#xff0c;到底系统盘C盘要有多大容量&#xff1f;而数据盘D盘又要给多大容量等&#xff0c;然后实际安装的时候&#xff0c;你会发现其实…

CV02_超强数据集:MSCOCO数据集的简单介绍

1.1 简介 MSCOCO数据集&#xff0c;全称为Microsoft Common Objects in Context&#xff0c;是由微软公司在2014年推出并维护的一个大规模的图像数据集&#xff0c;旨在推动计算机视觉领域的研究&#xff0c;尤其是目标识别、目标检测、实例分割、图像描述生成等任务。该数据集…

Qt项目:基于Qt实现的网络聊天室---注册模块

文章目录 基本页面设计创建登录界面创建注册界面优化样式完善注册类界面 客户端逻辑完善客户端增加post逻辑客户端配置管理 邮箱注册服务认证服务读取配置邮箱验证服务联调设置验证码过期封装redis操作类封装redis连接池注册功能Server端接受注册请求封装mysql连接池封装DAO操作…