教程基於http://pixelnest.io/tutorials/2d-game-unity/ , 這個例子感受仍是比較經典的, 網上轉載的也比較多. 恰好最近也在學習U3D, 作的過程當中本身又修改了一些地方, 寫篇文和你們一塊兒分享下, 同時也加深記憶. 有什麼紕漏的地方還請你們多包涵.html
1.建立第一個場景dom
新建工程,在Project面板建立文件夾, 是爲了更好的規劃管理資源文件.ide
接着在Hierarchy面板上建立多個空對象(這樣的結構也是清晰了整個遊戲的層次, 對象之間的關係一目瞭然), 這些空對象的Position保持(0,0,0)便可. 保存場景到Scenes文件夾中, 名稱爲Stage1.oop
2.添加顯示背景學習
將背景圖片放入Textures文件夾, 確認這張圖片的紋理類型Texture Type爲Sprite.測試
在場景中添加一個Sprite遊戲對象,命名爲Background1,設置Sprite屬性爲剛纔導入的背景圖片, 將它移動到0 - Background中, 設置Position爲(0,0,0).動畫
接着添加背景元素. 導入平臺島嶼圖片到Textures文件夾, 選中Platforms圖片, 設置它的Sprite Mode爲Multiple, 而後點擊Sprite Editor, 以下圖:ui
在彈出的Sprite Editor窗口中, 進行繪製每一個平臺島嶼的包圍矩形, 以便將紋理分隔成更小的部分. 而後分別命名爲platform1和platform2.this
建立一個新的Sprite對象, 設置它的Sprite爲platform1. 而後一樣再建立一個Sprite對象, 設置Sprite爲platform2. 將它們放置到1 - Middleground對象裏, 而且確認他們的Z座標爲0. 設置完成後, 將這兩個對象從Hierarchy面板拖動到Project面板下的Prefabs文件夾, 保存爲預製對象. 接着, 爲了不顯示順序問題, 修改下游戲對象的Z座標, 以下所示:spa
Layer | Z Position |
0 - Background | 10 |
1 - Middleground | 5 |
2 - Foreground | 0 |
此時, 點擊Scene面板上的2D到3D視圖切換, 能夠清除的看到層次:
3.建立玩家和敵人
導入主角圖片到Textures文件夾, 建立一個Sprite對象, 命名爲Player, 設置其Sprite屬性爲剛纔導入的主角圖片. 將它放入2 - Foreground中, 設置Scale爲(0.2,0.2,1). 接着, 爲主角添加碰撞機, 點擊Add Component按鈕, 選擇Box Collider 2D, 設置Size爲(10,10), 雖然大於實際區域, 可是已經比圖片小多了.
可是我更願意去使用Polygon Collider 2D來達到更精緻的效果, 這裏只是個例子, 你們能夠自由選擇.
接着, 再爲主角對象添加Rigidbody 2D剛體組件, 如今運行能夠看到以下結果:
能夠看到主角往下落了, 這是由於剛體帶有重力, 但在這個遊戲中咱們用不到重力, 將Gravity Scale設置爲0便可. 另外, 不想由於物理而引發的主角旋轉, 則將Fixed Angles勾選上.
開始準備讓主角移動. 在Scripts文件夾中, 建立一個C#腳本, 名稱爲PlayerScript, 實現讓方向鍵移動主角, 代碼以下:
using UnityEngine; using System.Collections; /// <summary> /// 玩家控制器和行爲 /// </summary> public class PlayerScript : MonoBehaviour { #region 1 - 變量 /// <summary> /// 飛船移動速度 /// </summary> private Vector2 speed = new Vector2(50, 50); // 存儲運動 private Vector2 movement; #endregion // Update is called once per frame void Update() { #region 運動控制 // 2 - 獲取軸信息 float inputX = Input.GetAxis("Horizontal"); float inputY = Input.GetAxis("Vertical"); // 3 - 保存運動軌跡 movement = new Vector2(speed.x * inputX, speed.y * inputY); #endregion } void FixedUpdate() { // 4 - 讓遊戲物體移動 rigidbody2D.velocity = movement; } }
這裏以改變剛體的速率來達到主角移動的效果, 而不是經過直接改變transform.Translate, 由於那樣的話, 可能會不產生碰撞. 另外, 這裏有人可能會疑問爲何實現移動的代碼要寫在FixedUpdate而不是Update中, 請看Update和FixedUpdate的區別: 傳送門.
如今將此腳本附加到主角對象上, 點擊運行, 方向鍵來控制移動.
接下來, 添加第一個敵人. 導入章魚敵人圖片到Textures文件夾, 建立一個Sprite對象, 命名爲Poulpi, 設置Sprite爲剛纔導入的章魚圖片, 設置Scale爲(0.4,0.4,1), 添加碰撞機(Polygon Collider 2D或Box Collider 2D均可以), 若是是Box Collider 2D, 設置Size爲(4,4), 添加Rigidbody 2D組件, 設置Gravity Scale爲0, 而且勾選Fixed Angles屬性. 設置完成後, 將對象保存爲預製. 在這裏只讓章魚簡單的往前行走, 建立一個腳本, 命名爲MoveScript, 代碼以下:
using UnityEngine; using System.Collections; /// <summary> /// 當前遊戲對象簡單的移動行爲 /// </summary> public class MoveScript : MonoBehaviour { #region 1 - 變量 /// <summary> /// 物體移動速度 /// </summary> public Vector2 speed = new Vector2(10, 10); /// <summary> /// 移動方向 /// </summary> public Vector2 direction = new Vector2(-1, 0); private Vector2 movement; #endregion // Use this for initialization void Start() { } // Update is called once per frame void Update() { // 2 - 保存運動軌跡 movement = new Vector2(speed.x * direction.x, speed.y * direction.y); } void FixedUpdate() { // 3 - 讓遊戲物體移動 rigidbody2D.velocity = movement; } }
將此腳本附加到章魚對象上, 如今運行嗎能夠看到章魚往前移動, 如圖:
此時若是主角和章魚發生碰撞, 會互相阻塞對方的移動.
4.射擊
導入子彈圖片到"Textures"文件夾,建立一個Sprite遊戲對象,命名爲"PlayerShot",設置其"Sprite"屬性爲剛纔導入的圖片,設置"Scale"屬性爲(0.75, 0.75, 1),添加"Rigidbody 2D"組件,其"Gravity Scale"屬性爲0,而且勾選"Fixed Angles"屬性框,添加"Box Collider 2D"組件,其Size爲(1, 1),而且勾選"IsTrigger"屬性。勾選"IsTrigger"屬性表示該碰撞體用於觸發事件,並將被物理引擎所忽略。意味着,子彈將穿過觸碰到的對象,而不會阻礙對象的移動,觸碰的時候將會引起"OnTriggerEnter2D"事件。建立一個腳本,命名爲"ShotScript",代碼以下:
using UnityEngine; using System.Collections; /// <summary> /// 子彈行爲 /// </summary> public class ShotScript : MonoBehaviour { #region 1 - 變量 /// <summary> /// 形成傷害 /// </summary> public int damage = 1; /// <summary> /// 子彈歸屬 , true-敵人的子彈, false-玩家的子彈 /// </summary> public bool isEnemyShot = false; #endregion // Use this for initialization void Start() { // 2 - 爲避免任何泄漏,只給予有限的生存時間.[20秒] Destroy(gameObject, 20); } }
將此腳本附加到子彈對象上,而後將"MoveScript"腳本也附加到子彈對象上以即可以移動。保存此對象爲預製。接着,讓碰撞產生傷害效果。建立一個腳本,命名爲"HealthScript",代碼以下:
using UnityEngine; using System.Collections; /// <summary> /// 處理生命值和傷害 /// </summary> public class HealthScript : MonoBehaviour { #region 1 - 變量 /// <summary> /// 總生命值 /// </summary> public int hp = 1; /// <summary> /// 敵人標識 /// </summary> public bool isEnemy = true; #endregion /// <summary> /// 對敵人形成傷害並檢查對象是否應該被銷燬 /// </summary> /// <param name="damageCount"></param> public void Damage(int damageCount) { hp -= damageCount; if (hp <= 0) { // 死亡! 銷燬對象! Destroy(gameObject); } } void OnTriggerEnter2D(Collider2D otherCollider) { ShotScript shot = otherCollider.gameObject.GetComponent<ShotScript>(); if (shot != null) { // 判斷子彈歸屬,避免誤傷 if (shot.isEnemyShot != isEnemy) { Damage(shot.damage); // 銷燬子彈 // 記住,老是針對遊戲的對象,不然你只是刪除腳本 Destroy(shot.gameObject); } } } }
將此腳本附加到Poulpi預製體上。如今運行,讓子彈和章魚碰撞,能夠看到以下結果:
若是章魚的生命值大於子彈的傷害值,那麼章魚就不會被消滅,能夠試着經過改變章魚對象的"HealthScript"的hp值。
接着,咱們來準備射擊。建立一個腳本,命名爲"WeaponScript",代碼以下:
using UnityEngine; using System.Collections; /// <summary> /// 發射子彈 /// </summary> public class WeaponScript : MonoBehaviour { #region 1 - 變量 /// <summary> /// 子彈預設 /// </summary> public Transform shotPrefab; /// <summary> /// 兩發子彈之間的發射間隔時間 /// </summary> public float shootingRate = 0.25f; /// <summary> /// 當前冷卻時間 /// </summary> private float shootCooldown; #endregion // Use this for initialization void Start() { // 初始化冷卻時間爲0 shootCooldown = 0f; } // Update is called once per frame void Update() { // 冷卻期間實時減小時間 if (shootCooldown > 0) { shootCooldown -= Time.deltaTime; } } /// <summary> /// 射擊 /// </summary> /// <param name="isEnemy">是不是敵人的子彈</param> public void Attack(bool isEnemy) { if (CanAttack) { if (isEnemy) { SoundEffectsHelper.Instance.MakeEnemyShotSound(); } else { SoundEffectsHelper.Instance.MakePlayerShotSound(); } shootCooldown = shootingRate; // 建立一個子彈 var shotTransform = Instantiate(shotPrefab) as Transform; // 指定子彈位置 shotTransform.position = transform.position; // 設置子彈歸屬 ShotScript shot = shotTransform.gameObject.GetComponent<ShotScript>(); if (shot != null) { shot.isEnemyShot = isEnemy; } // 設置子彈運動方向 MoveScript move = shotTransform.gameObject.GetComponent<MoveScript>(); if (move != null) { // towards in 2D space is the right of the sprite move.direction = this.transform.right; } } } /// <summary> /// 武器是否準備好再次發射 /// </summary> public bool CanAttack { get { return shootCooldown <= 0f; } } }
將這個腳本附加到主角對象上,設置其"Shot Prefab"屬性爲"PlayerShot"預製體。打開"PlayerScript"腳本,在Update()方法裏面,加入如下片斷:
// Update is called once per frame void Update() { #region 射擊控制 // 5 - 射擊 bool shoot = Input.GetButtonDown("Fire1"); shoot |= Input.GetButtonDown("Fire2"); // 當心:對於Mac用戶,按Ctrl +箭頭是一個壞主意 if (shoot) { WeaponScript weapon = GetComponent<WeaponScript>(); if (weapon != null) { weapon.Attack(false); } } #endregion }
當收到射擊的按鈕狀態,調用Attack(false)方法。如今運行,能夠看到以下結果:
接下來,準備建立敵人的子彈。導入敵人子彈圖片到"Textures"文件夾,選中"PlayerShot"預製體,按下Ctrl+D進行復制,命名爲"EnemyShot1",而後改變其Sprite爲剛纔導入的圖片,設置其Scale爲(0.35, 0.35, 1)。接着,讓章魚能夠射擊。將"WeaponScript"腳本附加到章魚對象上,拖動"EnemyShot1"預製體到其"Shot Prefab"屬性,建立一個腳本,命名爲"EnemyScript",用來簡單地每一幀進行自動射擊,代碼以下:
using UnityEngine; using System.Collections; /// <summary> /// 敵人通用行爲 /// </summary> public class EnemyScript : MonoBehaviour { #region 變量 private WeaponScript weapon; #endregion void Awake() { // 只檢索一次武器 weapon = GetComponent<WeaponScript>(); } // Update is called once per frame void Update() { // 自動開火 if (weapon != null && weapon.CanAttack) { weapon.Attack(true); } } }
將此腳本附加到章魚對象上,如今運行,能夠看到以下結果:
能夠看到章魚向右射擊了子彈,這是由於代碼就是讓它那麼作的。實際上,應該作到能夠朝向任何方向射擊子彈。修改"WeaponScript"中的Attack方法,代碼爲以下:
/// <summary> /// 射擊 /// </summary> /// <param name="isEnemy">是不是敵人的子彈</param> public void Attack(bool isEnemy) { if (CanAttack) { if (isEnemy) { SoundEffectsHelper.Instance.MakeEnemyShotSound(); } else { SoundEffectsHelper.Instance.MakePlayerShotSound(); } shootCooldown = shootingRate; // 建立一個子彈 var shotTransform = Instantiate(shotPrefab) as Transform; // 指定子彈位置 shotTransform.position = transform.position; // 設置子彈歸屬 ShotScript shot = shotTransform.gameObject.GetComponent<ShotScript>(); if (shot != null) { shot.isEnemyShot = isEnemy; } // 設置子彈方向 MoveScript move = shotTransform.gameObject.GetComponent<MoveScript>(); if (move != null) { // towards in 2D space is the right of the sprite //move.direction = move.direction; // 若是是敵人的子彈, 則改變方向和移動速度 if (shot.isEnemyShot) { move.direction.x = -1f; move.speed = new Vector2(15, 15); } else { move.direction.x = 1f; move.speed = new Vector2(10, 10); } } } }
能夠適當調整子彈的移動速度,它應該快於章魚的移動速度。如今運行,以下圖所示:
目前,當主角和章魚碰撞時,僅僅只是阻礙對方的移動而已,在這裏改爲互相受到傷害。打開"PlayerScript"文件,添加以下代碼:
void OnCollisionEnter2D(Collision2D collision) { if (collision.gameObject.ToString().IndexOf("Poulpi") >= 0) { bool damagePlayer = false; // 與敵人發生碰撞 EnemyScript enemy = collision.gameObject.GetComponent<EnemyScript>(); if (enemy != null) { // 殺死敵人 HealthScript enemyHealth = enemy.GetComponent<HealthScript>(); if (enemyHealth != null) { enemyHealth.Damage(enemyHealth.hp); } damagePlayer = true; } // 玩家也受到傷害 if (damagePlayer) { HealthScript playerHealth = this.GetComponent<HealthScript>(); if (playerHealth != null) { playerHealth.Damage(1); } } } }
5.視差卷軸效果
爲了達到這種視差卷軸的效果,可讓背景層以不一樣的速度進行移動,越遠的層,移動地越慢。若是操做得當,這能夠形成深度的錯覺,這將很酷,又是能夠容易作到的效果。在這裏存在兩個滾動:
一個循環的背景將在水平滾動的時候,一遍又一遍的重複進行顯示。現有的層以下:
Layer | Loop | Position |
0 - Background | Yes | (0, 0, 10) |
1 - Middleground | No | (0, 0, 5) |
2 - Foreground | No | (0, 0, 0) |
接下來,實現無限背景。當左側的背景對象遠離了攝像機的左邊緣,那麼就將它移到右側去,一直這樣無限循環,以下圖所示:
要作到檢查的對象渲染器是否在攝像機的可見範圍內,須要一個類擴展。建立一個C#文件,命名爲"RendererExtensions.cs",代碼以下:
using UnityEngine; using System.Collections; /// <summary> /// 渲染擴展 /// </summary> public static class RendererExtensions { /// <summary> /// 檢查對象渲染器是否在攝像機的可見範圍內 /// </summary> /// <param name="renderer">渲染對象</param> /// <param name="camera">攝像機</param> /// <returns></returns> public static bool IsVisibleFrom(this Renderer renderer, Camera camera) { Plane[] planes = GeometryUtility.CalculateFrustumPlanes(camera); return GeometryUtility.TestPlanesAABB(planes, renderer.bounds); } }
接下來,能夠開始實現不帶背景循環的滾動。建立一個腳本,命名爲"ScrollingScript",代碼以下:
using UnityEngine; using System.Collections; using System.Collections.Generic; using System.Linq; /// <summary> /// 背景視差滾動腳本 /// </summary> public class ScrollingScript : MonoBehaviour { #region 變量 /// <summary> /// 滾動速度 /// </summary> public Vector2 speed = new Vector2(2, 2); /// <summary> /// 移動方向 /// </summary> public Vector2 direction = new Vector2(-1, 0); /// <summary> /// 相機是否運動 /// </summary> public bool isLinkedToCamera = false; /// <summary> /// 背景是否循環 /// </summary> public bool isLooping = false; /// <summary> /// 渲染對象名單 /// </summary> private List<Transform> backgroundPart; #endregion // Use this for initialization void Start() { // 只循環背景 if (isLooping) { // 獲取該層渲染器的全部子集對象 backgroundPart = new List<Transform>(); for (int i = 0; i < transform.childCount; i++) { Transform child = transform.GetChild(i); // 只添加可見子集 if (child.renderer != null) { backgroundPart.Add(child); } } // 根據位置排序 // Note: 根據從左往右的順序獲取子集對象 // 咱們須要增長一些條件來處理全部可能的滾動方向。 backgroundPart = backgroundPart.OrderBy(t => t.position.x).ToList(); } } // Update is called once per frame void Update() { // 建立運動狀態 Vector3 movement = new Vector3(speed.x * direction.x, speed.y * direction.y, 0); movement *= Time.deltaTime; transform.Translate(movement); // 移動相機 if (isLinkedToCamera) { Camera.main.transform.Translate(movement); } // 循環 if (isLooping) { // 獲取第一個對象 // 該列表的順序是從左往右(基於x座標) Transform firstChild = backgroundPart.FirstOrDefault(); if (firstChild != null) { // 檢查子集對象(部分)是否在攝像機前已準備好. // We test the position first because the IsVisibleFrom // method is a bit heavier to execute. if (firstChild.position.x < Camera.main.transform.position.x) { // 若是子集對象已經在攝像機的左側,咱們測試它是否徹底在外面,以及是否須要被回收. if (firstChild.renderer.IsVisibleFrom(Camera.main) == false) { // 獲取最後一個子集對象的位置 Transform lastChild = backgroundPart.LastOrDefault(); Vector3 lastPosition = lastChild.transform.position; Vector3 lastSize = (lastChild.renderer.bounds.max - lastChild.renderer.bounds.min); // 將被回收的子集對象做爲最後一個子集對象 // Note: 當前只橫向滾動. firstChild.position = new Vector3(lastPosition.x + lastSize.x, firstChild.position.y, firstChild.position.z); // 將被回收的子集對象設置到backgroundPart的最後位置. backgroundPart.Remove(firstChild); backgroundPart.Add(firstChild); } } } } } }
在Start方法裏,使用了LINQ將它們按X軸進行排序。
Layer | Speed | Direction | Linked to Camera |
0 - Background | (1, 1) | (-1, 0, 0) | No |
1 - Middleground | (2.5, 2.5) | (-1, 0, 0) | No |
Player | (1, 1) | (1, 0, 0) | Yes |
將"0 - Background"對象裏的"ScrollingScript"組件的"Is Looping"屬性勾選,如今運行,就能夠看到視差卷軸的效果,以下圖所示:
接下來,修改敵人腳本,讓敵人靜止不動,且無敵,直到攝像機看到它們。另外,當它們移出屏幕時,則馬上移除它們。修改"EnemyScript"腳本,代碼爲以下:
using UnityEngine; using System.Collections; /// <summary> /// 敵人通用行爲 /// </summary> public class EnemyScript : MonoBehaviour { #region 變量 /// <summary> /// 是否登場 /// </summary> private bool hasSpawn; private MoveScript moveScript; private WeaponScript weapon; #endregion void Awake() { // 只檢索一次武器 weapon = GetComponent<WeaponScript>(); // 當未登場的時候檢索腳本以禁用 moveScript = GetComponent<MoveScript>(); } // Use this for initialization void Start() { hasSpawn = false; // 禁止一切 // -- 碰撞機 collider2D.enabled = false; // -- 移動 moveScript.enabled = false; // -- 射擊 weapon.enabled = false; } // Update is called once per frame void Update() { // 檢查敵人是否登場 if (hasSpawn == false) { if (renderer.IsVisibleFrom(Camera.main)) { Spawn(); } } else { // 自動開火 if (weapon != null && weapon.enabled && weapon.CanAttack) { weapon.Attack(true); } // 超出攝像機視野,則銷燬對象 if (renderer.IsVisibleFrom(Camera.main) == false) { Destroy(gameObject); } } } /// <summary> /// 激活自身 /// </summary> private void Spawn() { hasSpawn = true; // 啓用一切 // -- 碰撞機 collider2D.enabled = true; // -- 移動 moveScript.enabled = true; // -- 射擊 weapon.enabled = true; } }
在遊戲過程當中,能夠發現主角並非限制在攝像機區域內的,能夠隨意離開攝像機,如今來修復這個問題。打開"PlayerScript"腳本,在Update方法裏面添加以下代碼:
#region 確保沒有超出攝像機邊界 var dist = (transform.position - Camera.main.transform.position).z; var leftBorder = Camera.main.ViewportToWorldPoint(new Vector3(0, 0, dist)).x; var rightBorder = Camera.main.ViewportToWorldPoint(new Vector3(1, 0, dist)).x; var topBorder = Camera.main.ViewportToWorldPoint(new Vector3(0, 0, dist)).y; var bottomBorder = Camera.main.ViewportToWorldPoint(new Vector3(0, 1, dist)).y; transform.position = new Vector3( Mathf.Clamp(transform.position.x, leftBorder, rightBorder), Mathf.Clamp(transform.position.y, topBorder, bottomBorder), transform.position.z ); #endregion
6.粒子效果
製做一個爆炸的粒子,用於敵人或者主角被摧毀時進行顯示。建立一個"Particle System",導入煙圖片到"Textures"文件夾,改變其"Texture Type"爲"Texture",而且勾選"Alpha Is Transparent"屬性,附加這個紋理到粒子上,將其拖動到粒子對象上,更改其Shader爲"Particles"→"Alpha Blended",接着更改一些屬性,以下所示:
Category | Parameter name | Value |
General | Duration | 1 |
General | Max Particles | 15 |
General | Start Lifetime | 1 |
General | Start Color | Gray |
General | Start Speed | 3 |
General | Start Size | 2 |
Emission | Bursts | 0 : 15 |
Shape | Shape | Sphere |
Color Over Lifetime | Color | 見下圖 |
Size Over Lifetime | Size | 見下圖 |
其中Color Over Lifetime要設置成在結束時,有個淡出的效果,以下圖所示:
Size Over Lifetime選擇一個遞減曲線,以下圖所示:
當調整完成後,取消勾選"Looping",如今粒子效果爲以下:
保存成預製,命名爲"SmokeEffect",放在"Prefabs/Particles"文件夾下。如今建立另外一個粒子,火焰效果,使用默認材質便可。其餘設置以下:
Category | Parameter name | Value |
General | Looping | false |
General | Duration | 1 |
General | Max Particles | 10 |
General | Start Lifetime | 1 |
General | Start Speed | 0.5 |
General | Start Size | 2 |
Emission | Bursts | 0 : 10 |
Shape | Shape | Box |
Color Over Lifetime | Color | 見下圖 |
其中Color Over Lifetime要設置成有一個黃色到橙色的漸變,最後淡出,以下圖所示:
粒子效果爲:
保存成預製,命名爲"FireEffect"。建立一個腳本,命名爲"SpecialEffectsHelper",代碼以下:
using UnityEngine; using System.Collections; /// <summary> /// 從代碼中建立粒子特效 /// </summary> public class SpecialEffectsHelper : MonoBehaviour { /// <summary> /// Singleton /// </summary> public static SpecialEffectsHelper Instance; public ParticleSystem smokeEffect; public ParticleSystem fireEffect; void Awake() { // Register the singleton if (Instance != null) { Debug.LogError("Multiple instances of SpecialEffectsHelper!"); } Instance = this; } // Use this for initialization void Start() { } // Update is called once per frame void Update() { } /// <summary> /// 在給定位置建立爆炸特效 /// </summary> /// <param name="position"></param> public void Explosion(Vector3 position) { // 煙霧特效 instantiate(smokeEffect, position); // 火焰特效 instantiate(fireEffect, position); } /// <summary> /// 從預製體中實例化粒子特效 /// </summary> /// <param name="prefab"></param> /// <returns></returns> private ParticleSystem instantiate(ParticleSystem prefab, Vector3 position) { ParticleSystem newParticleSystem = Instantiate(prefab, position, Quaternion.identity) as ParticleSystem; // 確保它會被銷燬 Destroy(newParticleSystem.gameObject, newParticleSystem.startLifetime); return newParticleSystem; } }
這裏建立了一個單例,可讓任何地方均可以產生煙和火焰的粒子。將這個腳本附加到"Scripts"對象,設置其屬性"Smoke Effect"和"Fire Effect"爲對應的預製體。如今是時候調用這個腳本了,打開"HealthScript"腳本文件,在OnTriggerEnter方法裏面,更新成以下代碼:
/// <summary> /// 對敵人形成傷害並檢查對象是否應該被銷燬 /// </summary> /// <param name="damageCount"></param> public void Damage(int damageCount) { hp -= damageCount; if (hp <= 0) { // 爆炸特效 SpecialEffectsHelper.Instance.Explosion(transform.position); // 播放音效 SoundEffectsHelper.Instance.MakeExplosionSound(); // 死亡! 銷燬對象! Destroy(gameObject); } }
如今運行,射擊敵人,能夠看到以下效果:
7.遊戲音效
如今來添加一些聲音。將聲音資源放入"Sounds"文件夾,取消勾選每個聲音的"3D sound"屬性,由於這是2D遊戲。準備播放背景音樂,建立一個遊戲對象,命名爲"Music",其Position爲(0, 0, 0),將背景音樂拖到這個對象上,而後勾選"Mute"屬性。由於音效老是在必定的時機進行播放,因此建立一個腳本文件,命名爲"SoundEffectsHelper",代碼以下:
using UnityEngine; using System.Collections; /// <summary> /// 建立音效實例 /// </summary> public class SoundEffectsHelper : MonoBehaviour { /// <summary> /// 靜態實例 /// </summary> public static SoundEffectsHelper Instance; public AudioClip explosionSound; public AudioClip playerShotSound; public AudioClip enemyShotSound; void Awake() { // 註冊靜態實例 if (Instance != null) { Debug.LogError("Multiple instances of SoundEffectsHelper!"); } Instance = this; } public void MakeExplosionSound() { MakeSound(explosionSound); } public void MakePlayerShotSound() { MakeSound(playerShotSound); } public void MakeEnemyShotSound() { MakeSound(enemyShotSound); } /// <summary> /// 播放給定的音效 /// </summary> /// <param name="originalClip"></param> private void MakeSound(AudioClip originalClip) { // 作一個非空判斷, 防止異常致使剩餘操做被停止 if (Instance.ToString() != "null") { // 由於它不是3D音頻剪輯,位置並不重要。 AudioSource.PlayClipAtPoint(originalClip, transform.position); } } }
這裏我作了一個非空判斷, 由於按照原例中的程序, 當章魚死亡在攝像機邊界時, 會致使Script對象被銷燬! 從而引起程序異常. 我沒有找到被銷燬的準確緣由, 因此暫時折中一下, 加了個判斷, 以確保程序能照常運行. 若是哪位大神有知道緣由的話也歡迎告訴我來更新此文.
將此腳本附加到"Scripts"對象上,而後設置其屬性值,以下圖所示:
接着,在"HealthScript"腳本文件裏,播放粒子效果後面,添加代碼:
SoundEffectsHelper.Instance.MakeExplosionSound();
在"WeaponScript"腳本文件裏,Attack方法中,添加代碼:
if (isEnemy) { SoundEffectsHelper.Instance.MakeEnemyShotSound(); } else { SoundEffectsHelper.Instance.MakePlayerShotSound(); }
如今運行,就能夠聽到聲音了。
8.菜單
建立簡單的菜單,以便遊戲能夠從新開始。導入背景圖片和LOGO圖片到"Textures"文件夾的子文件夾"Menu"。建立一個新的場景,命名爲"Menu"。添加背景Sprite對象,其Position爲(0, 0, 1),Size爲(2, 2, 1)。添加LOGO的Sprite對象,其Position爲(0, 2, 0),Size爲(0.75, 0.75, 1)。添加一個空對象,命名爲"Scripts",用來加載腳本。如今爲這個啓動畫面,添加一個開始按鈕。建立一個腳本文件,命名爲"MenuScript",代碼以下:
using UnityEngine; using System.Collections; /// <summary> /// Title screen script /// </summary> public class MenuScript : MonoBehaviour { // Use this for initialization void Start() { } // Update is called once per frame void Update() { } void OnGUI() { const int buttonWidth = 84; const int buttonHeight = 60; // 在開始遊戲界面繪製一個按鈕 if ( // Center in X, 2/3 of the height in Y GUI.Button(new Rect(Screen.width / 2 - (buttonWidth / 2), (2 * Screen.height / 3) - (buttonHeight / 2), buttonWidth, buttonHeight), "開始遊戲") ) { // On Click, load the first level. // "Stage1" is the name of the first scene we created. Application.LoadLevel("Stage1"); } } }
將此腳本附加到"Scripts"對象上。如今運行,能夠看到以下效果:
可是,點擊按鈕會崩潰,由於沒有將Stage1場景添加進來。打開"File"→"Build Settings",將場景"Menu"和"Stage1"拖動到上面的"Scenes In Build"裏面。再次運行,就能夠看到按鈕正常切換場景了。當主角被摧毀時,須要能夠從新開始遊戲。建立一個腳本文件,命名爲"GameOverScript",代碼以下:
using UnityEngine; using System.Collections; /// <summary> /// 開始或退出遊戲 /// </summary> public class GameOverScript : MonoBehaviour { // Use this for initialization void Start() { } // Update is called once per frame void Update() { } void OnGUI() { const int buttonWidth = 120; const int buttonHeight = 60; // 在x軸中心, y軸1/3處建立"重來"按鈕 if (GUI.Button(new Rect(Screen.width / 2 - (buttonWidth / 2), (1 * Screen.height / 3) - (buttonHeight / 2), buttonWidth, buttonHeight), "再來一次")) { // 從新加載遊戲場景 Application.LoadLevel("Stage1"); } // x軸中心, y軸2/3處建立"返回菜單"按鈕 if (GUI.Button(new Rect(Screen.width / 2 - (buttonWidth / 2), (2 * Screen.height / 3) - (buttonHeight / 2), buttonWidth, buttonHeight), "返回菜單")) { // 加載菜單場景 Application.LoadLevel("Menu"); } } }
在主角死亡的時候,調用這個腳本。打開"PlayerScript"文件,添加以下代碼:
void OnDestroy() { // Game Over. // Add the script to the parent because the current game // object is likely going to be destroyed immediately. transform.parent.gameObject.AddComponent<GameOverScript>(); }
如今運行,當死亡時,就會出現按鈕,以下圖所示:
9.代碼建立平臺島嶼和敵人
如今, 一個簡單的橫版射擊小遊戲已經有雛形了, 而後手動建立有限的島嶼和敵人畢竟有耗盡的時候. 這個時候咱們能夠在代碼中動態的去建立敵人和島嶼, 這樣只要玩家還存活, 就會一直有敵人出現, 有點無盡版的意思.
建立一個腳本文件,命名爲"MakePlatformScript",代碼以下:
using UnityEngine; using System.Collections; /// <summary> /// 製造平臺 /// </summary> public class MakePlatformScript : MonoBehaviour { /// <summary> /// 平臺預設體1 /// </summary> public Transform platform1Prefab; /// <summary> /// 平臺預設體2 /// </summary> public Transform platform2Prefab; // Use this for initialization void Start() { } // Update is called once per frame void Update() { if (transform.childCount < 4) { if (Random.Range(0, 2) > 0) { CreatePlatform1(); } else { CreatePlatform2(); } } } void CreatePlatform1() { var platformTransform = Instantiate(platform1Prefab) as Transform; platformTransform.position = new Vector3(Camera.main.transform.position.x + Random.Range(14, 23), Random.Range(-3, 3), 5); platformTransform.transform.parent = transform; } void CreatePlatform2() { var platformTransform = Instantiate(platform2Prefab) as Transform; platformTransform.position = new Vector3(Camera.main.transform.position.x + Random.Range(14, 23), Random.Range(-3, 3), 5); platformTransform.transform.parent = transform; } }
將MakePlatformScript附加到1 - Middleground, 設置它的預製體爲對應的平臺島嶼預製體.
接着繼續建立一個腳本文件,命名爲"MakeEnemyScript",代碼以下:
using UnityEngine; using System.Collections; /// <summary> /// 製造敵人 /// </summary> public class MakeEnemyScript : MonoBehaviour { /// <summary> /// 敵人預設體 /// </summary> public Transform enemyPrefab; // Use this for initialization void Start() { } // Update is called once per frame void Update() { if (transform.childCount < 2) { CreateEnemy(); } } /// <summary> /// 建立敵人 /// </summary> void CreateEnemy() { var enemyTransform = Instantiate(enemyPrefab) as Transform; enemyTransform.position = new Vector3(Camera.main.transform.position.x + 15, Random.Range(-4, 4), 0); enemyTransform.transform.parent = transform; MoveScript move = enemyTransform.gameObject.GetComponent<MoveScript>(); if (move != null) { move.direction.x = -1f; move.speed = new Vector2(3, 3); } } }
將MakeEnemyScript附加到2 - Foreground, 一樣, 設置對應的預製體爲章魚.
如今再次運行, 就會看到系統自動建立的章魚敵人和漂浮島嶼了!
到這裏, 整個遊戲就完成了, 推薦你們能夠去頁首找原文連接去看看, 老外的原文解說的更詳細, 我也是在原文的基礎上本身再次整理.
最後附上程序源碼, 文中有紕漏或看不太明白的地方你們能夠對照着源碼一塊兒看, 歡迎留言指教.