【Unity实战笔记】第二十四 · 使用 SMB+Animator 实现基础战斗系统
转载请注明出处:🔗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 Stateprotected 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){// 如果当前状态已经初始化过,则跳过Initiateif (_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 Methodprotected 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 jumpif (_playerInput.jumpInput){StopTimeline(_playerTimeline);animator.Play(PLAYER_STATE_JUMPUP);}// 1.3 combat to fallif (!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!
下一篇见~
相关文章:

【Unity实战笔记】第二十四 · 使用 SMB+Animator 实现基础战斗系统
转载请注明出处:🔗https://blog.csdn.net/weixin_44013533/article/details/146409453 作者:CSDN|Ringleader| 1 结构 1.1 状态机 1.2 SMB 2 代码实现 2.1 核心控制 Player_Base_SMB 继承 StateMachineBehaviour ,控制变量初始…...
C/C++的OpenCV 进行图像梯度提取
使用 C/OpenCV 进行图像梯度提取 图像梯度表示图像中像素强度的变化率和方向。它是图像分析中的一个基本概念,广泛应用于边缘检测、特征提取和物体识别等任务。OpenCV 提供了多种计算图像梯度的函数。本文将介绍几种常用的梯度算子及其在 C/OpenCV 中的实现。 预备…...
Redis 缓存使用的BigKey问题
一、什么是 BigKey? BigKey 指在 Redis 中存储的 单个 Key 对应的 Value 过大,通常表现为: String 类型:Value 长度 > 10KB。Hash/List/Set/ZSet:元素数量 > 5,000 或总大小 > 10MB。 二、BigKey 的危害 问…...

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

软媒魔方——一款集合多种系统辅助组件的软件
停更4年,但依旧吊炸天! 亲们,是不是觉得电脑用久了就像老牛拉车,慢得让人着急?别急,我今天要给大家安利一个超好用的电脑优化神器——软媒魔方! 软件介绍 首先,这货真心是免费的&a…...
Unity场景的加载与卸载
Unity场景的加载与卸载 使用方法:把SceneLoader 脚本代码挂在场景中 使用示例: SceneLoader.Instance.LoadAdditiveScene(8);//通过场景索引加载SceneLoader.Instance.UnloadScene("ShiWaiScene");//通过场景名字卸载脚本代码如下࿱…...

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

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

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

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

代码管理平台Gitlab如何通过快解析实现远程访问?
一、Gitlab功能介绍 Gitlab是被广泛使用的基于git的开源代码管理平台,用于管理、存储开发人员代码,同时可以协同开发 二、外网试用Gitlab遇到的问题 运维人员将Gitlab服务器部署在总部机房,而分公司开发人员和出差运维人员就无法访问Gitlab…...

基于SpringBoot+Vue的足球青训俱乐部管理后台系统的设计与开发
项目背景与概述 随着足球青训行业的快速发展,如何高效、规范地管理学员、教练以及课程等日常工作,成为了青训俱乐部运营的重要课题。为了提升俱乐部的管理效率与用户体验,基于 Spring Boot 和 Vue.js 开发了一个 足球青训俱乐部管理后台系统…...
Redis 是否适合像 MySQL 一样当数据库使用?
Redis 可以在特定场景下作为数据库使用,但与 MySQL 等关系型数据库还是有很大的差异。Redis 确实有持久化功能,开启 AOF 并把 appendfsync 设置为 always 后,它会把每一次数据操作都立刻记录到文件里,相当于每发生一件事就马上记下…...
AI是否会取代人类?浔川问答①
提问者:浔川社团官方联合会 回答者:deepseek 关于AI是否会取代人类的问题,目前科技界和社会学界的主流观点认为:AI会在许多领域显著改变人类的工作和生活方式,但“完全取代人类”的可能性极低。更可能的是人机协作的深…...
JDBC-java操作数据库
1.基本结构: package com.atguigu.servlets;import java.sql.Connection; import java.sql.DriverManager; import java.sql.Statement;public class JDBCemo {public static void main(String[] args) throws Exception{String url "jdbc:mysql:///mysql&qu…...
[原创](现代Delphi 12指南):[macOS 64bit App开发]: 如何获取目录大小?
[作者] 常用网名: 猪头三 出生日期: 1981.XX.XX 企鹅交流: 643439947 个人网站: 80x86汇编小站 编程生涯: 2001年~至今[共24年] 职业生涯: 22年 开发语言: C/C++、80x86ASM、Object Pascal、Objective-C、C#、R、Python、PHP、Perl、 开发工具: Visual Studio、Delphi、XCode、…...

线程调度与单例模式:wait、notify与懒汉模式解析
一.wait 和 notify(等待 和 通知) 引入 wait notify 就是为了能够从应用层面,干预到多个不同线程代码的执行顺序,可以让后执行的线程主动放弃被调度的机会,等先执行的线程完成后通知放弃调度的线程重新执行。 自助取…...

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(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…...
Callable
一、Callable 接口定义 FunctionalInterface public interface Callable<V> {V call() throws Exception; // 返回类型为泛型V,可抛出异常 }二、基本使用步骤 1. 定义 Callable 任务 // 示例1:计算两个数的和 Callable<Integer> sumTask …...
Honeywell 05701-A-0302 单通道控制卡
Honeywell 05701-A-0302 是System 57系列中的单通道控制卡,专为工业气体检测和火灾报警系统设计。该控制卡为系统提供单个检测通道的完整控制解决方案,包括传感器驱动、信号采集和报警管理功能。 主要特性 单通道控制:支持单个气体传感器或火…...

爱普生晶振赋能UWB汽车数字钥匙,解锁未来出行新方式
随着科技的发展,尤其是国产新能源汽车的崛起,相信大家对数字钥匙的概念已经不陌生了,通过手机、智能穿戴实现对汽车的多功能控制已经是很多汽车的标配。但是目前数字钥匙也有一定的局限性,比如定位不准、安全性不强等等࿰…...

电子电路:深入理解电磁耦合的定义与应用
电场和磁场是独立存在的吗?,但实际上根据麦克斯韦理论,它们是同一现象的两个方面,通过变化相互产生。这时候需要强调时变场的重要性,以及静态场和动态场的区别。 通过电磁波的概念,说明电磁耦合如何导致电…...

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

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

微服务项目->在线oj系统(Java版 - 4)
相信自己,终会成功 目录 B端用户管理 C端用户代码 发送验证码: 验证验证码 退出登录 登录用户信息功能 用户详情与用户编辑 用户竞赛接口 用户报名竞赛 用户竞赛报名接口查询 用户信息列表 ThreadLocalUtil Hutool工具库 常用功能介绍 B端用户管理 进行列表显示与…...

ReAct 与 CoAct:AI 代理的推理与行动之旅
引言 能推理又能行动的 AI 代理,是朝着构建更自主系统迈出的重要一步。传统上,语言模型在“思维链”提示方面表现得很出色,也就是通过文本逐步思考问题来解决像算术、常识问题或符号推理这类任务。但用思维链时,模型只依赖自身的…...
电子电路:什么是滤波器,什么优势高通滤波器?
滤波器在信号处理中的基本类型有哪些?通常可能包括低通、高通、带通、带阻等。每种滤波器根据频率的不同来允许或阻止信号通过。高通滤波器就是其中一种,允许高频通过,衰减低频。 滤波器的实现方式,分为模拟和数字两种。模拟滤波器使用电阻、电容、电感等元件,而数字滤波…...

uni-app使用大集
1、手动修改页面标题 uni.setNavigationBarTitle({title: 修改标题 }); 2、单选 不止有 radio-group,还有 uni-data-checkbox 数据选择器 <!-- html部分 --> <uni-data-checkbox v-model"sex" :localdata"checkboxList"></u…...
[Spring Boot]整合Java Mail实现Outlook发送邮件
日常开发过程中,我们经常需要使用到邮件发送任务,比方说验证码的发送、日常信息的通知等。日常比较常用的邮件发送方包括:163、QQ等,本文主要讲解Outlook SMTP的开启方式、OutLook STARTTTL的配置、如何通过JavaMail来实现电子邮件的发送等。 Outlook作为微软提供的企业电子…...