Unity Game Development
第8&9話 實用UI、場景切換與管理
Lecturer:水獺
目錄(可點)
本堂課內容
本堂課內容
遊戲開始UI
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
今天所有的操作
皆由上星期的專案繼續
新建一個Scene
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
「Scene」,也就是場景,是個我們非常熟悉的東西
在開啟一個Unity專案的時候
Unity Editor會自動生成一個「Sample Scene」
你可以直接在裡面做開發
但當我們要做一些完全不同的遊戲畫面時
新增不同的Scene來管理是一個比較明智的方式
Canvas
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
「Canvas」,畫布,是Unity所有UI遊戲物件的基礎
所有的UI物件都要被放在Canvas裡
作為他的子物件才能夠運作
在Hierarchy裡按右鍵 --> UI --> Canvas創建一個新的Canvas,命名Start
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250294/pasted-from-clipboard.png)
Canvas
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
Editor會同時創建一個Event System物件
那跟後面的互動部分(按鈕、輸入)相關
點到Canvas物件
你會發現他非常大
我們要來調整一些設定
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250359/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250362/pasted-from-clipboard.png)
原本的編輯區
Canvas
Canvas
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
看到右邊Inspector裡的Canvas Scaler組件
裡面有一個UI Scale Mode
這會影響到的是Canvas的大小如何訂定
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250372/pasted-from-clipboard.png)
以像素為單位訂定的固定大小,除非調整Canvas組件設定否則無法變動
符合螢幕大小,可指定螢幕的大小數值,通常用這個
沒用過,但一律不建議固定
改成Scale With Screen Size
Canvas
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250387/pasted-from-clipboard.png)
把Reference Resolution改成1920*1080(Full HD)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250390/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250393/pasted-from-clipboard.png)
Game Panel的顯示比例也要記得改16:9(原本是Free Aspect)才不會跑掉
Canvas
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
之後他就會長這樣:
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250440/pasted-from-clipboard.png)
如果想預覽編輯後的結果可以按到Game Panel去檢視~
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250443/pasted-from-clipboard.png)
Image
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
對Start Canvas按右鍵選UI --> Image新增一個Image物件
裡面的Image組件有點類似一般的Sprite Renderer
可以理解為UI裡的Sprite Renderer
上面的Rect Transform也可以理解成一般Transform的進階版
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250402/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250410/pasted-from-clipboard.png)
Image
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250410/pasted-from-clipboard.png)
長寬,因為是背景所以設成跟螢幕一樣大(1920*1080)
位置
中心點(一個藍色圓圈,可以跟父物件的中心點對在一起,預設在中心,也可以影響座標基準)
錨點(不用改,可以影響座標基準點)
旋轉
大小
圖片,可以理解成一般的Sprite Renderer放圖像資源的地方
沒有圖片時的純色填充
材質
可否被Mask蓋住
Image
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250410/pasted-from-clipboard.png)
長寬,因為是背景所以設成跟螢幕一樣大(1920*1080)
位置
中心點(一個藍色圓圈,可以跟父物件的中心點對在一起,預設在中心,也可以影響座標基準)
錨點(不用改,可以影響座標基準點)
旋轉
大小
圖片,可以理解成一般的Sprite Renderer放圖像資源的地方
沒有圖片時的純色填充
材質
可否被Mask蓋住
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250430/pasted-from-clipboard.png)
點一下上面的錨點圖可以用GUI改
Image
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
目前長這樣
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250438/pasted-from-clipboard.png)
TextMeshPro
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
這是Unity新版的文字
舊版的可以改的東西比較少,同時也可能比較快被移除或成為過時組件
所以本堂課以新版為例
在Start Canvas上點右鍵 --> UI --> Text - TextMeshPro
創建一個新的TextMeshPro物件
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250453/pasted-from-clipboard.png)
TextMeshPro
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
之後可能會出現這個視窗
按Import TMP Essentials
等他跑一下之後 下面那個Extras也可以新增一下
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250458/pasted-from-clipboard.png)
TextMeshPro
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
右邊Inspector的上面一樣是Rect Transform和Canvas Renderer兩個組件,不管它們
我們看到下方的TextMeshPro - Text (UI)組件
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250479/pasted-from-clipboard.png)
文字,預設New Text
各種預設好的文字樣式,可以自己試試
字型,等等教你們怎麼新增
材質
文字樣式,由左到右:粗體、斜體、下劃線、刪除線、小寫、大寫、小寫用大寫表示但矮一點(w我不會形容,自己試試看)
文字大小
是否根據目前文字框長寬調整文字大小至完全符合換行
TextMeshPro
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
接續上一頁
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250479/pasted-from-clipboard.png)
文字顏色
可以調整字元之間、單字之間、行之間跟段落之間的間隔大小
對齊選項
啟用/停用自動換行
當超出大小後的顯示模式,預設顯示,也可以調遮住等等
TextMeshPro
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
調整文字、文字框長寬、位置和文字大小之後大概會長這樣
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250559/pasted-from-clipboard.png)
TextMeshPro
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
字體好醜,我們來自定字體吧
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250573/pasted-from-clipboard.png)
TextMeshPro
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
接著對字體檔點右鍵
選Create --> TextMeshPro --> Font Asset
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250584/pasted-from-clipboard.png)
TextMeshPro
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
再來就可以點到剛剛的TextMeshPro物件
把Font Asset拖進TextMeshPro - Text(UI)組件裡的Font Asset欄位
字體就定義好了
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250597/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250600/pasted-from-clipboard.png)
Button
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
最後我們來做開始按鈕
順便把後面的商店按鈕也做一做
Start Canvas上點右鍵之後Create --> UI --> Button - TextMeshPro
創建一個帶有TextMeshPro文字物件的按鈕
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250616/pasted-from-clipboard.png)
他特小
Button
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250623/pasted-from-clipboard.png)
物件旁邊有個箭頭可以下拉
有TextMeshPro物件可以調整按鈕上的文字
先來看一下Image組件
上面的Source Image可以改變按鈕的背景圖
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250629/pasted-from-clipboard.png)
Button
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
接著是Button組件
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250636/pasted-from-clipboard.png)
按鈕互動時會變換顏色的圖(預設是按鈕背景)
一般狀況下的顏色
滑鼠移動到上面的顏色
按下瞬間的顏色
按下後還沒點擊其他地方的顏色
禁用(不可點擊)時的顏色
漸變時長
點擊時的動作
Button
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
先來講個觀念
大家可以點左上角File --> Build Settings
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250668/pasted-from-clipboard.png)
這裡是調整各種輸出遊戲的設定的地方
不管是遊戲平台、全螢幕設定或要輸出的Scene之類的都可以調整
現在我們要看的是上面的Scenes In Build
如果這裡只有上個禮拜的Scene而沒有今天的Start的話
我們可以點右下角的Add Open Scene把現在正在編輯的Scene加進來
Button
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
好了之後會長這樣
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250693/pasted-from-clipboard.png)
看到右邊的數字了嗎?
那叫Build Index,也就是Unity Editor給要被輸出的Scene的編號
現在遊戲畫面的Index是0,開始Scene是1
等等我們就要利用這個數字來取得要變換到的Scene
Button
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
來寫程式吧!
這次的程式很簡單
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement; //記得加這個才能對Scene做操作
public class StartMenu : MonoBehaviour
{
public void ChangeScene() //記得加public不然等等在加按鈕事件的時候讀不到
{
//用LoadScene(buildIndex)來更換目前顯示的Scene
SceneManager.LoadScene(0);
}
}
用一個函式就可以換Scene了!
Button
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
不過要讓按鈕能在備按下時執行腳本的步驟有點複雜
大家可以先注意聽,等等再操作
首先是腳本要加在哪個遊戲物件上
其實都可以
但是因為這個腳本是針對開始介面的
所以我會習慣把它放在Start Canvas上
Button
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11251146/pasted-from-clipboard.png)
Button
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
接下來進到Button組件裡面
找到On Click()列表
按「+」加入一個事件
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11251155/pasted-from-clipboard.png)
Button
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
會長這樣
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11251168/pasted-from-clipboard.png)
附帶有按鈕按下後要執行的函式的遊戲物件
我們剛把換場景的腳本放在誰身上?
Start Canvas!
Button
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
把Start Canvas拖進來之後
這裡就是按下按鈕之後要執行的事情
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11251203/pasted-from-clipboard.png)
有幾種常用的類別
Button
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
如果你想讓Start Canvas在按下按鈕後消失(停用)
就將事件設為 Canvas --> bool enabled
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11251323/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11251326/pasted-from-clipboard.png)
並且把值設為false(框框不打勾)
按鈕按下後就會把Start Canvas的Canvas組件設為停用
也就不會顯示所有在Start Canvas中的物件了
適用於所有組件,也可以丟不同的物件進來
不打勾
Button
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
如果你想讓整個GameObject被停用
則要把事件設為GameObject --> SetActive (bool)
並且把值設為false(框框不打勾)
按鈕按下後就會把整個GameObject設為停用
適用於所有遊戲物件
不打勾
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11251357/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11251364/pasted-from-clipboard.png)
Button
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
如果你想讓改一個GameObject的名稱
那就要把事件設成GameObject --> string name
並且把值設為你想改成的名字
按下按鈕後名稱即會被變更
適用於所有遊戲物件
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11251373/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11251377/pasted-from-clipboard.png)
Button
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
還有其他很多,比如整數型的layer、GameObject的SetParent以及很多的內建函式等
基本上就是秉持一個原則:
如果是要修改值的,填入的都是修改後的值(要把A改成B則欄位中填B)
沒有要修改值的就是直接選擇函式就可以執行了
而我們的變更Scene已經把所有要更改的都寫在腳本裡了 所以屬於第二個
可以直接選StartMenu(或你自己的腳本名稱) --> ChangeScene()(或你自己的函式名稱)
來執行
如果你發現找不到,有可能是以下幾個問題:
- OnClick()事件的Object區沒有放進有附帶腳本的物件
- 腳本中的函式前方沒有加public
- 腳本附加錯遊戲物件
Button
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11251415/pasted-from-clipboard.png)
Button
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250186/images__2_.png)
好了之後按下按鈕,應該就可以切換Scene到遊戲畫面啦
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11251418/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11251419/pasted-from-clipboard.png)
本堂課內容
本堂課內容
血條和遊戲選單
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250184/game_life_health_bar_life_bar_health_level_game_bar_status_bar_games_game_item-512.webp)
Bossbar
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250184/game_life_health_bar_life_bar_health_level_game_bar_status_bar_games_game_item-512.webp)
加工一下遊戲
把敵人有關尋路範圍(TrackingRange)的物件和腳本拿掉或停用
讓他們一出生就會朝畫面上的玩家走去
再加一個生成敵人的腳本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyGenerator : MonoBehaviour
{
[SerializeField] GameObject enemyPrefab; //Enemy的Prefab參考
bool inGenerateCD = false; //生成是否已冷卻
private void Update() {
if(!inGenerateCD){ //如果生成已經冷卻
StartCoroutine(generate()); //啟動生成敵人的協程(下堂會講)
}
}
IEnumerator generate(){
inGenerateCD = true; //進冷卻
Instantiate(enemyPrefab, new Vector3(7.8f, Random.value*5,0),Quaternion.identity); //在x軸7.8、y軸隨機的地方生成一個敵人
yield return new WaitForSecondsRealtime(Random.value*3); //進入隨機0~3秒的冷卻(等待0~3秒才執行接下來的程式)
inGenerateCD = false; //冷卻完畢
}
}
Bossbar
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250184/game_life_health_bar_life_bar_health_level_game_bar_status_bar_games_game_item-512.webp)
再來把遊戲角色的轉向腳本寫好並加進Player
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerRotate : MonoBehaviour
{
Vector2 dir; //角色->滑鼠方向向量
void Update()
{
dir = Camera.main.ScreenToWorldPoint(Input.mousePosition-transform.position).normalized; //取得方向向量
transform.up = Vector2.Lerp(transform.up, dir, 0.2f); //漸變方向
}
}
Bossbar
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250184/game_life_health_bar_life_bar_health_level_game_bar_status_bar_games_game_item-512.webp)
再來把遊戲角色的射擊腳本寫好並加進Player
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerShoot : MonoBehaviour
{
[SerializeField] GameObject bulletPrefab; //子彈Prefab參考
bool inCD = false;
void Update()
{
if(Input.GetMouseButtonDown(0) && !inCD){ //按下左鍵時
Instantiate(bulletPrefab,transform.position,transform.rotation); //涉及
StartCoroutine(CD()); //冷卻協程
}
}
IEnumerator CD(){
inCD = true; //進CD
yield return new WaitForSecondsRealtime(0.5f); //等待0.5秒
inCD = false; //CD結束
}
}
Bossbar
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250184/game_life_health_bar_life_bar_health_level_game_bar_status_bar_games_game_item-512.webp)
再來把遊戲角色的射擊腳本寫好並加進Player
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerShoot : MonoBehaviour
{
[SerializeField] GameObject bulletPrefab; //子彈Prefab參考
bool inCD = false;
void Update()
{
if(Input.GetMouseButtonDown(0) && !inCD){ //按下左鍵及不在CD時
Instantiate(bulletPrefab,transform.position,transform.rotation); //涉及
StartCoroutine(CD()); //冷卻協程
}
}
IEnumerator CD(){
inCD = true; //進CD
yield return new WaitForSecondsRealtime(0.1f); //等待0.1秒
inCD = false; //CD結束
}
}
Bossbar
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250184/game_life_health_bar_life_bar_health_level_game_bar_status_bar_games_game_item-512.webp)
新建一顆圓形子彈
加入Collider2D、Rigidbody2D並調整大小
之後寫一個子彈的腳本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bullet : MonoBehaviour
{
float speed = 5; //子彈速度
[SerializeField] Rigidbody2D rb2D; //子彈剛體參考
void Start() //被生成時
{
StartCoroutine(Delete()); //啟動協程,4秒後刪除自己
}
private void Update() {
rb2D.velocity = transform.up * speed; //指定剛體速度讓子彈飛一會
}
IEnumerator Delete(){
yield return new WaitForSecondsRealtime(4); //等待四秒
Destroy(gameObject); //刪除自己
}
private void OnCollisionEnter2D(Collision2D col) {
if(col.gameObject.CompareTag("Obstacle")){ //如果碰到障礙物
Destroy(gameObject); //刪除自己
}
else if(col.gameObject.CompareTag("Enemy")){ //碰到敵人
Destroy(gameObject); //刪除自己
}
}
}
Bossbar
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250184/game_life_health_bar_life_bar_health_level_game_bar_status_bar_games_game_item-512.webp)
再來我們安排一下畫面
新增一個Image當底
在上面點右鍵之後新增另一個Image當空的血量(會是子物件)
大概長這樣
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11252688/pasted-from-clipboard.png)
Bossbar
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250184/game_life_health_bar_life_bar_health_level_game_bar_status_bar_games_game_item-512.webp)
接著再新增一個Image在空血量上面(會是子物件)
當成血量條
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11252705/pasted-from-clipboard.png)
Bossbar
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250184/game_life_health_bar_life_bar_health_level_game_bar_status_bar_games_game_item-512.webp)
接下來非常重要的一步
我們要把血量條的Pivot(中心點)的X座標從0.5改成0
這樣在縮放的時候才會向左邊縮
而不會向中間縮
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11252710/pasted-from-clipboard.png)
Bossbar
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250184/game_life_health_bar_life_bar_health_level_game_bar_status_bar_games_game_item-512.webp)
再來是最後的兩個程式
首先是敵人生成器加入總血量計算和血量條長度更新
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyGenerator : MonoBehaviour
{
int generateLimit = 50;
public int totalBlood; //新增總血量
public int totalBloodtmp; //新增總血量初始值(不會變)
[SerializeField] GameObject enemyPrefab; //Enemy的Prefab參考
[SerializeField] GameObject bossbar; //血量條參考
bool inGenerateCD = false; //生成是否已冷卻
float bossbarStartLength; //初始血條長度
private void Start() {
totalBlood = enemyPrefab.GetComponent<EnemyNoRange>().blood * generateLimit; //從EnemyPrefab的Enemy腳本取得單隻血量並更新總血量
totalBloodtmp = enemyPrefab.GetComponent<EnemyNoRange>().blood * generateLimit; //從EnemyPrefab的Enemy腳本取得單隻血量並更新總血量初始值
bossbarStartLength = bossbar.GetComponent<RectTransform>().localScale.x; //初始血條長度
}
private void Update() {
if(!inGenerateCD && generateLimit != 0){ //如果生成已經冷卻
StartCoroutine(generate()); //啟動生成敵人的協程(下堂會講)
generateLimit --;
}
//新增:修改bossbar的scale的寬(x),高(y)不改,寬變成寬度初始值 * 目前總血量 / 總血量初始值
bossbar.GetComponent<RectTransform>().localScale = new Vector2(bossbarStartLength * totalBlood/totalBloodtmp,bossbar.GetComponent<RectTransform>().localScale.y);
}
IEnumerator generate(){
inGenerateCD = true; //進冷卻
Instantiate(enemyPrefab, new Vector3(7.8f, Random.value*5,0),Quaternion.identity); //在x軸7.8、y軸隨機的地方生成一個敵人
yield return new WaitForSecondsRealtime(Random.value*3); //進入隨機0~3秒的冷卻(等待0~3秒才執行接下來的程式)
inGenerateCD = false; //冷卻完畢
}
}
Bossbar
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250184/game_life_health_bar_life_bar_health_level_game_bar_status_bar_games_game_item-512.webp)
然後敵人程式加入被打到的扣血和沒血消失
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class EnemyNoRange : MonoBehaviour
{
public int blood = 9; //敵人血量
public int bloodMinus = 3; //敵人被打到的損血量
public GameObject Player; //被追蹤的Transform
NavMeshAgent agent; //一個叫NavMeshAgent的腳本,是尋路物件的核心Component
GameObject enemyGenerator;
private void Start() {
agent = GetComponent<NavMeshAgent>(); //取得參考
agent.updateRotation = false; //不轉Sprite方向
agent.updateUpAxis = false; //不更新朝上軸(上方永遠為Z軸正向)
agent.speed = 1;
enemyGenerator = GameObject.Find("GameManager"); //取得敵人生成器參考
}
private void Update() {
Player = GameObject.FindGameObjectWithTag("Player");
if(Player != null){
agent.SetDestination(Player.transform.position); //如果在追蹤狀態就一直把尋路終點設為被追蹤者的位置
}
else{
agent.SetDestination(transform.position); //如果不在追蹤狀態就把尋路終點設為自己的位置
}
}
private void OnCollisionEnter2D(Collision2D col) {
if(col.gameObject.CompareTag("Player")){ //如果碰到被追蹤者
Destroy(col.gameObject); //就摧毀
}
if(col.gameObject.CompareTag("Bullet")){ //新增:如果碰到子彈
blood -= bloodMinus; //扣血
enemyGenerator.GetComponent<EnemyGenerator>().totalBlood -= bloodMinus; //更新目前總血量
}
if(blood == 0){ //如果沒血
Destroy(gameObject); //刪除自己
}
}
}
Bossbar
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250184/game_life_health_bar_life_bar_health_level_game_bar_status_bar_games_game_item-512.webp)
就結束了
再來將剛剛有寫SerializeField的遊戲物件全部填充好基本上就可以了
沒填充好一定會報錯
可以自己檢查看看
真的有問題可以舉手問我
血條
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250184/game_life_health_bar_life_bar_health_level_game_bar_status_bar_games_game_item-512.webp)
可以自己練習看看
基本上跟剛剛的bossbar是相同的概念
只不過血條要放在玩家/敵人的頭上才會跟著他們
並且可以用空物件+SpriteRenderer來代替Image
因為它一定要Canvas才能運作,對於會移動的角色來說有點麻煩
像醬
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11252882/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11252889/pasted-from-clipboard.png)
暫停/遊戲結束
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250184/game_life_health_bar_life_bar_health_level_game_bar_status_bar_games_game_item-512.webp)
當玩家生命歸零或死掉
則遊戲結束
這邊以玩家Hardcore來示範
暫停/遊戲結束
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250184/game_life_health_bar_life_bar_health_level_game_bar_status_bar_games_game_item-512.webp)
很簡單
在敵人腳本裡加上一個Canvas參考
一般情況停用
在玩家死掉時啟用即可
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class Enemy : MonoBehaviour
{
public bool Tracking = false; //是否在尋路狀態
public Transform Player; //被追蹤的Transform
NavMeshAgent agent; //一個叫NavMeshAgent的腳本,是尋路物件的核心Component
[SerializeField] Canvas GameOverCanvas; //遊戲結束Canvas參考
private void Start() {
agent = GetComponent<NavMeshAgent>(); //取得參考
agent.updateRotation = false; //不轉Sprite方向
agent.updateUpAxis = false; //不更新朝上軸(上方永遠為Z軸正向)
}
private void Update() {
if(Tracking){
agent.SetDestination(Player.position); //如果在追蹤狀態就一直把尋路終點設為被追蹤者的位置
}
else{
agent.SetDestination(transform.position); //如果不在追蹤狀態就把尋路終點設為自己的位置
}
}
private void OnCollisionEnter2D(Collision2D col) {
if(col.gameObject.CompareTag("Player")){ //如果碰到被追蹤者就摧毀
Tracking = false;
Destroy(col.gameObject);
}
GameOverCanvas.enabled = true; //新增:啟用遊戲結束Canvas
}
}
暫停/遊戲結束
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250184/game_life_health_bar_life_bar_health_level_game_bar_status_bar_games_game_item-512.webp)
之後把Canvas放入參考框
啟動遊戲之後應該就可以ㄌ(記得先把Canvas停用)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class Enemy : MonoBehaviour
{
public bool Tracking = false; //是否在尋路狀態
public Transform Player; //被追蹤的Transform
NavMeshAgent agent; //一個叫NavMeshAgent的腳本,是尋路物件的核心Component
[SerializeField] Canvas GameOverCanvas; //遊戲結束Canvas參考
private void Start() {
agent = GetComponent<NavMeshAgent>(); //取得參考
agent.updateRotation = false; //不轉Sprite方向
agent.updateUpAxis = false; //不更新朝上軸(上方永遠為Z軸正向)
}
private void Update() {
if(Tracking){
agent.SetDestination(Player.position); //如果在追蹤狀態就一直把尋路終點設為被追蹤者的位置
}
else{
agent.SetDestination(transform.position); //如果不在追蹤狀態就把尋路終點設為自己的位置
}
}
private void OnCollisionEnter2D(Collision2D col) {
if(col.gameObject.CompareTag("Player")){ //如果碰到被追蹤者就摧毀
Tracking = false;
Destroy(col.gameObject);
}
GameOverCanvas.enabled = true; //啟用遊戲結束Canvas
}
}
暫停/遊戲結束
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250184/game_life_health_bar_life_bar_health_level_game_bar_status_bar_games_game_item-512.webp)
也可以自己加入一些比如重新開始等等的功能
如果有時間我會示範
暫停畫面則是寫一個暫停按鍵偵測
並且把暫停Canvas叫出來
裡面也可以放Button跳回遊戲
等等有時間也會示範
本堂課內容
本堂課內容
商店UI
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250185/shopping-cart-icon-2048x2047-gv68pvgw.png)
用舊遊戲來舉例
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11302894/pasted-from-clipboard.png)
貨幣
商店標題
商品名稱
商品價格
商品圖示
購買按鈕
裝備欄(今天不會教)
回主畫面按鈕
商品展示框
畫面滾動
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250185/shopping-cart-icon-2048x2047-gv68pvgw.png)
如果你的商品無法在一個畫面中顯示
那就需要畫面滾動來讓玩家能瀏覽所有商品
現在我們就要來實做這個功能
*以下內容有關Canvas、Image、TextMeshPro及Button的內容請見前兩小節*
*本堂課只教橫向滾動,有需要直向滾動的可以自己研究看看,不行再問我*
畫面滾動
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250185/shopping-cart-icon-2048x2047-gv68pvgw.png)
新建一個Scene當成商店的場景
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11302925/pasted-from-clipboard.png)
畫面滾動
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250185/shopping-cart-icon-2048x2047-gv68pvgw.png)
快速點兩下打開空白的Scene、新建一個Canvas
然後新建(全部都是UI):
一個TextMeshPro當標題
一個Image + 一個TextMeshPro當貨幣計數的Icon以及數值
一個按鈕當回主畫面的按鈕(可以把造型改成三角形之類的)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11302932/pasted-from-clipboard.png)
好了之後大概長醬
畫面滾動
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250185/shopping-cart-icon-2048x2047-gv68pvgw.png)
再來新建一個Image
把他的寬度調整到跟螢幕兩側只剩一點空隙
高度Depends on you
寬高會是到時候展示商品的範圍
好了長醬
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11302949/pasted-from-clipboard.png)
畫面滾動
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250185/shopping-cart-icon-2048x2047-gv68pvgw.png)
接著在Image物件上Add Component
新增一個Scroll Rect跟一個Rect Mask 2D
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11302961/pasted-from-clipboard.png)
要被滾動的子物件
是否允許水平or垂直滾動
被滾動子物件移動模式,
分為Unstricted、Elastic及Clamped三種模式
其中Elastic及Clamped會將要被滾動的子物件
限制在父物件Image中,
差別在於Elastic在超出畫面時
會有彈簧的效果將子物件拉回父物件範圍,
而Clamped沒有;
Unstricted則允許子物件亂跑。
Elastic彈簧強度
是否啟用運動慣性(在滑鼠滾輪停止轉動停下來之後子物件還會持續運動一段時間)
慣性縮減率(建議0 < this < 1,越接近0則子物件繼續移動的距離越短)
滾動敏感度(越大則滾輪轉動或滑鼠拖曳一單位的距離會讓子物件移動的越多)
當滾動時要執行的東西(加入的方式跟Button一樣)
畫面滾動
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250185/shopping-cart-icon-2048x2047-gv68pvgw.png)
接著在Image物件上Add Component
新增一個Scroll Rect跟一個Rect Mask 2D
我的設定
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11303069/pasted-from-clipboard.png)
畫面滾動
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250185/shopping-cart-icon-2048x2047-gv68pvgw.png)
接著在Image物件上Add Component
新增一個Scroll Rect跟一個Rect Mask 2D
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11303032/pasted-from-clipboard.png)
四邊內距
X、Y方向柔邊
畫面滾動
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250185/shopping-cart-icon-2048x2047-gv68pvgw.png)
再來對Image點右鍵新增一個空物件
把高調整到跟Image一樣,寬Depends on you(大於Image寬度才會有滾動效果)
寬高會是全部商品一字排開的大小
Add Component加入一個Horizontal Layout Group
這個組件可以幫我們做等間距的水平排版
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11303071/pasted-from-clipboard.png)
四邊內距
子物件之間的間距大小
子物件相對於這個物件的對齊位置
是否啟用子物件倒序排列
是否讓排版組件控制子物件長寬
排版組件縮放時是否影響子排版組件之遊戲物件
是否強制子物件延伸以填充空白處
這我的設定
畫面滾動
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250185/shopping-cart-icon-2048x2047-gv68pvgw.png)
最後把剛剛的空物件拖到Image Scroll Rect組件裡的「Content」
這部分就基本上完成了
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11303089/pasted-from-clipboard.png)
物品展示
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250185/shopping-cart-icon-2048x2047-gv68pvgw.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11303093/pasted-from-clipboard.png)
這是我這次的展示框Prefab
底圖是用Inkscape畫的
建議先在Scene中的Canvas底下拖好一個之後
再整個拖曳到Project做成Prefab不然會有點麻煩
商品名稱,Tag為Name
商品圖片
商品價目,Tag為Price
購買按鈕,文字Tag為Status
物品展示
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250185/shopping-cart-icon-2048x2047-gv68pvgw.png)
做成Prefab之後你就可以對他做以下任意操作
- 一個一個新增商品再丟回Scene裡(這我上次)
- 用程式更改Prefab實例化出來的遊戲物件資訊(這我這次)
因為我發現如果要做到購買的功能一定要把商品資訊整理好
所以就加了一點東西達成第二個操作
程式碼如下
物品展示
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250185/shopping-cart-icon-2048x2047-gv68pvgw.png)
商品資訊初始化(GoodsManager.cs)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GoodsManager : MonoBehaviour
{
public static bool spend = false; //花錢狀態機(連到CoinsManager)
public static string equipped_skin = "NS"; //目前裝備的造型名稱縮寫(因為使用static所以為唯一,等等會用)
//一個Key為商品名稱縮寫、Value為「多個Key為商品資訊(含價格、名稱、購買&裝備狀態、類別及稀有度)、Value為資訊值的字典』的字典,用來存所有的商品資訊
public static Dictionary<string,Dictionary<string,string>> goods = new Dictionary<string,Dictionary<string,string>>(){};
//一個依照商品展示順序左到右存名稱縮寫的陣列
public static List<string> names = new List<string>(){"NS","T1","T2","T3","EM"};
//一個依照商品展示順序左到右存價格的陣列
public static List<string> prices = new List<string>(){"000","010","020","030","040"};
//一個依照商品展示順序左到右存名稱的陣列
public static List<string> realName = new List<string>(){"Normal\nSkin","Test1","Test2","Test3","Evil\nMoon"};
//一個依照商品展示順序左到右存購買&裝備狀態的陣列(0 = 尚未購買;1 = 已購買未裝備;2 = 已裝備)
public static List<int> statuses = new List<int>(){2,0,0,0,0};
//一個依照商品展示順序左到右存類別的陣列(Skin = 造型or皮膚)
public static List<string> roles = new List<string>(){"Skin","Skin","Skin","Skin","Skin"};
//一個依照商品展示順序左到右存稀有度的陣列(0 = 普通;1 = 稀有;2 = 史詩;3 = 神話;4 = 傳奇)
public static List<int> rarities = new List<int>(){0,1,2,3,4};
void Awake()
{
for(int i = 0;i < names.Count;i ++){
Dictionary<string,string> good_info_temp = new Dictionary<string, string>() {}; //建立一個空字典
//將價格、名稱、購買&裝備狀態、類別及稀有度等五個資訊加入字典
good_info_temp.Add("Price",prices[i]);
good_info_temp.Add("RealName",realName[i]);
good_info_temp.Add("Status",statuses[i].ToString());
good_info_temp.Add("Role",roles[i]);
good_info_temp.Add("Rarity",rarities[i].ToString());
goods.Add(names[i],good_info_temp); //將字典加入商品資訊的大字典裡
}
}
}
物品展示
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250185/shopping-cart-icon-2048x2047-gv68pvgw.png)
進入商店展示商品(StoreManager.cs)
using System.Collections.Generic;
using System.Globalization;
using UnityEngine;
using TMPro;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class StoreManager : MonoBehaviour
{
[SerializeField] GameObject showcasePrefab;
private void Start() {
List<string> names = GoodsManager.names; //商品名稱縮寫
Dictionary<string,Dictionary<string,string>> goods = GoodsManager.goods; //商品資訊
List<string> statusText = new List<string>(){"Buy it!","Equip!","Unequip!"}; //三個按鈕文字狀態
List<string> colorCodes = new List<string>(){"9FFF8A","8BEEFF","9C4CFF","FFE73D","FF3D43"}; //五個稀有度對應的顏色色碼
for (int i = 0;i < names.Count;i++){
//這個腳本放在Canvas,我要把展示框實例化在Canvas/Image/ShowcaseHorizontal裡,所以用兩次GetChild向下讀兩層
GameObject showcase = Instantiate(showcasePrefab,transform.GetChild(0).transform.GetChild(0).transform);
showcase.name = names[i]; //改實例化的物件名稱
string colorCode = colorCodes[int.Parse(goods[names[i]]["Rarity"])]; //讀色碼
//按照色碼改展示框Image的顏色(原本是白色,覆蓋顏色上去就有不同顏色了~)
showcase.GetComponent<Image>().color = new Color(int.Parse(colorCode.Substring(0,2),NumberStyles.HexNumber)/255f,int.Parse(colorCode.Substring(2,2),NumberStyles.HexNumber)/255f,int.Parse(colorCode.Substring(4,2),NumberStyles.HexNumber)/255f);
RectTransform[] children = showcase.GetComponentsInChildren<RectTransform>(); //取得展示框內所有物件的RectTransform
List<string> columns = new List<string>(){"RealName","Body","Head","Price","Status"}; //由上到下所需更改的子物件Tag
int count = 0; //計數,共5個
foreach(RectTransform gO in children){ //遍歷所有子物件
if(gO.gameObject.tag == columns[count]){ //如果子物件的Tag跟list輪到的Tag一樣的化
TextMeshProUGUI Tmp = gO.gameObject.GetComponent<TextMeshProUGUI>(); //取得TMP參考
Image IMG = gO.gameObject.GetComponent<Image>(); //取得Image參考
if(Tmp != null){ //如果有TMP組件代表是文字
if(gO.parent.TryGetComponent(out Button button)){ //若嘗試取得Button參考成功
Tmp.SetText(statusText[int.Parse(goods[names[i]][gO.tag])]); //更新Status
//更新Button(文字物件的父物件)的顏色
gO.parent.GetComponent<Image>().color = new Color(int.Parse(colorCode.Substring(0,2),NumberStyles.HexNumber)/255f,int.Parse(colorCode.Substring(2,2),NumberStyles.HexNumber)/255f,int.Parse(colorCode.Substring(4,2),NumberStyles.HexNumber)/255f);
count --;
continue;
}
Tmp.SetText(goods[names[i]][gO.tag]); //沒有button組件則為其他文字
}
else{
//沒有TMP則為圖片,這裡用到一個小技巧,只要在Project裡新增一個Resources資料夾(跟Assets同層級)
//再將資源丟入這個資料夾,就可以用「Resources.Load<類型>(相對路徑)來讀取資源
//如果你有一個資料夾Folder,路徑是.../Resources/Folder,相對路徑指的是「Folder」,連/都不用
IMG.sprite = Resources.Load<Sprite>("Skin/"+names[i]+"/"+columns[count]);
}
count ++;
}
}
}
}
public void BackToStartMenu(){ //回主畫面
if(GoodsManager.equipped_skin != ""){ //不可沒裝備造型
SceneManager.LoadScene("StartScene"); //以Scene Name切換Scene
}
}
}
物品展示
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250185/shopping-cart-icon-2048x2047-gv68pvgw.png)
不管你用哪種方式
商店運行之後應該都要長類似這樣
並且可以左右拖動
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11303134/pasted-from-clipboard.png)
購買
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250185/shopping-cart-icon-2048x2047-gv68pvgw.png)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class BuyAndEquipSkin : MonoBehaviour //放在展示框的購買按鈕上
{
private string goods_name;
private int price;
void Start(){
goods_name = gameObject.transform.parent.name; //取得展示框名稱,也就是該商品名稱縮寫
price = int.Parse(GoodsManager.goods[goods_name]["Price"]); //取得商品價格
//如果Status是0就不用變,初始為Buy it!
if(int.Parse(GoodsManager.goods[goods_name]["Status"]) == 1){ //如果Status是1則將按鈕文字改成Equip!
TextMeshProUGUI text = gameObject.transform.GetComponentInChildren<TextMeshProUGUI>();
text.SetText("Equip!");
}
else if(int.Parse(GoodsManager.goods[goods_name]["Status"]) == 2){ //如果Status是2則將按鈕文字改成Unequip!
TextMeshProUGUI text = gameObject.transform.GetComponentInChildren<TextMeshProUGUI>();
text.SetText("Unequip!");
}
}
public void OnClick(){ //當按鈕被按下
//判斷目前的前夠不夠及此商品是否已被購買
if(CoinsManager.Coins >= price && int.Parse(GoodsManager.goods[goods_name]["Status"]) == 0){ //若錢夠且未被購買
GoodsManager.goods[goods_name]["Status"] = "1"; //購買狀態
TextMeshProUGUI text = gameObject.transform.GetComponentInChildren<TextMeshProUGUI>(); //改按鈕文字
text.SetText("Equip!");
CoinsManager.Coins -= price; //花錢
GoodsManager.spend = true; //花錢狀態機更新
}
}
}
裝備
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250185/shopping-cart-icon-2048x2047-gv68pvgw.png)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class BuyAndEquipSkin : MonoBehaviour //放在展示框的購買按鈕上
{
private string goods_name;
private int price;
void Start(){
goods_name = gameObject.transform.parent.name; //取得展示框名稱,也就是該商品名稱縮寫
price = int.Parse(GoodsManager.goods[goods_name]["Price"]); //取得商品價格
//如果Status是0就不用變,初始為Buy it!
if(int.Parse(GoodsManager.goods[goods_name]["Status"]) == 1){ //如果Status是1則將按鈕文字改成Equip!
TextMeshProUGUI text = gameObject.transform.GetComponentInChildren<TextMeshProUGUI>();
text.SetText("Equip!");
}
else if(int.Parse(GoodsManager.goods[goods_name]["Status"]) == 2){ //如果Status是2則將按鈕文字改成Unequip!
TextMeshProUGUI text = gameObject.transform.GetComponentInChildren<TextMeshProUGUI>();
text.SetText("Unequip!");
}
}
public void OnClick(){ //當按鈕被按下
//判斷目前的錢夠不夠及此商品是否已被購買
if(CoinsManager.Coins >= price && int.Parse(GoodsManager.goods[goods_name]["Status"]) == 0){ //若錢夠且未被購買
GoodsManager.goods[goods_name]["Status"] = "1"; //購買&未裝備狀態
TextMeshProUGUI text = gameObject.transform.GetComponentInChildren<TextMeshProUGUI>(); //更新按鈕文字
text.SetText("Equip!");
CoinsManager.Coins -= price; //花錢
GoodsManager.spend = true; //花錢狀態機更新
}
//新增
else if(int.Parse(GoodsManager.goods[goods_name]["Status"]) == 1 && GoodsManager.equipped_skin == ""){ //若已被購買但未被裝備且目前沒裝備
GoodsManager.goods[goods_name]["Status"] = "2"; //裝備狀態
GoodsManager.equipped_skin = goods_name; //更新裝備造型名稱縮寫
TextMeshProUGUI text = gameObject.transform.GetComponentInChildren<TextMeshProUGUI>(); //更新按鈕文字
text.SetText("Unequip!");
}
else if(int.Parse(GoodsManager.goods[goods_name]["Status"]) == 2){ //若已被裝備
GoodsManager.goods[goods_name]["Status"] = "1"; //未裝備狀態
GoodsManager.equipped_skin = ""; //更新裝備造型名稱縮寫為無
TextMeshProUGUI text = gameObject.transform.GetComponentInChildren<TextMeshProUGUI>(); //更新按鈕文字
text.SetText("Equip!");
}
}
}
裝備
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250185/shopping-cart-icon-2048x2047-gv68pvgw.png)
之後就把腳本放到展示框Prefab的按鈕上
再將剛剛寫的OnClick函式加入當按鈕被按下時執行的清單後就完成啦
(忘記去看前兩小節)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11303219/pasted-from-clipboard.png)
裝備
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11250185/shopping-cart-icon-2048x2047-gv68pvgw.png)
至於如何實現裝備的功能就交給大家自己研究ㄌ
本堂課的範疇只在UI的部分w
造型的部分應該算簡單
提示是Resources以及SpriteRenderer
當然有問題的話可以問我
如果對更進階的裝備方式
如技能以及前面的裝備欄等等 有興趣也可以來找我
本堂課內容
本堂課內容
協程
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11019236/pasted-from-clipboard.png)
如果你需要做物件定時消失、定時生成、技能冷卻或倒數計時等等的功能
當然可以選擇用「數值-Time.deltaTime」來倒數
但缺點是每計一次時就要開一個變數
而且Time.deltaTime是一個你無法掌控的小數
有沒有什麼東西是我們容易控制而且可以同時用一打多的?
Coroutine!
協程
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11019236/pasted-from-clipboard.png)
void coroutine(){
//sth
StartCoroutine(ExampleCoroutine()); //開始協程
}
IEnumerator ExampleCoroutine(){ //協程回傳的為一個IEnumerator物件,可以理解為「反覆計算」
//sth before waiting...,在等待前執行的東西
//yield在等待second秒之後發出反覆運算結束的訊號,WaitForSecond會受到Time.Scale的影響導致快慢
yield return new WaitForSeconds(second);
//or,WaitForSecondRealtime不會受到Time.Scale的影響,時間跟現實世界一致
yield return new WaitForSecondsRealtime(second);
//sth after waiting...,在等待後執行的東西
}
基本架構
協程
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11019236/pasted-from-clipboard.png)
IEnumerator QS_s(){
Destroy(gameObject.GetComponent<PolygonCollider2D>()); //刪除碰撞器
yield return new WaitForSecondsRealtime(10);
gameObject.AddComponent<PolygonCollider2D>(); //加回碰撞器
}
例一、無敵一段時間(玩家or敵人)
例二、特定時間生成(食物、寶物or敵人)
IEnumerator EO_s(float sec){ //傳入等待時間參數
yield return new WaitForSecondsRealtime(sec); //等待一段時間
Instantiate(Wave,transform.position,transform.rotation); //生成
}
例三、定時統計(上線人數、生命值等)
IEnumerator ConnectionCount(){
while(true){
yield return new WaitForSecondsRealtime(2f);
Debug.Log(NetworkManager.Singleton.ConnectedClients.Count);
}
}
協程
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/11019236/pasted-from-clipboard.png)
IEnumerator SpawnOverTime(){
while(true){ //無限迴圈
yield return new WaitForSecondsRealtime(Random.Range(1,5)); //等待隨機取數1~5秒
//進階多人連線語法不用管,基本上就是判斷場上的物件數量並生成
if(NetworkManager.Singleton.ConnectedClients.Count > 0 && NetworkObjectPool.Singleton.Active(prefab) < MaxPrefabCount){
SpawnFood();
}
}
}
例四、隨機間隔生成(食物、寶物等)
例五、等待一段時間後摧毀(子彈等)
IEnumerator Example(float sec){ //傳入等待時間參數
yield return new WaitForSecondsRealtime(sec); //等待一段時間
Destroy(gameObject) //摧毀物件
}
以上是一些簡單應用,這些東西都可以依照
你的遊戲所需的功能做擴充和調整
Q&A&DO
![](https://s3.amazonaws.com/media-p.slid.es/uploads/2335947/images/10707371/62e131df7fe3599fdd46ecb3.png)
建北電資Unity遊戲開發小社第8and9堂
By MH Yang
建北電資Unity遊戲開發小社第8and9堂
- 85