在這個實例中,咱們要作一些敵人AI的簡單實現,其中自動跟隨和動畫是重點,咱們要達到的目標以下:ide
1.敵人可以自動跟隨主角 函數
2.敵人模型一共有四個動做:Idle(空閒) Run(奔跑) Attack(攻擊) Death(死亡).優化
3.要求敵人在合適的時機可以作出合適動做動畫
(一)自動跟隨的實現this
1)首先,新建一個場景 如圖,場景裏至少有兩個角色: 有一個敵人(刀骷髏兵) 還有一個主角(沒錯,就是那個膠囊體)spa
2)先選擇場景模型,而後在 Inspector 窗口選項 Static旁邊的小三角顯示出下拉菜單,肯定其中 Navigation Static 被選中. 對於與場景地形無關的模型選項,則要肯定沒有被選中,如圖所示。3d
1 public class PlayerControl : MonoBehaviour 2 { 3 4 //定義玩家的Transform 5 public Transform m_transform; 6 //定義玩家的角色控制器 7 CharacterController m_ch; 8 //定義玩家的移動速度 9 float m_movespeed = 10.0f; 10 //定義玩家的重力 11 float m_gravity = 2.0f; 12 //定義玩家的生命 13 public int m_life = 5; 14 15 //定義攝像機的Transform 16 Transform m_cameraTransform; 17 //定義攝像機的旋轉角度 18 Vector3 m_cameraRotation; 19 //定義攝像機的高度 20 float m_cameraHeight = 1.4f; 21 //定義小地圖攝像機 22 public Transform m_miniMap; 23 24 //定義槍口的Transform m_muzzlepPoint; 25 Transform m_muzzlePoint; 26 //定義射擊時,射線射到的碰撞層 27 public LayerMask m_layer; 28 //定義射中目標後粒子效果的Transform 29 public Transform m_fx; 30 //定義射擊音效 31 public AudioClip m_shootAudio; 32 //定義射擊間隔時間計時器 33 float m_shootTimer = 0; 34 35 36 37 // Use this for initialization 38 void Start() 39 { 40 //獲取玩家自己的Transform 賦給 m_transform 41 m_transform = this.transform; 42 //獲取玩家自己的CharacterController組件 賦給 m_ch 43 m_ch = this.GetComponent<CharacterController>(); 44 45 //攝像機的控制的初始化 46 //獲取攝像機的Transform 47 m_cameraTransform = Camera.main.transform; 48 //定義一個三維向量用來表示攝像機位置 並把玩家的位置賦給它 設置攝像機初始位置 49 Vector3 pos = m_transform.position; 50 //攝像機的Y軸座標 爲 原本的座標加上上面定義的攝像機高度 51 pos.y += m_cameraHeight; 52 //把修改後的攝像機座標從新賦給m_cameraTransform 53 m_cameraTransform.position = pos; 54 //把主角的旋轉角度 賦給 攝像機的旋轉角度 55 m_cameraTransform.rotation = m_transform.rotation; 56 //獲取攝像機的角度 57 m_cameraRotation = m_transform.eulerAngles; 58 59 //隱藏鼠標 60 Cursor.visible = false; 61 } 62 63 // Update is called once per frame 64 void Update() 65 { 66 //若是玩家的生命小於等於0 什麼也不作 67 if (m_life <= 0) 68 { 69 return; 70 } 71 72 //若是玩家的生命大於0 那麼調用玩家控制函數 73 //移動函數 74 MoveControl(); 75 //攝像機控制函數 76 CameraControl(); 77 //跳躍函數 78 Jump(); 79 } 80 81 82 //定義玩家的控制函數 83 void MoveControl() 84 { 85 86 //定義玩家在XYZ軸上的移動量 87 float xm = 0, ym = 0, zm = 0; 88 89 //玩家的重力運動 爲 減等於玩家的重力乘以每幀時間 90 ym -= m_gravity * Time.deltaTime; 91 92 //實現玩家上下左右的運動 93 //若是按下 W鍵 玩家在Z軸上的量增長 94 if (Input.GetKey(KeyCode.W)) 95 { 96 zm += m_movespeed * Time.deltaTime; 97 } 98 //若是按下 S鍵 玩家在Z軸上的量減小 這裏用else if是由於每幀只能按下相反方向的一個鍵 99 else if (Input.GetKey(KeyCode.S)) 100 { 101 zm -= m_movespeed * Time.deltaTime; 102 } 103 //若是按下 A鍵 玩家在X軸上的量減小 104 if (Input.GetKey(KeyCode.A)) 105 { 106 xm -= m_movespeed * Time.deltaTime; 107 } 108 //若是按下 D鍵 玩家在X軸上的量增長 109 else if (Input.GetKey(KeyCode.D)) 110 { 111 xm += m_movespeed * Time.deltaTime; 112 } 113 114 ////當玩家在地面上的時候 才能先後左右移動 在空中不能移動 115 if (!m_ch.isGrounded) 116 { 117 xm = 0; 118 zm = 0; 119 } 120 121 //經過角色控制器的Move()函數,實現移動 122 m_ch.Move(m_transform.TransformDirection(new Vector3(xm, ym, zm))); 123 124 125 } 126 127 128 //定義玩家的攝像機控制函數 129 void CameraControl() 130 { 131 132 //實現對攝像機的控制 133 //定義主角在horizon方向X軸移動的量 也就是獲取主角鼠標移動的量 134 float rh = Input.GetAxis("Mouse X"); 135 //定義主角在Vertical 方向Y軸移動的量 136 float rv = Input.GetAxis("Mouse Y"); 137 138 139 //旋轉攝像機 140 //把鼠標在屏幕上移動的量轉化爲攝像機的角度 rv(上下移動的量) 等於 角色X軸的角度 rh(水平移動的量) 等於 角色Y軸上的角度 141 m_cameraRotation.x -= rv; 142 //Debug.Log(rv); 向下時 rv 爲正值(順時針) 向上時 rv 爲負值(逆時針) 143 m_cameraRotation.y += rh; 144 //Debug.Log(rh); 向右時 rh 爲正值(順時針) 向左時 rh 爲負值(逆時針) 145 146 147 //限制X軸的移動在-60度到60度之間 148 if (m_cameraRotation.x >= 60) 149 { 150 m_cameraRotation.x = 60; 151 } 152 if (m_cameraRotation.x <= -60) 153 { 154 m_cameraRotation.x = -60; 155 } 156 m_cameraTransform.eulerAngles = m_cameraRotation; 157 158 //使主角的面向方向與攝像機一致 用Vector3定義一箇中間變量是由於 eularAngles 沒法直接做爲變量 159 Vector3 camrot = m_cameraTransform.eulerAngles; 160 //初始化攝像機的歐拉角爲0 161 camrot.x = 0; 162 camrot.z = 0; 163 //把攝像機的歐拉角 賦給 主角 164 m_transform.eulerAngles = camrot; 165 166 //使攝像機的位置與主角一致 用Vector3定義一箇中間變量是由於 position 沒法直接做爲變量 167 Vector3 pos = m_transform.position; 168 //攝像機的Y軸位置 爲 主角的Y軸位置加上攝像機的高度 169 pos.y += m_cameraHeight; 170 //把主角的位置 賦給 攝像機的位置 171 m_cameraTransform.position = pos; 172 173 } 174 175 176 //定義玩家的Jump函數 177 void Jump() 178 { 179 //當玩家在地面上的時候 玩家的i跳纔有效果 180 if (m_ch.isGrounded) 181 { 182 //此時玩家的重力爲10 183 m_gravity = 10; 184 //若是按下 space鍵 玩家的重力變爲負數 實現向上運動 185 if (Input.GetKey(KeyCode.Space)) 186 { 187 m_gravity = -8; 188 } 189 } 190 //此時玩家跳了起來 191 else 192 { 193 //玩家的重力 爲 玩家的重力10 乘以 每幀的時間 194 m_gravity +=10f*Time.deltaTime; 195 //若是玩家的重力大於10的話 讓他等於10 196 if (m_gravity>=10) 197 { 198 m_gravity = 10f; 199 } 200 } 201 202 }
5)給敵人寫自動追蹤腳本並賦給敵人 :代理
1 public class Enemy : MonoBehaviour 2 { 3 4 //定義敵人的Transform 5 Transform m_transform; 6 //CharacterController m_ch; 7 8 //定義動畫組件 9 Animator m_animator; 10 11 //定義尋路組件 12 NavMeshAgent m_agent; 13 14 //定義一個主角類的對象 15 PlayerControl m_player; 16 //角色移動速度 17 float m_moveSpeed = 0.5f; 18 //角色旋轉速度 19 float m_rotSpeed = 120; 20 //定義生命值 21 int m_life = 15; 22 23 //定義計時器 24 float m_timer = 2; 25 //定義生成點 26 //protected EnemySpawn m_spawn; 27 28 29 // Use this for initialization 30 void Start() 31 { 32 //初始化m_transform 爲物體自己的tranform 33 m_transform = this.transform; 34 35 //初始化動畫m_ani 爲物體的動畫組件 36 m_animator = this.GetComponent<Animator>(); 37 38 //初始化尋路組件m_agent 爲物體的尋路組件 39 m_agent = GetComponent<NavMeshAgent>(); 40 41 //初始化主角 42 m_player = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerControl>(); 43 44 45 } 46 47 // Update is called once per frame 48 void Update() 49 { 50 //設置敵人的尋路目標 51 m_agent.SetDestination(m_player.m_transform.position); 52 53 //調用尋路函數實現尋路移動 54 MoveTo(); 55 56 57 } 58 59 60 //敵人的自動尋路函數 61 void MoveTo() 62 { 63 //定義敵人的移動量 64 float speed = m_moveSpeed * Time.deltaTime; 65 66 //經過尋路組件的Move()方法實現尋路移動 67 m_agent.Move(m_transform.TransformDirection(new Vector3(0, 0, speed))); 68 } 69 70 71 72 }
這時,運行遊戲,敵人就能自動跟隨了.code
1 public class Enemy : MonoBehaviour 2 { 3 4 //定義敵人的Transform 5 Transform m_transform; 6 //CharacterController m_ch; 7 8 //定義動畫組件 9 Animator m_animator; 10 11 //定義尋路組件 12 NavMeshAgent m_agent; 13 14 //定義一個主角類的對象 15 PlayerControl m_player; 16 //角色移動速度 17 float m_moveSpeed = 0.5f; 18 //角色旋轉速度 19 float m_rotSpeed = 120; 20 //定義生命值 21 int m_life = 15; 22 23 //定義計時器 24 float m_timer = 2; 25 //定義生成點 26 //protected EnemySpawn m_spawn; 27 28 29 // Use this for initialization 30 void Start() 31 { 32 //初始化m_transform 爲物體自己的tranform 33 m_transform = this.transform; 34 35 //初始化動畫m_ani 爲物體的動畫組件 36 m_animator = this.GetComponent<Animator>(); 37 38 //初始化尋路組件m_agent 爲物體的尋路組件 39 m_agent = GetComponent<NavMeshAgent>(); 40 41 //初始化主角 42 m_player = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerControl>(); 43 44 45 } 46 47 // Update is called once per frame 48 void Update() 49 { 50 ////設置敵人的尋路目標 51 //m_agent.SetDestination(m_player.m_transform.position); 52 53 ////調用尋路函數實現尋路移動 54 //MoveTo(); 55 56 //敵人動畫的播放與轉換 57 //若是玩家的生命值小於等於0時,什麼都不作 (主角死後 敵人無需再有動做) 58 if (m_player.m_life <= 0) 59 { 60 return; 61 } 62 63 //獲取當前動畫狀態(Idle Run Attack Death 中的一種) 64 AnimatorStateInfo stateInfo = m_animator.GetCurrentAnimatorStateInfo(0); 65 66 //Idle 若是角色在等待狀態條 而且 沒有處於轉換狀態 (0表明的是Base Layer) 67 if (stateInfo.fullPathHash == Animator.StringToHash("Base Layer.Idle") && !m_animator.IsInTransition(0)) 68 { 69 //此時把Idle狀態設爲false (此時把狀態設置爲false 一方面Unity 動畫設置裏面has exit time已經取消 另外一方面爲了不和後面的動畫衝突 ) 70 m_animator.SetBool("Idle", false); 71 72 //待機必定時間後(Timer) 之因此有這個Timer 是由於在動畫播放期間 無需對下面的語句進行判斷(判斷也沒有用) 從而起到優化的做用 73 m_timer -= Time.deltaTime; 74 75 //若是計時器Timer大於0 返回 (什麼也不幹,做用是優化 優化 優化) 76 if (m_timer > 0) 77 { 78 return; 79 } 80 81 //若是距離主角小於3米 把攻擊動畫的Bool值設爲true (激活指向Attack的通道) 82 if (Vector3.Distance(m_transform.position, m_player.m_transform.position) < 3f) 83 { 84 m_animator.SetBool("Attack", true); 85 } 86 //若是距離主角不小於3米 87 else 88 { 89 //那麼把計時器重置爲1 90 m_timer = 1; 91 //從新獲取自動尋路的位置 92 m_agent.SetDestination(m_player.m_transform.position); 93 //激活指向Run的通道 94 m_animator.SetBool("Run", true); 95 } 96 } 97 98 99 //Run 若是角色指向奔跑狀態條 而且 沒有處於轉換狀態 (0表明的是Base Layer) 100 if (stateInfo.fullPathHash == Animator.StringToHash("Base Layer.Run") && !m_animator.IsInTransition(0)) 101 { 102 //關閉指向Run的通道 103 m_animator.SetBool("Run", false); 104 //計時器時間隨幀減小 105 m_timer -= Time.deltaTime; 106 //計時器時間小於0時 從新獲取自動尋路的位置 重置計時器時間爲1 107 if (m_timer < 0) 108 { 109 m_agent.SetDestination(m_player.m_transform.position); 110 m_timer = 1; 111 } 112 113 //調用跟隨函數 114 MoveTo(); 115 116 //當角色與主角的距離小於等於3米時 117 if (Vector3.Distance(m_transform.position, m_player.m_transform.position) <= 3f) 118 { 119 //清楚當前路徑 當路徑被清除 代理不會開始尋找新路徑直到SetDestination 被調用 120 m_agent.ResetPath(); 121 //激活指向Attack的通道 122 m_animator.SetBool("Attack", true); 123 124 } 125 } 126 127 128 //Attack 若是角色指向攻擊狀態條 而且 沒有處於轉換狀態 (0表明的是Base Layer) 129 if (stateInfo.fullPathHash == Animator.StringToHash("Base Layer.Attack") && !m_animator.IsInTransition(0)) 130 { 131 //調用轉向函數 132 RotationTo(); 133 134 //關閉指向Attack的通道 135 m_animator.SetBool("Attack", false); 136 137 //當播放過一次動畫後 normalizedTime 實現狀態的歸1化(1就是總體和所有) 整數部分是時間狀態的已循環數 小數部分是當前循環的百分比進程(0-1) 138 if (stateInfo.normalizedTime >= 1.0f) 139 { 140 //激活指向Idle的通道 141 m_animator.SetBool("Idle", true); 142 143 //計時器時間重置爲2 144 m_timer = 2; 145 146 147 //m_player.OnDamage(1); 148 149 } 150 } 151 152 153 //Death 若是角色指向死亡狀態條 而且 沒有處於轉換狀態 (0表明的是Base Layer) 154 if (stateInfo.fullPathHash == Animator.StringToHash("Base Layer.Death") && !m_animator.IsInTransition(0)) 155 { 156 //摧毀這個物體的碰撞體 157 Destroy(this.GetComponent<Collider>()); 158 159 //自動尋路時間被歸零 角色再也不自動移動 160 m_agent.speed = 0; 161 162 //死亡動畫播放一遍後 角色死亡 163 if (stateInfo.normalizedTime >= 1.0f) 164 { 165 //OnDeath() 166 } 167 168 169 } 170 } 171 172 173 //敵人的自動尋路函數 174 void MoveTo() 175 { 176 //定義敵人的移動量 177 float speed = m_moveSpeed * Time.deltaTime; 178 179 //經過尋路組件的Move()方法實現尋路移動 180 m_agent.Move(m_transform.TransformDirection(new Vector3(0, 0, speed))); 181 } 182 183 184 //敵人轉向目標點函數 185 void RotationTo() 186 { 187 //定義當前角度 188 Vector3 oldAngle = m_transform.eulerAngles; 189 //得到面向主角的角度 190 m_transform.LookAt(m_player.m_transform); 191 192 //定義目標的方向 Y軸方向 也就是敵人左右轉動面向玩家 193 float target = m_transform.eulerAngles.y; 194 //轉向目標的速度 等於時間乘以旋轉角度 195 float speed = m_rotSpeed * Time.deltaTime; 196 //經過MoveTowardsAngle() 函數得到轉的角度 197 float angle = Mathf.MoveTowardsAngle(oldAngle.y, target, speed); 198 199 //實現轉向 200 m_transform.eulerAngles = new Vector3(0, angle, 0); 201 } 202 203 }
自此,一個會自動尋找主角 並 攻擊 並且 有動畫 的敵人就作好了orm