FSM人物动画状态机

news2024/12/22 19:41:31

人物动画状态机

  • 介绍
  • FSM
  • 角色模型的设置
  • 角色动作的设置
  • 角色动画控制器的设置
  • 书写角色动画的具体状态,实现缓动起步的FSM
  • 总结

介绍

摇杆我就不介绍了,之前我在这里面讲过怎么用摇杆,摇杆连接。
这里我先说下什么是FSM人物动画状态机,说白了就是一个集中管理控制角色动画状态的一个管理器。可以切换操作对象的不同动作状态。下面我放一个实例Gif这个是人物FSM动画控制+人物动作匹配缓动起步的实例。
请添加图片描述

FSM

在这里插入图片描述
RoleState是角色的所有状态列举,后面切换状态是通过RoleFSMMgr切换玩家的RoleState来改变玩家的状态。
RoleState.cs

/// <summary>
/// 角色状态
/// </summary>
public enum RoleState
{
    None,
    Idle,
    Run,
    RunToIdle,
}

这里还有一个角色状态的抽象基类RoleStateAbstract,这里主要存储了动画状态片段以及信息,还有就是这里有三个方法分别是动作状态的进入OnEnter、动作状态的持续中OnStay、动作状态的离开OnExit,这里主要是为了具体的动作状态提供统一管理的方法,也方便了上层的RoleFSMMgr的调用管理整个状态的执行和切换状态。
RoleStateAbstract.cs

using UnityEngine;

/// <summary>
/// 角色状态的抽象基类
/// </summary>
public abstract class RoleStateAbstract
{
    /// <summary>
    /// 当前角色有限状态机管理器
    /// </summary>
    public RoleFSMMgr CurrRoleFSMMgr { get; private set; }

    /// <summary>
    /// 当前动画状态信息
    /// </summary>
    public AnimatorStateInfo CurrRoleAnimatorStateInfo { get; set; }

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="roleFSMMgr"></param>
    public RoleStateAbstract(RoleFSMMgr roleFSMMgr)
    {
        CurrRoleFSMMgr = roleFSMMgr;
    }

    /// <summary>
    /// 进入状态
    /// </summary>
    public virtual void OnEnter() { }

    /// <summary>
    /// 执行状态
    /// </summary>
    public virtual void OnStay() { }

    /// <summary>
    /// 离开状态
    /// </summary>
    public virtual void OnExit() { }
}

在RoleFSMMgr管理器中的Dictionary<RoleState, RoleStateAbstract>中存储了当前玩家的状态枚举跟具体的状态信息RoleStateAbstrac动作状态基类一一对应。并且还存储了玩家的角色控制器RoleCtrl、当前状态信息、当前状态的枚举等信息。
RoleFSMMgr除了切换玩家状态以外,还有一个作用就是要驱动玩家的当前状态,这里会提供了一个OnUpdate的方法来驱动玩家的当前状态。
因为当前管理器是角色对象需要实例化的,所以这里提供了一个构造函数,我们在构造函数中实例化我们所有的角色状态以及继承自RoleStateAbstract的所有具体角色状态。
RoleFSMMgr.cs

using System.Collections.Generic;


/// <summary>
/// 角色有限状态机管理器
/// </summary>
public class RoleFSMMgr
{
    /// <summary>
    /// 当前角色控制器
    /// </summary>
    public RoleCtrl CurrRoleCtrl { get; private set; }

    /// <summary>
    /// 当前角色状态枚举
    /// </summary>
    public RoleState CurrRoleStateEnum { get; private set; }

    /// <summary>
    /// 当前角色状态
    /// </summary>
    private RoleStateAbstract m_CurrRoleState = null;

    /// <summary>
    /// 状态基类字典
    /// </summary>
    private Dictionary<RoleState, RoleStateAbstract> m_RoleStateDic;

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="currRoleCtrl"></param>
    public RoleFSMMgr(RoleCtrl currRoleCtrl)
    {
        CurrRoleCtrl = currRoleCtrl;
        m_RoleStateDic = new Dictionary<RoleState, RoleStateAbstract>();
        m_RoleStateDic[RoleState.Idle] = new RoleStateIdle(this);
        m_RoleStateDic[RoleState.Run] = new RoleStateRun(this);
        m_RoleStateDic[RoleState.RunToIdle] = new RoleStateRunToIdle(this);
    }

    #region OnUpdate 每帧执行
    /// <summary>
    /// 每帧执行
    /// </summary>
    public void OnUpdate()
    {
        if (m_CurrRoleState != null)
        {
            m_CurrRoleState.OnStay();
        }
    }
    #endregion

    /// <summary>
    /// 切换状态
    /// </summary>
    /// <param name="newState">新状态</param>
    public void ChangeState(RoleState newState)
    {
        if (CurrRoleStateEnum == newState) return;

        //调用以前状态的离开方法
        if (m_CurrRoleState != null)
            m_CurrRoleState.OnExit();

        //更改当前状态枚举
        CurrRoleStateEnum = newState;

        //更改当前状态
        m_CurrRoleState = m_RoleStateDic[newState];

        ///调用新状态的进入方法
        m_CurrRoleState.OnEnter();
    }
}

RoleCtrl人物控制器我就不过多解释了,这里其实只是一个对象的载体脚本,只是为了获取到角色对象上面的Animator动画、CharacterController人形碰撞器、JoyStickBar摇杆、RoleFSMMgr实例化的角色fsm管理器。

这里的Update的方法中有一段代码如下,这个就是检测是否在地面如果不在地面上那么就向下位移瞬间到达平面的意思,如果你是想要做跳跃的话可以修改这里,自己写向下的作用力。

        ///非落地情况 使其快速落地
        if (!m_char.isGrounded)
        {
            m_char.Move(new Vector3 (0,-100,0));
        }

RoleCtrl是实体对象,所以需要在Start的时候创建我们得fsm状态管理器,然后设置自身的初始状态。
整体代码
RoleCtrl.cs
在这里插入图片描述

using UnityEngine;

public class RoleCtrl : MonoBehaviour {

    /// <summary>
    /// 摇杆
    /// </summary>
    public JoyStickBar m_Joy;

    /// <summary>
    /// 当前人物触发器
    /// </summary>
    public CharacterController m_char;

    /// <summary>
    /// 动画状态机
    /// </summary>
    public RoleFSMMgr m_fsm;

    /// <summary>
    /// 动画控制器
    /// </summary>
    public Animator m_anim;

    private void Start()
    {
        m_Joy.m_ctrl = this;
        m_char = GetComponent<CharacterController>();
        m_anim = GetComponent<Animator>();
        m_fsm = new RoleFSMMgr(this);
        m_fsm.ChangeState(RoleState.Idle);
    }

    // Update is called once per frame
    void Update () {

        if (!m_char) return;

        if (!m_Joy) return;

        if (m_fsm == null) return;

        ///非落地情况 使其快速落地
        if (!m_char.isGrounded)
        {
            m_char.Move(new Vector3 (0,-100,0));
        }

        m_fsm.OnUpdate();

    }
}

虽然我之前讲过摇杆,但是这里我把摇杆的脚本也贴出来,这里还是稍微有点变化的
摇杆继承了UnityEngine.EventSystems中的三个事件分别是IBeginDragHandler、IDragHandler、IEndDragHandler
JoyStickBar.cs
在这里插入图片描述

using UnityEngine;
using UnityEngine.EventSystems;

public class JoyStickBar : MonoBehaviour,IBeginDragHandler,IDragHandler,IEndDragHandler {

    /// <summary>
    /// 角色控制器
    /// </summary>
    public RoleCtrl m_ctrl;

    /// <summary>
    /// 最大半径
    /// </summary>
    public float maxRadius;

    /// <summary>
    /// 计算中的半径
    /// </summary>
    public float radius;

    /// <summary>
    /// 原始位置
    /// </summary>
    private Vector2 originalPos;

    /// <summary>
    /// 遥杆中心位置
    /// </summary>
    public RectTransform joystickradius;

    /// <summary>
    /// 箭头指针方向
    /// </summary>
    public Transform joystickpointer;

    #region 方向控制访问器

    /// <summary>
    /// 水平方向
    /// </summary>
    private float horizontal = 0;

    /// <summary>
    /// 垂直方向
    /// </summary>
    private float vertical = 0;

    /// <summary>
    /// 水平方向属性访问器
    /// </summary>
    public float Horizontal
    {
        get { return horizontal; }
    }

    /// <summary>
    /// 垂直方向属性访问器
    /// </summary>
    public float Vertical
    {
        get { return vertical; }
    }
    
    #endregion

    private void Start()
    {
        if (!joystickradius) return;
        originalPos = transform.position;
        maxRadius = - joystickradius.anchoredPosition.x;
        Debug.LogError(maxRadius);
        ShowPointer(false);
    }

    #region 方向受力

    /// <summary>
    /// 各个方向上的受力
    /// </summary>
    private void DirPotency()
    {
        //horizontal = transform.localPosition.x;
        //vertical = transform.localPosition.y;
        horizontal = GetComponent<RectTransform>().anchoredPosition.x;
        vertical = GetComponent<RectTransform>().anchoredPosition.y;
    }

    #endregion

    #region 继承接口事件逻辑处理

    /// <summary>
    /// 开始拖拽
    /// </summary>
    /// <param name="eventData"></param>
    public void OnBeginDrag(PointerEventData eventData)
    {
        ShowPointer(true);
    }

    /// <summary>
    /// 拖拽中
    /// </summary>
    /// <param name="eventData"></param>
    public void OnDrag(PointerEventData eventData)
    {
        //偏移量
        Vector2 dir = eventData.position - originalPos;
        //Vector2 dir = new Vector2 (Input.mousePosition.x, Input.mousePosition.y) - originalPos;

        //获取向量长度
        float distance = Vector3.Magnitude(dir);

        //获取当前
        radius = Mathf.Clamp(distance,0,maxRadius);

        //状态切换
        m_ctrl.m_fsm.ChangeState(RoleState.Run);

        //位置赋值
        transform.position = dir.normalized * radius + originalPos;

        //方向受力度量
        DirPotency();

        //角度转换
        CalculateAngle(dir.normalized);

    }

    /// <summary>
    /// 结束拖拽
    /// </summary>
    /// <param name="eventData"></param>
    public void OnEndDrag(PointerEventData eventData)
    {
        transform.position = originalPos;

        //当前半径
        radius = 0;

        //方向受力度量
        DirPotency();

        ShowPointer(false);

        //m_ctrl.m_fsm.ChangeState(RoleState.Idle);

        m_ctrl.m_fsm.ChangeState(RoleState.RunToIdle);
    }

    #endregion

    #region 指针逻辑
    
    /// <summary>
    /// 角度转换
    /// </summary>
    public void CalculateAngle(Vector2 dir)
    {
        if (!joystickpointer) return;
        float angle = Vector2.Angle(Vector2.up,dir);
        joystickpointer.rotation = Quaternion.Euler(new Vector3(0, 0, -(dir.x>0?angle:-angle)));
    }

    /// <summary>
    /// 显示隐藏指针
    /// </summary>
    /// <param name="isshow"></param>
    public void ShowPointer(bool isshow)
    {
        joystickpointer.gameObject.SetActive(isshow);
    }

    #endregion

}

角色模型的设置

角色的模型这里使用的是通用骨骼Generic,当然这里使用的是人形或者其他形也没有问题,只要模型跟动作是同意的骨骼就没有问题。
在这里插入图片描述
根骨骼节点记得也要跟动作选择一致,Root node这里选择的是骨骼的根节点Bip001这个根节点,动画对应的也需要选择这个。
在这里插入图片描述
下面是动画的设置在这里插入图片描述

角色动作的设置

动作的设置这里详细的说一下,因为这里有些动画是包含位移的,有些动作又是不包含位移的,这里需要看你具体要做的功能是什么,比如你要做帧同步的动画状态,那么你动画肯定是不能自己带位移的,也不能用动画的位移去应用到实际运动,因为这个动作底层使用的还是float是不一致的物理,除非这里只用做显示层。至于只要不是需要同步的位移的那么你动作带有位移或者使用这个也没有关系。
待机Idle动画
Anim.Compression这个时动画的压缩我通常直接设置Off,不启用动画的压缩
在这里插入图片描述
这里是动画的名字和帧数区间,如果是做了一个完整的动画需要截取动画的的,可以自己根据动作或者策划提供的配置文件进行截取动画这里我就不多说了,名字最好是要改一下并且跟RoleState中你创建的角色状态的枚举是一致的,这样方便在动画后面持续过程中方便操作,获取动画的信息等。
在这里插入图片描述
再看一下下面这几项Loop Time是是否需要循环播放动画,这里我们要做的是Idle和Run所以肯定是都要选择Loop Time。
Root Transform Rotation中
Bake Into Pose不够选代表的是使用动作的旋转来映射到真实人物上,勾选代表产生的旋转不会映射到实际人物上。
Base Upon(at Start)代表的是旋转映射到哪根骨骼上。
Offset代表的是偏移量

Root Transfrom Position(Y)
Bake Into Pose不够选代表的是使用动作的Y轴位移来映射到真实人物Y轴上,勾选代表产生位移不会映射到实际人物Y轴上。
Base Upon代表的是旋转映射到哪根骨骼上。
Offset代表的是偏移量

Root Transform Position(XZ)
Bake Into Pose不够选代表的是使用动作的XZ轴位移来映射到真实人物上,勾选代表产生位移不会映射到实际人物XZ轴上。
Base Upon代表的是旋转映射到哪根骨骼上。
Offset代表的是偏移量
在这里插入图片描述
Curvse动画曲线这里简单说一下,可以获取动画的曲线值来方便调式动作。
Events动画事件一般人物的脚步声音,动作特效事件等需要插事件。
Mask骨骼遮罩这个就不说了,这个是做分层动画

跑步Run动画
这个动画我就不单独设置了,跟Idle的原理是一样的,设置也是完全相同的。记住设置玩动画之后要点击Apply。

角色动画控制器的设置

创建两个参数Idle是一个bool类型的
Run是一个float类型的参数为后面使用
在这里插入图片描述

单层动画控制器,这里单层动画即可,将之前设置好的Idle拖上来,并且从Any State分别连线到Idle。
在这里插入图片描述
连线设置如下
Transition Duration这个融合度根据自己的动作合适的程度设置(融合度)
Has Exit Time取消勾选(无退出动作时间)
Can Transition To Self取消勾选(不能自己切换自己)
Conditions切换条件选择Idle,true
在这里插入图片描述

创建一个动画融合树,并命名为Run
在这里插入图片描述
在这里插入图片描述
动画融合树设置如下,将Idle和Run都拖拽上来,Parameter设置为刚才创建的参数Run,此时拖动BlendTree的值动画可以看到动画的变化
在这里插入图片描述
动画变化如下
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
调节Run的参数0-1,上面会得到一个从Idle到Run的不同程度的融合动画,这样后面就可以做融合缓动的起步的动画了。

拖动AnyState连线到Run并且设置如下
在这里插入图片描述

书写角色动画的具体状态,实现缓动起步的FSM

RoleStateIdle状态这里是常规动画的脚本书写


/// <summary>
/// 待机状态
/// </summary>
public class RoleStateIdle : RoleStateAbstract
{
    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="roleFSMMgr">有限状态机管理器</param>
    public RoleStateIdle(RoleFSMMgr roleFSMMgr) : base(roleFSMMgr)
    {

    }

    /// <summary>
    /// 实现基类 进入状态
    /// </summary>
    public override void OnEnter()
    {
        base.OnEnter();
        CurrRoleFSMMgr.CurrRoleCtrl.m_anim.SetBool(RoleState.Idle.ToString(), true);
    }

    /// <summary>
    /// 实现基类 执行状态
    /// </summary>
    public override void OnStay()
    {
        base.OnStay();

        //CurrRoleAnimatorStateInfo = CurrRoleFSMMgr.CurrRoleCtrl.m_anim.GetCurrentAnimatorStateInfo(0);
    }

    /// <summary>
    /// 实现基类 离开状态
    /// </summary>
    public override void OnExit()
    {
        base.OnExit();
        CurrRoleFSMMgr.CurrRoleCtrl.m_anim.SetBool(RoleState.Idle.ToString(), false);
    }
}

RoleStateRun状态这个就变成了缓动起步
下面我提供了两种缓动的方案,一种是根据摇杆拖拽的程度来控制跑动的幅度,另一种是设置了一个时间来做增速,相当于进行缓冲起步。
这里看的仔细的会发现我OnExit的方法并没有写逻辑,因为我这里如果停下的话还需要另一个状态来操作逻辑,从当前跑动的幅度在回到Idle的这么一个状态。当然这里还考虑了一点就是OnEnter的时候我提取了一个time来接收是否有初始速度,如果有初始速度那么按照初始速度来进行计算。

using UnityEngine;

/// <summary>
/// 跑状态
/// </summary>
public class RoleStateRun : RoleStateAbstract
{

    /// <summary>
    /// 旋转速度
    /// </summary>
    public float m_rotateSpeed = 3f;

    /// <summary>
    /// 移动速度
    /// </summary>
    public float m_moveSpeed = 3f;

    /// <summary>
    /// 移动速度
    /// </summary>
    private float m_MoveRatio = 0f;

    /// <summary>
    /// 转身的目标方向
    /// </summary>
    private Quaternion m_TargetQuaternion;

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="roleFSMMgr">有限状态机管理器</param>
    public RoleStateRun(RoleFSMMgr roleFSMMgr) : base(roleFSMMgr)
    {

    }

    /// <summary>
    /// 实现基类 进入状态
    /// </summary>
    public override void OnEnter()
    {
        base.OnEnter();
        time = CurrRoleFSMMgr.CurrRoleCtrl.m_anim.GetFloat(RoleState.Run.ToString());
    }

    /// <summary>
    /// 缓冲计时
    /// </summary>
    float time = 0;

    /// <summary>
    /// 缓冲速度
    /// </summary>
    float speed = 0;

    /// <summary>
    /// 实现基类 执行状态
    /// </summary>
    public override void OnStay()
    {
        base.OnStay();

        #region 移动方案一  根据方向按键拖动的距离来增加移动速度

        获取方向键拖动比例
        //float radius = CurrRoleFSMMgr.CurrRoleCtrl.m_Joy.radius;
        //float maxradius = CurrRoleFSMMgr.CurrRoleCtrl.m_Joy.maxRadius;
        //m_MoveRatio = radius / maxradius;

        //CurrRoleFSMMgr.CurrRoleCtrl.m_anim.SetFloat(RoleState.Run.ToString(), m_MoveRatio);

        //float hor = CurrRoleFSMMgr.CurrRoleCtrl.m_Joy.Horizontal;
        //float ver = CurrRoleFSMMgr.CurrRoleCtrl.m_Joy.Vertical;

        //Vector3 dir = new Vector3(hor, 0, ver);

        //if (dir != Vector3.zero)
        //{
        //    CurrRoleFSMMgr.CurrRoleCtrl.transform.rotation = Quaternion.Lerp(CurrRoleFSMMgr.CurrRoleCtrl.transform.rotation, Quaternion.LookRotation(dir), Time.deltaTime * m_rotateSpeed);
        //    CurrRoleFSMMgr.CurrRoleCtrl.m_char.Move(CurrRoleFSMMgr.CurrRoleCtrl.transform.forward * Time.deltaTime * m_moveSpeed * m_MoveRatio);
        //}

        #endregion

        #region 方案二  拖动按钮有加速度

        if (time <= 1)
        {
            time += Time.deltaTime;
            speed = m_moveSpeed * time;
            CurrRoleFSMMgr.CurrRoleCtrl.m_anim.SetFloat(RoleState.Run.ToString(), time);
        }

        float hor = CurrRoleFSMMgr.CurrRoleCtrl.m_Joy.Horizontal;
        float ver = CurrRoleFSMMgr.CurrRoleCtrl.m_Joy.Vertical;
        Vector3 dir = new Vector3(hor, 0, ver);

        if (dir != Vector3.zero)
        {
            CurrRoleFSMMgr.CurrRoleCtrl.transform.rotation = Quaternion.Lerp(CurrRoleFSMMgr.CurrRoleCtrl.transform.rotation, Quaternion.LookRotation(dir), Time.deltaTime * m_rotateSpeed);
            CurrRoleFSMMgr.CurrRoleCtrl.m_char.Move(CurrRoleFSMMgr.CurrRoleCtrl.transform.forward * Time.deltaTime * speed);
        }

        #endregion
        //CurrRoleAnimatorStateInfo = CurrRoleFSMMgr.CurrRoleCtrl.m_anim.GetCurrentAnimatorStateInfo(0);
    }

    /// <summary>
    /// 实现基类 离开状态
    /// </summary>
    public override void OnExit()
    {
        base.OnExit();
    }
}

RoleStateRunToIdle状态是从当前跑动的幅度切换回Idle的状态

using UnityEngine;

public class RoleStateRunToIdle : RoleStateAbstract
{

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="roleFSMMgr">有限状态机管理器</param>
    public RoleStateRunToIdle(RoleFSMMgr roleFSMMgr) : base(roleFSMMgr)
    {

    }

    /// <summary>
    /// 实现基类 进入状态
    /// </summary>
    public override void OnEnter()
    {
        base.OnEnter();
        time = CurrRoleFSMMgr.CurrRoleCtrl.m_anim.GetFloat(RoleState.Run.ToString());
        CurrRoleFSMMgr.CurrRoleCtrl.m_anim.SetFloat(RoleState.Run.ToString(), time);
    }

    float time;

    float speed = 0;

    /// <summary>
    /// 实现基类 执行状态
    /// </summary>
    public override void OnStay()
    {
        base.OnStay();
        if (time >= 0)
        {
            time -= Time.deltaTime;
            speed = 3 * time;
            CurrRoleFSMMgr.CurrRoleCtrl.m_anim.SetFloat(RoleState.Run.ToString(), time);
        }

        CurrRoleFSMMgr.CurrRoleCtrl.m_char.Move(CurrRoleFSMMgr.CurrRoleCtrl.transform.forward * Time.deltaTime * speed);
    }

    /// <summary>
    /// 实现基类 离开状态
    /// </summary>
    public override void OnExit()
    {
        base.OnExit();
        //CurrRoleFSMMgr.CurrRoleCtrl.m_anim.SetFloat(RoleState.Run.ToString(), 0f);
    }
}

这样就实现了这一套完整的缓动的FSM动画状态机。

总结

这个FSM其实还是很常用的,包括这里我其实也没有讲的特比深入,只是一个简单的FSM入门。这个还可以考虑动作的播放到什么程度和暂定动画,加速动画等,甚至后面还有HFSM分层管理器,有兴趣的话可以多了解了解,小主后面有时间的话可以在做一个HFSM,上面文章如果有看不懂的可以找到我的资源FSMRoleCtrl.unitypackage。(设置了10积分下载,希望各位可以理解)

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

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

相关文章

文心一言 VS 讯飞星火 VS chatgpt (114)-- 算法导论10.2 7题

七、用go语言&#xff0c;给出一个 O(n)时间的非递归过程&#xff0c;实现对一个含 n个元素的单链表的逆转。要求除存储链表本身所需的空间外&#xff0c;该过程只能使用固定大小的存储空间。 文心一言&#xff1a; 在Go语言中&#xff0c;你可以使用迭代方法来反转一个单链表…

【C++】:string用法详解

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关Linux的基础知识点&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到精通 数…

算法通过村第十四关-堆|黄金笔记|中位数

文章目录 前言数据流中的中位数的问题总结 前言 提示&#xff1a;我独自度过了太多的时光&#xff0c;沉默已成一种习惯。 帕瑞尔马卡姆《夜航西飞》 这个是一个比较难的题目&#xff0c;要不尝试一下看看。 数据流中的中位数的问题 参考题目地址&#xff1a;295. 数据流的中位…

身为程序员,你很有必要了解一下JNPF

一、JNPF是什么&#xff1f; JNPF是一个“低代码”的可视化编程平台&#xff0c;采用J2EE技术体系&#xff0c;基于Spring Boot框架封装的快速开发平台&#xff0c;包含多种开箱即用的功能&#xff0c;具备RBAC、自动生成代码、SaaS多租户等多个功能和模块。 JNPF&#xff0c;重…

真人现在猫鼠躲猫猫游戏搭建流程:专业思考与深度思考

真人现在猫鼠躲猫猫游戏是种充满乐趣和挑战的团队竞技游戏。本文将从游戏规则设计、场地布置、技术实现和用户体验等方面&#xff0c;深入探讨人现在猫鼠躲猫猫游戏的搭建流程&#xff0c;并结合专业思考与深度思考&#xff0c;为游戏搭建提供全面指导。 一、游戏规则设计&…

【uboot】Uboot的启动流程

引言 在驱动岗位上&#xff0c;每一位新员工刚入职期间都需要理解和掌握uboot&#xff0c;但深入的理解代码往往需要耗费大量的时间去反复阅读。本文希望对uboot进行尽可能详细的解析&#xff0c;帮助其他人更快的掌握和理解uboot源码。 准备工作 uboot源码 本文是基于Hi35…

Redux详解(二)

1. 认识Redux Toolkit Redux Toolkit 是官方推荐的编写 Redux 逻辑的方法。 通过传统的redux编写逻辑方式&#xff0c;会造成文件分离过多&#xff0c;逻辑抽离过于繁琐&#xff08;具体可看上篇文章 Redux详解一&#xff09;&#xff0c;React官方为解决这一问题&#xff0c;推…

【设计模式】单例模式、“多例模式”的实现以及对单例的一些思考

文章目录 1.概述2.单例模式实现代码2.1.饿汉式单例2.2.懒汉式单例2.3.双检锁单例2.4.静态内部类单例2.5.枚举单例 3.对单例的一些思考3.1.是否需要严格的禁止单例被破坏&#xff1f;3.2.懒汉式真的比饿汉式更佳吗&#xff1f;3.3.单例存在的问题 4.其他作用范围的单例模式4.1.线…

空调原理与结构、制冷剂类型及相关先进技术

一、空调相关知识 1. 空调定义 空调是指利用各种技术和设备对某一空间内空气的温度、湿度、洁净度和流速进行调节&#xff0c;以满足人们对舒适性要求或不同工艺环境要求。 2. 基本原理 蒸发吸热、冷凝放热 压力越低沸点越低 3. 空调主要结构 空调主要由压缩机、冷凝器、…

求臻医学:MRD的十八般武艺 AI双驱动流派

作为专注于肿瘤精准诊疗领域的国家高科技企业&#xff0c;求臻医学依托《中国肿瘤基因图谱计划》和《肿瘤精准医学大数据平台》项目&#xff0c;围绕肿瘤诊断监测、预后评估、肿瘤早筛、遗传筛查、药物研发服务等场景&#xff0c;开发了针对肺癌、结直肠癌、胃癌、前列腺癌等实…

【Qt高阶】Qt D-Bus 简介【2023.10.16】

Qt D-Bus介绍 简介总线技术名词消息&#xff08;阐述总线的消息内涵&#xff09;服务名对象路径接口备忘表(便于记住名字的格式)调试 麒麟V10 与D-Bus 简介 D-Bus 是一个进程间通信(IPC)和远程过程调用(RPC)机制,最初是为了 Linux 开发,用来取代现有的竞争的 IPC 解决方案,提供…

交通部 EDI是什么?如何处理?

交通部于1996年开始实施《国际集装箱运输电子信息传输和运作系统及示范工程》&#xff0c;即在中国远洋运输集团、上海口岸、宁波口岸、天津口岸和青岛口岸建立 EDI 示范工程。 交通部 EDI 的数据结构 电子口岸或者其他物流企业需要确保能够生成和解析符合交通部要求的EDI数据…

两个pdf合并成一个pdf?

两个pdf合并成一个pdf&#xff1f;pdf合并是我们在处理PDF中非常常见的一个操作。我们看似有很多方法能够实现这一操作&#xff0c;但是真正适合自己的方法确实能够帮助我们很多。那么多方法的话&#xff0c;小编今天打算汇总几个比较适合新手的快速方法&#xff0c;这样效率更…

建立线上线下一体化营销体系,数字化营销系统必不可少

​在当今的市场环境中&#xff0c;实体行业想要取得持续的收入增长&#xff0c;必须将线上线下业务相结合&#xff0c;充分利用数字化营销系统的功能&#xff0c;以构建“全链路式”数字化营销体系。 而数字化营销系统中&#xff0c;常见的如分销系统、拼团系统、分红系统、积分…

mission planner通过串口连接3DR数传,远程飞控

前提 pixhaw2.4.8已布线&#xff0c;有单独的电源供电&#xff0c;通过电量计接power接口 电量计的输入端接24V电源&#xff0c;飞控的输入是5v电源&#xff0c;电量计上有个模块可以分压将5v的电输入到飞控 数传接在接口telem 2上&#xff08;一个接飞控&#xff0c;一个接电…

用浏览器进行web应用测试,你会怎么做?

有没有遇到这样的一个场景&#xff1a;你在使用浏览器进行web应用测试&#xff0c;但是你想知道你在测试过程中的前端输出和后端响应的情况究竟如何。那么&#xff0c;你会怎么做呢&#xff1f;想必大多人会毫不犹豫地回答&#xff1a;通过浏览器console面板和network面板抓取信…

idea使用debug无法启动,使用run可以启动

1、将调试断点清除 使用快捷键ctrl shift F8&#xff0c;将勾选的选项去除即可 2、Error running SampleApplication: Command line is too long. Shorten command line for SampleApplication or also for Spring Boot default configuration&#xff0c;报这种错误&#x…

信号完整性分析基础知识之有损传输线、上升时间衰减和材料特性(五):有损传输线的特性阻抗和信号传输速度

有损传输线的特性阻抗 理想有损传输线特性阻抗是和频率相关的&#xff0c;很复杂。可以有以下公式&#xff1a; 按照代数知识&#xff0c;特性阻抗的实部和虚部如下&#xff1a; 其中RL表示单位长度导体的串联电阻 CL表示单位长度电容 LL单位长度串联环路电感 GL电介质单位长度…

番茄小说推文怎么申请授权?

以下为申请步骤 1.使用“巨量推文” 2.找到番茄小说这个小说app 3.按照要求申请关键词 完成以上步骤即可申请番茄小说推文关键词授权

前端新特性:Compute Pressure API!!!

前几天&#xff0c;review 同事代码的时候发现了一个新的 JS API PressureObserver。 通过一番搜索&#xff0c;发现这个 API 是 Compute Pressure API 的一部分。 Compute Pressure API&#xff1a;https://www.w3.org/TR/compute-pressure/ 它的作用是可以观察 CPU 的变…