# 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視窗拖曳至腳本上。 ![](https://i.imgur.com/5cq2QqU.png) ![](https://i.imgur.com/wq2sxp6.png) * 之後自由生成吧。 ---- ## 生成 ```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(畫布)作為他的子物件 ![](https://i.imgur.com/TWJZJye.png) ---- ### 步驟2 #### 點擊創建出來的畫布 #### 修改Inspector中的Canvas元件 #### 將渲染模式改為WorldSpace #### 事件相機改成MainCamera #### 然後把位置(PosX、Y、Z)都設置為0 #### 最後調整一下大小 ![](https://i.imgur.com/cwEC2bi.png) ---- ### 步驟3 #### 在Canvas下右鍵創建Image #### 作為血條的底框 ![](https://i.imgur.com/ycwimn1.png) ---- ### 步驟4 #### 在Inspector(屬性)視窗中的Image元件中 #### 拖入UI這張全白的圖片 ![](https://i.imgur.com/WPtUMV8.png) ---- ### 步驟5 #### 回到Hierarchy視窗 #### 再創建一個Image #### 作為主要控制的血條 ![](https://i.imgur.com/k5gTWc6.png) ---- ### 步驟6 #### 來到血條的Inspector(屬性)視窗 #### 將Image元件設置如下 ![](https://i.imgur.com/nIi5kKl.png) ---- #### 藉由變更Fill Amount的值控制目前血條的比例 #### 當然還需要調整下血條的長寬 ![](https://i.imgur.com/hXQUexW.png) ---- # 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) 可利用裡面已經設定好參數來偵測按鍵輸入 ---- ![](https://i.imgur.com/0kAlu4U.png) ---- ```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 ![](https://i.imgur.com/mmeEaQV.png) ---- ### 封裝 ---- #### 就是將所有屬性變數在類別(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: ![](https://i.imgur.com/1ZXlKR6.png) ---- static有人稱它是靜態的意思,是因為有用static修飾過的屬性是存放在靜態區域,並且在一開始就被載入記憶體,從程式碼開始就有這屬性,會直到結束後才消失,而這空間是大家共用的,因此如果數值有改變就不會有初始化的動作, ---- 而這個程式碼其實就是將一個static的變數去裝自己。 這樣的話就可以利用static的特性,不用特地抓取物件,就可以從其他物件存取到。 ---- 而用法1有一些缺點: 由於Awake的順序問題,當其他腳本也在Awake調用時,可能單例腳本還沒將其指向自己,導致沒有初始化,變數裡為Null。 ---- 用法2: 藉由變數本身get屬性來判斷 ![](https://i.imgur.com/ebIFwdp.png) ---- 這樣就可以不用擔心是否已經實例化出來了。 Get是取值。Set是寫入。 這邊在想要讀取單例變數時,就會去搜尋場景中的Player將其放入自己。 ---- 進階應用(略): 創建一個專門用來做單例的泛型Class,其他想使用單例模式的腳本都繼承它。 ![](https://i.imgur.com/APdQ8kR.png) ![](https://i.imgur.com/2x2FR1J.png) --- # 插件 ---- ## 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}]"}
    641 views