有限状态机和抽象类多态

news2024/11/26 4:32:50

学习有限状态机的写法,我们会用一个抽象类继承的方法来写

首先,现在我们已经用过类的继承了,就是在我们敌人和野猪的这个代码当中,

我们打开野猪的代码,它继承了Enemy这个父类,所以可以遗传它父类当中所有的变量和函数方法,我们还可以通过Override进行复写,重载,重新写里面的函数方法。这些都是类继承的好处。

首先考虑抽象类之前,我们先来看看状态机是什么(之前我们有写过一些代码,这个代码挂载在我们的Animator当中,是一个Animator State动画状态机的函数,)我们双击打开其中一个

其实这个就是一个状态机的函数的写法了

首先由这个状态进入的时候,要执行什么,在状态持续不断更新的时候Update要执行什么,以及在这个状态要退出的时候执行什么,这就是一个基本的状态机的写法

我们创建的代码名字,默认继承了StateMachineBehaviour,我们可以点击打开一下,打开一会就会发现,这就是一个抽象类,我们要写的就是这样的方法

class类型前有一个abstract(抽象的意思)

抽象类下面所有的函数方法,都使用的是一个虚方法virtual方法,它可以被重写

抽象类当中的方法执行方法的声明,不写这个函数的实现(抽象类继承)(类比,比萨一定有饼皮,但是面粉可以不同,饼皮的样子可以不同)

接下来,我们进行编写

首先我们要有一个基本的抽象的方法,在Enemy文件夹中创建一个c#文件,命名为BaseState,双击打开

我们把这个代码作为我们的抽象基类,所以它不会继承MonoBehaviour(如果一个代码没有继承MonoBehaviour,我们就没有办法把它挂载在我们的gameObject上。我们要先写上关键词,在class前写上abstract抽象类

public abstract class BaseState 
{
    
}

抽象类里面,我们要写一些基本的函数声明,和动画状态机一样,也要有Update和退出。我们先来写一下,这些函数方法,我们也用abstract来定义(之前用的是virtual,因为它继承了ScriptObject,也是unity当中的一种类的类型,里面也包含了一些,帮我们写好的预制函数的方法等等的一些逻辑),在这里,我们什么都没有继承,我们直接用abstract来修饰这个函数方法

我们要写它最基本的逻辑更新logicUpdate,这个逻辑更新,我们要放在最基类的Enemy当中的Update当中来进行执行,所以所有的布尔值的判断,我们都会放在这个逻辑判断当中

然后我们还有(FixedUpdate当中都执行的是物理判断),所以我们要添加一个PhysicsUpdate物理逻辑判断,要当到FixedUpdate当中去执行

最后写一个退出的方法

这样我们就有了一个最基本的抽象的类,作为它基本的状态,我们要根据这个状态去创建它各种各样的状态

public abstract class BaseState 
{
    public abstract void OnEnter();
    public abstract void LogicUpdate();
    public abstract void PhysicsUpdate();
    public abstract void OnExit();
}

保存代码,返回unity

以野猪为例,首先我们创建一个野猪的巡逻逻辑c#代码,在Enemy文件夹下,创建一个巡逻状态BoarPatrolState

打开代码,我们要用继承的方式来写,这个状态是通过我们抽象类继承而来的,我们先删掉里面基本的方法,让这个代码继承我们的BaseState。

出现红色波浪线,提示我们并没有实现抽象的方法

对于抽象类,我们一定要有所谓的饼皮,目前我们还没有这些东西,要把它添加进来。选中它之后,我们可以通过提示,帮助我们快速的创建所有的抽象的方法

所有在这些函数执行过程当中,要实现的方法,我们就可以写在大括号当中了

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BoarPatrolState : BaseState
{
    public override void LogicUpdate()
    {
        throw new System.NotImplementedException();
    }

    public override void OnEnter()
    {
        throw new System.NotImplementedException();
    }

    public override void OnExit()
    {
        throw new System.NotImplementedException();
    }

    public override void PhysicsUpdate()
    {
        throw new System.NotImplementedException();
    }
}

我们回到Enemy代码当中

方便观看,分屏处理

接下来我们要来做这个抽象类的状态,来执行抽象类的状态

我们在最下面来创建这些状态,创建BaseStste类型的状态,无论是什么样的一个状态名字,都可以通过BaseState来调用,起名为patrolState

无论是野猪,蜜蜂,蜗牛的巡逻,我们都把它叫成patrolState

  [Header("状态")]
  public bool isHurt;
  public bool isDead;

  protected BaseState patrolState;

然后我们去重新创建,去new一个新建对象,就可以来使用它了

有了这个类型之后,我们还要新建一个currentState,我们可以通过这样的方式去切换各种各样的状态。

我们再创建一个chaseState追击状态

[Header("状态")]
public bool isHurt;
public bool isDead;

protected BaseState currentState;
protected BaseState patrolState;//巡逻状态
protected BaseState chaseState;//追击状态

我们来快速写一下,我们希望在这个当前的敌人被激活的时候,我们就进入一个最新的基本状态。

我们添加一个周期函数叫做OnEnable,这个物体被激活的时候,我们让当前状态等于我们的巡逻状态,然后我们要在这里执行currentState.OnEbter,所有在这个状态一进入,一开始的代码,我们就在这个位置都给他执行了,无论切换到任何的状态,enter的函数都会在这个位置调用

 private void OnEnable()
 {
     currentState = patrolState;
     currentState.OnEnter();
    
 }

然后在Update这个过程当中,我们就可以在这里面持续不断地执行在当前的状态里面的逻辑LogicUpdate

 private void Update()
 {
     faceDir = new Vector3(-transform.localScale.x, 0, 0);

     if (physicsCheck.touchLeftWall && faceDir.x<0 || physicsCheck.touchRightWall && faceDir.x>0)
     {
         wait = true;
         anim.SetBool("walk",false);
     }

     TimeCounter();
     currentState.LogicUpdate();
 }

在FixedUpdate当中要执行我们的PhysicsUpdate

 private void FixedUpdate()
 {
     if(!isHurt && !isDead)
         Move();

     currentState.PhysicsUpdate();
 }

我们再创建一个OnDisable函数(会在我们人物被关闭,从场景中消失的时候执行一次),把退出写入这个函数当中

 private void OnDisable()
 {
     currentState.OnExit();
 }

这样我们就成功进入了一个状态,游戏一开始就会进入巡逻状态,执行巡逻一开始的函数方法,在Update当中也会持续调用我的逻辑的循环判断,在物理循环也调用,在退出的时候也会被调用一次。

所以我们要做的就是,比如在patrolState巡逻过程当中,如果一旦发现敌人,我们就切换到追击的状态,对应的currentState=chaseState,然后接下来就会调用chaseState当中里面的Enter,PhysicsUpdate,Exit,这就是抽象类的状态机的写法

有限状态机的意思就是一个物体,他在一段时间一定的条件下只执行一个状态,他不会有其他的额外的状态判断。巡逻的话,只有巡逻的状态在执行,不会受到其他的影响;追击的时候就只执行追击状态里面所有的逻辑

这样的话就可以非常好的帮助我们去继续不断的扩展有限状态机,有限状态机无限扩展下去(当前的敌人又很少的状态,后续可能会有很多状态,这些都可以通过状态机来无限扩展下去)我们要做的就是在每个状态中,写好逻辑的判断,符合逻辑就切换到下一个状态

我们先来把巡逻状态用有限状态机的方式来写一下

首先先返回到野猪的Boar代码,把以前写的Move函数删掉,我们不用重写我们的移动,就放在FixedUpdate当中去执行就好了,至于动画,我们会放在我们的状态机里面去执行,

打开状态机的代码,稍微改变一下函数的顺序

如果我们在当前的状态当中,想要调用对应Enemy当中的一些函数方法,目前我们没办法直接调用。

所以在一开始的时候,我们要知道当前的npc是谁,他身上的Enemy的代码是什么,我们要找到当前的Enemy代码,然后我们就可以调用他的这些公开变量和函数

我们再次来修改一下我们的BaseState代码,打开。在一开始我们要创建一下,用protected来修饰一下,可以帮助我们所有继承的子类和访问。创建一个Enemy类型的变量currentEnemy

public abstract class BaseState 
{
    protected Enemy currentEnemy;
    public abstract void OnEnter();
    public abstract void LogicUpdate();
    public abstract void PhysicsUpdate();
    public abstract void OnExit();
}

在OnEnter,状态一进入的时候,我们先获得一下我们当前的Enemy是谁

public abstract class BaseState 
{
    protected Enemy currentEnemy;
    public abstract void OnEnter(Enemy enemy);
    public abstract void LogicUpdate();
    public abstract void PhysicsUpdate();
    public abstract void OnExit();
}

保存一下

点开BoarPatrolState,在这里面抽象就会显示当前出错,我们把它改成对应相同的就可以了

在这里面我们就可以访问currentEnemy,因为在我们的父类当中,我们写了protectedEnemy;所以我们当前的Enemy就等于我们传进来的enemy,

那接下来通过currentEnemy访问Enemy中的内容,(访问hurtForce把它改成其他的数值,等等)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BoarPatrolState : BaseState
{   
    public override void OnEnter(Enemy enemy)
    {
        currentEnemy = enemy;
    }
    public override void LogicUpdate()
    {
        throw new System.NotImplementedException();
    }

    public override void PhysicsUpdate()
    {
        throw new System.NotImplementedException();
    }
    public override void OnExit()
    {
        throw new System.NotImplementedException();
    }
}

通过这样的方法,我们类型一旦进入进来的时候,我们就把当前的Enemy传递进去,所以野猪一,野猪二等等都会获得当前的这个物体对应的这个身上挂的代码了,在Enemy的代码的OnEable中,,我们添加一个this传递进去就可以了

private void OnEnable()
{
    currentState = patrolState;
    currentState.OnEnter(this);
   
}

接下来,我们把巡逻的方法在代码中完成

发现敌人就切换到追击状态,(目前我们还没有设计追击和发现敌人的功能,我们后面再来做),我们先把这个正常的巡逻,然后撞墙返回的部分在代码中写好。我们找一下Enemy代码Update当中的这个代码,整个if判断撞墙的代码我们就不要了;因为这部分的内容,我们就可以放到我们当前的逻辑里去判断(因为普通的巡逻是这样的代码,追击的时候就不这样了)

我们将我们的if这段代码放到BasePatrolState代码中,出现了波浪线

当前我们无法访问CurrentEnemy;我们调用currentEnemy.physicsCheck,,注意Enemy代码当中的physicsCheck为私有private,我们把它改为public,这样就可以访问它了

接下来设置一下faceDir使它可以被访问。前面也应该加上currentEnemy

anim也是同样的方法

这样我们就把整个逻辑,成功移植到我们的当前的这个状态当中了

//Enemy

  Rigidbody2D rb;
  public Animator anim;
  public  PhysicsCheck physicsCheck;

//BoarPartolState

public override void LogicUpdate()
 {
     //发动layer切换到chase
     if (currentEnemy.physicsCheck.touchLeftWall && currentEnemy.faceDir.x < 0 || currentEnemy.physicsCheck.touchRightWall && currentEnemy.faceDir.x > 0)
     {
         currentEnemy.wait = true;
         currentEnemy.anim.SetBool("walk", false);
     }
 }

保存代码,返回unity

选中野猪,查看野猪身上的代码

Boar中有非常多的变量,有些变量其实我们并不需要,例如Physics Check这是我们直接在本人身上去进行获得的,Anim也是一样的;我们不希望在这个组件当中看到他,

我们可以到Enemy代码当中,在前面添加一个[HideInInspector],在我们的inspector当中把它隐藏起来,不需要显示出来的都可以隐藏掉

public class Enemy : MonoBehaviour
{
    Rigidbody2D rb;
    [HideInInspector]public Animator anim;
    [HideInInspector] public  PhysicsCheck physicsCheck;

    [Header("基本参数")]
    public float normalSpeed;//普通速度
    public float chaseSpeed;//加速冲
    [HideInInspector] public float currentSpeed;//当前速度
    public Vector3 faceDir;//面朝方向
    public float hurtForce;//受伤带来的冲击力

    public Transform attacker;

    [Header("计时器")]
    public float waitTime;
    public float waitTimeCounter;
    public bool wait;

    [Header("状态")]
    public bool isHurt;
    public bool isDead;

    protected BaseState currentState;
    protected BaseState patrolState;//巡逻状态
    protected BaseState chaseState;//追击状态

保存代码,返回unity

现在那些变量就已经被隐藏起来了

可以用这样的方法清洁一下代码和显示

我们来看一下BoarPatrolState当中的逻辑,在我们的普通的巡逻的模式当中,持续不断的执行判断,撞墙了之后转身;不过在这里面,之前有一个我们忽略的状态,现在我们的野猪虽然可以左右移动,不过如果他一旦一旦到了悬崖的这个位置,他就掉下去了,因为悬崖不会撞墙。

我们不希望他在悬崖掉下去,增加判断野猪是否在地面上,不是在地面或装左墙撞右墙都应该停下来,然后进入计时,同时要停止播放我们移动的这个动画

 public override void LogicUpdate()
 {
     //发动layer切换到chase
     if (!currentEnemy.physicsCheck.isGround|| currentEnemy.physicsCheck.touchLeftWall && currentEnemy.faceDir.x < 0 || currentEnemy.physicsCheck.touchRightWall && currentEnemy.faceDir.x > 0)
     {
         currentEnemy.wait = true;
         currentEnemy.anim.SetBool("walk", false);
     }
 }

下面其他的逻辑我们暂时先不写

保存代码,那么我们这个状态怎么样才能进入进来,一开始这个变量patrolState这个变量我们创建了,但是是空的;我们的patrolState到底执行的是野猪,蜜蜂还是蜗牛

我们在Boar代码当中写一个awake来写(但是awake本身会执行获得我们基本的组件);我们先把Enemy代码当中的Awake的修饰词改一下,改为protected virtual

protected virtual void Awake()
{
    rb = GetComponent<Rigidbody2D>();
    anim = GetComponent<Animator>();
    physicsCheck = GetComponent<PhysicsCheck>();
    currentSpeed = normalSpeed;//初始速度=普通速度
    waitTimeCounter = waitTime;//不需要在Awake中初始化,后续会修改掉
}

然后再Boar当中,我们就可以Override awake,在基本的awake都执行的前提之下,我们要给这些变量进行赋值;当前我们是野猪,我们要new一个野猪的巡逻模式出来,这样我们就成功创建了一个野猪的巡逻逻辑,给到我们的Enemy的基类当中的patrolState;

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Boar : Enemy
{
    protected override void Awake()
    {
        base.Awake();
        patrolState = new BoarPatrolState();
    }

这个patrolState现在有了他的赋值之后,就可以执行OnEnable这里面的逻辑了,还有一点要留意的是我们代码是有执行的顺序的,从上往下一次去执行,所以我们的TimeCounter应该放在我们的逻辑判断的下边

 private void Update()
 {
     faceDir = new Vector3(-transform.localScale.x, 0, 0);

     currentState.LogicUpdate(); 
     TimeCounter();
 }

所以我们要持续判断逻辑,一旦进入wait了之后,他的OnEnable--TimeCounter这个部分要进入时间的计时保存

保存代码,返回unity,测试一下

目前我们还原了野猪的巡逻逻辑,我们重画地图,看野猪遇到悬崖的效果

野猪走到坑的位置,动画切换回去了,他本来应该停下来,可是他继续滑动的往前走了

回到代码Enemy当中,在FixedUpdate函数中,有我们的Move函数,Move在没有受伤,没有死亡的时候就会执行,所以wait的时候,他仍然会执行Move,还是朝当前方向继续移动,所以我们在上面要再加上另外一个约束

private void FixedUpdate()
{
    if(!isHurt && !isDead && !wait)
        Move();

    currentState.PhysicsUpdate();
}

保存代码,返回unity,再次测试

这就是整个的巡逻状态

我们在巡逻结束的时候,退出状态,walk=false;这样可以有效配合我们Animator状态机的动画的切换了

在BoarPatrolState

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BoarPatrolState : BaseState
{   
    public override void OnEnter(Enemy enemy)
    {
        currentEnemy = enemy;
    }
    public override void LogicUpdate()
    {
        //发动layer切换到chase
        if (!currentEnemy.physicsCheck.isGround ||( currentEnemy.physicsCheck.touchLeftWall && currentEnemy.faceDir.x < 0 || currentEnemy.physicsCheck.touchRightWall && currentEnemy.faceDir.x > 0))
        {
            currentEnemy.wait = true;
            currentEnemy.anim.SetBool("walk", false);
        }
        else
        {
            currentEnemy.anim.SetBool("walk", true);
        }
    }

    public override void PhysicsUpdate()
    {
        
    }
    public override void OnExit()
    {
        currentEnemy.anim.SetBool("walk", false);
    }
}

目前我们做好了一个状态,还需要另外一个状态追击状态,我们还没有完成发现Player的函数方法

抽象类只做函数声明,不写函数的实现,Enemy基本的父类也改成使用我们的状态机的方式,方便进行扩展状态,只需要添加对应的状态,每一次切换赋值给currentState

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

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

相关文章

线性代数基础02

目录 1.向量 1.1向量的定义 1.2向量的运算 1.2.1向量加法 1.2.2向量数乘 1.2.3向量点积 1.3矩阵的特征值和特征向量 1.4向量的模 1.4.1向量的模的定义 1.4.2向量的模的几何解释 1.4.3向量的模的性质 1.5向量的内积 1.5.1向量的内积的定义 1.5.2向量的内积的几何解…

【Linux】进程概念 PCB结构体 fork创建子进程

&#x1fa90;&#x1fa90;&#x1fa90;欢迎来到程序员餐厅&#x1f4ab;&#x1f4ab;&#x1f4ab; 主厨&#xff1a;邪王真眼 主厨的主页&#xff1a;Chef‘s blog 所属专栏&#xff1a;青果大战linux 总有光环在陨落&#xff0c;总有新星在闪烁 每日小感慨&#xff…

UDP/TCP协议详解

目录 一,自定义应用层协议: 1)xml 2),JSON 3),yml 4),google protobuffer 二,传输层UDP/TCP: UDP协议: TCP协议: TCP的核心机制一:确认应答 TCP核心机制二:超时重传 TCP核心机制三:连接管理 TCP核心机制四:滑动窗口 TCP核心机制五:流量控制 TCP核心机制六:拥塞控制…

c++ pdf文件提取txt文本示例

最近抽空采用之前封装的接口将pdf文件提取出txt文本&#xff0c;顺利完成&#xff0c;界面如下所示&#xff1a; 提起的效果如下所示&#xff1a; 输出的txt文本内容如下&#xff1a; 下载链接&#xff1a;https://download.csdn.net/download/u011269801/89905548

AI 3D拣选系统行业分析:物流行业是最主要的需求来源

AI 3D拣选系统是一种集成了先进传感技术、机器人技术和计算机视觉技术的自动化分拣解决方案。它能够在三维空间内快速、准确地识别和分拣各种形状、大小和材质的物品&#xff0c;大大提高了物流效率和准确性。该系统通过高精度的3D传感器和先进的视觉算法&#xff0c;能够实时捕…

【某农业大学计算机网络实验报告】实验五 TCP 运输连接管理

实验目的&#xff1a; 熟悉 TCP 通信的三个阶段&#xff1a;通过此次实验&#xff0c;结合理论课知识深入理解并熟悉 TCP 通信的三个主要阶段&#xff0c;即连接建立&#xff08;SYN-SYN&#xff09;&#xff0c;数据传输&#xff08;DATA&#xff09;&#xff0c;以及连接释放…

【论文速读】Prompt Tuning:The Power of Scale for Parameter-Effificient Prompt Tuning

arxiv&#xff1a;2104.08691v2 摘要 在这项工作中&#xff0c;我们探索了“prompt tuning&#xff08;提示调优&#xff09;”&#xff0c;这是一种简单而有效的机制&#xff0c;用于学习“soft prompts&#xff08;软提示&#xff09;”&#xff0c;以条件下冻结的语言模型…

Golang | Leetcode Golang题解之第485题最大连续1的个数

题目&#xff1a; 题解&#xff1a; func findMaxConsecutiveOnes(nums []int) (maxCnt int) {cnt : 0for _, v : range nums {if v 1 {cnt} else {maxCnt max(maxCnt, cnt)cnt 0}}maxCnt max(maxCnt, cnt)return }func max(a, b int) int {if a > b {return a}return …

矩阵matrix

点积 在 NumPy 中&#xff0c;dot 是矩阵或向量的点积&#xff08;dot product&#xff09;操作。 假设有两个向量a和 b&#xff0c;它们的点积定义为对应元素相乘&#xff0c;然后求和。公式如下&#xff1a; 例子&#xff1a; 点积的计算步骤是&#xff1a; 因此&#xf…

【动态规划】【路径问题】下降路经最小和、最小路径和、地下城游戏

4. 下降路径最小和 931. 下降路径最小和 算法原理 确定状态表示 dp[i][j] 表示&#xff1a;到达 [i, j] 位置&#xff0c;最小的下降路径 状态转移方程 dp[i][j] 从 [i-1, j-1] 到达 [i, j] > dp[i-1][j-1] m[i][j]从 [i-1, j] 到达 [i, j] > dp[i-1][j] m[i][j]从 …

leetcode_887_鸡蛋掉落___循序渐进的分析

分析&#xff1a;对于一组[n,k] 在一次尝试中选择了在dep层测试 其可以分为 如果在dep层炸了: 则变成了[dep-1,k-1]读作在dep-1层用k-1个鸡蛋来找鸡蛋的极限所需次数如果在dep层没炸: 则变成了[n-dep,k]读作在n-dep层用k个鸡蛋来找鸡蛋的极限所需次数可以发现这都是子问题的…

【Javaee】网络编程-TCP Socket

前言 前文中我们介绍了UDP Socket相关的构造方法和方法&#xff0c;并实现了UDP的回显服务器和客户端。 本篇将介绍TCP Socket&#xff0c;并使用TCP Socket api实现服务器和客户端的通信 一.TCP Socket的常见方法 1.ServerSocket ServerSocket是创建TCP服务端Socket的API …

线程池:高效管理并发任务的利器

线程池&#xff1a;高效管理并发任务的利器 什么是线程池&#xff1f; 线程池&#xff08;Thread Pool&#xff09;是Java并发编程中的一种设计模式&#xff0c;旨在通过重复利用线程资源&#xff0c;来提高程序执行效率。线程池的主要思想是提前创建一组可供使用的线程&#…

归一化输入

当输入的不同的特征取值范围差异过大&#xff0c;取得对应参数差别也会很大&#xff0c;在对参数进行优化的过程中&#xff0c;参数小的维度步长较小&#xff0c;参数大的维度步长较大&#xff0c;优化过程中路径曲折&#xff0c;将输入归一化&#xff0c;使特征取值范围差别小…

相控阵雷达电特性matlab模拟与仿真,带GUI界面,对比有限扫描阵,稀疏阵,多波束阵,共形阵等

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 相控阵雷达作为一种先进的雷达技术&#xff0c;具有高分辨率、多功能、快速扫描等优点&#xff0c;在军事和民用领域都有着广泛的应用。相控阵雷达的天线系统是其核心组成部分…

C#线性变换——缩放

前言&#xff1a; 大家好&#xff0c;我是上位机马工&#xff0c;硕士毕业4年年入40万&#xff0c;目前在一家自动化公司担任软件经理&#xff0c;从事C#上位机软件开发8年以上&#xff01;我们在C#开发中经常需要对平面中的坐标进行一些变换&#xff0c;比如缩放、旋转等&…

数据结构:二叉树、堆

目录 一.树的概念 二、二叉树 1.二叉树的概念 2.特殊类型的二叉树 3.二叉树的性质 4.二叉树存储的结构 三、堆 1.堆的概念 2.堆的实现 Heap.h Heap.c 一.树的概念 注意&#xff0c;树的同一层中不能有关联&#xff0c;否侧就不是树了&#xff0c;就变成图了&#xff…

PCL 点云配准 Trimed-ICP算法(精配准

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 2.1.1 perform_standard_icp 函数 2.1.2 perform_trimmed_icp 函数 2.1.3 visualize_registration 函数 2.2完整代码 PCL点云算法汇总及实战案例汇总的目录地址链接&#xff1a; PCL点云算…

国庆旅游高峰期,如何利用可视化报表来展现景区、游客及消费数据

国庆黄金周&#xff0c;作为国内旅游市场的年度盛宴&#xff0c;总是吸引着无数游客的目光。今年&#xff0c;随着旅游市场的强劲复苏&#xff0c;各大景区又再次迎来游客流量的高峰。全国国内出游7.65亿人次&#xff0c;同比增长5.9%&#xff0c;国内游客出游总花费7008.17亿元…

大型企业软件开发是什么样子的? - Web Dev Cody

引用自大型企业软件开发是什么样子的&#xff1f; - Web Dev Cody_哔哩哔哩_bilibili 一般来说 学技术的时候 我们会关注 开发语言特性 &#xff0c;各种高级语法糖&#xff0c;底层技术 但是很少有关注到企业里面的开发流程&#xff0c;本着以终为始&#xff08;以就业为导向…