# 玩轉 Unity
## 什麼是遊戲引擎?
所謂遊戲引擎是將製作遊戲所需要的許多功能,打包成一個工具,省略許多開發步驟,讓開發者更加專注在遊戲設計上。
### Unity
Untiy 有幾個優點:
1.網路上學習資源多。
2.Unity 官方有持續維護更新自家引擎。
3.Unity 支援多平台開發遊戲,例如桌機、手機、家用主機等。
## Unity 介面教學
- Hierarchy 用於顯示場上所有物件的階層關係
- Scene 場景,編輯、排列物品。
- Game 是測試、操作遊戲用。
- Project 顯示專案用到的資源。
- Inspector 偵錯工具。
## 父物件、子物件及 Prefab
### 父物件、子物件
- component SpriteRenderer 為 2D 物件的渲染器,能針對圖案做許多設定
- SpriteRenderer 中有一個 Order in Layer 選項,可以調節圖層
- Ctrl 鍵壓住,可以在 Hierarchy 視窗中多選務件,一口氣做 Component 變更
- 件立物件的父子關係,讓其他物件附著在指定物件上。在 Hierarchy 中,選取子物件並拖曳到父物件上面,即可件立父子關係
### Prefab
- Prefab 定義為預先製作好的物體
- 只要將已經在 Hierarchy 中做好的新物件組拉回 project 內的 Asset 資料夾中,就會成為 Prefab
- 即使刪除了場上的物件組,你也可以在拉出 Prefab 來代替
- 物件重複使用度高的時候,建議製成 Prefab
## 建立 Script & C# Script
```csharp=
public class ClassTest : MonoBehaviour
{
// Use this for initialization
void Start () // 只有在遊戲開始時執行一次
{
Debug.Log('Hello Game');
}
// Update is called once per frame
void Update () //Update 內的內容,每一次遊戲回圈都會運行一次(前提是該腳本的附著物與腳本都是啟用的)
{
Debug.Log('Update');
}
}
```
## 類別、屬性與方法簡介
### 腳本
- 每一個創造出來的腳本,都是一個 Class (類別)
### Class 類別
最主要的構成元素:
- 屬性:靜態、可倍數值化符號化的 (我有些什麼)=> 在類別內會使用到的物件
- 方法:動態、該物體可執行的事項 (我能夠做些什麼)=> 將我們想讓此類別做的事情在方法內描述
Example: Class 路人甲
- 屬性:身高、體重、年齡、智商
- 方法:睡眠、行走、閱讀、減肥
### 屬性會在方法內做調用
Example:
當使用"投彈"(方法)時,會使"機上飛彈數量"(屬性)減少、"重量"(屬性)減少。
### 方法內可以包含其他方法
Example:
"出任務"(方法)包含了"起飛"(方法)+"飛行"(方法)+"投彈"(方法)+"降落"(方法)
### 撰寫腳本前
- 想好腳本能做些什麼(方法)
- 想好腳本會用到什麼(屬性)
## 變數宣告
變數需要經過宣告才能使用,包含最基礎的變數型別(種類)和識別字(名稱)。
浮點數:變數型別之一,小數。
### 常用變數種類
System 內建
- int 整數範圍(-2147483648~2147483647),long 長整數
- float 浮點數(double倍精確數)
- string 字串
- bool 布林
Unity Engine 內建
- GameObject 遊戲物件
### 宣告的格式
- 存取修飾詞(決定誰可以對此變數做修改)
- 資料型別(此變數的類型)
- 變數名稱(此變數的名稱)
#### 存取修飾詞
共有五種,從 C# 7.2 版起有六種,這邊只提兩種:
- public 所有腳本皆可使用
- private 只有此腳本可以使用
#### 宣告位置
在 class 內宣告,或是方法內。
#### Box Script 製作
```csharp=
public class Box : MonoBehaviour
{
//高度 寬度 float
public float Height;
public float Width;
//名字
public string BoxName;
//盒子的開盒狀態
public bool IsOpen;
}
```
## 變數賦值
float 賦值:
float B = 3.14f; (需要加 f 從 double 轉型到 float,C#預設沒打 f 就是 double,double可以到小數點後非常多位)
### Box 賦值範例
```csharp=
public class Box : MonoBehaviour
{
//高度 寬度 float
public float Height;
public float Width;
//名字
public string BoxName;
//盒子的開盒狀態
public bool IsOpen;
private void Start()
{
Height = 100;
Width = 50;
BoxNam = '公事包';
IsOpen = true;
}
}
```
## 迴圈
遞增遞減運算子
```csharp=
// 先執行 X=a之後執行 a = a+1
X=a++;
// 先執行 a = a+1 之後執行 X=a
X=++a;
```
for 迴圈
```csharp=
fot(int i =0; i<2; i++)
{
Debug.Log(i);
}
```
### While
條件達成時就執行
```csharp=
while (Weight < 100)
{
Debug.Log( "我的體重是" + Weight);
Weight++;
}
Debug.Log("太胖啦");
```
## 方法
Example:
```csharp=
public class spaceShip : MonoBehaviour
{
public float height;
public float bottom;
public float area;
// Start is called before the first frame update
void Start()
{
area = triangle(height, bottom);
}
public float triangle (float height, float bottom)
{
float tempArea;
tempArea = bottom * height / 2;
return tempArea;
}
}
```
## 青蛙射擊(遊戲流程規劃+架構解說)
玩家操作一隻坐在噴射板上的青蛙,青蛙會受地心引力牽扯向下,點擊滑鼠左鍵會消耗一點能量使其噴射向上,點擊滑鼠右鍵會消耗兩點能量射出美乃滋攻擊阻礙物,失敗三次後遊戲結束。
補充能量:
1.吃場地上的美乃滋+1點。
2.擊落阻礙物補滿能量。
失敗條件:
1.撞擊到阻礙物。
2.墜地。
### 遊戲流程
### 程式架構
GameManager 遊戲主流程:
1.物件生成
- BackgroundController(背景)
- SpawnMonster(怪物)
- SpawnEnergy(能量球)
2.物件控制
- PlayerController(角色)
- MonsterController(怪物)
- BulletController(子彈)
3.其他
- DelayDestroy DeadSpace(物件銷毀相關)
- MoveObject(移動物件)
- TimerMachine(計時器)
- AudioPlayer_Monster(怪物音效)
### 靜態方法
不需要宣告,也不需要賦值即可使用的方法
靜態方法的使用方式是
類別名.靜態方法名();
ex: Debug.Log();
```csharp=
//在 Update 內使用
if(Input.GetMouseButtonDown(0))//0左鍵 1右鍵 2中鍵
{
//如果按下滑鼠按鍵瞬間要做的事情
}
//在 Update 內使用
if(Input.GetMouseButton(0))//0左鍵 1右鍵 2中鍵
{
//只要按著滑鼠左鍵就會反覆觸發的事情
}
```
## Collider 2D(碰撞器)、Rigidbody 2D(剛體)
- Collider 2D 控制碰撞的範圍及效果
- Rigidbody 2D 賦予物件物理效果模擬,且能做多項調整
## OnTrigger function
- OnTriggerEnter 2D 撞入的一瞬間
- OnTriggerStay 2D 重疊時期
- OnTriggerExit 2D 離開的一瞬間
```csharp=
public void OnTriggerEnter2D(Collider2D collision)
{
if(collision.tag == "floor")
{
Debug.Log("撞到地板");
}
}
```
### Player function
1.Jump
```csharp=
public Rigidbody2D RB;
// Update is called once per frame
void Update()
{
}
//Jump
public void Jump()
{
//x為0(水平0),y增加為8(向上8)。
RB.velocity = new Vector2(0, 8);
}
```
2.Prepare
```csharp=
public Rigidbody2D RB;
public Vector2 StartPosition;
// Update is called once per frame
void Update()
{
}
//Prepare
public void Prepare()
{
//速度為0
RB.velocity = Vector2.zero;
//調整為運動學控制
RB.bodyType = RigidbodyType2D.Kinematic;
//回到初始的位置
gameObject.transform.position = StartPosition;
}
```
3.Go
```csharp=
public void Go()
{
//回歸受重力影響
RB.bodyType = RigidbodyType2D.Dynamic;
}
```
## 動畫
- 動畫 單獨動作的動態
- 動畫機 整合多個動畫,決定其觸發時機點以及播放邏輯
青蛙方面要製作 4 個動畫
1.待機
2.跳躍
3.掉落中
4.受傷
### 動畫機開啟
- 新建動畫機流程: project 區域右鍵 create => Animation Controller,然後將動畫機黏至父物件
- 新建動畫流程: project 區域右驗 create => Animation
- 錄製動畫: window => Animation => Animator
```csharp=
public class PlayerController : MonoBehaviour
{
public Rigidbody2D RB;
public Vector2 StartPosition;
public Animator Anim; //建立一個 Anim 變數,型別為 Animator
// Update is called once per frame
void Update()
{
if(Input.GetMouseButtonDown(0))
{
Jump();
//Prepare();
//Go();
}
}
//Go
public void Go()
{
//回歸受到重力影響
RB.bodyType = RigidbodyType2D.Dynamic;
//開始遊戲設定為"Fall"
Anim.SetTrigger("Fall"); //呼叫 Anim,抓取裡面的trigger。
}
}
```
## 粒子系統
選擇指定的 gameObject,右鍵 Effects,選particle system。
- Duration 噴發多久
- Loop 重複播放
- Start LifeTime 代表每一顆粒子生成後的存活時間
Emission:
- Rate over time 粒子的噴發量
Size over lifetime:
- 每一顆粒子從出現到消失期間,大小的變化。
Play On Awake: 一開始就會先噴一次。
### Material
Create => Material
自己建立粒子:
- Shader => Standard => albedo => 選擇想要的圖片=> Sprites => Default
## 創建怪獸
Create 區域右鍵按 Create empty 建立 Game Object
### 讓怪獸以自訂速度水平移動
創建怪獸後,建立一個 script,這個 class 要做的事情是讓怪獸水平移動。
完成怪獸的 prefab 後,記得按 apply 更新。Prefab 完成後,就可以將場上原本製作的 object 移除。
```csharp=
public class MoveObject : MonoBehaviour
{
//想移動的物體
public GameObject Obj;
//自訂速度
public float Speed;
// Update is called once per frame
void Update()
{
//(Speed * Time.deltaTime, 0, 0) 中,前面第一組數據(Speed * Time.deltaTime)代表 X軸
//,後面兩組0表示YZ兩軸,這邊我們只需要怪物往左移動,故只移動X軸。
//Time.deltaTime 是每一幀所花的時間,每一個畫面所花的時間。由於每個人的電腦效能不同,每秒能跑的幀數可能也不同。
//Time.deltaTime 可以確保在不同裝置上的速度一樣。
Obj.transform.position += new Vector3(Speed * Time.deltaTime, 0, 0);
}
}
```
### 怪物生成點

先創建一個新 gameobject,命名為 SpawnMonster,並且 reset。

在 SpawnMonster 中創建5個 object,並定義為怪物生成點。
創建一個 SpawnMonster script。
```csharp=
//產生位置
public Transform[] SpawnPosition;
// monster prefab dog and cat
public GameObject MonsterPop;
public GameObject MonsterUnya;
// Start is called before the first frame update
void Start()
{
InvokeRepeating("Spawn", 0, 1.5f);//InvokeRepeating("Spawn", 0, 3) 呼叫 Spawn 方法,從第 0 秒開始,每 3 秒呼叫一次。
}
public void Spawn()
{
Vector3 tempSpawnPos; //此次決定要生成怪物的位置
tempSpawnPos = SpawnPosition[ Random.Range(0, 5) ].position;
int temp = Random.Range(1, 100);
GameObject newMonster;
if(temp >= 80)
{
//20% create cat
//Instantiate(物件 位置 旋轉) 物件:GameObject 位置:Vector3 旋轉量:Quaternion
newMonster = Instantiate(MonsterUnya, tempSpawnPos, Quaternion.identity);
}
else
{
//80% create dog
newMonster = Instantiate(MonsterPop, tempSpawnPos, Quaternion.identity);
}
}
```
### 遊戲空間設定

## 計時器製作
功能:
- 歸零
- 開始計時
- 暫停
建立一個計時器腳本
```csharp=
public class TimerMachine : MonoBehaviour
{
//當前秒數
public float Timer;
//控制 Timer 開始暫停
public bool AllowRun;
void Update()
{
if (Input.GetMouseButtonDown(0))
{
Run();
} else if(Input.GetMouseButtonDown(1))
{
Init();
}
if (AllowRun)
{
Timer += Time.deltaTime;
}
}
//初始化 歸零
public void Init()
{
Timer = 0;
AllowRun = false;
}
//讓碼表開始計時
public void Run()
{
AllowRun = true;
}
//暫停
public void Pause()
{
AllowRun = false;
}
}
```
## 回合流程管理

### Enum 枚舉
當一個東西有特定的多種狀態時,可以考慮用 Enum 做列舉,ex:
角狀狀態:健康/中毒/麻痺/無敵
```csharp=
public Enum status(){
健康,
中毒,
麻痺,
無敵
}
```
寫成 Enum 在 Unity 上會變成下拉選單,可以搭配 Switch 使用。
首先創建一個 GameManager 腳本,然後在場上建立一個 GameManager 遊戲物件,並將 GameManager 腳本放到上面
```csharp=
//只有 Enum 可以宣告在 Class 外面,方便其他腳本取用。
public enum GameState
{
Prepare,
InGame,
Die,
Score
}
public class GameManager : MonoBehaviour
{
//用來記錄當前的回合流程是哪一個
public GameState State;
//玩家腳本
public PlayerController Player;
public SpawnMonster Spawner_Monster;
//青蛙的次數
public int Chance;
void Update()
{
if (Input.GetMouseButtonDown(0))
{
switch (State)
{
case GameState.Prepare:
//讓玩家開始掉落
Player.Go();
//前往下一個遊戲狀態
SetGameState(GameState.InGame);
break;
case GameState.InGame:
Player.Jump();
break;
case GameState.Die:
Debug.Log("已經死亡不能動");
break;
case GameState.Score:
break;
default:
break;
}
}
}
//設定流程
public void SetGameState(GameState state)
{
State = state;
switch (State)
{
case GameState.Prepare:
//角色回到準備狀態
Player.Prepare();
//停止生成怪物
Spawner_Monster.StopGenerate();
break;
case GameState.InGame:
//開始生成怪物
Spawner_Monster.StartGenerate();
break;
case GameState.Die:
Chance--;
Spawner_Monster.StopGenerate();
Invoke("GameCheck", 1); //延遲一秒執行指定方法
break;
case GameState.Score:
break;
default:
break;
}
}
//確認是否還有遊玩機會
public void GameCheck()
{
if (Chance > 0)
{
//如果還有機會
SetGameState(GameState.Prepare);
} else
{
//機會用完
SetGameState(GameState.Score);
}
}
}
```
另外也要修改 playerController 的 script 來對應 GameManager script。
## 能量系統製作(美乃滋)

- 撰寫一個方法用來對能量做變動
- PolygonCollider 2D ,可以調整多邊形碰撞範圍。
- 製作美乃滋動畫。
- 參考建立怪物生成點的方式設置美乃滋生成點
- 建立生成美乃滋的 script。
```csharp=
//產生位置
public Transform[] SpawnPosition;
public GameObject Prefab_Energy;
//生成頻率
public float Delay;
//開始生成能量球
public void StartGenerate()
{
InvokeRepeating("Spawn", 0, Delay);
}
//停止生成能量球
public void StopGenerate()
{
CancelInvoke();
}
//生成單顆能量球
public void Spawn()
{
Vector3 tempSpawnPos; //此次決定要生成能量球的位置
tempSpawnPos = SpawnPosition[Random.Range(0, SpawnPosition.Length)].position;
Instantiate(Prefab_Energy, tempSpawnPos, Quaternion.identity); //生成的物件、生成位置,不需要旋轉
}
```
## 讓背景移動
原理:
- 每 1-3 秒生成一棵樹,大小不固定。
- 建立樹的預製物
- 建立樹的腳本
```csharp=
//樹的預製物
public GameObject Prefab_Tree;
//倒數計時器
public float Timer;
//最長生成間隔
public float DelayMax;
//最短生成間隔
public float DelayMin;
//每一次的生成會從兩個數字間取值
//樹最大體積
public float TreeSizeMax;
//樹最小體積
public float TreeSizeMin;
private void Start()
{
//初始化第一次的倒數值
Timer = Random.Range(DelayMin, DelayMax);
}
void Update()
{
Timer -= Time.deltaTime;
if(Timer <= 0)
{
//生成一顆樹
SpawnTree();
//重新倒數
Timer = Random.Range(DelayMin, DelayMax);
}
}
public void SpawnTree()
{
GameObject tempTree = Instantiate(Prefab_Tree, new Vector3(12, -4.6f, 1), Quaternion.identity);
float tempSize = Random.Range(TreeSizeMin, TreeSizeMax);
tempTree.transform.localScale = new Vector3(tempSize, tempSize, 1);
}
```
## 銷毀物件
建立 Deadspace 腳本,在銷毀的 GameObject 掛上 Deasspace。
要產生碰撞至少要有其中一方掛上 Rigidbody 2D。
在需要銷毀物件的死亡區域掛上 DeadSpace 腳本。
```csharp=
private void OnTriggerEnter2D(Collider2D collision)
{
Destroy(collision.gameObject);
}
```
## 製作子彈
- 製作子彈物件(box collider2d, Rigidbody2d),完成後做成預製物。
- 製作子彈動畫
- 如果要對 Prefab 物件的欄位賦值,不能拉入場景上的物件,只能拉 Asset 資料夾內的物件。
```csharp=
```
### 銷毀子彈
```csharp=
public GameObject Bullet;
private PlayerController Player;
void Start()
{
//如果要對 Prefab 物件的欄位賦值,不能拉入場景上的物件,只能拉 Asset 資料夾內的物件。
//在 Start 內執行,找到 Player 物件,並且抓取其上的 PlayerController 腳本
//GameObject.Find("物件名"),會找尋場地上的物件中有對應名稱的
//找尋物件上的component,GetComponent<類別名稱>()
Player = GameObject.Find("Player").GetComponent<PlayerController>();
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.tag == "Monster")
{
//銷毀怪物
Destroy(collision.gameObject);
//回復能量
Player.EnergyFull();
//銷毀子彈
Destroy(Bullet);
}
}
```
## UI製作
UGUI:Unity的UI系統
注意:

### 常用UI Components
- Canvas
- Image
- Text
- Button
### 邀要製作的 UI
- 左上方分數
- 右上方暫停/青蛙機會顯示
- 左下角能量條
- 正下方的操作提示
- 結算頁面
- 暫停面板
### UI 製作
Hierachy 視窗點右鍵選 UI,點選要創建的物件。
- Canvas 的 Render Mode 中有三個選項,overlay 是所有的 ui 物件一定在畫面最上方。Camera 要指定攝影機。
- Canvas Scaler 可以依照不同的螢幕大小,幫畫面大小作調整。
- React Transform 是 UI 專用的 Transform,
- EventSystem 偵測事件。
## UI 套用功能套用
能量 UI 套用:在 PlayerContorller 腳本裡新增一個陣列用來擺放所有能量點物件。
```csharp=
public GameObject[] Obj_EnergyPoint;//能量點 UI 顯示物件
//計算能量
public void SetEnergy(int add)
{
//在計算開始前先把能量 ui 全部關閉
for (int i = 0; i < Obj_EnergyPoint.Length; i++)
{
Obj_EnergyPoint[i].SetActive(false);//GameObject物件.SetActive(bool是否開啟)開關物件用的方法
}
if(Energy + add > 6)
{
Energy = 6;
}
else if(Energy + add < 0)
{
Energy = 0;
}
else
{
Energy += add;
}
//依序打開能量點 UI
for (int i = 0; i < Energy; i++)
{
Obj_EnergyPoint[i].SetActive(true);
}
}
//補滿能量
public void EnergyFull()
{
Energy = 6;
//打開每一個能量點
for (int i = 0; i < Obj_EnergyPoint.Length; i++)
{
Obj_EnergyPoint[i].SetActive(true);
}
}
```
### 暫停面板
Button UI 中會附帶一個 Button Script,當中有一個 On Click 選項,可以將點擊時希望發生的事情寫在裡面。
將暫停面板的 GameObject 拉到 On Click 中。
先在 GameManager 腳本中寫一個暫停方法。
```csharp=
//暫停
public void Pause()
{
//暫停時間
Time.timeScale = 0;//Time.timeScale = float; 時間流速,設定時間速度,0為暫停。
}
//回到遊戲
public void BackToGame()
{
//回復時間
Time.timeScale = 1;
}
//離開遊戲
public void ExitGame()
{
//離開遊戲
Application.Quit();
}
```
### 結算面板
```csharp=
//顯示結算內容
public void ShowGameDetail()
{
//當前分數 分數 = 當前的時間*2 + 怪物擊殺*5
Text_Score.text = (int)((Timer.Timer * 2) + (MonsterKill * 5)) + "分";
//歷史高分
//生存時間
Text_Time.text = (int)(Timer.Timer) + "秒";
//擊殺怪物
Text_Kill.text = MonsterKill + "隻";
}
```
### 重來
```csharp=
//遊戲初始化
public void InitGame()
{
//將機會調整成3
Chance = 3;
//顯示青蛙硬幣
for (int i = 0; i < Coins.Length; i++)
{
if (i < Chance)
{
Coins[i].SetActive(true);
}
else
{
Coins[i].SetActive(false);
}
}
//回到準備狀態
SetGameState(GameState.Prepare);
//關閉結算
ScorePanel.SetActive(false);
//碼表歸零
Timer.Init();
//怪物擊殺數歸零
MonsterKill = 0;
}
```
## 添加音效
在要添加音效的 gameobject 上面新增 Audio Source Component。
在 PlayerController script 中新增 Audio Object 和播放方法。
```csharp=
//AudioClip 表示我們要用的音源檔
public AudioClip Clip_Die;
public AudioClip Clip_Jump;
public AudioClip Clip_Eat;
//AudioSource 播放器,AudioClip CD
public AudioSource Audio;
public void Jump()
{
if( Energy > 0)
{
RB.velocity = new Vector2(0, 8);
//跳躍設定為"Jump"
Anim.SetTrigger("Jump");
Particle.Play();
//消耗一點能量
SetEnergy(-1);
//跳躍音效
PlayEfect(Clip_Jump);
}
else
{
Debug.Log("能量不足。");
}
}
```
## 存檔系統
Unity 存檔系統主要依靠 PlayerPrefs

```csharp=
//歷史高分
public int HighestScore;
//顯示結算內容
public void ShowGameDetail()
{
int Score = (int)((Timer.Timer * 2) + (MonsterKill * 5));
//當前分數 分數 = 當前的時間*2 + 怪物擊殺*5
Text_Score.text = Score + "分";
//歷史高分
if (Score > HighestScore)
{
HighestScore = Score;
PlayerPrefs.SetInt("HighestScore", HighestScore);
}
Text_Top.text = HighestScore + "分";
//生存時間
Text_Time.text = (int)(Timer.Timer) + "秒";
//擊殺怪物
Text_Kill.text = MonsterKill + "隻";
}
```
## 細節處理
製作 DelayDestroy 腳本,用來刪除 flash, smoke。
```csharp=
public float Delay;//要在幾秒之後銷毀
void Start()
{
Destroy(gameObject, Delay);//多載寫法,延遲刪除
}
```
## AssetStore 資源引用~自適屏(螢幕自動調適)
自適屏種類
- 硬性保持比例,其餘部分以黑色取代
- 依照螢幕改變物體及畫面比例
先用 Windows 內的 AssetStore 功能導入資源包
## 物件池
物件池是一種優化手段


