成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Unity俯視角射擊游戲腳本實戰(zhàn)

譯文
開發(fā) 游戲開發(fā)
Unity的強(qiáng)大功能主要得益于其豐富的腳本語言。你可以使用腳本來處理用戶輸入、移動場景中的物體、檢測碰撞、使用預(yù)制對象以及沿場景四周投射定向光線來增強(qiáng)你的游戲邏輯等。

簡介

Unity的強(qiáng)大功能主要得益于其豐富的腳本語言。你可以使用腳本來處理用戶輸入、移動場景中的物體、檢測碰撞、使用預(yù)制對象以及沿場景四周投射定向光線來增強(qiáng)你的游戲邏輯等。這聽起來有點令人生畏,但由于Unity官方提供了良好的API文檔支持,所以完成上述任務(wù)變得輕而易舉——即使對于Unity開發(fā)新手亦然!

在本教程中,你將創(chuàng)建一個基于俯視角的Unity射擊游戲。游戲中,你將使用Unity #腳本來生成敵人、控制玩家、發(fā)射炮彈以及實現(xiàn)游戲其他重要方面的控制。

【提示】本文假設(shè)你有一個C#或類似的編程語言開發(fā)經(jīng)驗。另外,本文示例游戲使用Unity 5.3+開發(fā)而成。

準(zhǔn)備

首先,請下載本文示例啟動項目(http://www.raywenderlich.com/wp-content/uploads/2016/03/BlockBuster.zip)并解壓縮。為了在Unity中打開啟動器項目,你可以從【Start Up Wizard】下單擊【Open】命令,然后導(dǎo)航到項目文件夾。或者,您可以直接從路徑【BlockBuster/Assets/Scenes】下打開文件Main.unity。

下圖給出您的示例工程中場景的初始樣子。

 

首先,請觀察一下場景視圖周圍的情況。有一個小的場地,這將作為本示例游戲的戰(zhàn)場;還有一部相機(jī),當(dāng)玩家在戰(zhàn)場上走動時相機(jī)會跟隨他們。如果您的布局與截圖有所不同,你可以選擇右上角的下拉菜單,把其中的選項改為「2 by 3」。

 

沒有英雄存在,那算是什么游戲呢?因此,你的第一個任務(wù)是創(chuàng)建一個游戲?qū)ο螅员硎緫?zhàn)場中的英雄。

創(chuàng)建玩家對象

在【Hierarchy】中,點擊【Create】按鈕,然后從「3D」部分選擇「Sphere」。將球體拖動到坐標(biāo)位置(0,0.5,0),并將其命名為Player,如圖所示。

從現(xiàn)在起,你將引用這一個球體作為玩家(Player)對象。

Unity使用組件系統(tǒng)來構(gòu)建它的游戲?qū)ο螅贿@意味著,在一個場景中的所有對象都可以通過組件的任何組合來創(chuàng)建。這些組合包括:用來描述一個對象位置的變換(Transform);網(wǎng)格過濾器(Mesh Filter),其中包含圖形幾何體或者任何個數(shù)的腳本(Scripts)。

玩家(Player)對象需要響應(yīng)與場景中的其他對象的碰撞。

要做到這一點,請從【Hierarchy】中選擇「Player」。然后,從【Inspector】選項卡下點擊【Add Component】按鈕。在【Physics】類別中,選擇【Rigidbody】組件。這將使Player對象為Unity的物理引擎所控制。

現(xiàn)在,請更改Rigidody的值,如下所示:

1.Drag:1

2.Angular Drag:0

3.Constraints: Freeze Position:Y

 

編寫玩家運(yùn)動腳本

現(xiàn)在你有了一個Player對象。接下來,我們來編寫腳本以便接收鍵盤輸入,進(jìn)而移動玩家。

在項目瀏覽器(Project Browser)中點擊【Create】按鈕,然后選擇「Folder」。命名新文件夾為「Scripts」并在名為「Player」的文件夾下創(chuàng)建一個子文件夾。接下來,在「Player」文件夾下,點擊【Create】按鈕,并選擇【C# Script】。命名新的腳本為PlayerMovement。順序大致如下圖所示:

 

【提示】Player對象將包含多個腳本,各自負(fù)責(zé)其行為的不同部分。在一個單獨的文件夾下保存所有相關(guān)的腳本,使項目中文件更容易管理,并減少混亂。

現(xiàn)在,請雙擊PlayerMovement.cs腳本。在Mac上,這將打開隨同Unity一起打包的MonoDevelop開發(fā)環(huán)境;在Windows上,它應(yīng)該打開Visual Studio。本教程假設(shè)你使用MonoDevelop。

在類中聲明下面兩個公共變量:

public float acceleration;

public float maxSpeed;

其中,acceleration用于描述玩家的速度如何隨時間增加,而maxSpeed代表“速度極限”。制作一個public類型的變量將會使之顯示于【Inspector】之中,這樣你就可以通過Unity界面來設(shè)置它的值,并根據(jù)需要調(diào)整它。

緊接著上面的聲明,再聲明以下變量:

private Rigidbody rigidBody;

private KeyCode[] inputKeys;

private Vector3[] directionsForKeys;

注意,私有變量無法通過【Inspector】進(jìn)行設(shè)置。因此,需要由程序員在適當(dāng)?shù)臅r候以完全手動方式對它們進(jìn)行初始化。

接下來,把Start()函數(shù)修改成如下所示的代碼:

  1. void Start () { 
  2.   inputKeys = new KeyCode[] { KeyCode.W, KeyCode.A, KeyCode.S, KeyCode.D }; 
  3.   directionsForKeys = new Vector3[] { Vector3.forward, Vector3.left, Vector3.back, Vector3.right }; 
  4.   rigidBody = GetComponent<Rigidbody>(); 

上述代碼中的inputKeys數(shù)組包含了您將用于移動玩家的鍵碼。directionsForKeys包含相應(yīng)于每個鍵的方向;例如,按下W用于向前移動對象。至于最后一行代碼,你還記得前面添加的剛體嗎?這是可以得到對該組件的引用的一種方式。

要移動玩家,你就必須處理來自于鍵盤的輸入。

現(xiàn)在,請重命名函數(shù)Update()為FixedUpdate(),并給它添加以下代碼:

  1. // 1 
  2. void FixedUpdate () { 
  3.   for (int i = 0; i < inputKeys.Length; i++){ 
  4.     var key = inputKeys[i]; 
  5.   
  6.     // 2 
  7.     if(Input.GetKey(key)) { 
  8.       // 3 
  9.       Vector3 movement = directionsForKeys[i] * acceleration * Time.deltaTime; 
  10.     } 
  11.   } 

這里發(fā)生了幾件重要的事情:

1.FixedUpdate()函數(shù)是幀速率獨立的,在操作剛體時應(yīng)該調(diào)用此函數(shù)。

2.這個循環(huán)檢查是否有任何輸入鍵被按下。

3.在這里,你得到按鍵的方向,并把它乘以加速度和完成最后一幀的修復(fù)所耗費(fèi)的秒數(shù)。這將產(chǎn)生您創(chuàng)建一個矢量,正是使用它來移動Player對象。

注意,當(dāng)您創(chuàng)建一個新的Unity腳本時,你實際上是創(chuàng)建一個新的MonoBehaviour對象。如果你熟悉iOS編程世界,那么你會知道它是一個UIViewController的等價物;也就是說,你可以使用這個對象來響應(yīng)Unity內(nèi)的事件,從而訪問你自己的數(shù)據(jù)對象。

MonoBehaviours有很多不同的方法,它們分別對各種事件作出響應(yīng)。舉例來說,當(dāng)MonoBehaviour實例化時如果你想初始化一些變量,那么你就可以實現(xiàn)方法Awake()。在MonoBehaviour被禁用時為了運(yùn)行代碼,你可以實現(xiàn)方法OnDisable()。

【提示】如果你想研究這些事件的完整列表,請訪問Unity官方文檔,地址是 http://docs.unity3d.com/ScriptReference/MonoBehaviour.html。

如果你是游戲編程新手,你可能會問自己,為什么必須乘以Time.deltaTime?一般的規(guī)律是,當(dāng)你每隔固定的時間幀數(shù)執(zhí)行一個動作時,你需要乘以Time.deltaTime。在本例情況下,你想要沿按鍵方向加速移動玩家,加速數(shù)值為固定的更新時間。

接下來,在方法FixedUpdate()下面添加以下方法:

  1. void movePlayer(Vector3 movement) { 
  2.   if(rigidBody.velocity.magnitude * acceleration > maxSpeed) { 
  3.     rigidBody.AddForce(movement * -1); 
  4.   } else { 
  5.     rigidBody.AddForce(movement); 
  6.   } 

上述方法用于對剛體施加力作用,使其移動。如果當(dāng)前速度超過maxSpeed,力會變成相反的方向......這有點像一個速度極限。

現(xiàn)在,請在方法FixedUpdate()中,在if語句的結(jié)束括號之前,添加以下行:

movePlayer(movement);

很好!回到Unity中。然后,在項目瀏覽器中,將PlayerMovement腳本拖動到【Hierarchy】中的Player對象上。然后,使用【Inspector】來把「Acceleration」的值設(shè)置為625并把最大速度(Max Speed)修改為4375:

 

現(xiàn)在,請運(yùn)行一下游戲場景,并試著使用鍵盤上的WASD鍵移動玩家對象,觀察效果:

 

到目前,我們僅僅實現(xiàn)了幾行代碼,這已經(jīng)算是一個相當(dāng)不錯的結(jié)果了!

然而,現(xiàn)在有一個明顯的問題:玩家可以移出人們的視線之外,這在打壞人時是個麻煩事。

編寫攝相機(jī)腳本

在「Scripts」文件夾中,創(chuàng)建一個名為CameraRig的新的腳本,并將其附加到主攝像機(jī)(Main Camera)上。

【提示】在選擇【Scripts】文件夾情況下,點擊工程瀏覽器中的【Create】按鈕,然后選擇【C# Script】。命名新的腳本為「CameraRig」。最后,把此腳本拖動到「Main Camera」對象上即可。

現(xiàn)在,在新創(chuàng)建的CameraRig類中添加下列變量:

public float moveSpeed;

public GameObject target;

private Transform rigTransform;

正如你可能已經(jīng)猜到的,moveSpeed代表了相機(jī)跟蹤目標(biāo)的速度——這可能是場景里面的任何游戲?qū)ο蟆?/p>

接下來,在Start()函數(shù)中添加以下代碼行:

rigTransform= this.transform.parent;

此代碼獲取場景層次樹中的到父Camera對象的引用。場景中的每個對象具有一個變換(Transform),其中描述了一個對象的位置旋轉(zhuǎn)和縮放等信息。

 

然后,在與上面同一個腳本文件中添加下面的方法:

  1. void FixedUpdate () { 
  2.   if(target == null){ 
  3.     return; 
  4.   } 
  5.   
  6.   rigTransform.position = Vector3.Lerp(rigTransform.position, target.transform.position, 
  7.     Time.deltaTime * moveSpeed); 

這部分CameraRig移動代碼要比在PlayerMovement中的簡單一些。這是因為你不需要一個剛體;只需要在rigTransform的位置和目標(biāo)之間進(jìn)行插值就足夠了。

Vector3.Lerp()函數(shù)使用了空間中的兩個點,還有一個界于[0,1]范圍內(nèi)的浮點數(shù)(它描述了沿兩個端點的中間的某一點)作參數(shù)。左端點為0,右側(cè)端點是1。于是,把0.5傳遞給Lerp()函數(shù)將正好返回位于兩個端點中間的一個點。

這會將rigTransform移到距目標(biāo)位置更近一些,而且略有緩動效果。總之,相機(jī)跟隨玩家運(yùn)動。

現(xiàn)在,返回到Unity。確保層次樹(Hierarchy)中的主攝像機(jī)(Main Camera)仍處于選中狀態(tài)。在【Inspector】中,把Move Speed(移動速度)設(shè)置為8,并把Target(目標(biāo))設(shè)置為Player:

再次運(yùn)行游戲工程,沿場景四處移動;你會注意到,無論玩家走到哪里,相機(jī)都能夠平滑地跟隨目標(biāo)變換。

創(chuàng)建敵人對象

一款沒有敵人的射擊游戲很容易被擊敗,當(dāng)然也很無聊。所以,現(xiàn)在我們來通過單擊頂部菜單中的【GameObject\3D Object\Cube】創(chuàng)建一個用于表示敵人的立方體對象。然后,把此立方體重命名為「Enemy」,并添加一個Rigidbody(剛體)組件。

在【Inspector】中,首先設(shè)置立方體的變換為(0,0.5,4)。并在剛體組件的「Constraints」部分的「Freeze Position」類別下勾選「Y」選擇對應(yīng)的復(fù)選框。

很好,現(xiàn)在使你的敵人氣勢洶洶地走動吧。然后,在【Scripts】文件夾下創(chuàng)建一個命名為「Enemy」的腳本。現(xiàn)在,你應(yīng)該對這種操作很熟練了;恕不再贅述。

接下來,在類內(nèi)部添加下列公共變量:

public float moveSpeed;

public int health;

public int damage;

public Transform targetTransform;

你也許可以很容易地確定出這些變量所代表的含義。你可以使用如前面一樣的moveSpeed變量技巧來操縱攝像機(jī),而且它們的效果相同。Health和damage這兩個變量分別用于確定何時敵人死了以及他們死多少會傷害玩家。最后,變量targetTransform用于引用玩家對象對應(yīng)的變換。

說到玩家對象,你需要創(chuàng)建一個類來描述敵人想破壞的所有玩家的健康值。

在項目瀏覽器中,選擇「Player」文件夾,并創(chuàng)建一個名為「Player」的新腳本。這個腳本會響應(yīng)于碰撞,并跟蹤玩家的健康值。現(xiàn)在,我們通過雙擊此腳本來編輯它。

添加下列公共變量來保存玩家的健康值:

public int health = 3;

這樣便提供了玩家健康值的默認(rèn)值,但它也可以通過【Inspector】進(jìn)行修改。

為了處理沖突,添加以下方法:

  1. void collidedWithEnemy(Enemy enemy) { 
  2.   // Enemy attack code 
  3.   if(health <= 0) { 
  4.     // Todo 
  5.   } 
  6. void OnCollisionEnter (Collision col) { 
  7.     Enemy enemy = col.collider.gameObject.GetComponent<Enemy>(); 
  8.     collidedWithEnemy(enemy); 

當(dāng)兩個剛體發(fā)生碰撞時,OnCollisionEnter()即被觸發(fā)。其中,Collision參數(shù)中包含了諸如接觸點和沖擊速度相關(guān)的信息。在本示例情況下,我們只對碰撞物體中的Enemy組件感興趣,所以可以調(diào)用collidedWithEnemy()并執(zhí)行攻擊邏輯——接下來就會實現(xiàn)這種邏輯。

切換回文件Enemy.cs,并添加以下方法:

  1. void FixedUpdate () { 
  2.   if(targetTransform != null) { 
  3.     this.transform.position = Vector3.MoveTowards(this.transform.position, targetTransform.transform.position, Time.deltaTime * moveSpeed); 
  4.   } 
  5. }  
  6. public void TakeDamage(int damage) { 
  7.   health -damage
  8.   if(health <= 0) { 
  9.     Destroy(this.gameObject); 
  10.   } 
  11.   
  12. public void Attack(Player player) { 
  13.   player.health -this.damage; 
  14.   Destroy(this.gameObject); 

你已經(jīng)熟悉了FixedUpdate()函數(shù),略有不同的是現(xiàn)在使用的是MoveTowards()而不是Lerp()函數(shù)。這是因為敵人應(yīng)該一直以相同的速度移動而不會在接近目標(biāo)時出現(xiàn)快速移動。當(dāng)敵人被彈丸擊中時,TakeDamage()即被調(diào)用;當(dāng)敵人到達(dá)值為0的健康值時他會自我毀滅。Attack()函數(shù)的實現(xiàn)邏輯是與之很類似的——對玩家進(jìn)行傷害,然后敵人破壞自身。

切換回Player.cs。然后,在函數(shù)collidedWithEnemy()中,使用下面代碼替換注釋// Enemy attack code:

enemy.Attack(this);

游戲中,玩家將受到傷害,而敵人在該過程中將自我毀滅。

切換回Unity。把Enemy腳本附加到 Enemy對象上;并在【Inspector】中,針對Enemy對象設(shè)置以下值:

1.Move Speed:5

2.Health:2

3.Damage:1

4.Target Transform:Player

現(xiàn)在,你應(yīng)該能夠自己做這一切了。結(jié)束后,你可以與文后完整的工程源碼進(jìn)行比較。

在游戲中,敵人與玩家碰撞,從而實現(xiàn)一種有效的敵對攻擊。使用Unity的物理碰撞檢測幾乎是一個很簡單的任務(wù)。

最后,在層次結(jié)構(gòu)中把Player腳本附加到Player對象。

運(yùn)行游戲工程,并留意在控制臺上輸出的結(jié)果:

 

當(dāng)敵人接觸到玩家時,它能夠成功地進(jìn)行攻擊,并把玩家的健康值變量降低到2。但是,現(xiàn)在在控制臺中拋出一個NullReferenceException異常,錯誤指向Player腳本:

哈哈,現(xiàn)在玩家不僅可以與敵人碰撞,也可能與游戲世界中的其他部分,如戰(zhàn)場,發(fā)生碰撞!這些游戲?qū)ο蟛]有Enemy腳本,因此GetComponent()函數(shù)將返回null。

接下來,打開文件Player.cs。然后,在OnCollisionEnter()函數(shù)中,把collidedWithEnemy()函數(shù)調(diào)用使用一個if語句包括起來,如下所示:

  1. if(enemy) { 
  2.   collidedWithEnemy(enemy); 

此時,異常消失!

使用預(yù)制

只是簡單地在戰(zhàn)場上跑來跑去,而且避開敵人;這只能算是一個一邊倒的游戲。現(xiàn)在,我們來武裝一下玩家,使之能夠作戰(zhàn)。

單擊層次結(jié)構(gòu)中的【Create】按鈕,并選擇【3D Object/Capsule】。命名它為Projectile,并給它指定下列變換值:

1.    Position:(0, 0, 0)

2.    Rotation:(90, 0, 0)

3.    Scale:(0.075, 0.246, 0.075)

 

每當(dāng)玩家射擊時,他就會發(fā)射Projectile(炮彈)的一個實例。要做到這一點,你需要創(chuàng)建一個預(yù)制(Prefab)。不像場景中你已經(jīng)擁有的其他對象,預(yù)制對象是根據(jù)游戲邏輯需要而創(chuàng)建的。

現(xiàn)在,在文件夾「Assets」下創(chuàng)建一個新的文件夾,名為Prefabs。現(xiàn)在,把Projectile對象拖動到這個文件夾上。就是這樣:你創(chuàng)建了一個預(yù)制!

您的預(yù)制還需要一點腳本。現(xiàn)在,在【Scripts】文件夾內(nèi)創(chuàng)建一個名為「Projectile」的新腳本,并添加下面的類變量:

public float speed;

public int damage;

Vector3 shootDirection;

就像目前為止在本教程中任何可移動的物體一樣,這個對象也會有速度和傷害對應(yīng)的變量,因為它是戰(zhàn)斗邏輯的一部分。其中,shootDirection矢量決定了炮彈將向哪兒發(fā)射。

在類中實現(xiàn)下面的方法即可使這個矢量發(fā)揮作用:

  1. // 1 
  2. void FixedUpdate () { 
  3.   this.transform.Translate(shootDirection * speed, Space.World); 
  4. // 2 
  5. public void FireProjectile(Ray shootRay) { 
  6.   this.shootDirection = shootRay.direction; 
  7.   this.transform.position = shootRay.origin; 
  8. // 3 
  9. void OnCollisionEnter (Collision col) { 
  10.   Enemy enemy = col.collider.gameObject.GetComponent<Enemy>(); 
  11.   if(enemy) { 
  12.     enemy.TakeDamage(damage); 
  13.   } 
  14.   Destroy(this.gameObject); 

在上面的代碼中發(fā)生了下面的事情:

1.炮彈在游戲中的運(yùn)動方式與其他對象不同。它不具有一個目標(biāo),或者一直對它施加一些力;相反,它在其整個生命周期中的按照預(yù)定方向進(jìn)行運(yùn)動。

2.在這里,我們設(shè)置了預(yù)制對象的起始位置和方向。Ray參數(shù)看上去似乎很神秘吧,但你很快就會知道它是如何計算出來的。

3.如果一個炮彈與敵人發(fā)生碰撞,它會調(diào)用TakeDamage(),并進(jìn)行自我毀滅。

在場景層次中,把Projectile腳本附加到Projectile游戲?qū)ο笊稀TO(shè)置它的速度為0.2,并把損壞值設(shè)置為1,然后點擊【Inspector】頂部的【Apply】按鈕。這將針對這個預(yù)制的所有實例保存剛才所做的更改。

現(xiàn)在,請從場景層次樹中刪除Projectile對象,因為我們不再需要它了。 

發(fā)射炮彈

現(xiàn)在,你既然已經(jīng)擁有了可以移動并施加傷害能力的預(yù)制對象,那么,接下來你就可以開始考慮實現(xiàn)發(fā)射炮彈相關(guān)的編程了。

在Player文件夾下,創(chuàng)建一個名為PlayerShooting的新腳本,并將其附加到場景中的Player游戲?qū)ο蟆H缓螅赑layer類中,聲明以下變量:

public Projectile projectilePrefab;

public LayerMask mask;

第一個變量將包含對前面創(chuàng)建的Projectile預(yù)制對象的引用。每當(dāng)玩家發(fā)射炮彈時,您將從這個預(yù)制創(chuàng)建一個新的實例。mask變量是用來篩選游戲?qū)ο螅℅ameObject)的。

現(xiàn)在,我們要介紹一下光線投射的問題。何謂光線投射(casting Ray)?這是什么魔法?

其實,并不存在什么黑魔法。但是,有時候在你的游戲中,你的確需要知道是否在一個特定方向上存在碰撞。要做到這一點,Unity在您指定的方向上能夠從某一個點投出一條看不見的射線。你可能會遇到很多與射線相交的游戲?qū)ο螅灰虼耍褂煤Y選器可以過濾掉任何不需要參與碰撞的對象。

光線投射是非常有用的,并且可以用于各種用途。它們常用于測試是否另一名玩家已經(jīng)被炮彈擊中;而且,你也可以使用它們來測試是否在鼠標(biāo)指針下方存在任何的幾何形狀。要更多地了解關(guān)于光線投射的內(nèi)容,請參考一下Unity官方網(wǎng)站提供的在線培訓(xùn)視頻(https://unity3d.com/learn/tutorials/modules/beginner/physics/raycasting)。

下圖顯示了從一個立方體到一個錐體的光線投射情況。由于射線上有一個圖標(biāo)掩碼,因此它忽略掉游戲?qū)ο蠖到y(tǒng)給出的提示是擊中了錐體:

接下來,我們需要創(chuàng)建自己的射線了。

把如下代碼添加到文件PlayerShooting.cs:

  1.  void shoot(RaycastHit hit){ 
  2.  
  3.   // 1 
  4.  
  5.   var projectile = Instantiate(projectilePrefab).GetComponent<Projectile>(); 
  6.  
  7.   // 2 
  8.  
  9.   var pointAboveFloor = hit.point + new Vector3(0, this.transform.position.y, 0); 
  10.  
  11.   // 3 
  12.  
  13.   var direction = pointAboveFloor - transform.position; 
  14.  
  15.  // 4 
  16.  
  17.   var shootRay = new Ray(this.transform.position, direction); 
  18.  
  19.   Debug.DrawRay(shootRay.origin, shootRay.direction * 100.1f, Color.green, 2);  
  20.  
  21.   // 5 
  22.  
  23.   Physics.IgnoreCollision(GetComponent<Collider>(), projectile.GetComponent<Collider>());  
  24.  
  25.   // 6 
  26.  
  27.   projectile.FireProjectile(shootRay); 
  28.  

概括來看,上面的代碼主要實現(xiàn)如下功能:

1.    實例化一個炮彈預(yù)制并獲得它的Projectile組件,從而可以把它初始化。

2.    這個坐標(biāo)點總是使用像(X,0.5,Z)這樣的格式。其中,X和Z坐標(biāo)位于地面上,正好對應(yīng)于射線投射擊中的鼠標(biāo)點擊位置的坐標(biāo)。這里的計算是很重要的,因為炮彈必須平行于地面;否則,你會向下射擊,而只有外行的玩家才會出現(xiàn)向地面射擊的情況。

3.    計算從游戲物體Player指向pointAboveFloor的方向。

4.    創(chuàng)建一條新的射線,并通過其原點和方向來共同描述炮彈軌跡。

5.    這行代碼告訴Unity的物理引擎忽略玩家與炮彈之間的碰撞。否則,在炮彈飛出去前將調(diào)用Projectile腳本中的OnCollisionEnter()方法。

6.    最后,設(shè)置炮彈的運(yùn)動軌跡。

【注意】當(dāng)光線投射不可見時,你可以使用Debug.DrawRay()方法來輔助調(diào)試程序,因為它可以幫助您更直觀地觀察光線的外觀和它所擊中的對象。

好,現(xiàn)在既然發(fā)射炮彈的邏輯已經(jīng)實現(xiàn),請繼續(xù)添加下面的方法來讓玩家真正扣動扳機(jī):

  1.  // 1 
  2.  
  3. void raycastOnMouseClick () { 
  4.  
  5.   RaycastHit hit; 
  6.  
  7.   Ray rayToFloor = Camera.main.ScreenPointToRay(Input.mousePosition); 
  8.  
  9.   Debug.DrawRay(rayToFloor.origin, rayToFloor.direction * 100.1f, Color.red, 2); 
  10.  
  11.    if(Physics.Raycast(rayToFloor, out hit, 100.0f, mask, QueryTriggerInteraction.Collide)) { 
  12.  
  13.     shoot(hit); 
  14.  
  15.   } 
  16.  
  17.  
  18. // 2 
  19.  
  20. void Update () { 
  21.  
  22.   bool mouseButtonDown = Input.GetMouseButtonDown(0); 
  23.  
  24.   if(mouseButtonDown) { 
  25.  
  26.     raycastOnMouseClick();  
  27.  
  28.   } 
  29.  

讓我們按上面編號進(jìn)行逐個解釋:

1.這個方法把射線從攝相機(jī)射向鼠標(biāo)點擊的位置,然后檢查是否射線相交于符合給定LayerMask掩碼值的游戲?qū)ο蟆?/strong>

2.在每次更新中,腳本都會檢查一下鼠標(biāo)左鍵按下情況。如果發(fā)現(xiàn)存在按下的情況,就調(diào)用raycastOnMouseClick()方法。

現(xiàn)在,請返回到Unity中,并在【Inspector】中設(shè)置下列變量:

  Projectile Prefab:引用文件夾prefab下的Projectile;

  Mask:Floor

【注意】Unity使用數(shù)量有限的預(yù)定義掩碼——也稱為層。

你可以通過點擊一個游戲物體的【Layer】下拉菜單然后選擇【Add Layer】(添加圖層)來定義你自己的掩碼:

您也可以通過從【Layer】下拉菜單中選擇一個層來給游戲?qū)ο蠓峙溲诖a:

有關(guān)Unity3d引擎中層的更多的信息,請參考官方文檔,地址是http://docs.unity3d.com/Manual/Layers.html

現(xiàn)在,請運(yùn)行示例項目并隨意發(fā)射炮彈!你會注意到:炮彈按照希望的方向發(fā)射,但看起來還缺少點什么,不是嗎?

如果炮彈是沿著其發(fā)射的方向行進(jìn)的,那將酷多了。為了解決這個問題,打開Projectile.cs腳本并添加下面的方法:

  1. void rotateInShootDirection() { 
  2.  
  3.  Vector3 newRotation = Vector3.RotateTowards(transform.forward, shootDirection, 0.01f, 0.0f); 
  4.  
  5.  transform.rotation = Quaternion.LookRotation(newRotation); 

【注意】RotateTowards非常類似于MoveTowards,但它把矢量作為方向,而不是位置。此外,你并不需要一直改變旋轉(zhuǎn);因此,使用一個接近零的步長值就足夠了。在Unity中實現(xiàn)旋轉(zhuǎn)變換是使用四元組實現(xiàn)的,這已超出了本教程的討論范圍。在本教程中,你只需要知道在涉及三維旋轉(zhuǎn)計算時使用四元組的優(yōu)勢超過矢量即可。當(dāng)然,如果你有興趣更多地了解關(guān)于四元組以及它們有何用處,請參考這篇優(yōu)秀的文章,地址是http://developerblog.myo.com/quaternions/

接下來,在FireProjectile()方法的結(jié)束處,添加對rotateInShootDirection()方法的調(diào)用。 現(xiàn)在,F(xiàn)ireProjectile()方法看起來應(yīng)該像下面這樣:

  1.  public void FireProjectile(Ray shootRay) { 
  2.  
  3.   this.shootDirection = shootRay.direction; 
  4.  
  5.   this.transform.position = shootRay.origin; 
  6.  
  7.   rotateInShootDirection(); 
  8.  

再次運(yùn)行游戲,并沿幾個不同的方向發(fā)射炮彈。此時,炮彈將指向它們發(fā)射的方向。現(xiàn)在,你可以清除代碼中的Debug.DrawRay調(diào)用了,因為你不再需要它們了。

生成更多敵人對象

只有一個敵人的游戲并不具有挑戰(zhàn)性。但現(xiàn)在,你已經(jīng)知道了預(yù)制的用法。于是,你可以生成任意數(shù)目的對手了!

為了讓玩家不斷猜想,你可以隨機(jī)地控制每個敵人的健康值、速度和位置等。

現(xiàn)在,使用命令【GameObject】-【Create Empty】創(chuàng)建一個空的游戲?qū)ο蟆C鼮椤窫nemyProducer」,并添加一個Box碰撞器組件。最后,在【Inspector】設(shè)置其值如下:

1.    Position:(0, 0, 0)

2.    Box Collider:

3.    Is Trigger:true

4.    Center:(0, 0.5, 0)

5.    Size:(29, 1, 29) 

上面你附加的這個碰撞器實際上在戰(zhàn)場中定義了一個特定的3D空間。為了看到這個對象,請從層次結(jié)構(gòu)樹下選擇【Enemy Producer】游戲物體;于是,在場景視圖中你會看到這個對象,如下圖所示。

圖中用綠線框出的部分代表了一個碰撞器

現(xiàn)在,你要編寫一個腳本實現(xiàn)沿X軸和Z軸方向選取空間中的一個隨機(jī)位置并實例化一個敵人預(yù)制。

創(chuàng)建一個名為EnemyProducer的新腳本,并將其附加到游戲?qū)ο驟nemyProducer。然后,在新設(shè)置的類內(nèi)部,添加以下實例成員:

public bool shouldSpawn;

public Enemy[] enemyPrefabs;

public float[] moveSpeedRange;

public int[] healthRange;

private Bounds spawnArea;

private GameObject player;

第一個變量控制啟用還是禁用敵人對象的生成。該腳本將從enemyPrefabs中選擇一個隨機(jī)的敵人預(yù)制并創(chuàng)建其實例。接下來的兩個數(shù)組將分別指定速度和健康值的最小值和最大值。生成敵人的地方是你在場景視圖中看到的綠色框。最后,你需要一個到玩家Player的引用,并把它作為目標(biāo)參數(shù)傳遞給敵人對象。

在腳本中,接著定義以下方法:

  1.  public void SpawnEnemies(bool shouldSpawn) { 
  2.  
  3.   if(shouldSpawn) { 
  4.  
  5.     player = GameObject.FindGameObjectWithTag("Player"); 
  6.  
  7.   } 
  8.  
  9.   this.shouldSpawn = shouldSpawn; 
  10.  
  11.  
  12. void Start () { 
  13.  
  14.   spawnArea = this.GetComponent<BoxCollider>().bounds; 
  15.  
  16.   SpawnEnemies(shouldSpawn); 
  17.  
  18.   InvokeRepeating("spawnEnemy", 0.5f, 1.0f); 
  19.  

SpawnEnemies()方法獲取到標(biāo)簽為Player的游戲?qū)ο蟮囊茫⒋_定是否應(yīng)該生成一個敵人。

Start()方法初始化敵人生成的位置并在游戲開始0.5秒之后調(diào)用一個方法。每一秒它都會被反復(fù)調(diào)用。除了作為一個setter方法外,SpawnEnemies()方法還得到一個到標(biāo)簽為「Player」的游戲?qū)ο蟮囊谩?/p>

注意,到現(xiàn)在為止,玩家游戲?qū)ο笊形礃?biāo)記。現(xiàn)在,就要做這件事情。請從【Hierarchy】中選擇Player對象,然后在【Inspector】選項卡中從「Tag」下拉菜單中選擇Player,如下圖所示。

現(xiàn)在,你需要編寫實際的生成單個敵人的代碼。

打開Enemy腳本,并添加下面的方法:

  1.  public void Initialize(Transform target, float moveSpeed, int health) { 
  2.  
  3.   this.targetTransform = target; 
  4.  
  5.   this.moveSpeed = moveSpeed; 
  6.  
  7.   this.health = health; 
  8.  

這個方法充當(dāng)用于創(chuàng)建對象的setter方法。下一步:要編寫生成成群的敵人的代碼。打開EnemyProducer.cs文件,并添加以下方法:

  1.  Vector3 randomSpawnPosition() { 
  2.  
  3.   float x = Random.Range(spawnArea.min.x, spawnArea.max.x); 
  4.  
  5.   float z = Random.Range(spawnArea.min.z, spawnArea.max.z); 
  6.  
  7.   float y = 0.5f; 
  8.  
  9.   return new Vector3(x, y, z); 
  10.  
  11. }  
  12.  
  13. void spawnEnemy() { 
  14.  
  15.   if(shouldSpawn == false || player == null) { 
  16.  
  17.     return; 
  18.  
  19.   }  
  20.  
  21.   int index = Random.Range(0, enemyPrefabs.Length); 
  22.  
  23.   var newEnemy = Instantiate(enemyPrefabs[index], randomSpawnPosition(), Quaternion.identity) as Enemy; 
  24.  
  25.   newEnemy.Initialize(player.transform, 
  26.  
  27.       Random.Range(moveSpeedRange[0], moveSpeedRange[1]), 
  28.  
  29.       Random.Range(healthRange[0], healthRange[1])); 
  30.  

這個spawnEnemy()方法所做的就是選擇一個隨機(jī)的敵人預(yù)制,在隨機(jī)位置實例化并初始化腳本Enemy中的公共變量。

現(xiàn)在,腳本EnemyProducer.cs快要準(zhǔn)備好了!

返回到Unity中。通過把Enemy對象從【Hierarchy】拖動到【Prefabs】文件夾創(chuàng)建一個Enemy預(yù)制。然后,從場景中移除Enemy對象——你不需要它了。接下來,設(shè)置Enemy Producer腳本中的公共變量:

1.    Should Spawn:True

2.    Enemy Prefabs:

   Size:1

   Element 0:引用敵人預(yù)制

3.    Move Speed Range:

     Size:2

    Element 0:3

     Element 1:8

4.    Health Range:

  Size:2

  Element 0:2

  Element 1:6 

現(xiàn)在,運(yùn)行游戲并注意觀察。你會注意到場景中無休止地出現(xiàn)成群的敵人!

好吧,這些立方體看起來還不算非常可怕。現(xiàn)在,我們再來添加一些細(xì)節(jié)修飾。

在場景中創(chuàng)建一個三維圓柱(Cylinder)和一個膠囊(Capsule)。分別命名為「Enemy2」和「Enemy3」。就像前面你針對第一個敵人所做的那樣,向這兩個對象分別都添加一個剛體組件和一個Enemy腳本。然后,選擇Enemy2,并在【Inspector】中像下面這樣更改它的配置:

1.    Scale:(0, 0.5, 0)

2.    Rigidbody:

  Use Gravity:False

  Freeze Position:Y

  Freeze Rotation:X, Y, Z

3.    Enemy Component:

    Move Speed: 5

   Health: 2

  Damage: 1

  Target Transform: None

現(xiàn)在,針對Enemy3也進(jìn)行與上面同樣的設(shè)置,但是把它的Scale設(shè)置成0.7,如下圖所示。

接下來,把他們轉(zhuǎn)換成預(yù)制,就像你操作最開始的那個敵人那樣,并在「Enemy Producer」中引用它們。在【Inspector】中的值應(yīng)該像下面這樣:

  Enemy Prefabs:

  Size: 3

  Element 0: Enemy

  Element 1: Enemy2

  Element 2: Enemy3 

再次運(yùn)行游戲;現(xiàn)在,你會觀察到在場景中生成不同的預(yù)制。

其實,在你意識到你是不可戰(zhàn)勝的之前,不會花費(fèi)太長的時間!

開發(fā)游戲控制器

現(xiàn)在,您已經(jīng)能夠射擊、移動,而且能夠把敵人放在指定位置。在本節(jié)中,你將實現(xiàn)一個基本的游戲控制器。一旦玩家“死”了,它將重新啟動游戲。但首先,你必須建立一種機(jī)制以通知所有有關(guān)各方——玩家已達(dá)到0健康值。

現(xiàn)在,打開Player腳本,并在類聲明上方添加如下內(nèi)容:

using System;

然后,在類中添加以下新的公共事件:

public event Action<Player> onPlayerDeath;

【提示】事件是C#語言中的重要功能之一,讓你向所有監(jiān)聽者廣播對象中的變化。要了解如何使用事件,你可以參考一下官方的事件培訓(xùn)視頻(https://unity3d.com/learn/tutorials/topics/scripting/events)。

接下來,編輯collidedWithEnemy()方法,使之最終看起來具有像下面這樣的代碼:

 

  1. void collidedWithEnemy(Enemy enemy) { 
  2.  
  3.  enemy.Attack(this); 
  4.  
  5.  if(health <= 0) { 
  6.  
  7.    if(onPlayerDeath != null) { 
  8.  
  9.      onPlayerDeath(this); 
  10.  
  11.    } 
  12.  
  13.  } 

事件為對象之間的狀態(tài)變化通知提供了一種整潔的實現(xiàn)方案。游戲控制器對上述聲明的事件是很感興趣的。在Scripts文件夾中,創(chuàng)建一個名為GameController的新腳本。然后,雙擊該文件進(jìn)行編輯,并給它添加下列變量:

public EnemyProducer enemyProducer;

public GameObject playerPrefab;

腳本在生成敵人時需要進(jìn)行一定的控制,因為一旦玩家喪生再生成敵人是沒有任何意義的。此外,重新啟動游戲意味著你將不得不重新創(chuàng)建玩家,這意味著……是的,你要通過把玩家變成預(yù)制來更靈活地實現(xiàn)這一目的。

于是,請?zhí)砑酉铝蟹椒ǎ?/p>

  1.  void Start () { 
  2.  
  3.   var player = GameObject.FindGameObjectWithTag("Player").GetComponent<Player>(); 
  4.  
  5.   player.onPlayerDeath += onPlayerDeath; 
  6.  
  7. }  
  8.  
  9. void onPlayerDeath(Player player) { 
  10.  
  11.   enemyProducer.SpawnEnemies(false); 
  12.  
  13.   Destroy(player.gameObject);  
  14.  
  15.   Invoke("restartGame", 3); 
  16.  

在Start()方法中,該腳本先獲取到Player腳本的引用,并訂閱你先前創(chuàng)建的事件。一旦玩家的健康值達(dá)到0, onPlayerDeath()方法即被調(diào)用,從而停止敵人的生成,從場景中移除Player對象和并在3秒鐘后調(diào)用restartGame()方法。

最后,重新啟動游戲的動作實現(xiàn)如下:

  1. void restartGame() { 
  2.  
  3.  var enemies = GameObject.FindGameObjectsWithTag("Enemy"); 
  4.  
  5.  foreach (var enemy in enemies) 
  6.  
  7.  { 
  8.  
  9.    Destroy(enemy); 
  10.  
  11.  }  
  12.  
  13.  var playerObject = Instantiate(playerPrefab, new Vector3(0, 0.5f, 0), Quaternion.identity) as GameObject; 
  14.  
  15.  var cameraRig = Camera.main.GetComponent<CameraRig>(); 
  16.  
  17.  cameraRig.target = playerObject
  18.  
  19.  enemyProducer.SpawnEnemies(true); 
  20.  
  21.  playerObject.GetComponent<Player>().onPlayerDeath += onPlayerDeath; 

在這里,我們做了一些清理工作:摧毀場景中的所有敵人,并創(chuàng)建一個新的Player對象。然后,重新指定攝像機(jī)的目標(biāo)為玩家對象,恢復(fù)敵人生成支持,并為游戲控制器訂閱玩家死亡的事件。

現(xiàn)在返回到Unity,打開Prefebs文件夾,更改所有敵人預(yù)制為標(biāo)簽Enemy。接下來,通過拖動Player游戲?qū)ο蟮絇refebs文件夾使玩家變成預(yù)制。再創(chuàng)建一個空的游戲?qū)ο螅瑢⑵涿麨镚ameController,并將您剛剛創(chuàng)建的腳本附加到其上。綁定【Inspector】中所有對應(yīng)的需要的引用。

現(xiàn)在,你應(yīng)該很熟悉這種模式了。建議你試著自己實現(xiàn)引用,再次運(yùn)行游戲。請觀察游戲控制器是如何實現(xiàn)游戲控制的。

故事至此結(jié)束;你已經(jīng)成功地使用腳本實現(xiàn)了你的第一個Unity游戲!祝賀你!

小結(jié)

本文示例工程完整的下載地址是http://www.raywenderlich.com/wp-content/uploads/2016/03/BlockBusterFinal.zip

現(xiàn)在,你應(yīng)該對編寫一個簡單的動作游戲所需要的內(nèi)容有了一個很好的理解。實際上,制作游戲決不是一個簡單的任務(wù);它肯定需要大量的工作,而腳本只是把一個項目實現(xiàn)為一款真正的游戲所必需的要素之一。為了進(jìn)一步添加游戲修飾效果,還需要將動畫和漂亮的UI及粒子效果等添加到您的游戲中。當(dāng)然,要實現(xiàn)一款真正意義上的商業(yè)游戲,您還要克服更多的困難。

 

 

責(zé)任編輯:陳琳 來源: 51cto
相關(guān)推薦

2011-05-04 10:22:27

2011-04-15 12:48:28

雙搖桿射擊游戲游戲iOS

2012-04-01 10:02:00

HTML5

2012-03-16 09:35:52

HTML 5

2013-04-25 00:06:06

unity3D手機(jī)游戲引擎

2012-12-24 08:51:23

iOSUnity3D

2023-07-26 07:59:28

2012-04-24 22:10:37

2015-08-27 16:35:10

Unity游戲引擎Linux

2012-03-11 15:20:36

Android

2012-05-15 13:57:41

HTML5

2012-12-24 08:46:50

iOSUnity3D

2018-02-06 10:46:53

2012-03-06 10:56:32

HTML 5

2024-03-22 09:45:34

大型語言模型Unity引擎游戲開發(fā)

2015-04-22 20:33:06

寶德云游戲蠻牛unity

2015-08-10 14:45:50

游戲開發(fā)在線資源

2015-08-10 11:21:47

在線資源游戲開發(fā)

2013-04-25 09:56:24

unity3D手機(jī)游戲引擎
點贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 国产精品久久久 | 奇米影视在线 | 九九久久国产 | 国产精品免费视频一区 | 精品综合久久 | 国产一区二区三区视频 | av中文字幕在线观看 | 中文字幕一区二区三区在线观看 | 精品一区二区三区在线播放 | 亚洲视频二区 | 337p日本欧洲亚洲大胆精蜜臀 | 三级在线视频 | 成人免费在线播放视频 | 国产精品久久欧美久久一区 | 九九久久精品 | 精品99在线 | 欧美国产日韩精品 | 亚洲欧美日韩精品久久亚洲区 | 一区二区av | 欧美日韩在线视频观看 | 精品国产乱码久久久久久88av | 中文字幕国产视频 | 国产精品久久久久久久久久99 | 激情 婷婷 | 亚洲一区有码 | 天堂一区 | 一级黄色短片 | 亚洲中午字幕 | 女同久久另类99精品国产 | 免费看国产a | 日韩一区二区在线观看视频 | 殴美一级片| 成人国产在线观看 | 97精品超碰一区二区三区 | 黄色av网站免费看 | 污视频在线免费观看 | 中文字幕一区二区三区四区五区 | 免费观看黄色一级片 | 午夜精品久久久久久久久久久久 | 精产国产伦理一二三区 | 亚洲 精品 综合 精品 自拍 |