自制Unity小游戲TankHero-2D(2)制作敵方坦克


自制Unity小游戲TankHero-2D(2)制作敵方坦克

我在做這樣一個坦克游戲,是仿照(http://game.kid.qq.com/a/20140221/028931.htm)這個游戲制作的。僅為學習Unity之用。圖片大部分是自己畫的,少數是從網上搜來的。您可以到我的github頁面(https://github.com/bitzhuwei/TankHero-2D)上得到工程源碼。

clip_image002

本篇主要記錄制作敵方坦克(Tank1)的一些重點。

原本制作敵方坦克是很簡單的,只要把TankHero復制一份,改改貼圖就差不多了。不過考慮到代碼的簡潔和可重用,本篇花了些心思在重構上。

關於自定義鼠標箭頭

上一篇介紹了如何自定義鼠標箭頭的事。這里補個漏。經過上一篇的研究,已經可以顯示自定義的鼠標樣式了,但是原有的鼠標箭頭仍然存在,這怎么辦?容易,只需制作一個1*1像素的全透明的png圖片,賦給Default Cursor即可。實際上就是讓默認鼠標樣式透明掉。

clip_image003

敵方坦克模型

clip_image004

這個模型依舊是用PPT做的。SmartArt+“形狀”解決問題。具體技巧可參考上一篇

clip_image006clip_image008

敵方坦克結構

clip_image009

如上圖所示,使用Duplicate從TankHero復制一個,重命名為Tank1。Tank1就是我們要做的敵方坦克了。其炮塔、底座、炮彈起始點這些結構都是一樣的。

重構坦克運動代碼

玩家坦克和敵方坦克有很多共同點,比如坦克對象的結構。也有一些特征(移動、旋轉、開炮等)既相似又不同。具體來說,玩家坦克是由鼠標鍵盤指揮的,敵方坦克則要由AI指揮。指揮者不同,但是指揮的效果都是移動旋轉開炮,是可以用同樣的代碼處理的。所以我在這里抽象出一個專門保存指揮信息的Movement類,這樣就隔開了指揮者與執行者。

clip_image010

PlayerMovement接收用戶輸入的信息,存到基類的字段。Tank1Movement用AI獲取指揮信息,存到基類的字段。

clip_image011

clip_image012

這樣一來,在其它地方(不同類型的坦克的炮塔、底座)就都可以用 Movement m = this.GetComponentXXX<PlayerMovement>(); 這樣統一的方式獲取平移、旋轉、目標、目的地等信息了。

下面我們來詳細介紹。

底座的旋轉和輪子滾動

兩種坦克的底座部分,只有輪子滾動部分是不同的。兩者使用的腳本則都是TankBaseRotation和WheelMovement。

clip_image013

WheelMovement代碼沒有任何改變,只不過在Inspector里的Wheels數組元素不同而已。

在TankBaseRotation中則出現了這樣的代碼:

1     private Movement movementScript;
2 
3     void Awake()
4     {
5         movementScript = this.GetComponentInParent<Movement> ();
6     }

 

有了 movementScript 就可以得到坦克的移動方向 movementScript.baseDirection ,就可以更新坦克底座的旋轉角度了。

 1     void Update () {
 2         if (movementScript == null) { return; }
 3 
 4         var angle = Mathf.Atan2 (movementScript.baseDirection.y, movementScript.baseDirection.x) * Mathf.Rad2Deg;
 5         if (Mathf.Abs(angle - this.targetAngle) > 0.01f)
 6         {
 7             this.targetAngle = angle;
 8             this.targetRotation = Quaternion.Euler (0, 0, angle);
 9         }
10 
11         this.transform.rotation = Quaternion.Slerp (
12             this.transform.rotation,
13             Quaternion.Euler (0, 0, angle),
14             rotationSpeed * Time.deltaTime);
15     }

 

炮塔的旋轉和武器管理

這樣就無需為兩種坦克寫兩套旋轉底座的腳本了。以后添加了新型坦克也仍然只需這一個腳本。

clip_image014

TankHero和Tank1的炮塔旋轉中心(Rotation Center)和武器(Weapons)不同,但他們使用了相同的腳本(TankHeadRotation和WeaponManager)。這兩個腳本中也都有如下的代碼。

1     private Movement movementScript;
2 
3     void Awake()
4     {
5         this.movementScript = this.GetComponentInParent<Movement> ();
6     }

 

在movementScript中保存着目標的位置( fireTarget ),旋轉炮塔也很容易。

 1     void Update () {
 2         if (this.movementScript == null) { return; }
 3 
 4         var y = this.movementScript.fireTarget.y - this.transform.position.y;
 5         var x = this.movementScript.fireTarget.x - this.transform.position.x;
 6         if (Mathf.Abs(y) > Quaternion.kEpsilon || Mathf.Abs(x) > Quaternion.kEpsilon)
 7         {
 8             this.targetAngle = Mathf.Atan2(y, x) * Mathf.Rad2Deg;
 9             var angle = this.targetAngle - this.transform.rotation.eulerAngles.z; 
10             this.transform.RotateAround (this.rotationCenter.position, new Vector3 (0, 0, 1), angle);
11         }
12     }

 


TankHero是玩家用鼠標開炮的,敵方坦克是自動開炮的。用 autoFire 標記坦克是否自動開炮即可。

 

 1     void Update () {
 2         passedInterval += Time.deltaTime * 10;
 3         if (passedInterval >= currentWeaponConfig.interval)
 4         {
 5             if (this.autoFire || Input.GetButton("Fire1"))
 6             {
 7                 passedInterval = 0;
 8                 var bullet = Instantiate(currentBullet, bulletStartPosition.position, this.transform.rotation) as Transform;
 9                 bullet.renderer.enabled = true;
10                 var bulletFly = bullet.GetComponent<BulletFly>();
11                 bulletFly.undying = false;
12                 bulletFly.velocity = currentWeaponConfig.velocity;
13                 bulletFly.shooter = this.gameObject;
14                 bulletFly.targetPosition = movementScript.fireTarget;
15             }
16         }
17     }

 武器系統

clip_image015

有了新的坦克,我們需要給它設計新的武器。只需Duplicate一下NormalBulletWeapon,再在WeaponConfig組件里調整一下敵方坦克武器的參數(炮彈速度調慢一點,不然敵人就太厲害了)。將新武器EnemyNormalBulletWeapon賦給Tank1的炮塔即可。

clip_image016

炮彈仍然是原有的那個,只需換一個貼圖即可。

clip_image017

多種炮彈

玩過(http://game.kid.qq.com/a/20140221/028931.htm)的會發現有多種炮彈。其速度、攻擊形式都不一樣。

clip_image018

就是說,在不同類型的炮彈碰撞到某物時,會發生不同的事。因此我對控制炮彈飛行的腳本進行了抽象,在具體的子類里編寫 Trigger 碰撞事件,用以處理不同的炮彈。

clip_image019

注意:如果在子類(NormalBulletFly)你添加了 void Update(); 方法,那么Unity就不會調用父類(BulletFly)的Update方法了。這對 Awake 等都適用。就是說,Unity引擎只查找那些在繼承層次上離MonoBehaviour最遠的事件函數,找到之后就不再理會其它層上的同名函數了。

為什么是最遠的?因為一個gameobject,其具有NormalBulletFly這個組件,意思是此gameobject擁有一個類型為NormalBulletFly的實例。很自然地,Unity會選中此類型的方法表中的Update方法。只有在NormalBulletFly中不存在時,才會輪到其父類的方法表。

當然目前為止只有1種炮彈,所以只有1個具體的NormalBulletFly腳本。這樣,以后無論有多少種炮彈,只需一個Bullet的prefab即可。

clip_image020

總結

坦克的運動和炮彈的攻擊,我都進行了重構。重構的目的是為了將重復的代碼(平移、旋轉、開炮、飛行)合並到一處,不同的代碼(用戶輸入vs AI控制,不同的攻擊方式)分別寫如不同的腳本。重構的技術就是面向對象設計。

您可以到我的github頁面(https://github.com/bitzhuwei/TankHero-2D)上得到工程源碼。

請多多指教~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM