Unity自學--CreatorKit代碼解析 1
Unity初學者項目 Creator Kit Beginner
ExampleScene 樣例場景如下

1.附著腳本:(Character)
Character對象是游戲項目的PLayer,即玩家操縱的游戲對象,對于Character來說,需要擁有自己的運行腳本。
附著在Character對象上的運行腳本有如下:

可以看到,除了基礎的Transform模塊外還存在CapsuleCollider 用于檢測碰撞,NavMeshAgent用于自動尋路,Rigidbody用于描述物體的物理性質,官方文檔有詳細描述。
2.CharacterControl 代碼解析
using System.Collections; using System.Collections.Generic; using System.Data; using System.Timers; using CreatorKitCode; using UnityEngine; using UnityEngine.AI; using UnityEngine.EventSystems; using UnityEngine.Serialization; namespace CreatorKitCodeInternal { //繼承了基類,實現了攻擊,移動的接口 public class CharacterControl : MonoBehaviour, AnimationControllerDispatcher.IAttackFrameReceiver, AnimationControllerDispatcher.IFootstepFrameReceiver { //單例對象 public static CharacterControl Instance { get; protected set; } //移動速度 public float Speed = 10.0f; //角色數據 => 依賴于m_CharacterData 即附著在Character中的CharacterData腳本類 public CharacterData Data => m_CharacterData; //目標數據 => 依賴于m_CurrentTargetCharacterData 即附著在其他對象中的CharacterData腳本類 public CharacterData CurrentTarget => m_CurrentTargetCharacterData; //武器的位置數據等 public Transform WeaponLocator; //[Header] 顯示標頭 在Unity中顯示Audio [Header("Audio")] //音頻數據 public AudioClip[] SpurSoundClips; //最后射線的位置 Vector3 m_LastRaycastResult; //動畫播放器 Animator m_Animator; //尋路器 NavMeshAgent m_Agent; //用戶數據 CharacterData m_CharacterData; //高光類,用于高光渲染 HighlightableObject m_Highlighted; //RaycastHit用于存儲RayCast(射線)命中的數據 RaycastHit[] m_RaycastHitCache = new RaycastHit[16]; int m_SpeedParamID; int m_AttackParamID; int m_HitParamID; //眩暈id int m_FaintParamID; //復位,出生ID int m_RespawnParamID; bool m_IsKO = false; float m_KOTimer = 0.0f; //交互層級--層級 int m_InteractableLayer; int m_LevelLayer; //碰撞器 Collider m_TargetCollider; //交互對象 InteractableObject m_TargetInteractable = null; //主視角 Camera m_MainCamera; //自動尋路組件產生的路徑,保存在corners中 NavMeshPath m_CalculatedPath; //主角音頻 CharacterAudio m_CharacterAudio; //目標層 當前目標數據 int m_TargetLayer; CharacterData m_CurrentTargetCharacterData = null; //this is a flag that tell the controller it need to clear the target once the attack finished. //usefull for when clicking elwswhere mid attack animation, allow to finish the attack and then exit. //當攻擊完成后需要清除的標志位,用于攻擊動畫途中點擊某個位置時允許退出攻擊狀態并離開 bool m_ClearPostAttack = false; //復生點,當經過存在復生點的區域時,設置復生點 SpawnPoint m_CurrentSpawn = null; //枚舉狀態 默認 受擊 攻擊 enum State { DEFAULT, HIT, ATTACKING } //當前狀態 State m_CurrentState; //初始化實例 獲得主攝像機 Camera.main tag標簽為MainCamera的攝像機 void Awake() { Instance = this; m_MainCamera = Camera.main; } // Start is called before the first frame update void Start() { //QualitySettings品質接口 此處為不等待垂直同步 (垂直同步防止跳幀,撕裂(關閉會更流暢)) QualitySettings.vSyncCount = 0; //嘗試60fps 設置了QualitySettings.vSyncCount之后,targetFrameRate不使用,使用平臺默認刷新率 Application.targetFrameRate = 60; //新建路徑 m_CalculatedPath = new NavMeshPath(); //獲取尋路組件,動畫組件 m_Agent = GetComponent<NavMeshAgent>(); m_Animator = GetComponentInChildren<Animator>(); //尋路的速度設為當前移動速度 m_Agent.speed = Speed; //設置最大回轉速度 需要轉彎的速度 m_Agent.angularSpeed = 360.0f; //射線位置設為當前位置 m_LastRaycastResult = transform.position; //初始化動畫ID m_SpeedParamID = Animator.StringToHash("Speed"); m_AttackParamID = Animator.StringToHash("Attack"); m_HitParamID = Animator.StringToHash("Hit"); m_FaintParamID = Animator.StringToHash("Faint"); m_RespawnParamID = Animator.StringToHash("Respawn"); //獲得人物數據 m_CharacterData = GetComponent<CharacterData>(); //給人物數據中的武器裝備動作提供 m_CharacterData.Equipment.OnEquiped += item => { if (item.Slot == (EquipmentItem.EquipmentSlot)666) { //初始化預制件,父類是當前對象,不在世界坐標系下生成(父類坐標系下生成) var obj = Instantiate(item.WorldObjectPrefab, WeaponLocator, false); //將每一層都變為PlayerEquipment層循環調用 Helpers.RecursiveLayerChange(obj.transform, LayerMask.NameToLayer("PlayerEquipment")); } }; m_CharacterData.Equipment.OnUnequip += item => { if (item.Slot == (EquipmentItem.EquipmentSlot)666) { //摧毀所有的角色 foreach(Transform t in WeaponLocator) Destroy(t.gameObject); } }; //角色數據初始化 m_CharacterData.Init(); //獲得層級位置 m_InteractableLayer = 1 << LayerMask.NameToLayer("Interactable"); m_LevelLayer = 1 << LayerMask.NameToLayer("Level"); m_TargetLayer = 1 << LayerMask.NameToLayer("Target"); //設置默認狀態 m_CurrentState = State.DEFAULT; //設置音頻 m_CharacterAudio = GetComponent<CharacterAudio>(); //受傷時的動作函數 (設置動畫,調用Hit函數播放) m_CharacterData.OnDamage += () => { m_Animator.SetTrigger(m_HitParamID); m_CharacterAudio.Hit(transform.position); }; } // Update is called once per frame void Update() { Vector3 pos = transform.position; //判斷是否死了,死了設置復活時間m_KOTimer,達到3s后調用GoToRespawn()復活 if (m_IsKO) { m_KOTimer += Time.deltaTime; if (m_KOTimer > 3.0f) { GoToRespawn(); } return; } //The update need to run, so we can check the health here. //Another method would be to add a callback in the CharacterData that get called //when health reach 0, and this class register to the callback in Start //(see CharacterData.OnDamage for an example) //當前生命值為 0時死亡 if (m_CharacterData.Stats.CurrentHealth == 0) { //設置眩暈狀態 m_Animator.SetTrigger(m_FaintParamID); //尋路停止 m_Agent.isStopped = true; //清空路徑 m_Agent.ResetPath(); //狀態值為死亡 m_IsKO = true; //設置復活計時器 m_KOTimer = 0.0f; //調用死亡函數 Data.Death(); //調用死亡音頻 m_CharacterAudio.Death(pos); return; } //沒死亡的話 獲取在鼠標位置射線 Ray screenRay = CameraController.Instance.GameplayCamera.ScreenPointToRay(Input.mousePosition); //交互對象不為空 ,進入交互狀態 if (m_TargetInteractable != null) { CheckInteractableRange(); } //目標數據不為空 判斷對象是否死亡,是置為空,否則進入攻擊狀態 if (m_CurrentTargetCharacterData != null) { if (m_CurrentTargetCharacterData.Stats.CurrentHealth == 0) m_CurrentTargetCharacterData = null; else CheckAttack(); } //鼠標滾輪事件 float mouseWheel = Input.GetAxis("Mouse ScrollWheel"); //如果滾動了 if (!Mathf.Approximately(mouseWheel, 0.0f)) { //獲取當前的鼠標位置(從屏幕空間變換為視口空間) Vector3 view = m_MainCamera.ScreenToViewportPoint(Input.mousePosition); //如果處在攝像機范圍內 if(view.x > 0f && view.x < 1f && view.y > 0f && view.y < 1f) //變換攝像機視角 CameraController.Instance.Zoom(-mouseWheel * Time.deltaTime * 20.0f); } if(Input.GetMouseButtonDown(0)) { //if we click the mouse button, we clear any previously et targets //按下按鍵但不是攻擊狀態時,清空所有的對象(攻擊對象,交互對象) 否則攻擊后清除 if (m_CurrentState != State.ATTACKING) { m_CurrentTargetCharacterData = null; m_TargetInteractable = null; } else { //處于攻擊狀態時 m_ClearPostAttack = true; } } //EventSystem.current.IsPointerOverGameObject()判斷觸點是否在對象上而不是UI上 if (!EventSystem.current.IsPointerOverGameObject() && m_CurrentState != State.ATTACKING) { //Raycast to find object currently under the mouse cursor //用射線的方式獲取光標位置下的對象 //設置為高光對象 沒有 則不設置 ObjectsRaycasts(screenRay); if (Input.GetMouseButton(0)) { //如果按下按鍵 if (m_TargetInteractable == null && m_CurrentTargetCharacterData == null) { //判斷高光對象 InteractableObject obj = m_Highlighted as InteractableObject; if (obj) { InteractWith(obj); } else { CharacterData data = m_Highlighted as CharacterData; if (data != null) { m_CurrentTargetCharacterData = data; } else { //高光對象也為NULL則尋路檢查 MoveCheck(screenRay); } } } } } //設置平滑的移動 m_Animator.SetFloat(m_SpeedParamID, m_Agent.velocity.magnitude / m_Agent.speed); //Keyboard shortcuts //按I鍵獲取物品欄 if(Input.GetKeyUp(KeyCode.I)) UISystem.Instance.ToggleInventory(); } void GoToRespawn() { m_Animator.ResetTrigger(m_HitParamID); //設置代理路徑 m_Agent.Warp(m_CurrentSpawn.transform.position); //停止尋路 m_Agent.isStopped = true; //清空路徑 m_Agent.ResetPath(); //復活 m_IsKO = false; //所有狀態重置 m_CurrentTargetCharacterData = null; m_TargetInteractable = null; m_CurrentState = State.DEFAULT; m_Animator.SetTrigger(m_RespawnParamID); //恢復狀態 m_CharacterData.Stats.ChangeHealth(m_CharacterData.Stats.stats.health); } void ObjectsRaycasts(Ray screenRay) { //bool值 bool somethingFound = false; //first check for interactable Object //往screenRay方向投射球體,半徑為1,m_RaycastHitCache存儲命中對象,m_InteractableLayer遮罩層忽略不在層中的對象 int count = Physics.SphereCastNonAlloc(screenRay, 1.0f, m_RaycastHitCache, 1000.0f, m_InteractableLayer); //緩沖區個數不為0時 if (count > 0) { for (int i = 0; i < count; ++i) { //獲取每個碰撞體中的可交互對象(附著體) InteractableObject obj = m_RaycastHitCache[0].collider.GetComponentInParent<InteractableObject>(); //不為NULL且可交互時 if (obj != null && obj.IsInteractable) { //調用函數使物體高光 SwitchHighlightedObject(obj); //設置為找到東西,跳出循環 somethingFound = true; break; } } } else { //往screenRay方向投射球體,半徑為1,m_RaycastHitCache存儲命中對象,m_InteractableLayer遮罩層忽略不在層中的對象 count = Physics.SphereCastNonAlloc(screenRay, 1.0f, m_RaycastHitCache, 1000.0f, m_TargetLayer); if (count > 0) { CharacterData data = m_RaycastHitCache[0].collider.GetComponentInParent<CharacterData>(); //數據不為null時 if (data != null) { SwitchHighlightedObject(data); somethingFound = true; } } } //沒找到對象并且高光對象不為NULL時 if (!somethingFound && m_Highlighted != null) { //清除高光對象 SwitchHighlightedObject(null); } } void SwitchHighlightedObject(HighlightableObject obj) { //高光對象不為null時 取消高光 if(m_Highlighted != null) m_Highlighted.Dehighlight(); //設置高光對象 m_Highlighted = obj; //高光對象不為null時 設置高光 if (m_Highlighted != null) m_Highlighted.Highlight(); } //移動檢測 void MoveCheck(Ray screenRay) { //判斷尋路狀態是否在目的地終止 NavMeshPathStatus.PathComplete if ( m_CalculatedPath.status == NavMeshPathStatus.PathComplete) { //設置新路徑 m_Agent.SetPath(m_CalculatedPath); //清除路徑 m_CalculatedPath.ClearCorners(); } if (Physics.RaycastNonAlloc(screenRay, m_RaycastHitCache, 1000.0f, m_LevelLayer) > 0) { //拋射對象大于0時,點擊位置的對象,選擇碰到的第一個 //獲取第一個對象 Vector3 point = m_RaycastHitCache[0].point; //avoid recomputing path for close enough click //避免重復計算近距離點擊(點在上次點的位置附近的就不移動了) if (Vector3.SqrMagnitude(point - m_LastRaycastResult) > 1.0f) { NavMeshHit hit; //NavMesh.SamplePosition判斷是否是可行區域 point 目標點 hit 輸出最近路徑 NavMesh.AllAreas全路段 if (NavMesh.SamplePosition(point, out hit, 0.5f, NavMesh.AllAreas)) {//sample just around where we hit, avoid setting destination outside of navmesh (ie. on building) //是可行區域設置之前的位置 m_LastRaycastResult = point; //m_Agent.SetDestination(hit.position); //計算路徑并將其儲存到m_CalculatedPath中 m_Agent.CalculatePath(hit.position, m_CalculatedPath); } } } } //交互 void CheckInteractableRange() { //攻擊狀態,則不交互 if(m_CurrentState == State.ATTACKING) return; //ClosestPointOnBounds與目標碰撞體碰撞的最近的位置 相減得出距離 Vector3 distance = m_TargetCollider.ClosestPointOnBounds(transform.position) - transform.position; //距離小于一個范圍 if (distance.sqrMagnitude < 1.5f * 1.5f) { //停止尋路 StopAgent(); //交互 m_TargetInteractable.InteractWith(m_CharacterData); //交互完了,交互對象為NULL,再次點擊才會繼續交互 m_TargetInteractable = null; } } //停止尋路 void StopAgent() { m_Agent.ResetPath(); //速度為0 立刻停止 m_Agent.velocity = Vector3.zero; } void CheckAttack() { //處于攻擊狀態,返回 if(m_CurrentState == State.ATTACKING) return; //是否達到攻擊范圍 if (m_CharacterData.CanAttackReach(m_CurrentTargetCharacterData)) { //達到,停止尋路 StopAgent(); //if the mouse button isn't pressed, we do NOT attack //判斷是否按下攻擊鍵 if (Input.GetMouseButton(0)) { //獲得兩者的距離 Vector3 forward = (m_CurrentTargetCharacterData.transform.position - transform.position); //垂直距離為0 forward.y = 0; //歸一化 forward.Normalize(); //設置標準化矢量 transform.forward = forward; //判斷是否能夠攻擊到目標 if (m_CharacterData.CanAttackTarget(m_CurrentTargetCharacterData)) { //能則進入攻擊狀態 m_CurrentState = State.ATTACKING; //播放動畫和音效 m_CharacterData.AttackTriggered(); m_Animator.SetTrigger(m_AttackParamID); } } } else { //沒達到則設置攻擊目標的位置為尋路位置 m_Agent.SetDestination(m_CurrentTargetCharacterData.transform.position); } } public void AttackFrame() { //攻擊幀 攻擊人物不存在了 放棄攻擊 返回 if (m_CurrentTargetCharacterData == null) { m_ClearPostAttack = false; return; } //if we can't reach the target anymore when it's time to damage, then that attack miss. //攻擊范圍是否達到了 if (m_CharacterData.CanAttackReach(m_CurrentTargetCharacterData)) { //攻擊 m_CharacterData.Attack(m_CurrentTargetCharacterData); //設置攻擊位置和播放動畫和音頻 var attackPos = m_CurrentTargetCharacterData.transform.position + transform.up * 0.5f; VFXManager.PlayVFX(VFXType.Hit, attackPos); SFXManager.PlaySound(m_CharacterAudio.UseType, new SFXManager.PlayData() { Clip = m_CharacterData.Equipment.Weapon.GetHitSound(), PitchMin = 0.8f, PitchMax = 1.2f, Position = attackPos }); } //攻擊過了 if(m_ClearPostAttack) { //清空所有對象 m_ClearPostAttack = false; m_CurrentTargetCharacterData = null; m_TargetInteractable = null; } //恢復默認狀態 m_CurrentState = State.DEFAULT; } //設置出生點 public void SetNewRespawn(SpawnPoint point) { //設置為死寂狀態(enable屬性為可用) if(m_CurrentSpawn != null) m_CurrentSpawn.Deactivated(); m_CurrentSpawn = point; //設置為活躍狀態 m_CurrentSpawn.Activated(); } //交互函數 public void InteractWith(InteractableObject obj) { if (obj.IsInteractable) { //可交互則獲得碰撞器 m_TargetCollider = obj.GetComponentInChildren<Collider>(); //獲得交互對象 m_TargetInteractable = obj; //設置尋路目標 m_Agent.SetDestination(obj.transform.position); } } //實現的接口函數 public void FootstepFrame() { Vector3 pos = transform.position; m_CharacterAudio.Step(pos); SFXManager.PlaySound(SFXManager.Use.Player, new SFXManager.PlayData() { Clip = SpurSoundClips[Random.Range(0, SpurSoundClips.Length)], Position = pos, PitchMin = 0.8f, PitchMax = 1.2f, Volume = 0.3f }); VFXManager.PlayVFX(VFXType.StepPuff, pos); } } }
3.整體人物邏輯設計(Update邏輯詳解)

浙公網安備 33010602011771號