【Unity实战笔记】第二十四 · 使用 SMB+Animator 实现基础战斗系统

news2025/5/24 8:47:06

转载请注明出处:🔗https://blog.csdn.net/weixin_44013533/article/details/146409453
作者:CSDN@|Ringleader|

1 结构

1.1 状态机

在这里插入图片描述

1.2 SMB

在这里插入图片描述
在这里插入图片描述

2 代码实现

2.1 核心控制

Player_Base_SMB 继承 StateMachineBehaviour ,控制变量初始化,以及OnStateUpdate每帧控制状态切换和逻辑处理。
具体 SwitchState DoStateJob交由继承的SMB来实现。

public class Player_Base_SMB : StateMachineBehaviour
{
    protected static int PLAYER_STATE_IDLE = Animator.StringToHash("Idle");
    protected static int PLAYER_STATE_RUN = Animator.StringToHash("Run");
    protected static int PLAYER_STATE_JUMPUP = Animator.StringToHash("JumpUp");
    protected static int PLAYER_STATE_FALL = Animator.StringToHash("Fall");
    protected static int PLAYER_STATE_LAND = Animator.StringToHash("Land");
    
    // Combat State
    protected static int PLAYER_COMBAT_IDLE = Animator.StringToHash("Combat_Idle");
    protected static int PLAYER_COMBAT_BAREHANDS_COMBO1 = Animator.StringToHash("Combat_BareHands_Combo1");
    protected static int PLAYER_COMBAT_BAREHANDS_COMBO2 = Animator.StringToHash("Combat_BareHands_Combo2");
    protected static int PLAYER_COMBAT_BAREHANDS_COMBO4 = Animator.StringToHash("Combat_BareHands_Combo4");

    public string StateName;

    protected PlayerInput _playerInput;
    protected PlayerController _playerController;
    protected Transform _playerTransform;
    protected Transform _camTransform;
    protected Rigidbody _playerRig;
    protected PlayableDirector _playerTimeline;

    [Tooltip("在project中右键添加对应SO,并在状态机状态中添加SO,那样运行时就可在SO中调整参数")]
    public Player_State_SO playerStateSo;

    protected bool isOnGround() => _playerController.isOnGround();

    protected bool AnimationPlayFinished(AnimatorStateInfo stateInfo)
    {
        return stateInfo.normalizedTime >= 1.0f;
    }

    // 只进行一次的初始化
    private void Initiate(Animator animator)
    {
        // 如果当前状态已经初始化过,则跳过Initiate
        if (_playerInput != null && _playerRig != null)
        {
            return;
        }

        _playerInput = animator.GetComponent<PlayerInput>();
        _playerController = animator.GetComponent<PlayerController>();
        _playerTransform = _playerController.transform;
        _playerRig = animator.GetComponent<Rigidbody>();
        _camTransform = Camera.main.transform;
        _playerTimeline = _playerController.playerTimeline;

        // 注意log用到了PlayerController logEnable之类参数,使用Log方法前要确保依赖类已经初始化
        LogStateAndMethod(StateName, "StateInitiation");
    }

    public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        Initiate(animator);
        // 注意log用到了PlayerController logEnable之类参数,使用Log方法前要确保依赖类已经初始化
        LogStateAndMethod(StateName, "OnStateEnter");
    }

    public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        SwitchState(animator, stateInfo, layerIndex);
        DoStateJob(animator, stateInfo, layerIndex);
    }

    protected virtual void DoStateJob(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        LogStateAndUpdateMethod(StateName, "OnStateUpdate-DoStateJob");
    }

    protected virtual void SwitchState(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        LogStateAndUpdateMethod(StateName, "OnStateUpdate-SwitchState");
    }

    public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        LogStateAndMethod(StateName, "OnStateExit");
    }

    protected void DoMoveInPhysics()
    {
        if (_playerInput.moveInput != Vector2.zero)
        {
            Vector3 moveInput = new Vector3(_playerInput.moveInput.x, 0, _playerInput.moveInput.y);
            // slopeNormal用于计算地面坡度
            var slopeNormal = _playerController.slopeNormal();
            // 相对主摄的移动(注意最后需要投影到水平面,否则会有上下位移导致镜头波动)
            Vector3 _camMoveWithSlope = Vector3.ProjectOnPlane(_camTransform.TransformDirection(moveInput).normalized,
                slopeNormal != Vector3.zero ? slopeNormal : Vector3.up);
            Vector3 _camMoveWithoutSlope =
                Vector3.ProjectOnPlane(_camTransform.TransformDirection(moveInput).normalized, Vector3.up);
            // 转向
            _playerRig.MoveRotation(Quaternion.RotateTowards(_playerTransform.rotation,
                Quaternion.LookRotation(_camMoveWithoutSlope), playerStateSo.rotateSpeed));
            // 移动
            _playerRig.MovePosition(_playerRig.position +
                                    _camMoveWithSlope * playerStateSo.runSpeed * Time.fixedDeltaTime);
        }
    }

    protected void SetVelocityY(float y)
    {
        var velocity = _playerRig.linearVelocity;
        velocity = new Vector3(velocity.x, y, velocity.z);
        _playerRig.linearVelocity = velocity;
    }

    protected void SoundPlayRandom(AudioClip[] clips, float minPitch, float maxPitch, float minVolume, float maxVolume,
        bool loop = false)
    {
        if (clips.Length == 0)
        {
            LogDebug($"请检查{StateName}状态的audio音效是否添加");
            return;
        }

        _playerController.PlayRandomSound(clips, minPitch, maxPitch, minVolume, maxVolume, loop);
    }

    protected void StopSound()
    {
        _playerController.StopSound();
    }

    #region Log Method

    protected void LogDebug(string str)
    {
        if (_playerController.logEnable)
        {
            Debug.Log(str + " Current frame:" + Time.frameCount);
        }
    }

    protected void LogStateAndMethod(string StateName, string methodName)
    {
        LogDebug($"Current state: {StateName}, Current method execute : {methodName};\r\n");
    }

    protected void LogStateAndUpdateMethod(string StateName, string methodName)
    {
        if (_playerController.stateUpdateLogEnable)
        {
            Debug.Log($"Current state: {StateName}, Current method execute : {methodName};\r\n" + " Current frame:" +
                      Time.frameCount);
        }
    }

    #endregion
}

2.2 combat状态 统一父类+combat状态入口&出口

出口就是Interrupt方法,决定何时中断当前状态。
父类主要做通用技能中断,比如被 移动、跳跃、坠落等状态中断的情况。
技能衔接如combo之类交由子类SMB控制。

public class SMB_Combat_Base : Player_Base_SMB
{
    protected float interruptNormalizedTime = 0.8f;//todo 待细化
    protected bool canInterrup = true;
    public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        base.OnStateEnter(animator, stateInfo, layerIndex);
        // combat 采用 root motion,动作产生位移
        if (!animator.applyRootMotion)
        {
            animator.applyRootMotion = true;//必须加,就算有OnStateMove也要开启。
        }
    }

    protected override void SwitchState(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        base.SwitchState(animator, stateInfo, layerIndex);
        Interrupt(animator);
    }

    protected void Interrupt(Animator animator)
    {
        // 如果攻击正在播放,无法中断,攻击收尾阶段方可中断(当然中断还可以细化,产生优先级,最高优先级甚至可以无视当前出手)
        if (!canInterrup) return;
        // any state transform
        // 1.any combat state  → movement state 
        // 1.1 combat to Run(copy from movement Idle的状态转换)
        if (_playerInput.moveInput != Vector2.zero)
        {
            StopTimeline(_playerTimeline);
            animator.Play(PLAYER_STATE_RUN);
        }

        // 1.2 combat to jump
        if (_playerInput.jumpInput)
        {
            StopTimeline(_playerTimeline);
            animator.Play(PLAYER_STATE_JUMPUP);
        }

        // 1.3 combat to fall
        if (!isOnGround())
        {
            StopTimeline(_playerTimeline);
            animator.CrossFade(PLAYER_STATE_FALL, playerStateSo.idle_to_fall_duration);
        }
    }
    private void StopTimeline(PlayableDirector timeline)
    {
        Debug.Log("timeline.duration="+timeline.duration);
        // timeline.Stop();
    }
}

2.3 具体状态SMB

2.3.1 战斗待机
// 战斗待机
public class SMB_Combat_Idle : SMB_Combat_Base
{
    protected override void SwitchState(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        base.SwitchState(animator, stateInfo, layerIndex);
        // 攻击
        if (_playerInput.fireInput)
        {
            animator.Play(PLAYER_COMBAT_BAREHANDS_COMBO1);
        }

        // 脱离战斗,这里简化为战斗待机播两次后回答基础待机(实战时可能条件很复杂,比如主动收刀、周围无敌人、未受到伤害等)
        if (stateInfo.normalizedTime > 1f)
        {
            animator.Play(PLAYER_STATE_IDLE);
        }
    }
}
2.3.2 空手连接1
// 空手连击1
public class SMB_Combat_Barehands_Combo1 : SMB_Combat_Base
{
    private Vector3 moveInput;
    private Vector3 _camMoveWithoutSlope;
    private Vector3 _camMoveWithSlope;
    private bool canMoveBeforeAttack = false;
    public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        base.OnStateEnter(animator, stateInfo, layerIndex);
        canInterrup = false;
    }

    private void StopTimeline(PlayableDirector timeline,string reason)
    {
        Debug.Log("for reason:"+reason+",timeline.duration="+timeline.duration);
        timeline.Stop();
    }
    protected override void SwitchState(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        base.SwitchState(animator, stateInfo, layerIndex);
        if (animator.IsInTransition(layerIndex))
        {
            return;
        }
        // 播放结束回到战斗待机状态
        if (stateInfo.normalizedTime >= 1f)
        {
            StopTimeline(_playerTimeline,"播放结束回到战斗待机状态");
            animator.Play(PLAYER_COMBAT_IDLE);
        }
        else if (stateInfo.normalizedTime >= 0.4f)
        {
            // 慢击
            if (_playerInput.fireInput)
            {
                StopTimeline(_playerTimeline,"慢击");
                animator.Play(PLAYER_COMBAT_BAREHANDS_COMBO1,layerIndex,0f);
            }
        }
        else if (stateInfo.normalizedTime is > 0.3f and < 0.4f)
        {
            // 连击
            if (_playerInput.fireInput)
            {
                StopTimeline(_playerTimeline,"连击");
                animator.CrossFade(PLAYER_COMBAT_BAREHANDS_COMBO2,0.1f);
            }
        }
        // 前摇时接收最后的转向
        if (stateInfo.normalizedTime <= 0.2f)
        {
            if (_playerInput.moveInput != Vector2.zero)
            {
                canMoveBeforeAttack = true;
                moveInput = new Vector3(_playerInput.moveInput.x, 0, _playerInput.moveInput.y);
                // slopeNormal用于计算地面坡度
                var slopeNormal = _playerController.slopeNormal();
                // 相对主摄的移动(注意最后需要投影到水平面,否则会有上下位移导致镜头波动)
                _camMoveWithSlope = Vector3.ProjectOnPlane(_camTransform.TransformDirection(moveInput).normalized,
                    slopeNormal != Vector3.zero ? slopeNormal : Vector3.up);
                _camMoveWithoutSlope =
                    Vector3.ProjectOnPlane(_camTransform.TransformDirection(moveInput).normalized, Vector3.up);

            }
        }
        // 设定攻击打断条件,一般与连招断续窗口一致,即攻击打实后准备收招那一刻(后摇开始时)
        if (stateInfo.normalizedTime >= 0.4f)
        {
            canInterrup = true;
        }
    }

    // 执行转向和移动
    public override void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        base.OnStateMove(animator, stateInfo, layerIndex);
        // animator.bodyPosition += animator.deltaPosition;//有问题,没有成功执行
        // _playerRig.linearVelocity = animator.velocity;//移动跳跃异常,转向正常
        _playerRig.MovePosition(_playerRig.position+animator.deltaPosition);//完美!
        if (canMoveBeforeAttack && stateInfo.normalizedTime > 0.2f)
        {
            canMoveBeforeAttack = false;
            // 转向
            _playerRig.MoveRotation(Quaternion.LookRotation(_camMoveWithSlope));
            // 移动(可以添加索敌吸附功能)
            _playerRig.MovePosition(_playerRig.position + _camMoveWithSlope * 5);
        }
    }
}
2.3.3 空手连击2
// 空手连击2
public class SMB_Combat_Barehands_Combo2 : SMB_Combat_Base
{
    private Vector3 moveInput;
    private Vector3 _camMoveWithoutSlope;
    private Vector3 _camMoveWithSlope;
    private bool canMoveBeforeAttack = false;
    public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        base.OnStateEnter(animator, stateInfo, layerIndex);
        canInterrup = false;
    }

    protected override void SwitchState(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        base.SwitchState(animator, stateInfo, layerIndex);
        if (animator.IsInTransition(layerIndex))
        {
            return;
        }
        // 播放结束回到战斗待机状态
        if (stateInfo.normalizedTime >= 1f)
        {
            animator.Play(PLAYER_COMBAT_IDLE);
        }
        else if (stateInfo.normalizedTime >= 0.3f)
        {
            // 慢击
            if (_playerInput.fireInput)
            {
                animator.Play(PLAYER_COMBAT_BAREHANDS_COMBO1);
            }
        }
        else if (stateInfo.normalizedTime is > 0.2f and < 0.3f)
        {
            // 连击
            if (_playerInput.fireInput)
            {
                animator.CrossFade(PLAYER_COMBAT_BAREHANDS_COMBO4,0.1f);
            }
        }
        // 前摇时接收最后的转向
        if (stateInfo.normalizedTime <= 0.1f)
        {
            if (_playerInput.moveInput != Vector2.zero)
            {
                canMoveBeforeAttack = true;
                moveInput = new Vector3(_playerInput.moveInput.x, 0, _playerInput.moveInput.y);
                // slopeNormal用于计算地面坡度
                var slopeNormal = _playerController.slopeNormal();
                // 相对主摄的移动(注意最后需要投影到水平面,否则会有上下位移导致镜头波动)
                _camMoveWithSlope = Vector3.ProjectOnPlane(_camTransform.TransformDirection(moveInput).normalized,
                    slopeNormal != Vector3.zero ? slopeNormal : Vector3.up);
                _camMoveWithoutSlope =
                    Vector3.ProjectOnPlane(_camTransform.TransformDirection(moveInput).normalized, Vector3.up);

            }
        }
        // 设定攻击打断条件,一般与连招断续窗口一致,即攻击打实后准备收招那一刻(后摇开始时)
        if (stateInfo.normalizedTime >= 0.3f)
        {
            canInterrup = true;
        }
    }
    // 执行转向和移动
    public override void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        base.OnStateMove(animator, stateInfo, layerIndex);
        // animator.bodyPosition += animator.deltaPosition;//有问题,没有成功执行
        // _playerRig.linearVelocity = animator.velocity;//移动跳跃异常,转向正常
        _playerRig.MovePosition(_playerRig.position+animator.deltaPosition);//完美!
        if (canMoveBeforeAttack && stateInfo.normalizedTime > 0.1f)
        {
            canMoveBeforeAttack = false;
            // 转向
            _playerRig.MoveRotation(Quaternion.LookRotation(_camMoveWithSlope));
        }
    }
}
2.3.4 空手连击3

(我取的动画是combo4)

// 空手连击4
public class SMB_Combat_Barehands_Combo4 : SMB_Combat_Base
{
    private Vector3 moveInput;
    private Vector3 _camMoveWithoutSlope;
    private Vector3 _camMoveWithSlope;
    private bool canMoveBeforeAttack = false;
    public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        base.OnStateEnter(animator, stateInfo, layerIndex);
        canInterrup = false;
    }
    protected override void SwitchState(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        base.SwitchState(animator, stateInfo, layerIndex);
        // 播放结束回到战斗待机状态
        if (stateInfo.normalizedTime >= 1f)
        {
            animator.Play(PLAYER_COMBAT_IDLE);
        }
        else if (stateInfo.normalizedTime >= 0.4f)
        {
            // 慢击
            if (_playerInput.fireInput)
            {
                animator.Play(PLAYER_COMBAT_BAREHANDS_COMBO1);
            }
        }
        // 前摇时接收最后的转向
        if (stateInfo.normalizedTime <= 0.2f)
        {
            if (_playerInput.moveInput != Vector2.zero)
            {
                canMoveBeforeAttack = true;
                moveInput = new Vector3(_playerInput.moveInput.x, 0, _playerInput.moveInput.y);
                // slopeNormal用于计算地面坡度
                var slopeNormal = _playerController.slopeNormal();
                // 相对主摄的移动(注意最后需要投影到水平面,否则会有上下位移导致镜头波动)
                _camMoveWithSlope = Vector3.ProjectOnPlane(_camTransform.TransformDirection(moveInput).normalized,
                    slopeNormal != Vector3.zero ? slopeNormal : Vector3.up);
                _camMoveWithoutSlope =
                    Vector3.ProjectOnPlane(_camTransform.TransformDirection(moveInput).normalized, Vector3.up);

            }
        }
        
        // 设定攻击打断条件,一般与连招断续窗口一致,即攻击打实后准备收招那一刻(后摇开始时)
        if (stateInfo.normalizedTime >= 0.4f)
        {
            canInterrup = true;
        }
    }
    // 执行转向和移动
    public override void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        base.OnStateMove(animator, stateInfo, layerIndex);
        // animator.bodyPosition += animator.deltaPosition;//有问题,没有成功执行
        // _playerRig.linearVelocity = animator.velocity;//移动跳跃异常,转向正常
        _playerRig.MovePosition(_playerRig.position+animator.deltaPosition);//完美!
        if (canMoveBeforeAttack && stateInfo.normalizedTime > 0.2f)
        {
            canMoveBeforeAttack = false;
            // 转向
            _playerRig.MoveRotation(Quaternion.LookRotation(_camMoveWithSlope));
        }
    }
}

2.4 从移动向战斗状态的切换

这里省略了移动相关SMB,这里只取移动的基类,控制 any movement state → 攻击

// 移动状态基类,主要用于anystate转换
public class SMB_Movement_Base : Player_Base_SMB
{
    public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        base.OnStateEnter(animator, stateInfo, layerIndex);
        // movement 不采用 root motion,由开发者控制位移
        if (animator.applyRootMotion)
        {
            animator.applyRootMotion = false;
        }
        
    }
    protected override void SwitchState(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        base.SwitchState(animator, stateInfo, layerIndex);
        // any state transform
        
        // any movement state → 攻击
        if (_playerInput.fireInput)
        {
            animator.Play(PLAYER_COMBAT_BAREHANDS_COMBO1);
        }
    }
}

3 最终效果展示

在这里插入图片描述

4 遇到的问题及解决

4.1 角色原地攻击、攻击完毕后瞬移

添加连击状态后,发现角色只能原地运动。
在这里插入图片描述
于是可以考虑将攻击动画的RootTransform Position xz 分量 bake into pose,但会发现角色攻击完毕后会瞬移回模型原点。
在这里插入图片描述
在这里插入图片描述
在我第十一篇文章《动画基础》7.6.2 节中曾详细比较了humanoid动画 root motion和bake into pose的情况:
在这里插入图片描述
从上面可以知道,要想模型父节点跟随模型必须应用 root motion
在这里插入图片描述
而基础移动(movement)又需要交给程序精确控制,如果不使用OnAnimatorMove()(rootMotion handle by script),可以这样分开处理:

  • 在进入movement 的状态,animator.applyRootMotion = false;
  • 进入combat 的状态,animator.applyRootMotion = true;

这样攻击就能正常位移了(注意攻击动画的RootTransform Position xz 分量不要 bake into pose)
在这里插入图片描述

4.2 连击动画父节点瞬移

但如果做连击动画
在这里插入图片描述
会发现第二、三段攻击父节点发生位移。
在这里插入图片描述
原因就是原动画第二三段模型就是偏离模型空间原点。
在这里插入图片描述
解决办法就是将动画的RootTransform Position xz 选择based upon Center of Mass
在这里插入图片描述
最终效果:
在这里插入图片描述

4.3 同时移动和攻击会产生鬼畜

在这里插入图片描述

anystate transform的问题,移动时攻击就会瞬间高速循环切换状态:移动→combat→移动

在这里插入图片描述
move combat状态瞬时切换  

解决方法就是让攻击动画不能无条件随时打断,至少播放到40%才能打断(技能打断会开新专题)

protected bool canInterrup = true;

OnStateEnter(){
	...
	canInterrup = false;
	...
}

switchState(Animator animator, AnimatorStateInfo stateInfo, int layerIndex){
	...
	if (stateInfo.normalizedTime >= 0.4f)
	   {
	        // 攻击产生后摇后方可打断
	        canInterrup = true;
	    }
	if (canInterrup) {
		// movement 相关输入判断
	}
	...
}

4.4 状态无法自转移(无法重播当前动画)

假设有攻击1、攻击2、攻击3,这三段连续的攻击动画,当播放到20%~40%按下攻击,便可触发连击,40%后再按攻击键便回到攻击1播放。

现在的问题是,animator.Play(“stateName”) 的状态是自身的话,也就是慢速连按攻击键,攻击1动画无法再次触发,状态也没有切换(exit state 然后再enter state)。

if (stateInfo.normalizedTime >= 0.4f)
        {
            // 慢击
            if (_playerInput.fireInput)
            {
                animator.Play(PLAYER_COMBAT_BAREHANDS_COMBO1);
            }
        }

可能是animator.Play方法优化的原因,不指定方法的normalizedTime参数,当前stateName与待播放的stateName相同,则不触发状态切换。

解决方法就是指定normalizedTime参数。

animator.Play(PLAYER_COMBAT_BAREHANDS_COMBO1,layerIndex,normalizedTime: 0f);

4.5 连击切换死板,加过渡后产生鬼畜

如果连击动画交接处差异过大,会明显感觉到跳切,于是可以考虑加上过渡

animator.CrossFade(PLAYER_COMBAT_BAREHANDS_COMBO4,0.1f);

但快速连点攻击时,会发现鬼畜/慢动作,原因在于,过渡过程两个状态的update其实都是进行中的,过渡过程如果也按下攻击,便会反复触发这个转换过程,便是鬼畜。

在这里插入图片描述
需要在前者状态切换添加过滤

SwitchState(Animator animator, AnimatorStateInfo stateInfo, int layerIndex){
	...
	if (animator.IsInTransition(layerIndex))
	{
	     return;
	}
	...
}

这一点在我 【Unity实战笔记】第二十二 跳跃到fall有突然前移现象 小节有提到
在这里插入图片描述

5 总结

本文使用SMB+Animator 实现了基础战斗系统,但可以看到技能衔接是通过 stateInfo.normalizedTime判断的,这种方式不够完美:

  • 第一,基于时间的控制不够准确,调整起来也很麻烦
  • 第二,当需要中断的条件变多变复杂时,这种if else判断出现bug的可能会越来越大
  • 第三,如果要添加匹配音效、特效,以及更复杂的需求,这种技能编辑方式就捉襟见肘了

所以我希望有一种更直观的方式去编辑技能,且基于帧的控制,除了能编辑动画,还能配置音效、粒子特效等。

这就是Timeline!

下一篇见~

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

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

相关文章

【Java高阶面经:消息队列篇】22、消息队列核心应用:高并发场景下的解耦、异步与削峰

一、消息队列:分布式系统的核心枢纽 在分布式架构日益普及的今天,消息队列(Message Queue, MQ)已成为解决系统复杂性的核心组件。它通过异步通信、系统解耦和流量控制等能力,有效应对高并发场景下的数据流动挑战。 1.1 核心特性:异步、解耦与弹性 1.1.1 异步通信:释放…

软媒魔方——一款集合多种系统辅助组件的软件

停更4年&#xff0c;但依旧吊炸天&#xff01; 亲们&#xff0c;是不是觉得电脑用久了就像老牛拉车&#xff0c;慢得让人着急&#xff1f;别急&#xff0c;我今天要给大家安利一个超好用的电脑优化神器——软媒魔方&#xff01; 软件介绍 首先&#xff0c;这货真心是免费的&a…

多路径可靠传输协议(比如 MPTCP)为什么低效

可靠就不能多路径&#xff0c;多路径求可靠必然要多费劲。这不难理解&#xff0c;多路径必异步&#xff0c;这无疑增加了可靠性判断的难度。 前文 多路径传输(比如 MPTCP)对性能的意义 阐述了作为单连接的多子流 MPTCP 对传输性能的意义是无意义&#xff0c;本文接着阐述作为隧…

塔能高温冰蓄冷技术:工厂能耗精准节能的创新之路

在工厂的能耗构成中&#xff0c;制冷系统是重要的耗能环节。传统的水蓄冷和冰蓄冷技术在实际应用中存在一些局限性&#xff0c;难以满足工厂对节能和成本控制的更高要求。塔能科技的高温冰蓄冷技术&#xff0c;凭借其独特的优势&#xff0c;为工厂能耗精准节能提供了创新的解决…

内存优化笔记1

欢迎关注更多精彩 关注我&#xff0c;学习常用算法与数据结构&#xff0c;一题多解&#xff0c;降维打击。 问题提出 在很多工业软件中&#xff0c;需要对对象进行微分细化&#xff0c;这样会产生很多&#xff08;几百万到几千万&#xff09;对象。随着业务的发展&#xff0c…

人脸识别,使用 deepface + api + flask, 改写 + 调试

1. 起因&#xff0c; 目的&#xff0c; 感受: github deepface 这个项目写的很好&#xff0c; 继续研究使用这个项目&#xff0c;改写 api。增加一个前端 flask app 2. 先看效果 3. 过程: 大力改写原始项目中 api 这部分的代码&#xff0c; 原始项目的文件结构太繁杂了: 我把…

代码管理平台Gitlab如何通过快解析实现远程访问?

一、Gitlab功能介绍 Gitlab是被广泛使用的基于git的开源代码管理平台&#xff0c;用于管理、存储开发人员代码&#xff0c;同时可以协同开发 二、外网试用Gitlab遇到的问题 运维人员将Gitlab服务器部署在总部机房&#xff0c;而分公司开发人员和出差运维人员就无法访问Gitlab…

基于SpringBoot+Vue的足球青训俱乐部管理后台系统的设计与开发

项目背景与概述 随着足球青训行业的快速发展&#xff0c;如何高效、规范地管理学员、教练以及课程等日常工作&#xff0c;成为了青训俱乐部运营的重要课题。为了提升俱乐部的管理效率与用户体验&#xff0c;基于 Spring Boot 和 Vue.js 开发了一个 足球青训俱乐部管理后台系统…

线程调度与单例模式:wait、notify与懒汉模式解析

一.wait 和 notify&#xff08;等待 和 通知&#xff09; 引入 wait notify 就是为了能够从应用层面&#xff0c;干预到多个不同线程代码的执行顺序&#xff0c;可以让后执行的线程主动放弃被调度的机会&#xff0c;等先执行的线程完成后通知放弃调度的线程重新执行。 自助取…

MySQL中TCP和套接字SSL加密连接行为分析

目录 一、前言 二、背景 三、参数介绍 3.1、 have_openssl 3.2、have_ssl 3.3、require_secure_transport 四、--ssl-modemode 五、CREATE USER SSL/TLS选项 六、问题验证 6.1、使用套接字连接 6.2、使用TCP连接 七、分析与总结 一、前言 SSL&#xff08;Secure S…

php本地 curl 请求证书问题解决

错误: cURL error 60: SSL certificate problem: unable to get local issuer certificate (see https://curl.haxx.se/libcurl/c/libcurl-errors.html) for 解决方案 在php目录下创建证书文件夹, 执行下面生成命令, 然后在php.ini 文件中配置证书路径; 重启环境 curl --eta…

爱普生晶振赋能UWB汽车数字钥匙,解锁未来出行新方式

随着科技的发展&#xff0c;尤其是国产新能源汽车的崛起&#xff0c;相信大家对数字钥匙的概念已经不陌生了&#xff0c;通过手机、智能穿戴实现对汽车的多功能控制已经是很多汽车的标配。但是目前数字钥匙也有一定的局限性&#xff0c;比如定位不准、安全性不强等等&#xff0…

电子电路:深入理解电磁耦合的定义与应用

电场和磁场是独立存在的吗&#xff1f;&#xff0c;但实际上根据麦克斯韦理论&#xff0c;它们是同一现象的两个方面&#xff0c;通过变化相互产生。这时候需要强调时变场的重要性&#xff0c;以及静态场和动态场的区别。 通过电磁波的概念&#xff0c;说明电磁耦合如何导致电…

宝塔安装的 MySQL 无法连接的情况及解决方案

宝塔安装的 MySQL 无法连接的情况及解决方案 宝塔面板是一款流行的服务器管理工具&#xff0c;其中集成的 MySQL 数据库有时会出现连接问题。本文详细介绍两种最常见的 MySQL 连接错误&#xff1a;“1130 - Host is not allowed to connect” 和 “1045 - Access denied”&…

今日行情明日机会——20250523

上证指数缩量下跌&#xff0c;个股下跌超过4000个&#xff0c;总体跌多涨少&#xff0c;日线总体处于高位&#xff0c;注意风险。 深证60分钟级别下跌趋势线压制&#xff0c;总体日线转下跌的概率大&#xff0c;注意风险。 2025年5月23日涨停股主要行业方向分析 一、核心主…

微服务项目->在线oj系统(Java版 - 4)

相信自己,终会成功 目录 B端用户管理 C端用户代码 发送验证码: 验证验证码 退出登录 登录用户信息功能 用户详情与用户编辑 用户竞赛接口 用户报名竞赛 用户竞赛报名接口查询 用户信息列表 ThreadLocalUtil Hutool工具库 常用功能介绍 B端用户管理 进行列表显示与…

ReAct 与 CoAct:AI 代理的推理与行动之旅

引言 能推理又能行动的 AI 代理&#xff0c;是朝着构建更自主系统迈出的重要一步。传统上&#xff0c;语言模型在“思维链”提示方面表现得很出色&#xff0c;也就是通过文本逐步思考问题来解决像算术、常识问题或符号推理这类任务。但用思维链时&#xff0c;模型只依赖自身的…

uni-app使用大集

1、手动修改页面标题 uni.setNavigationBarTitle({title: 修改标题 }); 2、单选 不止有 radio-group&#xff0c;还有 uni-data-checkbox 数据选择器 <!-- html部分 --> <uni-data-checkbox v-model"sex" :localdata"checkboxList"></u…

零件剖切配置教学 | 玩转EasyTwin 工业产线第三期(上)课程回顾

-在工业数字孪生领域&#xff0c;工程施工模拟、车间产线运转、机械装置和零件配置等交互效果的呈现至关重要。通过EasyTwin&#xff0c;我们能够以更低成本、更高效率来构建数字孪生场景&#xff0c;但在搭建的过程中&#xff0c;也会因为复杂的场景交互配置产生一些疑问。该案…

onnx模型转入rknn3399平台上工作记录

1.rknn虚拟环境使用时报错问题 使用rknn17环境的报错&#xff1a; ImportError: libdc1394.so.22: cannot open shared object file: No such file or directory 参考链接&#xff1a;https://blog.csdn.net/2301_80032564/article/details/142316410 创作软连接&#xff1a; …