【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!

下一篇见~

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

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

相关文章

Python虚拟环境再PyCharm中自由切换使用方法

Python开发中的环境隔离是必不可少的步骤,通过使用虚拟环境可以有效地管理不同项目间的依赖,避免包冲突和环境污染。虚拟环境是Python官方提供的一种独立运行环境,每个项目可以拥有自己单独的环境,不同项目之间的环境互不影响。在日常开发中,结合PyCharm这样强大的IDE进行…

大模型智能体入门扫盲——基于camel的概述

前言 本篇博客想带读者进行一个智能体入门扫盲&#xff0c;了解基础知识&#xff0c;为什么用camel呢&#xff0c;因为小洛发现它们文档对这种智能体的基本组件介绍得很全面深入。 基础概念 agent 一个典型的agent智能体包含三个核心部分&#xff1a; 感知模块&#xff1…

目标检测 RT-DETR(2023)详细解读

文章目录 主干网络&#xff1a;Encoder&#xff1a;不确定性最小Query选择Decoder网络&#xff1a; 将DETR扩展到实时场景&#xff0c;提高了模型的检测速度。网络架构分为三部分组成&#xff1a;主干网络、混合编码器、带有辅助预测头的变换器编码器。具体来说&#xff0c;先利…

DeepSeek 赋能数字农业:从智慧种植到产业升级的全链条革新

目录 一、数字农业的现状与挑战二、DeepSeek 技术解析2.1 DeepSeek 的技术原理与优势2.2 DeepSeek 在人工智能领域的地位与影响力 三、DeepSeek 在数字农业中的应用场景3.1 精准种植决策3.2 病虫害监测与防治3.3 智能灌溉与施肥管理3.4 农产品质量追溯与品牌建设 四、DeepSeek …

<uniapp><vuex><状态管理>在uniapp中,如何使用vuex实现数据共享与传递?

前言 本专栏是基于uniapp实现手机端各种小功能的程序&#xff0c;并且基于各种通讯协议如http、websocekt等&#xff0c;实现手机端作为客户端&#xff08;或者是手持机、PDA等&#xff09;&#xff0c;与服务端进行数据通讯的实例开发。 发文平台 CSDN 环境配置 系统&…

高速串行差分信号仿真分析及技术发展挑战续

7.3 3.125Gbps 差分串行信号设计实例仿真分析 7.3.1 设计用例说明 介绍完 Cadence 系统本身所具有的高速差分信号的仿真分析功能之后&#xff0c;我们以一个实例来说明 3.125Gbps 以下的高速差分系统的仿真分析方法。 在网上下载的设计文件“Booksi_Demo_Allegro160_Finishe…

【Golang】部分语法格式和规则

1、时间字符串和时间戳的相互转换 func main() {t1 : int64(1546926630) // 外部传入的时间戳&#xff08;秒为单位&#xff09;&#xff0c;必须为int64类型t2 : "2019-01-08 13:50:30" // 外部传入的时间字符串//时间转换的模板&#xff0c;golang里面只能是 &quo…

第十六章:数据治理之数据架构:数据模型和数据流转关系

本章我们说一下数据架构&#xff0c;说到数据架构&#xff0c;就很自然的想到企业架构、业务架构、软件架构&#xff0c;因为个人并没有对这些内容进行深入了解&#xff0c;所以这里不做比对是否有相似或者共通的地方&#xff0c;仅仅来说一下我理解的数据架构。 1、什么是架构…

Day126 | 灵神 | 二叉树 | 层数最深的叶子结点的和

Day126 | 灵神 | 二叉树 | 层数最深的叶子结点的和 1302.层数最深的叶子结点的和 1302. 层数最深叶子节点的和 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 这道题用层序遍历的思路比较好想&#xff0c;就把每层的都算一下&#xff0c;然后返回最后一层的和就…

PCIE 4.0 vs PCIE 5.0固态硬盘——区别、科普与选购场景全解析

随着数字内容和高性能计算需求的爆发&#xff0c;固态硬盘&#xff08;SSD&#xff09;已成为PC、游戏主机和工作站不可或缺的核心硬件。面对市面上层出不穷的新一代SSD产品&#xff0c;大家最常见的一个疑惑&#xff1a;**PCIe 4.0和PCIe 5.0固态硬盘&#xff0c;到底有啥区别…

vue pinia 独立维护,仓库统一导出

它允许您跨组件/页面共享状态 持久化 安装依赖pnpm i pinia-plugin-persistedstate 将插件添加到 pinia 实例上 pinia独立维护 统一导出 import { createPinia } from pinia import piniaPluginPersistedstate from pinia-plugin-persistedstateconst pinia creat…

Dify源码学习

文章目录 1 大模型基本原理1.1 model_context_tokens、max_tokens和prompt_tokens1.1.1 三者之间的关系1.1.2 总结对比 2 Dify源代码2.0 前后端代码跑起来【0】准备开发环境【1】下载代码【2】运行后端&#xff08;1&#xff09;Start the docker-compose stack&#xff08;2&a…

连接表、视图和存储过程

1. 视图 1.1. 视图的概念 视图&#xff08;View&#xff09;&#xff1a;虚拟表&#xff0c;本身不存储数据&#xff0c;而是封装了一个 SQL 查询的结果集。 用途&#xff1a; 只显示部分数据&#xff0c;提高数据访问的安全性。简化复杂查询&#xff0c;提高复用性和可维护…

微信小程序中,解决lottie动画在真机不显示的问题

api部分 export function getRainInfo() {return onlineRequest({url: /ball/recruit/getRainInfo,method: get}); }data存储json数据 data&#xff1a;{rainJson:{} }onLoad方法获取json数据 onLoad(options) {let that thisgetRainInfo().then((res)>{that.setData({r…

从加密到信任|密码重塑车路云一体化安全生态

目录 一、密码技术的核心支撑 二、典型应用案例 三、未来发展方向 总结 车路云系统涉及海量实时数据交互&#xff0c;包括车辆位置、传感器信息、用户身份等敏感数据。其安全风险呈现三大特征&#xff1a; 开放环境威胁&#xff1a;V2X&#xff08;车与万物互联&#xff0…

光谱相机在地质勘测中的应用

一、‌矿物识别与蚀变带分析‌ ‌光谱特征捕捉‌ 通过可见光至近红外&#xff08;400-1000nm&#xff09;的高光谱分辨率&#xff08;可达3.5nm&#xff09;&#xff0c;精确识别矿物的“光谱指纹”。例如&#xff1a; ‌铜矿‌&#xff1a;在400-500nm波段反射率显著低于围…

理论篇三:如何编写自定义的Webpack Loader或Plugin插件

在 Webpack 中,自定义 Loader 和 Plugin 是扩展构建能力的关键方式。以下是它们的实现方法和核心逻辑,通过代码示例和步骤拆解帮助你快速掌握。 一、自定义 Loader 1. Loader 的本质 作用:将非 JS 文件转换为 Webpack 能处理的模块。特点:纯函数,接收源文件内容,返回处理…

【算法】力扣体系分类

第一章 算法基础题型 1.1 排序算法题 1.1.1 冒泡排序相关题 冒泡排序是一种简单的排序算法&#xff0c;它重复地走访过要排序的数列&#xff0c;一次比较两个元素&#xff0c;如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换&#xff0c…

C11 日期时间处理案例

文章目录 显示当前日期时间得到当前日期时间的17位数字形式(YYYYmmddHHMMSSsss)从日期时间字符串得到time_t 类型时间戳从时期时间字符串得到毫秒单位的时间戳得到当前日期时间以毫秒为单位的时间戳一个综合案例 所有例子在VS2019上编译运行通过 显示当前日期时间 #include &…

Python 训练营打卡 Day 34

GPU训练及类的call方法 一、GPU训练 与day33采用的CPU训练不同&#xff0c;今天试着让模型在GPU上训练&#xff0c;引入import time比较两者在运行时间上的差异 import torch # 设置GPU设备 device torch.device("cuda:0" if torch.cuda.is_available() else &qu…