Unity Game Development
第1~2話 遊戲專案組成 & 讓角色動起來!
Lecturer:水獺
目錄(可點)
本堂課內容
本堂課內容
遊戲物件 & 屬性

- Component
- 調整技巧
- 啟用停用
- 下拉選單
細談GameObject、Component

- GameObject
- 子物件
- 啟用停用
- 多選
細談GameObject、Component

- Component

將鼠標移到值為數字的屬性上會出現雙箭頭,可以左右拖動直接調整數值
有三角形的區塊是一組,不用的時候可以按一下收起來不占空間
這裡可以控制Component的啟用與否,未打勾是停用(不會有任何效果),打勾是啟用
細談GameObject、Component

- Component

按這個會連到這個Component的Unity Scripting Reference
回復出廠初始設置
快速移動屬性順序至最前/後
移動屬性順序至前/後
移除Component
複製Component
以新增的方式貼上複製的Component
以覆寫參數值的方式貼上複製的Component
也可以用拖的
在Scene搜尋有參考此Component的GameObject(少用)
將此視窗彈出為浮動視窗(少用)
細談GameObject、Component

- GameObject
一個GameObject盡量不要加種類相同的兩個Component,避免執行時混亂、出錯
那如果我們想要兩個東西是一個整體,可以一起移動,但同時又保有各自的Component可以做不同的動作或被分開操控,像是角色與他的武器、火柴人的手與腳等等,那該怎麼做?

✋
細談GameObject、Component

- GameObject
子物件!我們可以:
- 選定一個部分當父物件,類似這個群體的頭領
- 之後將其他部分選取、拖曳到父物件上,這些部分就會變成父物件的子物件





當父物件被移動,子物件也會跟著移動,如繪圖軟體的「群組」功能,我自己也會拿來做分類
細談GameObject、Component

- GameObject
Unity內部運作的原理為:
將子物件的Transform數值改為相對於父物件
舉個例,當子物件的LocalPosition(相對於父物件,會顯示於Inspector上)為(-5,9,0),父物件的Position為(6,15,0)
則子物件的實際位置為(1,24,0)
細談GameObject、Component

- GameObject
GameObject跟Component一樣也有停用與否的勾選框,
停用某個物件代表的便是停用整個物件的Component

結合子物件的概念,當子物件被停用,父物件不受影響
但當父物件被停用,子物件也會同時受影響




- Sprite是什麼?
- Sprite名稱來源?
- Sprite Renderer是什麼?
- 圖像Assets設置
- 細談Sprite Renderer
- 細談Draw Mode
Sprite & SpriteRenderer

翻譯一下:Sprite n.雪碧精靈
Sprite & SpriteRenderer

簡單來說,Sprite就是一個將多圖層/圖像整合在一個2D平面上的技術
"在電腦圖學中,當一張二維圖像整合進場景中,成為整個顯示圖像的一部分時,這張圖就稱為精靈圖"———維基百科
- Sprite是什麼

因為這樣的圖像並不是背景的一部份,而是「懸浮」在背景之上,就像幽靈或精靈一樣,所以才以「精靈圖」命名當然你要叫他雪碧圖我也沒意見
- Sprite名稱來源?
Sprite & SpriteRenderer


鏡頭(Camera)
遊戲物件(GameObjects)
遊戲畫面
再翻譯一下:Sprite Renderer n.精靈圖渲染器
Sprite & SpriteRenderer

- SpriteRenderer是什麼?
這大概是在2D遊戲開發中你唯一會用到的渲染器,我們會用這個Component來渲染Sprite到畫面上,實踐「多圖像整合」,有不同的渲染模式/功能,等等介紹
記得弄清楚:Sprite ≠ SpriteRenderer
Sprite(物件外觀圖):資料夾中的Assets
SpriteRenderer:綁定在GameObject上的Component,
需要提供一個Sprite給它。

- 開啟上次的專案(沒來上課的就創一個)
- 點擊Project視窗中的圖片(綠框處),你就可以看到右方的Inspector視窗出現五花八門的設置選項
Sprite & SpriteRenderer

- 圖像Assets設置

這個東西是專案管理,
之後會教
Sprite & SpriteRenderer

- 圖像Assets設置

修改完記得按Apply
圖片壓縮品質,依照官方說法是會影響一部份的效能但我覺得不多,一般不用改,如果圖片畫素很低或者為像素風格則建議改None
圖片過濾器模式,若圖片不夠平滑(像素感太重)可使其平滑,但如果是像素風格圖片則須改Point,否則會模糊
可以改圖片的旋轉中心(Pivot,原為中心(0.5,0.5))、拉伸限制
一般會用Tight以節省效能,有興趣可以來問我,除非要用到其他的繪製模式(Draw Mode)不然別改Full Rect
類似比例尺,初始值為100,改小會放大圖像(如50即邊長變為兩倍),改大則相反。如果圖像不合大小可先改這裡,盡量不要先動GameObject的大小
圖像類型(一般只用Sprite)
Sprite & SpriteRenderer

- 細談SpriteRenderer

渲染對象Sprite,可以從Project視窗將圖像直接拖進這個框裡來新增/切換Sprite,也可以按右邊的小圈圈雙擊檔案/搜尋檔名
渲染後疊加於Sprite上的顏色,之前我有用過的如:
- 一個白色的立體按鈕,疊加後就可以變成不同顏色,不須重畫;
- 可以實做移動到按鈕上變色的效果
- 也可以利用腳本調整Alpha透明度值實現物件淡入淡出
X、Y軸鏡像翻轉
繪製模式,微複雜,等等說
渲染材質,之後如果有教光線再改,不然不用改
圖片重疊排序順序,適用於較少物件之排序,可直接在同排序階層(Layer)做上下排序,數字越小越下面(類似同圖層)
圖片重疊排序階層,適用於較多物件之排序(類似不同圖層)
記得選取Scene中的遊戲物件,Inspector視窗才會顯示在右側
Sprite & SpriteRenderer

- 細談Draw Mode


單純渲染,只能依原比例調整大小、位置
可依照自己喜好調整Sprite之長寬,單位為場景中的座標單位長


把圖片以如同磁磚般重複的形式渲染出來
(貓貓大軍!!!!
長寬調整如Sliced模式
Sprite & SpriteRenderer

- 實作!
- 注意Sliced跟Tiled兩個模式都要把Mesh Type改成Full Rect,顯示才不會出問題
- 大家可以先玩玩看,也可以想想這個可以用在哪邊~


Transform

- Transform是什麼?
- Transform名稱來源?
- Transform會用到的資料型態?
Transform

一個儲存GameObject在場景座標中的位置、旋轉、尺寸比例的Component,對於每個GameObject來說唯一
- Transform是什麼
Scale(尺寸比例):

GameObject在場景座標中的位置,由於Unity最早是純3D引擎,所以這裡使用三維坐標系(x-左右、y-上下、z-前後,上、右、前為正向),2D遊戲中除了Camera的Z軸座標值為 -10 外,其他物件一律是0(否則Camera有機會看不到)
GameObject在三個座標軸上的旋轉角度,2D遊戲只會碰到Z軸上的旋轉。另外,雖然在Inspector中看起來是三維,實際上Unity中儲存角度的是一個叫四元數(Quaternion)的東西,等等會講
GameObject在三個座標軸上的尺寸比例,大於1會放大,在0~1之間會縮小,小於0時效果與以該軸為基準鏡像相同,比如說要讓遊戲角色面相反方向可以將X軸上的Scale改為-1;2D遊戲Z座標上的值不影響
Rotation(旋轉):
Position(位置):
顧名思義,Transform指的是「變形」,指的就是:
- 平移
- 旋轉
- 縮放
- 傾斜(以二維的角度來看,X、Y軸上的旋轉即是傾斜)
如果有用一些繪圖軟體的話,裡面也會有類似的選項
Transform

- Transform名稱來源?
無論是Position、Rotation、Scale的數值都是用Vector3(三維向量)的資料型態來在腳本中儲存和使用的,等一下會講
Transform

- Transform會用到的資料型態?
本堂課內容
本堂課內容

腳本(Script)
腳本(Script)

- C#語言特性?
相信大家多少有碰過C++,不管是建電還是北資的大社課應該都有教
C#和C++的相同處:
- 分號很重要!!!一定要加不然會報錯
- 資料型態如:int、float、double、bool、string、char
- 運算子如:+ - * / = += -= *= /= && || ==
- 語法如:for、while、if else、switch case、struct(之後做商店很好用)
- 函式的定義方式
腳本(Script)

- C#語言特性?
需要注意的地方:
- 變數作用域(全域、區域變數)
- 使用小數的時候,因為Unity默認讀取double格式,建議都定義成double型態,真的要用float記得在數字後加f,如:
- C#中並沒有類似#define的東西可以省字
- C#中有指標但不建議用,很麻煩,可用全域變數解決就用
- 還是有與C++ STL略有不同的資料結構,用之前記得確認
float a = 0.1f
腳本(Script)

- 基本架構
操作時間!這裡要認真聽喔!
- 在左下角的Project視窗點進Assets資料夾
- 按右鍵 → Create → Folder新增一個Scripts資料夾存放腳本
- 在Scripts資料夾裡再按右鍵 → Create → C# Script新增一個腳本檔案,取名甚麼隨便你,但記得第一個字母要大寫(之後建議用該腳本功能命名較好管理),副檔名會是.cs

注意!不要直接從VS Code裡新增.cs檔案,雖然副檔名對但腳本格式需要自己新增很麻煩
然後記得把腳本拖到角色的Inspector裡面,讓他變成一個Component
很重要
腳本(Script)

- 基本架構
點兩下用VS code打開檔案後你會發現裡面長醬:

腳本(Script)

- 基本架構
所以在Unity裡寫程式到底是怎麼一回事?
第一件要認知的事情是,C#語言中的所有東西,包含現在的腳本、GameObject、Component、各種不同的資料格式(Vector、圖像、資料結構等)都是一個由使用者自訂義的「Class(類別)」
Class?
接下來我們來看看這個聽起來很抽象的名詞是個什麼東西
腳本(Script)

- 基本架構
以下是Class的大致結構
public class Color{ //大寫
//那我們就讓它隨機好了,跟前面那隻ㄎㄧㄤˉ掉的一樣
}
public class Cat{ //大寫
public int ID; //在變數前加上public可以讓腳本中的其他Class與其函式存取
public Color color; //一個Color Class型態的變數
int age; //沒有public的則為私有,不可被其他Class及其函式存取
float h;
float w;
public float GetBMI(){ /*Class中也可以放函式,第一個字記得大寫,
同樣加public可被外部取用*/
if(h <= 0 || w <= 0){
return -1;
}
else{
return w / (h * h);
}
}
}
public class MyCat{ //大寫
Cat myCat = new Cat(); /*宣告一個Cat實例,
裡面包含Cat class中的所有變數、函式*/
void catInfo(){
myCat.ID = 951031; //public,可被存取
myCat.h = 20.5; //無public,不可被存取,會出錯
float BMI = myCat.GetBMI();
}
}
這些類別的實例稱為物件,每個物件底下都有成員,這些成員可能是變數(資料型態可以是其他已宣告的Class)或是函式,成員變數的名稱為小寫字母開頭,成員函式(需有括號)的名稱是大寫開頭。
成員如果沒有在宣告時加上public,就無法被其他類別存取及使用,也可以設定成只能讀取不能修改。除非你的腳本中有需要被其他物件使用的函式或變數,否則不要隨便將成員設為public,確保不會被修改會比較好管理。
腳本(Script)

- 基本架構
當我們在寫Unity腳本時,我們實際上是在創造出一個新的Class
而這個Class的物件會變成GameObject的一個Component
寫腳本時我們可以使用Unity已經寫好的一大堆Class
(即為Unity Scripting API)及其成員來做出我們想完成的功能
以上大概就是Unity Coding的模式
腳本(Script)

- 基本架構
以Transform舉個例(等等的移動也會用這個Component):

宣告一個Transform物件
宣告一個Test函式
(所有除了宣告外的程式都要寫在裡面,不然會出錯)
(之後一樣盡量照功能命名,方便管理)
腳本(Script)

- 基本架構
之後在函式中打出你宣告的Transform物件名稱加一個點,
如果你VSC模組有裝對則可以發現有個清單:

紫色方塊代表函式
扳手代表變數
這裡會列出所有這個物件擁有的成員,可以往下捲動
腳本(Script)

- 基本架構
接著打上position並將鼠標停在字上,
你會發現有個提示框出現:
此成員的資料型態為Vector3,等等會講,簡單來說就是一個三維座標
剛剛講到的存取權:
- get為可以取得值
- set為可以改變值
兩個都有則代表此成員為public

成員簡短介紹
這個成員是屬於哪個Class的
腳本(Script)

- 基本架構
給一個public的反例:static
這種成員型態是透過Class本身存取,即「Class名稱.成員名稱」
比如Vector3.forward,是一個已經定義好的、值為(0,0,1)的向量
這時將鼠標移到上面,會發現存取權處只有「get」而沒有「set」
即可以存取但不能修改

腳本(Script)

- 基本架構
:好那我這樣寫

把位置設成(0,0,1),然後把角色隨便放到畫面中的一個位置
:執行!接下來呢?角色位置沒變啊?
腳本(Script)

- 基本架構
讓我們看回現在的腳本

MonoBehaviour中的內建函式
寫在這裡的程式是當腳本開始運作時首先會執行一次的事
MonoBehaviour中的內建函式
當腳本開始運作後於每一幀執行一次的事
(還有一個FixedUpdate之後介紹,常用於角色移動、物理狀態更新)
類似C++的include、Python的import,之後如果會用到文字會在加,現在不用改
這行的意義是你正在撰寫一個叫「Sample」、繼承(等等會講一下) MonoBehaviour 的 Class
腳本(Script)

- 基本架構

這是一張有關MonoBehaviour內建函式執行順序的圖,有興趣可以自己造訪Unity Documentation看官方說明
(真的很難)
常用的為Awake、Start、FixedUpdate及Update:
- Awake:最先,遊戲初始化,如讀取資料庫、建構清單等
- Start:第二,初始化變數
- FixedUpdate:不斷,會每0.02秒執行一次,相對於Update計算幀數的執行方式更為穩定
- Update:不斷,每一幀執行一次
腳本(Script)

- 基本架構

:那我這樣寫就可以了對吧?
顯然否。
注意,現在的catTransform只是一個空的物件,根本還沒對應到任何一個實際的Transform Component
腳本(Script)

- 基本架構
要讓catTransform對應到Component有兩種方式:
1.使用GetComponent<Component Name>()函式

功能是尋找同一個GameObject下的指定Component並回傳其參考(可以理解成一個與Component連結的東西,讓你能夠存取/修改其內容)
記得寫在Start()之內、Test()之前才有初始化的效果,不然跑到Test()就來不及了
2.於catTransform的宣告前加入[SerializeField],
Inspector中會多一個可互動的框,並手動拖曳Transform Component進框框
當然也可以寫public但不建議(難管理、Debug)


腳本(Script)

- 基本架構
大功告成!
再次開始遊戲,這次你的角色應該就會回到中間啦!
腳本(Script)

- 基本架構
補充:Unity Scripting API其實有內建一個名為
「transform」的成員來表示Script所屬的GameObject的Transform Component,所以可以這樣寫就OK

不過也不要覺得剛剛在浪費時間,因為除了Transform之外的Component幾乎都要用剛剛的方法才能存取及修改,只是因為目前只有提到Transform才以其為例
腳本(Script)

之前想到一個不錯的例子
我們可以用「杯子」來理解物件導向的概念
舉個例
一個杯子可能會有好幾種形式
有沒有杯耳、高度高低、杯口大小、材質...等等
這時我們就可以把這些「杯子的特性」
統整歸納成一張藍圖
就會比較好維護及方便設計使用



- 淺談物件導向
腳本(Script)

那剛剛提到的「繼承」又是什麼意思?
再舉個例
如果現在你發現有些杯子有自己獨特的屬性
比如名貴的茶杯可能會有生產國家、生產年代等特殊資訊
但他同時也會有一般杯子所有擁有的屬性
那我們就可以透過「繼承」一般杯子的藍圖後
再增加一些特別的屬性
也就不用重新、重複地再畫一次一個杯子的藍圖




- 淺談物件導向
本堂課內容
本堂課內容
讓角色動起來
- 實作並讓角色移動

讓角色動起來
- Vector2、Vector3
簡單來說就是存一個二維/三維座標系中的點



讓角色動起來
- Vector2、Vector3

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class VectorSample : MonoBehaviour
{
float exampleFloat;
//宣告
Vector2 vec2a,vec2b,vec2c;
Vector3 vec3a,vec3b;
void Start(){
//賦值
vec2a = Vector2.up; //(0,1)
vec2b = Vector2.right; //(1,0)
vec3a = Vector3.forward; //(0,0,1)
vec3b.Set(0,0,0); //直接設定Vector的值
vec3b = new Vector3(95.0f,10,31); //也可以使用new運算符,之後可能會細講
//運算
vec2c = vec2a + vec2b; //(1,1),(x1 + x2 , y1 + y2),減法同規則,Vector3也可用
vec2c = vec2c * 10; //(10,10),(x * k , y * k),係數積,Vector3也可用
vec2c *= 10; //同上
//一些好用的方法/屬性,Vector3也可用,回傳型別為float
exampleFloat = Vector2.Distance(vec2a,vec2b); //回傳兩個座標間的距離,計算方式就是直線長度公式
exampleFloat = Vector2.Angle(vec2a,vec2b); //回傳起始座標與結束座標連線與水平方向的夾角
exampleFloat = Vector2.SqrMagnitude(vec2a); //回傳向量長度的平方,若只是為了長度比較可以用這個代替(花費效能較少)
exampleFloat = vec2a.sqrMagnitude; //同上
exampleFloat = vec2a.magnitude; //回傳向量長度(花費效能較多)
transform.position = Vector2.MoveTowards(transform.position, vec2c , 0.1f); /*計算一次向目標Vector移動後的位置後回傳,
把這行寫在Update或FixedUpdate裡面可以達成不斷向目標Vector移動的效果。
第一個參數為起點Vector,第二個為終點Vector,最後一個是每次執行會移動的距離,
可以當成移動速度來想*/
transform.position = Vector2.Lerp(transform.position, vec2c , 0.5f); /*計算一次兩個參數Vector的插值後回傳,
把這行寫在Update或FixedUpdate裡面可以達成「更加平滑地」不斷向目標Vector移動的效果。
第一個參數為起點Vector,第二個為終點Vector,最後一個是插值,可以想成分點公式的兩線段比例,
0會回傳起點Vector,1會回傳終點Vector,0.5則會回傳兩個向量的平均值*/
exampleFloat = vec2a.x; //Vector的x項
exampleFloat = vec2a.y; //Vector的y項
exampleFloat = vec3a.z; //Vector的z項(Vector3獨有)
vec2a.Normalize(); //將此Vector變成一個方向不變、長度為1的Vector
vec2c = vec2b.normalized; //回傳一個同上的Vector,但不更改原Vector的值
}
}
讓角色動起來
- Vector2、Vector3

- Transform
儲存一個GameObject的位置、旋轉及尺寸比例資訊
讓角色動起來


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TransformSample : MonoBehaviour
{
void Start(){
transform.position = new Vector3(95,10,31); //(world) position,(世界中的)座標位置,以一個新的Vector3表示
float x = transform.position.x; //position資料型態為Vector3,所以可以存取xyz值,但transform的各個變數都是唯獨
transform.localPosition = Vector3.zero; //相對於父物件的位置,若無父物件則與position相同
transform.localScale = Vector3.one; //GameObject於三維座標上的尺寸比例,有父物件則此數值為相對於父物件,沒有則同transform.scale
transform.localScale = new Vector3(-transform.localScale.x,transform.localScale.y,transform.localScale.z); //x軸Scale設成與目前相反可以達成左右旋轉的效果
transform.rotation = Quaternion.identity; //GameObject的旋轉,以Quaternion(四元數)格式儲存,非常複雜,此處的identity為不旋轉
transform.rotation = Quaternion.Euler(95,10,31); //若想以較易理解的三維方式(歐拉角)旋轉可使用此函式
Vector3 angle = transform.eulerAngles; //回傳當時之歐拉角
Vector3 faceDir = transform.up; //為一指向GameObject初始狀態時上方的向量,會隨rotation而變,可直接修改以改變朝向
faceDir = -transform.up; //只有transform.up及transform.right,若要左/下則加負號即可
transform.Translate(new Vector3(95,10,31)); //將Position以指定的Vector3變動
}
}
- Transform
讓角色動起來


讓角色動起來

- 其他比較細的
using System;
using System.Collections.Generic;
using UnityEngine;
public class OtherSample : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
float exampleFloat = 2.5f;
float exampleFloatAbs = Mathf.Abs(exampleFloat); //abs函式可以回傳數值的絕對值
SpriteRenderer exampleSpriteRenderer = GetComponent<SpriteRenderer>();
GameObject exampleObject1 = GameObject.Find("cat"); //用名字找Scene中的遊戲物件並回傳第一個符合的
GameObject exampleObject2 = GameObject.FindGameObjectWithTag("Cat"); //用Tag找Scene中的遊戲物件並回傳第一個符合的
GameObject[] exampleObjects = GameObject.FindGameObjectsWithTag("Cat"); //用Tag找Scene中的遊戲物件並以Array回傳擁有此Tag的所有物件
GameObject.Destroy(this.gameObject); //摧毀以此Script作為Component的遊戲物件
exampleSpriteRenderer.enabled = true; //exampleSpriteRenderer的啟用狀態,可直接修改(相當於框框打勾與否)
this.enabled = true; //此Script的啟用狀態,可直接修改(相當於框框打勾與否)
exampleObject2.SetActive(false); //停用exampleObject2(相當於框框不打勾),啟用則改為true
float DeltaTime = Time.deltaTime; //在FixedUpdate中,這個值大約為0.02秒,其他地方則為一幀的時間
}
}
讓角色動起來

- 其他比較細的
讓角色動起來

- 鍵鼠輸入讀取—前言
Unity中提供了兩種讀取使用者輸入的方式
因為是先後出現
所以我以新舊版系統稱呼
因為各有優缺所以我兩種都會說
大家可以依自己的喜好和需求決定要使用哪種
讓角色動起來

- 鍵鼠輸入讀取—舊版
雖然說是舊版
但我自己比較常使用這種
優點是他的移動開始及停下可以做到非常平滑
不會有突然中斷的感覺(當然要直接停下也可以)
因為是內建的 所以引入使用也很容易
缺點是改綁定鍵比較複雜
而且好像做不到遊戲中改按鍵綁定的功能
讓角色動起來

- 鍵鼠輸入讀取—舊版設定
按左上方的Edit --> Project Settings --> Input Manager
點一下Axes旁的小鍵頭
在這裡你可以設定想要綁定的按鍵及其他參數


這裡的鍵頭點開之後就可以看到設定選項
讓角色動起來

- 鍵鼠輸入讀取—舊版設定

在寫程式的時候用來存取這個Axis的名字(第一個字盡量大寫)
第一組用來改變這個Axis的value的正負按鍵
第二組(以上四格留空則不會有該格的效果)
據說打開可以做到類似急停的效果
(正負鍵一起按可以停下)但疑似沒用
輸入類型(目前應該都用鍵鼠)
控制的軸(x、y、z),因為是Horizontal所以為x軸
用搖桿控制的時候可以設定哪支搖桿要對應這個Axis
讓角色動起來

- 鍵鼠輸入讀取—舊版程式
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class OldPlayerMoveSample : MonoBehaviour
{
float horizontal; //水平軸值,-1~1
float vertical; //垂直軸值,-1~1
float dx; //一次水平方向移動距離
float dy; //一次垂直方向移動距離
float boundedx; //被限制過的水平移動位置
float boundedy; //被限制過的垂直移動位置
[SerializeField] float moveSpeed = 10f; //移動速度
[SerializeField] float xBound; //水平邊界
[SerializeField] float yBound; //垂直邊界
SpriteRenderer spriteR; //圖像渲染器(獲取Sprite邊界)
Vector3 spriteRBound; //Sprite邊界
void Start() {
spriteR = GetComponent<SpriteRenderer>();
spriteRBound = spriteR.bounds.size/2; //Vector直接除2可以得到半長寬
yBound = 5f;
xBound = yBound/9f*16f; //16:9
}
void Facing(){ //前面講過的翻轉,以軸值來判斷角色移動方向,讓角色能左右轉向(之後可以把功能像這樣包裝成函式會很好管理)
if(horizontal <= -0.01f && transform.localScale.x > 0){
transform.localScale = new Vector3(-transform.localScale.x,transform.localScale.y,transform.localScale.z);
}
else if(horizontal >= 0.01f && transform.localScale.x < 0){
transform.localScale = new Vector3(-transform.localScale.x,transform.localScale.y,transform.localScale.z);
}
}
void Move(){ //負責移動的函式,兩種改位置方法擇一
//計算一次移動的距離,乘Time.deltaTime是為了讓一秒內移動的距離接近Input * moveSpeed的值
dx = horizontal * Time.deltaTime * moveSpeed;
dy = vertical * Time.deltaTime * moveSpeed;
transform.Translate(new Vector3(dx, dy, transform.localPosition.z)); /*改位置方法1:transfrom.Translate(Vector3)
會把position.x/y/z值分別加上傳入的
Vector3.x/y/z(如果要限制邊界要寫if else,邊界內才移動)*/
//計算限制後的位置,前面的半長寬會用在這,spriteRBound.x為半寬(水平),.y則為半長(垂直)
// Mathf.Clamp(value, min, max)可以將傳入的value限制在min跟max之間,低於min則等於min,高於max則等於max
boundedx = Mathf.Clamp(transform.localPosition.x + dx , -xBound + spriteRBound.x, xBound - spriteRBound.x);
boundedy = Mathf.Clamp(transform.localPosition.y + dy , -yBound + spriteRBound.y , yBound - spriteRBound.y);
transform.position = new Vector3(boundedx, boundedy, transform.localPosition.z); /*改位置方法2:分別計算最後的x/y值後
直接改localPosition*/
}
void Update(){ //輸入一律放在Update裡
horizontal = Input.GetAxis("Horizontal"); //回傳平滑處理過的水平軸值(-1~1)
vertical = Input.GetAxis("Vertical"); //回傳平滑處理過的垂直軸值(-1~1)
// horizontal = Input.GetAxisRaw("Horizontal"); //回傳未平滑處理的水平軸值(-1 or 0 or 1)
// vertical = Input.GetAxisRaw("Vertical"); //回傳未平滑處理的垂直軸值(-1 or 0 or 1)
Facing();
}
void FixedUpdate() { //移動程式放在FixedUpdate裡
Move();
}
}
寫好之後記得拖進Inspector
讓角色動起來

- 鍵鼠輸入讀取—新版須知:KeyCode
鍵盤上的每一個鍵都有自己對應的KeyCode
用來讓電腦知道你在說的是哪個鍵

讓角色動起來

- 鍵鼠輸入讀取—新版須知:KeyCode
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class KeyCodeSample : MonoBehaviour
{
KeyCode wKey = KeyCode.W; //字母
KeyCode mouseLeftKey = KeyCode.Mouse0; //滑鼠
KeyCode alpha0Key = KeyCode.Alpha0; //上排數字
KeyCode f1Key = KeyCode.F1; //F1-12
KeyCode keypad0 = KeyCode.Keypad0; //右邊數字
KeyCode upArrowKey = KeyCode.UpArrow; //箭頭
void Start() { //會在等等講到的變更綁定中用到
string wKeyName = wKey.ToString(); //可以用.ToString()將KeyCode轉為string,值是該Key的名稱
bool isContain = "sdfASFaWez".Contains(wKeyName); //string.Contains(string)可判斷一個字串中是否包含某個字串
}
}
常用的一些例子以及可能會用到的方法
其他KeyCode如果不知道可以去這裡查
讓角色動起來

- 鍵鼠輸入讀取—新版
Unity在2020年4月新出的一個Package
說實話也蠻久了(笑
但之前的學長沒有去研究
這次一起教給你們
優點是調整比較人性化 可以做到遊戲內更改按鍵綁定
缺點是無法做到平滑處理 而且引入比較麻煩
讓角色動起來

- 鍵鼠輸入讀取—新版設定
按右上方的Window --> Package Manager
左上方的Packages選單往下拉 選Unity Registry


讓角色動起來

- 鍵鼠輸入讀取—新版設定
在左邊會看到一堆Package的名字
向下捲找到Input System 按下Install

讓角色動起來

- 鍵鼠輸入讀取—新版設定
按左上方的Edit --> Project Settings --> Player
點一下下面的「Other Settings」


讓角色動起來

- 鍵鼠輸入讀取—新版設定
向下捲找到Active Input Handling,改成Both
如果之後不能用可以來檢查一下這邊有沒有跑掉

讓角色動起來

- 鍵鼠輸入讀取—新版程式-1
using System.Collections;
using System.Collections.Generic;
using UnityEngine.InputSystem; //這要自己加!!沒加用不了Input System
using UnityEngine;
using System;
public class PlayerInputActionSample : MonoBehaviour
{
[SerializeField] InputAction movement; //可於編輯器修改的InputAction class實例
void Start() {
}
void Update() {
}
}
讓角色動起來

- 鍵鼠輸入讀取—新版:設定InputAction
把腳本拖到Inspector裡
把剛剛的舊版腳本先停用
之後你會看到這個畫面

讓角色動起來

- 鍵鼠輸入讀取—新版:設定InputAction
按下Movement旁邊的齒輪
選Vector2

讓角色動起來

- 鍵鼠輸入讀取—新版:設定InputAction
按下Movement旁邊的+
點Add Up\Down\Left\Right Composite
三維雙向輸入,Value用Vector3
一維雙向輸入,Value用Axis
單鍵輸入
二維雙向輸入,Value用Vector2
雙鍵組合輸入,如ctrl+v
三鍵組合輸入,如ctrl+alt+del

讓角色動起來

- 鍵鼠輸入讀取—新版:設定InputAction

之後會看到這樣,等等會細說這些是什麼
可以改名字
讓角色動起來

- 鍵鼠輸入讀取—新版:設定InputAction
Input System分為三個階層
Action(動作集)
Composite(多個按鍵&行為綁定的組合)
Binding(一個按鍵&行為的綁定)
此處對於Vector:
Up (按W) --> +Y
Down (按S) --> -Y
Left (按A) --> -X
Right (按D) --> +X

讓角色動起來

- 鍵鼠輸入讀取—新版:設定InputAction
在其中一個Binding上點兩下

這裡點開可以
手動改按鍵綁定
這裡可以改這個Binding在Composite裡的角色
(比如Up,Down,Left或Right)


讓Editor偵測你的輸入
直接輸入
讓角色動起來

- 鍵鼠輸入讀取—新版:InputAction常用方法

讓角色動起來

- 鍵鼠輸入讀取—新版:InputAction常用方法
using System.Collections;
using System.Collections.Generic;
using UnityEngine.InputSystem;
using UnityEngine;
public class InputSystemSample : MonoBehaviour
{
[SerializeField] InputAction movement; //可於編輯器修改的InputAction class實例
Vector2 input;
void Start() {
movement.Enable(); //啟用InputAction(可以輸入)
movement.Disable(); //停用InputAction(不可輸入)
movement.ChangeBinding("Up").WithPath("<Keyboard>/k"); /*變更按鍵綁定,其中
movement為InputAction實例
ChangeBinding(BindingName)指定要變更的綁定名稱
WithPath(KeyPath)指定按鍵路徑*/
}
void Update() {
input = movement.ReadValue<Vector2>(); //讀取InputAction被設定的資料型態裡的資料,剛剛設定的是Vector2
}
}
讓角色動起來

- 鍵鼠輸入讀取—新版程式
using System.Collections;
using System.Collections.Generic;
using UnityEngine.InputSystem; //這要自己加!!沒加用不了Input System
using UnityEngine;
using System;
public class PlayerInputActionSample : MonoBehaviour
{
[SerializeField] InputAction movement; //可於編輯器修改的InputAction class實例
float dx; //一次水平方向移動距離
float dy; //一次垂直方向移動距離
float boundedx; //被限制過的水平移動位置
float boundedy; //被限制過的垂直移動位置
Vector2 input;
float horizontalInput; //水平軸值
float verticalInput; //垂直軸值
[SerializeField] float moveSpeed = 10f; //移動速度
[SerializeField] float xBound; //水平邊界
[SerializeField] float yBound; //垂直邊界
SpriteRenderer spriteR; //圖像渲染器(獲取Sprite邊界)
Vector3 spriteRBound;//Sprite邊界
public static bool changing = false; //等待按鍵變更的狀態機
public static string BindingName; //要變更的按鍵
System.Array keycodes = System.Enum.GetValues(typeof(KeyCode)); //把所有KeyCode存成一個Array
void Start()
{
spriteR = GetComponent<SpriteRenderer>();
spriteRBound = spriteR.bounds.size/2; //Vector直接除2可以得到半長寬
yBound = 5f;
xBound = yBound/9f*16f; //16:9
}
private void OnEnable() { //啟用物件時同時啟用InputAction
movement.Enable();
}
private void OnDisable() { //停用物件時同時停用InputAction
movement.Disable();
}
void Facing(){ //翻轉方向
if(horizontalInput <= -0.01f && transform.localScale.x > 0){
transform.localScale = new Vector3(-transform.localScale.x,transform.localScale.y,transform.localScale.z);
}
else if(horizontalInput >= 0.01f && transform.localScale.x < 0){
transform.localScale = new Vector3(-transform.localScale.x,transform.localScale.y,transform.localScale.z);
}
}
void Change(){
if(changing){ //當案件正在等待變更(狀態機為true)
movement.Disable(); //因為Input.GetKey()方法和Input System會衝突,所以先停用
//以下是一個很爛的「讀取現在按下的鍵」的寫法,但Unity沒有Input.keycode之類的東西就是沒有,超可悲,目前只能有這種跟另一種(比較吃效能)
foreach(KeyCode keycode in keycodes) { //類似C++的for(int v:vector),反正就是一個遍歷Array值的for迴圈
if (Input.GetKey(keycode)){ /*Input.GetKey(KeyCode or KeyName)可以判斷輸入的是否為指定的按鍵,
用這個方式判斷目前迴圈跑到的KeyCode跟按下的KeyCode是不是一樣,達成判斷按鍵的功能*/
//用來偵測按下按鍵之後變更按鍵綁定的基礎程式
if(keycode.ToString().Contains("Mouse")){ //判斷是不是滑鼠,因為滑鼠鍵位在Input System及KeyCode中是不同的名字,需要特判
if(keycode == KeyCode.Mouse0){ //左鍵在KeyCode中為Mouse0
movement.ChangeBinding(BindingName).WithPath("<Mouse>/leftButton");
//左鍵在Input System中的Path為<Mouse>/leftButton
}
else if(keycode == KeyCode.Mouse1){ //右鍵在KeyCode中為Mouse1
movement.ChangeBinding(BindingName).WithPath("<Mouse>/rightButton");
//右鍵在Input System中的Path為<Mouse>/rightButton
}
else if(keycode == KeyCode.Mouse2){ //中鍵在KeyCode中為Mouse2
movement.ChangeBinding(BindingName).WithPath("<Mouse>/middleButton");
//中鍵在Input System中的Path為<Mouse>/middleButton
}
changing = false; //狀態機設定回false
}
else{
movement.ChangeBinding(BindingName).WithPath("<Keyboard>/" + keycode.ToString());
//其他鍵在Input System中的Path為<Keyboard>/Name
changing = false; //狀態機設定回false
}
}
}
}
else if(gameObject.activeSelf){ //檢查遊戲物件的停用/啟用狀態
movement.Enable(); //若物件為啟用則啟用movement
}
}
void Move(){ //負責移動的函式,兩種改位置方法擇一
//計算一次移動的距離
dx = horizontalInput * Time.deltaTime * moveSpeed;
dy = verticalInput * Time.deltaTime * moveSpeed;
transform.Translate(new Vector3(dx, dy, transform.localPosition.z)); /*改位置方法1:transfrom.Translate(Vector3)
會把position.x/y/z值分別加上傳入的
Vector3.x/y/z(如果要限制邊界要寫if else,邊界內才移動)*/
//計算限制後的位置,前面的半長寬會用在這,spriteRBound.x為半寬(水平),.y則為半長(垂直)
// Mathf.Clamp(value, min, max)可以將傳入的value限制在min跟max之間,低於min則等於min,高於max則等於max
boundedx = Mathf.Clamp(transform.localPosition.x + dx , -xBound + spriteRBound.x, xBound - spriteRBound.x);
boundedy = Mathf.Clamp(transform.localPosition.y + dy , -yBound + spriteRBound.y , yBound - spriteRBound.y);
transform.position = new Vector3(boundedx, boundedy, transform.localPosition.z); /*改位置方法2:分別計算最後的x/y值後
直接改localPosition*/
}
void Update() {
input = movement.ReadValue<Vector2>(); //讀取前面Input System的Vector2 Value
horizontalInput = input.x; //水平軸值
verticalInput = input.y; //垂直軸值
Facing();
Change();
}
void FixedUpdate() {
Move();
}
}
讓角色動起來

- 鍵鼠輸入讀取—新版程式
另外我有寫一個
用按鈕控制現在是移動還是改按鍵的程式
也把基礎樣板做出來了 但背景、美編那些還沒放
有興趣可以來找我 大概會長這樣

讓角色動起來

- 鍵鼠輸入讀取—新版
之後,按下開始
在Inspector裡調整你想要的速度之後,就可以移動你的角色了!

如果你想知道他是怎麼變色的可以來問我
讓角色動起來

- 鍵鼠輸入讀取—新版
但是
退出之後...
:速度怎麼又變回10了?

讓角色動起來

- 鍵鼠輸入讀取—新版
因為你是在遊戲模式裡面改Component參數
前面有講過 這樣不會儲存
那如果要讓遊戲模式裡改的參數可以被保存要怎麼做?

先複製
再貼上數值就可以了
但是如果有很多 那就截圖之後結束遊戲模式再慢慢打吧
讓角色動起來

- 番外篇—旋轉程式
前面的移動都會了
那我們來做點其他的
現在我想做一顆會跟著貓移動
而且會隨著貓的移動輸入而旋轉的毛線球(感覺反了?
那程式怎麼寫?
讓角色動起來

- 番外篇—旋轉程式
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Yarn_ball : MonoBehaviour
{
[SerializeField] GameObject cat; //宣告一個可以在Editor中存取的GameObject物件
[SerializeField] float rotatingSpeed = 1000f; //旋轉速度
[SerializeField] float movingSpeed = 0.075f; //移動速度
PlayerInputActionSample catInputAction; //一個PlayerInputActionSample物件,用來存取貓的移動輸入
void Start() {
catInputAction = cat.GetComponent<PlayerInputActionSample>(); //取得Component
transform.position = new Vector3(0,4,0); //設定初始位置
}
void Update() {
//旋轉,其中x,y角度不變,z角度則參照貓的x,y輸入決定大小,乘Time.deltaTime是為了讓一秒內旋轉的度數接近Input * RotatingSpeed的值
transform.rotation = Quaternion.Euler(0,0,transform.eulerAngles.z + rotatingSpeed * Time.deltaTime * Mathf.Sqrt(Mathf.Pow(catInputAction.input.x,2) + Mathf.Pow(catInputAction.input.y,2)));
}
void FixedUpdate() { //兩種移動方式擇一
transform.position = Vector3.Lerp(transform.position,cat.transform.position,0.01f); //平滑移動
transform.position = Vector3.MoveTowards(transform.position,cat.transform.position,movingSpeed); //等速移動
}
}
讓角色動起來

- 番外篇—旋轉程式

剛剛應該有看到
腳本中是需要從另一個腳本Component讀取數值的
所以要記得把PlayerInputSystemSample裡的input加public
這樣才能用其他腳本存取
讓角色動起來

- 番外篇—旋轉程式
新建一個毛線球 把腳本拖進去Inspector之後你會發現
下面多了一個空格
把遊戲角色從Hierarchy中拖進去

讓角色動起來

- 番外篇—旋轉程式
就大功告成啦!!!
開始遊戲之後毛線球應該就會追著貓跑了
而且在按下WASD的時候毛線球會旋轉
如果要用舊版輸入也可以自己試著改改看
有問題歡迎問我
之後也可以自己試著做出其他的效果和功能!
Q&A&DO

建北電資Unity遊戲開發小社第1、2堂
By MH Yang
建北電資Unity遊戲開發小社第1、2堂
- 182