文章出處

[Unity3D入門]分享一個自制的入門級游戲項目"坦克狙擊手"

我在學Unity3D,TankSniper(坦克狙擊手)這個項目是用來練手的。游戲玩法來自這里(http://www.4399.com/flash/127672_3.htm),雖然抄襲了人家的創意,不過我只用來練習(目前還很不成熟,離人家的境界相差很大),坦克、導彈、建筑模型來自網絡,應該不會有版權問題吧。

由于模型和代碼總共10M以上了,需要源代碼和發布的Windows版、網頁版程序的同學麻煩支付100元并留下你的郵箱~

 

到目前為止,用到的Unity3D知識有:地形Terrain,子物體gameObject,預制體Prefab,粒子系統Shuriken,剛體rigidbody,碰撞體collider,場景scene。

本文將非常簡略,因為我也不知道該詳寫什么略寫什么。有任何問題的話請留言,我會詳細回復,并且根據情況加入正文。

需要step by step指導的同學,可以參考(http://pixelnest.io/tutorials/2d-game-unity/)。我就是從這篇文章開始學習Unity3D的。有了這個基礎,看本文就沒有什么問題了。

如何創建大量坦克

目前TankSniper里有4個坦克模型。如你所見,游戲中需要出現大量的坦克。在Unity3D中,我們不用new SomeTank()這種方式創建坦克,而是用Unity3D自帶的Instantiate(prefab)方法創建坦克。其中的prefab就是預先設計訂制的坦克模板,所以叫預制體。

創建預制體很簡單,你只需

  • 在Hierarchy中創建一個Cube。
  • 把導入的坦克模型拖拽到Hierarchy面板。
  • 調整Cube和坦克模型的position、rotation、Scale,使Cube恰好包住坦克模型,然后把坦克模型拖拽到Cube下,成為Cube的子物體
  • 把Cube拖拽到Project面板的Asset文件夾下(或Asset的子文件夾下)。

這樣,一個以Cube為名稱的預制體就做好了。以后你就可以在C#腳本中通過寫

Instantiate(Cube);

這樣的句子來創建坦克了。

一個小問題是,為什么要把坦克模型當做Cube的子物體?理由有2:首先,這樣可以任意調整坦克模型的transform屬性,而預制體整體的transform仍舊可以是0,0,0,這樣方便使用;然后,用Cube嚴密包裹坦克模型后,Cube可以作為碰撞檢測的邊界,長方體之間的碰撞計算量比復雜的坦克模型要小得多。這是一種常用的做法。

 

爆炸效果和導彈尾焰

爆炸和尾焰都是用粒子系統做的,通過調整粒子系統的參數就可以實現,而且我沒有用任何紋理圖片。視覺效果雖然一般,不過目前這不是我要學的重點,暫時知足常樂一下好了。

 

導彈攻擊坦克

實際上就是在碰撞事件OnCollisionEnter中寫代碼:在導彈的OnCollisionEnter事件中添加爆炸的粒子系統并銷毀導彈;在坦克的OnCollisionEnter事件中減掉一定數值的HP值,若HP<=0了,就用Unity3D自帶的Destroy()方法銷毀坦克。

 1     void OnCollisionEnter(Collision collision) { //當碰撞體與剛體與其他碰撞體或剛體接觸時調用
 2 //        foreach (ContactPoint contact in collision.contacts) {
 3 //            Debug.Log(string.Format("{0}", contact.ToString()));
 4 //            Debug.DrawRay(contact.point, contact.normal, Color.white);
 5 //        }
 6 //        if (collision.relativeVelocity.magnitude > 2)
 7 //            audio.Play();
 8         foreach (ContactPoint contact in collision.contacts) {
 9             ExplosionEffectHelper.Instance.Explode(ExplosionEffectHelper.ExplosionEffect.MissileExplosion, contact.point);
10             SoundEffectHelper.Instance.MakeExplosionSound();
11             Destroy(this.gameObject);
12             break;
13         }
14     }
MissileScript.cs
 1     void OnCollisionEnter(Collision collision) { //當碰撞體與剛體與其他碰撞體或剛體接觸時調用
 2         //foreach (ContactPoint contact in collision.contacts) {
 3         //    Debug.DrawRay(contact.point, contact.normal, Color.white);
 4         //}
 5         //if (collision.relativeVelocity.magnitude > 2)
 6         //    audio.Play();
 7         var missileScript = collision.gameObject.GetComponent<MissileScript>();
 8         if (missileScript != null) {
 9             var power = missileScript.power;
10             this.Damage(power);
11         }
12     }
TankHealth.cs

最開始我用的是OnTriggerEnter事件。不過OnTriggerEnter無法獲取導彈和坦克碰撞的準確位置,也就無法在最準確的位置釋放爆炸效果,所以換成了OnCollisionEnter。

關于isTrigger與觸發OnTriggerEnter、OnCollisionEnter之間的關系,可參考(http://www.cnblogs.com/infly123/p/3920393.html),本文不再詳細說明。

 

發射導彈

導彈也要做成預制體

我的設定是:鼠標左鍵按下時,在攝像機正下方距地面一定高度處發射導彈,導彈速度方向要指向點擊到的三維場景中的坐標。這就要求從屏幕坐標轉換到世界坐標。我愁了兩天,終于找到了辦法。

 1     // 每幀調用一次,用于更新游戲場景和狀態(和物理狀態有關的更新應放在FixedUpdate里)
 2     void Update () {
 3         if (Input.GetMouseButtonDown(0)) { shooting = true; }
 4         if (Input.GetMouseButtonUp(0)) { shooting = false; }
 5         
 6         if (shooting) {
 7             elapsedInterval += Time.deltaTime;
 8             if (elapsedInterval >= shootInterval) {
 9                 elapsedInterval = 0;
10                 Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);//從攝像機發出到點擊坐標的射線
11                 RaycastHit hitInfo;
12                 if (Physics.Raycast(ray, out hitInfo)) {
13                     /*
14                     Debug.DrawLine(ray.origin, hitInfo.point);//劃出射線,只有在scene視圖中才能看到
15                     GameObject gameObj = hitInfo.collider.gameObject;
16                     Debug.Log(string.Format("Clicked object's name: {0}", gameObj.name));
17                     */
18                     Transform missile = Instantiate(missilePrefab) as Transform;
19                     Vector3 position = Camera.main.transform.position;
20                     missile.position = new Vector3(position.x, position.y * 2 / 3, position.z);
21                     Vector3 dirPos = (hitInfo.point - missile.position);
22                     dirPos.Normalize();
23                     missile.gameObject.rigidbody.velocity = dirPos * 150;
24                     SoundEffectHelper.Instance.MakePlayerShotSound();
25                 }
26             }
27         }
28     }
ShootMissile.cs

這時你會發現,導彈雖然按照要求的方向走了,但是全部是像螃蟹一樣橫著飛過去的。這不科學。所以要把導彈的旋轉方向調整到飛行方向。這個問題我又琢磨了一天,找到了辦法。

 1     static readonly Vector3 missileInitialRotation = new Vector3(-1, 0, 0);
 2     // 每幀調用一次,用于更新游戲場景和狀態(和物理狀態有關的更新應放在FixedUpdate里)
 3     void Update () {
 4         if (Input.GetMouseButtonDown(0)) { shooting = true; }
 5         if (Input.GetMouseButtonUp(0)) { shooting = false; }
 6         
 7         if (shooting) {
 8             elapsedInterval += Time.deltaTime;
 9             if (elapsedInterval >= shootInterval) {
10                 elapsedInterval = 0;
11                 Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);//從攝像機發出到點擊坐標的射線
12                 RaycastHit hitInfo;
13                 if (Physics.Raycast(ray, out hitInfo)) {
14                     /*
15                     Debug.DrawLine(ray.origin, hitInfo.point);//劃出射線,只有在scene視圖中才能看到
16                     GameObject gameObj = hitInfo.collider.gameObject;
17                     Debug.Log(string.Format("Clicked object's name: {0}", gameObj.name));
18                     */
19                     Transform missile = Instantiate(missilePrefab) as Transform;
20                     Vector3 position = Camera.main.transform.position;
21                     missile.position = new Vector3(position.x, position.y * 2 / 3, position.z);
22                     Vector3 dirPos = (hitInfo.point - missile.position);
23                     dirPos.Normalize();
24                     missile.rotation = Quaternion.FromToRotation( //從螃蟹式到科學式,需要這樣的旋轉。
25                         missileInitialRotation, //螃蟹式的旋轉向量
26                         dirPos); //科學式的旋轉向量
27                     missile.gameObject.rigidbody.velocity = dirPos * 150;
28                     SoundEffectHelper.Instance.MakePlayerShotSound();
29                 }
30             }
31         }
32     }
ShootMissile.cs

 

目前的缺點

如你所見,有的坦克由于前后撞擊加上地形起伏,竟然飛了起來。我已經用代碼和物理屬性調整過,但還是沒有完全消除這種情況。

導彈尾焰和爆炸效果還不是很理想。

沒有開始、存檔、選項等菜單,沒有我方HP、關卡、敵方剩余坦克數等信息。

敵方坦克還不會開炮。(欺負人。。。)

敵方坦克只知道向右(Z軸正方向)走,沒有一點AI。

導彈只能攻擊命中的坦克,對附近的坦克沒有波及傷害。

總結

有了Unity3D,做游戲涉及的很多算法都不需要自己寫了。Unity3D對提高生產效率的確有非常大的幫助。

需要源代碼和發布的Windows版、網頁版程序的同學麻煩支付100元并留下你的郵箱~


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()