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

news2024/11/24 7:01:04

最终效果

在这里插入图片描述

文章目录

  • 最终效果
  • 前言
  • 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);    // 初始状态为Idle
            currentState.OnEnter();
        }

        void Update()
        {
            parameter.animatorStateInfo = parameter.animator.GetCurrentAnimatorStateInfo(0);// 获取当前动画状态信息
            GetPlayerTransform();

            currentState.OnUpdate();    // 每帧更新当前状态

            //TODO:用于测试 如果按下回车键,设置被击中状态为true
            if (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 平滑过渡到 0
            var 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.coloradmin.cn/o/1898203.html

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

相关文章

【Odoo开源ERP】别把ERP与进销存软件混为一谈

导读&#xff1a;企业使用ERP软件能够实现管理升级&#xff0c;多方信息集成&#xff0c;按照既定策略逻辑运算&#xff0c;生成计划建议&#xff0c;减少人力成本&#xff0c;提高准确率的同时提高经营能力。 ERP&#xff0c;是MRP II的下一代软件&#xff0c;除了MRP II已有的…

WEB安全-靶场

1 需求 2 语法 3 示例 男黑客|在线渗透测试靶场|网络安全培训基地|男黑客安全网 4 参考资料

三万字带你一遍跑通uer

三万字带你一遍跑通uer 参考文档 今天给大家介绍个非常强大的项目uer&#xff0c;集成了许多可以做自然语言的东西&#xff0c;效果的话也非常好&#xff0c;很适合企业级的应用&#xff01; 1. 先将项目uer从github拉取下来&#xff08;zip或git都ok&#xff09; 2. 用pycha…

Vue88-Vuex中的mapActions、mapMutations

一、mapMutations的调用 此时结果不对&#xff0c;因为&#xff1a;若是点击事件不传值&#xff0c;默认传的是event&#xff01;&#xff0c;所以&#xff0c;修改如下&#xff1a; 解决方式1&#xff1a; 解决方式2&#xff1a; 不推荐&#xff0c;写法麻烦&#xff01; 1-…

【代码大全2 选读】看看骨灰级高手消灭 if-else 逻辑的瑞士军刀长啥样

文章目录 1 【写在前面】2 【心法】这把瑞士军刀长啥样3 【示例1】确定某个月份的天数&#xff08;Days-in-Month Example&#xff09;4 【示例2】确定保险费率&#xff08;Insurance Rates Example&#xff09;5 【示例3】灵活的消息格式&#xff08;Flexible-Message-Format …

基于深度学习的图像背景剔除

在过去几年的机器学习领域&#xff0c;我一直想打造真正的机器学习产品。 几个月前&#xff0c;在参加了精彩的 Fast.AI 深度学习课程后&#xff0c;似乎一切皆有可能&#xff0c;我有机会&#xff1a;深度学习技术的进步使许多以前不可能实现的事情成为可能&#xff0c;而且开…

链篦机回转窑球团生产工艺

生球在回转窑氧化焙烧&#xff0c;回转窑头部设有燃烧器&#xff0c;燃料可以采用气体、固体、液体。 来自环冷机一冷却段的高温废气作为二次风进入窑内参与燃烧&#xff0c;烧成成品球进入环冷机。 环冷机采用鼓风冷却&#xff0c;热风风箱分为四段&#xff1a; 一段气体引至…

人工智能系列-numpy(三)

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 副本和视图 副本 副本是一个数据的完整的拷贝&#xff0c;如果我们对副本进行修改&#xff0c;它不会影响到原始数据&#xff0c;物理内存不再同一位置。副本一般发生在Pytho…

中英双语介绍英国伦敦(London)

中文版 伦敦简介 伦敦&#xff08;London&#xff09;是英国的首都&#xff0c;也是全球最重要的金融、文化、艺术和交通中心之一。作为一座历史悠久的城市&#xff0c;伦敦融合了现代化的城市生活与丰富的历史遗产。以下是对伦敦的详细介绍&#xff0c;包括其经济状况、高等…

zed摄像头 orin域控掉帧问题

cpu性能并没有吃满&#xff0c;双目摄像头的帧率忽高忽低 原因是域控没有开性能模式&#xff0c;调成MAXN模式就行。

实在智能对话钉钉:宜搭+实在Agent,AI时代的工作方式

比起一个需求需要等产品、技术排期&#xff0c;越来越多的人开始追求把自己武装成「全能战士」&#xff0c;通过低代码工具一搭&#xff0c;一个高效的工作平台便产生了。 宜搭是钉钉自研的低代码应用构建平台&#xff0c;无论是专业开发者还是没有代码基础的业务人员&#xf…

PCL从理解到应用【03】KDTree 原理分析 | 案例分析 | 代码实现

前言 本文分析KDTree的原理&#xff0c;集合案例深入理解&#xff0c;同时提供源代码。 三个案例&#xff1a;K近邻搜索、半径内近邻搜索、近似最近邻搜索。方法对比&#xff0c;如下表所示&#xff1a; 特性K近邻搜索半径内近邻搜索近似最近邻搜索描述查找K个最近邻点查找指…

spdlog一个非常好用的C++日志库(五): 源码分析之线程池thread_pool

目录 1.线程池 2.thread_pool简介 3.多生产者-多消费者阻塞队列模型 3.1.阻塞与非阻塞方式插入数据 3.2.取出数据 3.3.overrun异常处理机制 3.4.其他接口 4.环形队列circular_q 5.thread pool模型 6.thread pool实现 6.1.thread_pool类接口 6.2.构造与析构 6.3.po…

pip install包出现哈希错误解决

如图&#xff0c;当遇到此类错误时&#xff0c;多半是连接不稳定导致的校验失败。我们可以在PC端&#xff0c;或Ubuntu通过浏览器下载.whl安装文件&#xff1a;直接复制报错信息中的网址到浏览器即可弹出下载窗口。

tomcat 安装和优化

tomcatat tomcat和http一样&#xff0c;都是用来处理动态页面的 tomcat也可以作为web服务器&#xff0c;开源的 php.php tomcat.jsp nginx.html tomcat使用java代码写的程序&#xff0c;运行的是java的web服务程序 tomcat的特点和功能&#xff1a; 1、servlet容器&…

【笔记】TimEP Safety Mechanisms方法论

1.TimEPM Overview 三大监控方法: Alive Supervision 实时监督Logical Supervision 逻辑监督Deadline Supervision 限时监督相关模块框图: 相关模块调用框图: 每个MCU核开启内狗(1核1狗),内狗用于监控相应核的TASK超时,超时后软reset MCU内狗时钟需要独立于OS时钟,两…

22_嵌入式微处理器

目录 嵌入式微处理器分类 嵌入式硬件结构 嵌入式微处理器的分类 典型8位微处理器 8位微处理器结构 8051单片机的硬件组成 8051单片机的引脚 时钟电路 MCS-51指令集 典型16位微处理器 16位微处理器结构 MSP430单片机硬件结构 典型32位微处理器 32位微处理器特点 A…

误删分区后的数据拯救:双管齐下恢复策略

在数字化时代&#xff0c;数据的价值日益凸显&#xff0c;而误删分区作为常见的数据安全威胁之一&#xff0c;常常让用户措手不及。本文将深入探讨误删分区的现象&#xff0c;并为您揭示两种高效的数据恢复方案&#xff0c;旨在帮助您在最短时间内找回失去的数据&#xff0c;同…

C++学习笔记二

一、常量 1.用const关键字声明常量变量 const常量变量在定义时必须进行初始化&#xff0c;并且不能通过赋值来改其值 const double gravity { 9.8 }; //首选在类型之前使用const int const sidesInSquare { 4 }; // “east const”风格&#xff0c;可以&#xff0c;但不是首…

小程序分包加载、独立分包、分包预加载等

一、小程序分包加载 小程序的代码通常是由许多页面、组件以及资源等组成&#xff0c;随着小程序功能的增加&#xff0c;代码量也会逐渐增加&#xff0c; 体积过大就会导致用户打开速度变慢&#xff0c;影响用户的使用体验。分包加载是一种小程序优化技术。将小程序不同功能的代…