# C#基礎程式
---
## 列舉(Enum)
----
##### 列舉其實不是必要的東西
##### 但是他可以讓你比較好閱讀
----
``` csharp=
// 列舉遊戲狀態
enum GameStatus
{
GAME_READY, // =0 遊戲準備
GAME_START, // =1 遊戲開始
GAME_OVER // =2 遊戲結束
//不論定義幾個狀態 這個列舉都只視為一個變數的大小
}
// 宣告你的列舉變數 遊戲狀態
GameStatus gameStatus;
// Use this for initialization
void Start ()
{
// 用switch來判斷你的列舉狀態
switch (gameStatus)
{
case GameStatus.GAME_READY: //等同於 case 0
Debug.Log("GAME_READY");
break;
case GameStatus.GAME_START:
Debug.Log("GAME_START");
break;
case GameStatus.GAME_OVER:
Debug.Log("GAME_OVER");
break;
}
}
```
---
# 抓取物件
----
修改自己的父物件
```
transform.parent = 父物件.transform;
```
獲取自己的子物件
```
transform.GetChild(0);//獲取排在其下的第一個子物件
```
----
#### Find方法 抓取物件
效率低蠻耗資源的做法,可以在場景中依據物件名字去搜尋出來。
除此之外,能搜尋到的只限於沒有被隱藏的的物件。
建議只用在Start()中獲取再存在變數裡面,以便之後調用。
```
GameObject.Find("場景中的物件名稱");
```
----
也有通過tag之類的方式抓取物件,具體用法都大同小異。
其他:
http://theunity3d.blogspot.com/2012/04/find.html
---
# 預製物
----
#### 預製物(Prefab)
###### 將Hierarchy視窗裡的物件拖曳到Project視窗,即可視為創建預製物(Prefab),預製物可以從腳本中動態生成。
----
##### 1. 直接拖曳
* 在腳本裡宣告公開變數,之後直接從Project視窗拖曳至腳本上。


* 之後自由生成吧。
----
## 生成
```csharp=
Instantiate(物件, 位置,旋轉);
```
```csharp=
Instantiate(gameObject, transform.position,transform.rotation);
```
----
##### 2. Resources方法
創建一個名為Resources的資料夾在Project的最外層即可使用。
```csharp=
Resources.Load<資料型態>(資料位置);
```
----
生成:
```csharp=
//如果是物件的話:
//GameObject Obj = Resources.Load<資料型態>(資料位置);
GameObject Obj = Resources.Load<GameObject>("Folder/Cube");
Instantiate(Obj, transform.position,transform.rotation);
```
---
# 計時器與協程
----
### 使用Time.deltaTime
一秒內從第1個Frame到最後一個Frame所花的時間,
所以不管電腦是一秒跑60格或者一秒30格、24格,值都會趨近於一。
就結果而言,deltaTime是為了遊戲「公平性」而產生的東西,
因此最常用於位移(translate),但拿來計時也是勉強可以用。
----
```csharp=
float ti = 0;
void Update()
{
ti += Time.deltaTime;//
if(ti >= 3)
{
print("每三秒計一次時");
ti = 0;
}
}
```
----
### StartCoroutine協程
啟動一個「協程」。
協程簡言之就是執行一個可繼續、可中斷的函式,
所以我可以告訴程式我要隔幾秒在執行下一行陳述式,
因此可以拿來當做計時器。
載入場景時,會把資源都花在讀取上,場景畫面會卡住。
此時也可以用協程來做些Loading動畫。
----
```csharp=
IEnumerator Clock()
{
while(true)
{
yield return new WaitForSecondsRealtime(5);
print("每5秒執行一次");
}
}
void Start()
{
StartCoroutine(Clock());//必須用此函式才有辦法呼叫協程 不然不會呼叫
}
```
----
直接將協程放在Update不優,會大量產生Clock協程,產生不可預期之結果。
----
### InvokeRepeating
調用一個方法/秒 。
InvokeRepeating接受三個參數,第一個是方法名,第二個是「第一次調用」要隔幾秒,第三個則是「每隔幾秒調用一次」,與StartCoroutine一樣,是可以中斷的,只要使用CancelInvoke()。
```csharp=
void Clock()
{
print("每秒調用");
}
void Start()
{
InvokeRepeating("Clock",1f,1f);
}
```
----
### Time.time
從遊戲開始到現在所使用的時間。
---
# 發射子彈
----
```csharp=
void Update(){
transform.Translate(transform.up * speed * Time.deltaTime);
}
```
----
將子彈給予往上飛的向量。
而這個transform.up指向的是物體的上方。
所以如果將物件往右轉向的話,物體也會跟著變成往右飛。
----
```csharp=
public IEnumerator Fire(float time)
{
while(true)
{
yield return new WaitForSeconds(time);//每time秒發射子彈
if (Input.GetKey(KeyCode.Mouse0))
{
Vector3 rot = new Vector3(0,0,90);
Instantiate(gameObject, transform.position,Quaternion.Euler(rot));
}
}
}
```
----
旋轉比較特別,單位不是向量,而是歐拉角度,具體應用如下:
```csharp=
transform.rotation = Quaternion.Euler(向量);
```
---
## 血條控制
----
### 步驟1
#### 於Hierarchy視窗找到主角
#### 右鍵新增Canvas(畫布)作為他的子物件

----
### 步驟2
#### 點擊創建出來的畫布
#### 修改Inspector中的Canvas元件
#### 將渲染模式改為WorldSpace
#### 事件相機改成MainCamera
#### 然後把位置(PosX、Y、Z)都設置為0
#### 最後調整一下大小

----
### 步驟3
#### 在Canvas下右鍵創建Image
#### 作為血條的底框

----
### 步驟4
#### 在Inspector(屬性)視窗中的Image元件中
#### 拖入UI這張全白的圖片

----
### 步驟5
#### 回到Hierarchy視窗
#### 再創建一個Image
#### 作為主要控制的血條

----
### 步驟6
#### 來到血條的Inspector(屬性)視窗
#### 將Image元件設置如下

----
#### 藉由變更Fill Amount的值控制目前血條的比例
#### 當然還需要調整下血條的長寬

----
# HP血條
``` csharp =
public float hp;
public float maxHp;
public float setHpUi(Transform hpCanvas, float hp, float maxHp)
{
var hpImage = hpCanvas.GetChild(0).GetChild(0).GetComponent<Image>();
if (hp > maxHp)
hp = maxHp;
else if (hp < 0)
hp = 0;
hpImage.fillAmount = hp / maxHp;
//fillAmount等於血量/血量最大值的比例
return hp;
}
```
----
# 輸入管理器
----
Unity有內建的輸入管理器(Input Manager)
可利用裡面已經設定好參數來偵測按鍵輸入
----

----
```csharp=
void Move()
{
float moveX = Input.GetAxis("Horizontal");
//GetAxis可透過字串遍歷在InputManger裡的參數
//其中值會在-1~0~1之間
//以這個參數Horizontal為例
//按住不動即為0
//按住左鍵會逐漸減少至-1
//按住右鍵會逐漸增加至1
Rig.velocity = new Vector2(moveX * Speed, Rig.velocity.y);
}
```
---
# 物件導向
## 物件導向概念
----
### 程式的運作就是由不同物件來運行的
### 每個物件都由類別來產生
----
### 在Project視窗右鍵創建兩個script
### 分別命名為Biological以及Player

----
### 封裝
----
#### 就是將所有屬性變數在類別(class)裡面
#### 都定義為private(私有的)
#### 這樣其他腳本就無法存取到這個變數
#### 所以要在設計公共方法set來賦予值
#### 以及公共方法get來得到值
#### 用意是在於保護code
#### ㄅ過廢物如我都懶得這樣寫 ㄅ歉
----
### 程式碼(封裝):
``` csharp=
public class Biological : MonoBehaviour
{
private int hp;
private int atk;
public void setHp(int hp)
{
this.hp = hp;
//this.hp是指這個腳本本身的hp(第3行)
//程式碼中呼叫某個變數時是先看括號{}setHp的hp明顯最近
//所以直接寫hp是使用setHp(int hp)的參數
}
public int getHp()
{
return hp;
}
}
```
之後其他腳本想存取這些屬性都要利用set、get這些公開方法
----
### 繼承
----
#### 玩家跟敵人都是生物
#### 他們的共同點是都有血量
#### 也會發射子彈進行攻擊
#### 那麼就定義一個生物的Class
#### 讓玩家和敵人都繼承它
#### 就可以不用再寫一次
----
#### 然後不一樣的地方
#### 像是玩家還會揮刀之類的
#### 再在玩家這邊的Class定義
#### 敵人也是同理
----
### 程式碼(繼承):
``` csharp=
public class Biological : MonoBehaviour
{
protected float hp;
//protected被保護的型別 只可自己使用以及繼承自自己的腳本去做使用
}
```
打開命名為生物的腳本加入變數hp
```csharp=
public class Player : Biological
{
}
```
將原本Player繼承的MonoBehaviour
改為繼承自生物
繼承後即可直接在Player使用hp這個變數
----
### 多型
----
#### class中可做多個同名的方法
#### 並將參數設置成不同型別
#### 或者不同數量
#### 之後使用此方法時
#### 系統就會依照你輸入的參數去調用方法
----
### 程式碼(多型):
``` csharp=
public class Biological : MonoBehaviour
{
public void move()
{}
public void move(int x)
{}
public void move(float x)
{}
}
```
----
### 覆寫
----
#### 用於修改繼承來的方法
#### 玩家繼承生物後
#### 獲得了發射子彈這項方法
#### 但玩家的彈幕發射可以升級來加強
#### 敵人彈幕則是會依據不同種類使用不同彈幕
#### 這些不同點必須使用覆寫來修改原本的方法
#### 才能實現
----
### 程式碼(覆寫):
``` csharp=
public class Biological : MonoBehaviour
{
public virtual void Fire(float time)
{
//程式敘述...
}
}
```
原腳本方法須加上關鍵字virtual
``` csharp=
public class Player : Biological
{
public override void Fire(float time)
{
//改寫程式敘述
}
}
```
----
使用關鍵字override進行方法覆寫
這樣即可修改在Player時的Fire方法
----
### 呼叫原腳本屬性、方法
----
#### 有時候可能會需要原繼承腳本的屬性/方法
----
### 程式碼:
``` csharp=
public class Biological : MonoBehaviour
{
public virtual void Fire(float time)
{
//程式敘述...
}
}
```
``` csharp=
public class Player : Biological
{
public override void Fire(float time)
{
base.Fire(time);
}
}
```
使用base可使用原腳本屬性變數、方法
---
# 單例模式
用在只有唯一一個腳本(單機遊戲的主角、GameManager等)時,方便其他腳本上去調用。
----
用法1:

----
static有人稱它是靜態的意思,是因為有用static修飾過的屬性是存放在靜態區域,並且在一開始就被載入記憶體,從程式碼開始就有這屬性,會直到結束後才消失,而這空間是大家共用的,因此如果數值有改變就不會有初始化的動作,
----
而這個程式碼其實就是將一個static的變數去裝自己。
這樣的話就可以利用static的特性,不用特地抓取物件,就可以從其他物件存取到。
----
而用法1有一些缺點:
由於Awake的順序問題,當其他腳本也在Awake調用時,可能單例腳本還沒將其指向自己,導致沒有初始化,變數裡為Null。
----
用法2:
藉由變數本身get屬性來判斷

----
這樣就可以不用擔心是否已經實例化出來了。
Get是取值。Set是寫入。
這邊在想要讀取單例變數時,就會去搜尋場景中的Player將其放入自己。
----
進階應用(略):
創建一個專門用來做單例的泛型Class,其他想使用單例模式的腳本都繼承它。


---
# 插件
----
## Dotween動畫
----
### 簡單來說就是用程式碼
### 直接動態插入動畫
### 接下來進行打擊感的製作
----
## 來到Biological腳本
----
#### 在使用之前需在腳本上方引入此函式庫
``` csharp=
using DG.Tweening;
```
----
## 程式碼(Dotween):
``` csharp=
Tween shake_Do;//用來存取晃動的動畫
Tween color_Do;//用來存取變色的動畫
public void Shake()
{
if (shake_Do == null)//當晃動動畫不存在時
{
shake_Do = transform.DOShakePosition(0.5f, 0.2f);
//賦址給一個新的晃動動畫
//第一個參數為晃動大小 第二個參數為執行秒數
//此方法為用來晃動位置
transform.DOShakeScale(0.5f, 0.2f);
//同理 晃動大小
color_Do = transform.GetComponent<SpriteRenderer>().DOColor(new Color32(255, 200, 200, 255), 0.075f);
//獲取圖片元件做顏色漸變
//將顏色設置為紅色
//此RGBA顏色參數請自行去查顏色表
}
else
{
if (!shake_Do.IsPlaying())
{
//當動畫沒被執行的話 做跟上面同樣的效果
//這樣寫的理由 是因為如果shake_Do為空的話
//直接使用方法會報錯 或許也可改寫成try catch
shake_Do = transform.DOShakePosition(0.2f, 0.2f);
transform.DOShakeScale(0.2f, 0.2f);
color_Do = transform.GetComponent<SpriteRenderer>().DOColor(new Color32(255, 200, 200, 255), 0.075f);
}
}
}//將此動畫放置在碰撞處理中 即可做出受擊效果
public void Re_Do()
{
if (color_Do != null)
{
if (!color_Do.IsPlaying())
{
color_Do = transform.GetComponent<SpriteRenderer>().DOColor(Color.white, 0.1f);
}
}//將此放置在Update裡面 將顏色漸變動畫結束後可以還原原本顏色
}
```
---
https://drive.google.com/file/d/1oQdWiURUUOvINrX6oI7bi6UdSINTVHoK/view?usp=sharing
{"metaMigratedAt":"2023-06-15T22:05:41.902Z","metaMigratedFrom":"Content","title":"C#基礎程式","breaks":true,"contributors":"[{\"id\":\"faca3329-cc98-475b-ac42-c0a846116bf8\",\"add\":9861,\"del\":291}]"}