Unity3D 敵人AI 和 動畫( Animator )系統的實例講解

在這個實例中,咱們要作一些敵人AI的簡單實現,其中自動跟隨和動畫是重點,咱們要達到的目標以下:ide

1.敵人可以自動跟隨主角  函數

2.敵人模型一共有四個動做:Idle(空閒) Run(奔跑) Attack(攻擊) Death(死亡).優化

3.要求敵人在合適的時機可以作出合適動做動畫

 

(一)自動跟隨的實現this

1)首先,新建一個場景  如圖,場景裏至少有兩個角色:  有一個敵人(刀骷髏兵) 還有一個主角(沒錯,就是那個膠囊體)spa

2)先選擇場景模型,而後在 Inspector 窗口選項 Static旁邊的小三角顯示出下拉菜單,肯定其中 Navigation Static 被選中.  對於與場景地形無關的模型選項,則要肯定沒有被選中,如圖所示。3d

                            

Navigation 窗口的選項主要是定義地形對尋路的影響。Radius 和 Height 能夠理解爲尋路者的半徑和高度。Max Slope 是最大坡度,超過這個坡度尋路者則沒法經過。Step Height 是樓梯的最大高度 ,超過這個高度尋路者則沒法經過。Drop Height表示尋路者能夠跳落的高度極限。Jump Distance 表示尋路者的跳躍距離極限。
 
3)選擇菜單欄裏的Window 選項裏的Navigation選項   點擊下面的bake   渲染完成後是這個樣子  這些藍色的區域就是能自動尋路的區域
                    
 
4) 而後給主角寫 移動控制腳本 和 鏡頭控制腳本  並賦給主角(膠囊體):
 
  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)首先,在Project面板裏面create一個Animator Controller   雙擊它 就會發現多了一個BaseLayer面板  以下面第一個圖  這個是用來控制動畫的邏輯關係的    在敵人模型的動畫分類裏 以下圖中間  選擇本身須要的動畫  而後拖到BaseLayer面板裏面  右鍵標籤能夠建立箭頭,這裏爲了便於講解,選了四個動畫(idle空閒  run奔跑  attack攻擊   death死亡)  按照下面右圖把動畫標籤的關係調節好.
 
            
 
2)給敵人添加動畫播放腳本  這個腳本與上面的敵人腳本不一樣 註釋的很清楚 很容易理解
  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

 
 
---未完待續---
相關文章
相關標籤/搜索