自制Unity小游戲TankHero-2D(2)制作敵方坦克
我在做這樣一個坦克游戲,是仿照(http://game.kid.qq.com/a/20140221/028931.htm)這個游戲制作的。僅為學習Unity之用。圖片大部分是自己畫的,少數是從網上搜來的。您可以到我的github頁面(https://github.com/bitzhuwei/TankHero-2D)上得到工程源碼。
本篇主要記錄制作敵方坦克(Tank1)的一些重點。
原本制作敵方坦克是很簡單的,只要把TankHero復制一份,改改貼圖就差不多了。不過考慮到代碼的簡潔和可重用,本篇花了些心思在重構上。
關於自定義鼠標箭頭
上一篇介紹了如何自定義鼠標箭頭的事。這里補個漏。經過上一篇的研究,已經可以顯示自定義的鼠標樣式了,但是原有的鼠標箭頭仍然存在,這怎么辦?容易,只需制作一個1*1像素的全透明的png圖片,賦給Default Cursor即可。實際上就是讓默認鼠標樣式透明掉。
敵方坦克模型
這個模型依舊是用PPT做的。SmartArt+“形狀”解決問題。具體技巧可參考上一篇。
敵方坦克結構
如上圖所示,使用Duplicate從TankHero復制一個,重命名為Tank1。Tank1就是我們要做的敵方坦克了。其炮塔、底座、炮彈起始點這些結構都是一樣的。
重構坦克運動代碼
玩家坦克和敵方坦克有很多共同點,比如坦克對象的結構。也有一些特征(移動、旋轉、開炮等)既相似又不同。具體來說,玩家坦克是由鼠標鍵盤指揮的,敵方坦克則要由AI指揮。指揮者不同,但是指揮的效果都是移動旋轉開炮,是可以用同樣的代碼處理的。所以我在這里抽象出一個專門保存指揮信息的Movement類,這樣就隔開了指揮者與執行者。
PlayerMovement接收用戶輸入的信息,存到基類的字段。Tank1Movement用AI獲取指揮信息,存到基類的字段。
這樣一來,在其它地方(不同類型的坦克的炮塔、底座)就都可以用 Movement m = this.GetComponentXXX<PlayerMovement>(); 這樣統一的方式獲取平移、旋轉、目標、目的地等信息了。
底座的旋轉和輪子滾動
兩種坦克的底座部分,只有輪子滾動部分是不同的。兩者使用的腳本則都是TankBaseRotation和WheelMovement。
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 }
炮塔的旋轉和武器管理
這樣就無需為兩種坦克寫兩套旋轉底座的腳本了。以后添加了新型坦克也仍然只需這一個腳本。
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 }
武器系統
有了新的坦克,我們需要給它設計新的武器。只需Duplicate一下NormalBulletWeapon,再在WeaponConfig組件里調整一下敵方坦克武器的參數(炮彈速度調慢一點,不然敵人就太厲害了)。將新武器EnemyNormalBulletWeapon賦給Tank1的炮塔即可。
炮彈仍然是原有的那個,只需換一個貼圖即可。
多種炮彈
玩過(http://game.kid.qq.com/a/20140221/028931.htm)的會發現有多種炮彈。其速度、攻擊形式都不一樣。
就是說,在不同類型的炮彈碰撞到某物時,會發生不同的事。因此我對控制炮彈飛行的腳本進行了抽象,在具體的子類里編寫 Trigger 碰撞事件,用以處理不同的炮彈。
注意:如果在子類(NormalBulletFly)你添加了 void Update(); 方法,那么Unity就不會調用父類(BulletFly)的Update方法了。這對 Awake 等都適用。就是說,Unity引擎只查找那些在繼承層次上離MonoBehaviour最遠的事件函數,找到之后就不再理會其它層上的同名函數了。
為什么是最遠的?因為一個gameobject,其具有NormalBulletFly這個組件,意思是此gameobject擁有一個類型為NormalBulletFly的實例。很自然地,Unity會選中此類型的方法表中的Update方法。只有在NormalBulletFly中不存在時,才會輪到其父類的方法表。
當然目前為止只有1種炮彈,所以只有1個具體的NormalBulletFly腳本。這樣,以后無論有多少種炮彈,只需一個Bullet的prefab即可。
總結
坦克的運動和炮彈的攻擊,我都進行了重構。重構的目的是為了將重復的代碼(平移、旋轉、開炮、飛行)合並到一處,不同的代碼(用戶輸入vs AI控制,不同的攻擊方式)分別寫如不同的腳本。重構的技術就是面向對象設計。
您可以到我的github頁面(https://github.com/bitzhuwei/TankHero-2D)上得到工程源碼。
請多多指教~