【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
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/82322.shtml
英文地址,請注明出處:http://en.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…