版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2019.01.26 星期六 |
前言
Unity是由Unity Technologies開發的一個讓玩家輕松創建諸如三維視頻游戲、建筑可視化、實時三維動畫等類型互動內容的多平臺的綜合型游戲開發工具,是一個全面整合的專業游戲引擎。Unity類似于Director,Blender game engine, Virtools 或 Torque Game Builder等利用交互的圖型化開發環境為首要方式的軟件。其編輯器運行在Windows 和Mac OS X下,可發布游戲至Windows、Mac、Wii、iPhone、WebGL(需要HTML5)、Windows phone 8和Android平臺。也可以利用Unity web player插件發布網頁游戲,支持Mac和Windows的網頁瀏覽。它的網頁播放器也被Mac 所支持。網頁游戲 坦克英雄和手機游戲王者榮耀都是基于它的開發。
下面我們就一起開啟Unity之旅。感興趣的看下面幾篇文章。
1. Unity開啟篇(一) —— Unity界面及創建第一個簡單的游戲 (一)
2. Unity開啟篇(二) —— Unity界面及創建第一個簡單的游戲 (二)
3. Unity開啟篇(三) —— 一款簡單射擊游戲示例 (一)
4. Unity開啟篇(四) —— 一款簡單射擊游戲示例 (二)
5. Unity開啟篇(五) —— 一款簡單射擊游戲示例 (三)
6. Unity開啟篇(六) —— Unity動畫簡介 (一)
7. Unity開啟篇(七) —— Unity動畫簡介 (二)
8. Unity開啟篇(八) —— Unity聲音簡介(一)
9. Unity開啟篇(九) —— Unity聲音簡介(二)
10. Unity開啟篇(十) —— Unity粒子系統簡介(一)
11. Unity開啟篇(十一) —— Unity粒子系統簡介(二)
開始
在本教程中,通過創建一個經典的競技場射擊游戲來學習Unity中腳本的基礎知識。
Unity的大部分功能都在于其豐富的腳本語言C#
。 您可以使用它來處理用戶輸入,操縱場景中的對象,檢測碰撞,生成新的游戲對象以及在場景周圍投射定向光線以幫助您的游戲邏輯。 這可能聽起來令人生畏,但Unity公開了具有完備文檔的API,使這些任務變得輕而易舉 - 即使對于新手開發人員也是如此!
在本教程中,您將創建一個自上而下的射擊游戲,它使用Unity腳本來處理敵人的產生,玩家控制,射擊射彈以及游戲玩法的其他重要方面。
注意:本教程假設您已具備使用C#或類似編程語言的經驗,并了解Unity的界面和工作流程。
本教程是為Unity 5.3
或更高版本編寫的。 您可以在此處here下載最新版本的Unity。
雖然Unity也支持UnityScript
和Boo
,但C#
是大多數開發人員傾向于使用的編程語言,并且有充分的理由。 C#被全球數百萬開發人員用于應用程序,Web和游戲開發,并且有大量的信息和教程可以幫助您。
打開已有的工程,具體頁面如下所示:
在Scene view
視圖中查看。 這是一個小型區域,將成為游戲的戰場,相機和燈光。 如果您的布局與屏幕截圖中的布局不同,請選擇右上角的下拉菜單并將其更改為2 by 3
。
沒有英雄角色算什么游戲? 您的第一個任務是創建一個GameObject
來表示場景中的玩家。
Creating the Player
在Hierarchy
中,單擊Create
按鈕,然后從“3D”部分中選擇Sphere
。 將球體定位在(X:0,Y:0.5,Z:0)
并將其命名為Player
:
Unity
使用entity-component
系統來構建其GameObjects
。 這意味著所有GameObject
都是組件的容器,可以附加這些組件以賦予其行為和屬性。 以下是Unity內置組件的幾個示例:
-
Tranform:每個
GameObject
都附帶此組件。 它保存GameObject
的位置,旋轉和縮放。 - Box Collider:立方體形狀的對撞機,可用于探測碰撞。
- Mesh Filter:用于顯示3D模型的網格數據。
Player GameObject
需要響應與場景中其他對象的碰撞。
要實現此目的,請在Hierarchy window
窗口中選擇Player
,然后單擊“檢查器”窗口中的Add `Component按鈕。 在彈出的菜單中選擇Physics> Rigidbody,這將為播放器添加一個Rigidbody組件,以便它可以使用Unity的物理引擎。
像這樣調整Rigidody
的值:設置Drag
到1,Angular Drag
到0并選中Freeze Position
旁邊的Y復選框。
這將確保Player
無法上下移動,并且在旋轉時不會增加阻尼。
Creating the Player Movement Script
現在Player
已準備就緒,是時候創建將從鍵盤輸入并移動Player
的腳本。
在Project window
中,單擊Create
按鈕并選擇Folder
。 將新文件夾命名為Scripts
,并在其中創建一個名為Player
的子文件夾。
在Player
文件夾中,單擊Create
按鈕并選擇C#Script
。 將新腳本命名為PlayerMovement
。 序列如下所示:
注意:使用這樣的文件夾可以輕松地按角色組織所有內容并減少混亂。 你將為
Player
制作幾個腳本,所以給它自己的文件夾是有意義的。
雙擊PlayerMovement.cs
腳本。 這將打開您首選的代碼編輯器并加載腳本。 Unity附帶預裝在所有平臺上的MonoDevelop
,Windows
用戶可以選擇安裝Visual Studio
,并在運行安裝程序時使用它。
本教程假設您使用的是MonoDevelop
,但Visual Studio
用戶應該能夠順利進行操作而不會出現任何問題。
一旦您選擇的編輯器打開,您將看到以下界面:
這是Unity
為新腳本生成的默認類。它源自基類MonoBehaviour
,它確保此腳本將在游戲循環中運行,并具有對某些事件作出反應的附加功能。如果你來自iOS世界,這個對象相當于UIViewController
。當腳本運行時,Unity會按預定順序調用多個方法。以下是一些最常見的:
-
Start()
:在腳本第一次更新之前,將調用此方法一次。 -
Update()
:當游戲運行且腳本啟用時,此方法將在每一幀被觸發。 -
OnDestroy()
:此方法在附加到此腳本的GameObject
之前被調用。 -
OnCollisionEnter()
:當連接此腳本的碰撞器或剛體觸及另一個碰撞器或剛體時,此方法被調用。
有關事件的完整列表,請查看Unity’s documentation on MonoBehaviours的文檔。
在Start()
方法上方添加以下兩行:
public float acceleration;
public float maxSpeed;
它應該是這樣的:
這些是公共變量聲明(variable declarations)
,這意味著這些變量將在Inspector
中可見,并且可以調整而無需在腳本和編輯器之間來回切換。
acceleration
描述了Player's
速度隨著時間的推移而增加的程度。 maxSpeed
是“速度限制”。
在其下方,聲明以下變量:
private Rigidbody rigidBody;
private KeyCode[] inputKeys;
private Vector3[] directionsForKeys;
私有變量無法通過Inspector
設置,開發人員有責任在適當的時候初始化它們。
rigidBody
將保存對附加到Player GameObject
的Rigidbody
組件的引用。
inputKeys
是一個用于檢測輸入的密鑰代碼數組。
directionsForKeys
包含一個Vector3
變量數組,它將保存方向數據。
用以下內容替換Start()
方法:
void Start () {
inputKeys = new KeyCode[] { KeyCode.W, KeyCode.A, KeyCode.S, KeyCode.D };
directionsForKeys = new Vector3[] { Vector3.forward, Vector3.left, Vector3.back, Vector3.right };
rigidBody = GetComponent<Rigidbody>();
}
這段代碼鏈接每個密鑰的相應方向,例如 按W
可將物體向前移動。 最后一行獲取對atttached Rigidbody
組件的引用,并將其保存在rigidBody
變量中供以后使用。
要實際移動Player
,您必須處理鍵盤輸入。
將Update()
重命名為FixedUpdate()
并添加以下代碼:
// 1
void FixedUpdate () {
for (int i = 0; i < inputKeys.Length; i++){
var key = inputKeys[i];
// 2
if(Input.GetKey(key)) {
// 3
Vector3 movement = directionsForKeys[i] * acceleration * Time.deltaTime;
}
}
}
這里有幾件重要的事情:
- 1)
FixedUpdate()
與幀速率無關,在使用Rigidbodies
時應該使用。此方法將以恒定間隔觸發,而不是盡可能快地運行。 - 2) 此循環檢查是否按下了任何輸入鍵。
- 3) 獲取按下的鍵的方向,將其乘以加速度和完成最后一幀所花費的秒數。這將生成一個方向向量(X,Y和Z軸上的速度),用于移動
Player
對象。
如果您不熟悉游戲編程,您可能會問自己為什么必須乘以Time.deltaTime
。當游戲運行時,幀速率(或每秒幀數)將根據硬件和它所承受的壓力而變化,這可能導致在強大的機器上發生很快,而在較弱的機器上發生太慢可能導致不期望的行為。一般規則是當你每個(固定)幀執行一個動作時,你需要乘以Time.deltaTime
。
在FixedUpdate()
下面添加以下方法:
void movePlayer(Vector3 movement) {
if(rigidBody.velocity.magnitude * acceleration > maxSpeed) {
rigidBody.AddForce(movement * -1);
} else {
rigidBody.AddForce(movement);
}
}
上述方法對ridigbody
施加力,使其移動。 如果當前速度超過maxSpeed
,則力量向相反方向移動以減慢玩家的速度,并有效地限制最大速度。
在FixedUpdate()
中,在if語句的右括號之前,添加以下行:
movePlayer(movement);
很好! 保存此腳本并返回Unity編輯器。 在Project window
中,將PlayerMovement
腳本拖動到“層次結構”內的Player
上。
向GameObject
添加腳本會創建組件的實例,這意味著將為您附加的GameObject
執行所有代碼。
使用Inspector
將Acceleration
設置為625
,將Max Speed
設置為4375
:
運行場景并使用WASD
鍵移動Player
:
只有幾行代碼,這是一個非常好的結果!
然而,有一個明顯的問題 - 玩家可以快速移出視線。
Creating the Camera Script
在Scripts
文件夾中,創建一個名為CameraRig
的新腳本,并將其附加到Main Camera
。 需要一些幫助來弄清楚步驟? 您可以查看下面的提示。
選擇
Scripts
文件夾后,單擊項目瀏覽器(Project Browser)
中的Create
按鈕,然后選擇C#Script
。 將新腳本命名為CameraRig
。 最后,將其拖放到Main Camera
對象上,如下所示:
現在,在新創建的CameraRig
類中,在Start()
方法的正上方創建以下變量:
public float moveSpeed;
public GameObject target;
private Transform rigTransform;
您可能已經猜到了,moveSpeed
是攝像機跟隨目標的速度 - 可以是場景中的任何游戲對象。
在Start()
內,添加以下行:
rigTransform = this.transform.parent;
此代碼獲取對場景層次結構中父Camera
對象的變換的引用。 場景中的每個對象都有一個變換Transform
,它描述了對象的位置,旋轉和縮放。
在同一個腳本中,添加以下方法:
void FixedUpdate () {
if(target == null){
return;
}
rigTransform.position = Vector3.Lerp(rigTransform.position, target.transform.position,
Time.deltaTime * moveSpeed);
}
CameraRig
移動代碼比PlayerMovement
中的移動代碼稍微簡單一些。 這是因為你不需要Rigidbody
;只需在rigTransform
和目標的位置之間進行插值即可。
Vector3.Lerp()
在空間中取兩個點,在[0,1]
范圍內取浮點,它描述了沿兩個端點的點。 左端點為0
,右端點為1
。將0.5
傳遞給Lerp()
將在兩個端點之間準確返回一個點。
這會使rigTransform
更接近目標位置并稍微緩和一下。 簡而言之 - 相機跟隨player
。
回到Unity
。 確保在層次結構中仍然選擇了Main Camera
。 在Inspector
中,將Move Speed
設置為8,將Target
設置為Player
:
運行游戲并在場景中移動;無論走到哪里,攝像機都應該順利地跟蹤目標變換。
Creating an Enemy
沒有敵人的射擊游戲很容易被擊敗,但有點無聊。 通過單擊頂部菜單中的GameObject \ 3D Object \ Cube
創建敵人立方體。 將您的Cube重命名為Enemy
并添加Rigidbody
組件。
在Inspector
中,首先將Cube
的Transform
設置為(0,0.5,4)
。 在Rigidbody
組件的Constraints
部分中,選中Freeze Position
類別中的Y復選框。
優秀 - 現在讓你的敵人以威脅的方式四處走動。 在Scripts
文件夾中創建一個名為Enemy
的腳本。 您現在應該成為專家,但如果沒有,請查看教程前面的說明以供參考。
接下來,在類中添加以下公共變量:
public float moveSpeed;
public int health;
public int damage;
public Transform targetTransform;
您可能無需太多困難就能弄清楚這些變量代表什么。 您之前使用moveSpeed
創建了攝像機裝備,它在此處具有相同的效果。 health
和damage
有助于確定敵人何時死亡以及他們的死亡會對Player
造成多大傷害。 最后,targetTransform
引用了Player
的變換。
說到玩家,你需要創建一個類來表示敵人想要摧毀的所有玩家。
在項目瀏覽器中,選擇Player
文件夾并創建一個名為Player
的新腳本;此腳本將對碰撞做出反應并跟蹤玩家的健康狀況。 雙擊腳本進行編輯。
添加以下公共變量以存儲Player's
的生命值:
public int health = 3;
這為健康狀況提供了默認值,但也可以在檢查器中進行修改。
要處理碰撞,請添加以下方法:
void collidedWithEnemy(Enemy enemy) {
// Enemy attack code
if(health <= 0) {
// Todo
}
}
void OnCollisionEnter (Collision col) {
Enemy enemy = col.collider.gameObject.GetComponent<Enemy>();
collidedWithEnemy(enemy);
}
當兩個帶有碰撞器的剛體接觸時,OnCollisionEnter()
會觸發。 Collision
參數包含有關接觸點和碰撞速度等信息。 在這種情況下,您只對碰撞對象的Enemy
組件感興趣,因此您可以調用collidedWithEnemy()
并執行攻擊邏輯 - 您接下來將添加該邏輯。
切換回Enemy.cs
并添加以下方法:
void FixedUpdate () {
if(targetTransform != null) {
this.transform.position = Vector3.MoveTowards(this.transform.position, targetTransform.transform.position, Time.deltaTime * moveSpeed);
}
}
public void TakeDamage(int damage) {
health -= damage;
if(health <= 0) {
Destroy(this.gameObject);
}
}
public void Attack(Player player) {
player.health -= this.damage;
Destroy(this.gameObject);
}
你已經熟悉FixedUpdate()
了,不同的是你使用的是MoveTowards()
而不是Lerp()
。 這是因為敵人應該始終以相同的速度移動,而不是在接近目標時輕松進入。 當敵人被射彈擊中時,會調用TakeDamage()
;當敵人達到0健康時,它會自我毀滅。Attack()
類似 - 它對玩家施加傷害然后敵人摧毀自己。
切換回Player.cs
并在collidedWithEnemy()
中,用以下內容替換Enemy attack code
:
enemy.Attack(this);
玩家將受到傷害,敵人將在此過程中自我毀滅。
切換回Unity
。 將Enemy
腳本附加到Enemy
對象并在Inspector
中,在Enemy上設置以下值:
- 1)
Move Speed: 5
- 2)
Health: 2
- 3)
Damage: 1
- 4)
Target Transform: Player
到現在為止,您應該能夠自己完成所有這些工作。 自己嘗試一下,然后將結果與下面的GIF進行比較:
在游戲中,與玩家對抗的敵人構成有效的敵人攻擊。 使用Unity的物理檢測碰撞幾乎是一項微不足道的任務。
最后,將Player
腳本附加到層次結構中的Player
。
運行游戲,并密切關注控制臺:
當敵人到達玩家時,它會成功執行攻擊并將玩家的健康變量減少到2。但是在控制臺中拋出NullReferenceException
,指向Player
腳本:
啊哈 - 玩家不僅可以與敵人發生碰撞,還可以與游戲世界的其他部分發生碰撞,例如競技場。 這些游戲對象沒有Enemy
腳本,因此GetComponent()
返回null
。
打開Player.cs
。 在OnCollisionEnter()
中,在if
語句中包裝collidedWithEnemy()
:
if(enemy) {
collidedWithEnemy(enemy);
}
這樣就沒有null
了。
后記
本篇主要講述了Unity腳本簡介,感興趣的給個贊或者關注~~~