Unity學習筆記(04):transform、Find/FindChild、GetChild、deltaTime、方向向量、座標轉換、縮放、旋轉、LookAt、Quaternion、Vector3

六、transform組件

🚩transform組件的作用:

  • 1、控制3D物體的平移 縮放 旋轉
  • 2、維護場景樹

每個MonoBehavior都有一個成員指向當前節點的transform組件
每個MonoBehavior都有成員gameObject指向該腳本的組件實例所掛載的節點對象

🚩代碼控制屬性

void Start () {
		// this.gemeObject 組件實例所掛載的場景的節點對象
        Debug.Log(this.gameObject.name);
	}

當然 還有別的屬性可以獲取:

Debug.Log(this.gameObject.layer);
Debug.Log(this.gameObject.tag);

在這裏插入圖片描述

🚩可見性

控制可見性:
在這裏插入圖片描述

Debug.Log(this.gameObject.activeSelf); // 自己是否可見
Debug.Log(this.gameObject.activeInHierarchy); // 自己在體系中是否可見
// 設置可見性
this.gameObject.SetActive(true);

在這裏 若game_root節點隱藏了 那麼Cube和Sphere的可見性勾還是打上的 這就是activeSelf
但是他們其實已經不可見了(在game_root這個體系中) 因此 這就是activeInHierarchy
在這裏插入圖片描述

🚩父子關係

場景樹是有父子關係的 若父親不可見 那麼下面全部孩子都不可見

任何組件都會有數據成員指向該節點的transform示例 因此可以通過this來訪問
通過任何一個組件來獲得每一個節點的transform

this.transform

transform組件不能有多個 只能有一個 無法刪除
同理 transform也是一個組件 因此也有一個gemeObject指向其掛載的節點

Debug.Log(this.transform.gameObject.name);

🚩場景樹的構建方式

Unity的場景樹是基於transform構建的
game_root有個transform指向了gameObject
在它下面有棵樹保存了它的孩子 - Cube和Sphere 實際上是Cube的transform和Sphere的transform
其實整個場景樹是由transform組件構成的 每個transform都會指向節點

name其實是這裏的名稱:
在這裏插入圖片描述

🚩Find和FindChild按名稱查找

(類似於HTML的父子節點的查找…)

// 獲取指定的transform
Transform trans=this.transform.Find("Cube"); // 名稱注意大小寫
GameObject cube = trans.gameObject;
Debug.Log(cube.name);

樹形關係是由transform串聯起來的 而並不是節點串聯的

trans=this.transform.FindChild("Sphere");
GameObject sphere=trans.gameObject;
Debug.Log(sphere.name);

Find函數和FindChild函數返回的都是Transform對象
區別:
Find可以去子節點的子節點中查找 (需寫多層路徑 比如Cube/Sphere代表Cube節點下的Sphere子節點)
而FindChild則無此功能

// parent屬性
Debug.Log(trans.parent.gameObject.name);

🚩GetChild按下標獲取子節點

還可以按下標獲取子節點

Debug.Log(this.transform.GetChild(0).gameObject.name); // Cube
Debug.Log(this.transform.GetChild(1).gameObject.name); // Sphere

任意組件都有gameObject和transform組件 且transform組件是gameObject必帶的
因此 只要獲得transform那麼就能獲取到gameObject了
transform是三維位置定位最重要的組件 節點的位置都是由transform進行控制的
且transform是幫助建立場景樹的

根transform 比如game_root Main Camera 或是Directional Lignt 都是沒有父節點
在這裏插入圖片描述
除了可以基於transform查找 還可以基於gameObject的全局查找:

// 基於GameObject的查找 查找的是GameObject而不是transform了
Debug.Log(GameObject.Find("game_root/Sphere").name);

相對來說 從子節點查找的性能會比全局查找高一些

🚩tag標籤

可以爲節點打上tag標籤 然後根據標籤查找
(類似於HTML的根據class或id查找…)
點擊在這裏插入圖片描述的Add Tag…
點擊小加號
在這裏插入圖片描述
然後輸入名稱即可
在這裏插入圖片描述
在這裏插入圖片描述
然後即可選擇標籤了:
在這裏插入圖片描述
在查找的時候 標籤就是標籤 和路徑無關 無論在什麼路徑下 只要打了標籤 那麼直接根據標籤名查找即可

// 基於Tag標籤名的全局查找
Debug.Log(GameObject.FindWithTag("mytag").name); // Cube

若有多個節點都是同樣的tag 那麼先找到哪個帶有該tag的節點 就返回哪個
只會返回一個 而不是返回多個

當然 也有函數可以查找多個
FindGameObjectsWithTag()返回的是GameObject數組

// 查找多個
GameObject[] gameObjects=GameObject.FindGameObjectsWithTag("mytag");
for (int i=0;i<gameObjects.Length;i++)
{
    Debug.Log(gameObjects[i].name);
}

比如 若要查詢敵人集合 可以將所有敵人都打上tag 然後一次性查詢全部敵人

🚩絕對座標系和相對座標系

絕對座標世界座標 就是transform組件中的Position
在這裏插入圖片描述
當根節點移動位置後 Cube的座標還是不會改變 但實際上世界座標已經改變了
因此 可以知道 相對座標即爲相對於父節點的位置
世界座標相對於世界(整個場景)的位置

例如 家的位置在地球的東經XX北緯XX
而人在家的XX方向XX距離的位置
那麼 將這兩個座標疊加 即爲人的世界座標了

代碼中的座標

在代碼中的相對座標是localPosition

// 在Unity的編輯器中的position是相對座標 而代碼中的position並不是!乃是絕對座標
Debug.Log(this.transform.position);
// 在代碼中的相對座標是localPosition
Debug.Log(this.transform.localPosition);

比如子節點的位置是(0,2,0) 而它的父節點的位置是(0,0,10) 那麼 兩個相加 子節點的世界座標就是 (0,2,10)

Transform cube=this.transform.FindChild("Cube");
// 在Unity的編輯器中的position是相對座標 而代碼中的position並不是!乃是絕對座標
Debug.Log(cube.position); // (1.0,4.0,0.0)
// 在代碼中的相對座標是localPosition
Debug.Log(cube.localPosition); // (0.0,4.0,0.0)

若父節點還有父節點 那麼還會繼續疊加

🚩距上一次刷新的時間 deltaTime/fixedDeltaTime

每個組件都會在遊戲刷新的時候調用Update生命週期 由於是每隔一段時間調用的 因此會產生時間間隔

用Time中的deltaTime來獲取
若在FixedUpdate生命週期中 則用fixedDeltaTime來獲取

// 更新
void Update () {
	// deltaTime 距離上一次刷新的時間
	float dt = Time.deltaTime;
} 

// 根據物理時間更新(是固定的)
void FixedUpdate()
{
	// 物理引擎固定更新的時間間隔 在FixedUpdate中用的是fixedDeltaTime
	float dt = Time.fixedDeltaTime;
}

🚩transform成員變量

  • parent - 父級的transform
  • childCount - 該transform有多少子節點
  • forward up right
    • 當點擊一個物體 會出現它的三維座標在這裏插入圖片描述
      三維座標會有其三個方向 綠色是y 紅色是x 藍色是z在這裏插入圖片描述
    • forward (z的正方向 正前)
    • up (y的正方向 正上)
    • right (x的正方向 正右)

對於前後左右上下 只需要三個向量即可 剩餘三個是方向量

返回的是一個座標

Debug.Log(cube.forward);

在遊戲中的向前走 實際上就是在向前的方位上行走

Debug.Log(cube.right);
Debug.Log(cube.up);

🚩方向向量

forward up 和 right都是世界座標系下的單位方向向量 表示的是方向
在這裏插入圖片描述
單位向量*標量(只有大小 沒有方向的量)=該標量在該單位向量的x y z方向上的分解

因此 在下面的例子中 cube_trans.forward就代表了模型在z軸的正方向
若旋轉了 那麼cube_trans.forward也會自動隨之改變

public class game_scene : MonoBehaviour {
    Transform cube_trans;

	// Use this for initialization
	void Start () {
        cube_trans = this.transform.Find("Cube");

        // 世界座標下的前 右 上方向
        // cube_trans.forward;
	}
	
	// Update is called once per frame
	void Update () {
        // 移動距離:10m/s 這是個標量(只有大小 沒有方向的量)
        float s = 10 * Time.deltaTime;
        // 在該標量的方向上移動s
        // 單位向量*標量=該標量在該單位向量的x y z方向上的分解
        cube_trans.position += cube_trans.forward * s;
	}
}

🚩座標的轉換

局部座標[/相對座標]轉換爲世界座標

若有個球體在這裏插入圖片描述
要將該球體放置於世界座標0,0,10的位置 那麼只需在代碼中修改該球體的position即可
還有個方法 使用TransformPoint
TransformPoint可以將當前局部座標系下的座標轉換爲世界座標

// 【將局部座標轉換爲世界座標】
// 即爲 在以this.transform爲原點的座標系中將相對座標0,0,0轉換爲世界座標
// Vector3即爲三維座標的意思
Debug.Log(this.transform.TransformPoint(new Vector3(0, 1, 2)));

世界座標轉換爲局部座標[/相對座標]

使用InverseTransformPoint函數

// 【將世界座標轉換爲局部座標】
// 即 對於世界座標0,6,2 當前世界座標(即this.transform)的相對位置
Vector3 local_post = this.transform.InverseTransformPoint(new Vector3(0, 6, 2));
Debug.Log(local_post);

🚩平移

有Space.World世界空間和Space.Self自己的空間

Space.World和Space.Self

比如 按住Alt然後旋轉畫面 這是以節點爲中心進行旋轉的 這就是Space.Self自己的空間
而Space.World是以世界座標系的原點爲旋轉點進行旋轉

Translate

若要平移物體 只需使用Translate()即可

void Update () {
	// 移動距離:10m/s 這是個標量(只有大小 沒有方向的量)
	float s = 10 * Time.deltaTime;
	
	// 默認是在原來的基礎上按照x y z的方向來平移
	this.cube_trans.Translate(new Vector3(0, 0, s));
}

默認是在原來的基礎上按照x y z的方向來平移的 若要以世界座標軸來平移 可以換添加一個構造參數

this.cube_trans.Translate(new Vector3(0, 0, s),Space.World);

還可設置以使得以指定transform作爲參照物移動

this.cube_trans.Translate(new Vector3(0, 0, s), this.transform);

平移物體有兩個關鍵:參考系在該參考系下每個方向的分量
參考系有三個 分別是自己(Self) 和 世界(World) 和 自定義目標

🚩縮放

縮放 同樣也是有x方向的縮放 y方向的縮放z方向的縮放

若將父節點進行縮放 那麼裏面的所有子節點都會跟着縮放相應的大小 比例係數會影響子節點

使用localScale來控制縮放 這是相當於父節點而言的
改變縮放

this.cube_trans.localScale = new Vector3(0.5f, 0.5f, 0.5f);

查詢縮放

// 若父節點進行了縮放 那麼比例係數會影響子節點
Debug.Log("localScale " + this.cube_trans.localScale);

還有個lossyScale 這是隻讀的 是相對於全局而言的

// 整個對象在全局的縮放係數
Debug.Log(this.cube_trans.lossyScale);

🚩旋轉的表示法

矩陣旋轉

任何一個旋轉都可以使用矩陣來進行表示
優點:旋轉軸可以是任意向量
缺點:旋轉其實只需要知道一個向量 + 一個角度 總共4個值的信息
但矩陣法卻使用了16個元素 比較耗費內存

因此 矩陣法使用的並不多

歐拉角

歐拉角是用來唯一地確定定點轉動剛體位置的三個一組獨立角參量 由章動角θ 進動角ψ和自轉角φ組成

歐拉角要按照一定的次序形成不同的角度 (就像是轉魔方…)
若順序不同 雖然是同樣的角度 但是最終的結果值也都是不同的
Unity是按照z x y的順序進行旋轉的
在這裏插入圖片描述

  • 優點:
    • 很容易理解 形象直觀
    • zxy的順序不同會造成不同的結果
  • 缺點:會造成萬向節死鎖(Gimbal Lock)的現象
    比如下圖 x和z兩個軸在同一個平面內 這樣 無論旋轉哪個角 都是繞着y軸轉
    如此 就丟掉了一個維度
    在這裏插入圖片描述
    萬向節死鎖在某些情況下 會導致動畫不夠流暢
    若出現該情況 則必須同時修改兩個旋轉軸纔行了

Quaternion 四元數

在某些情況下不方便使用歐拉角 此時就需使用四元數
四元數是一種表示旋轉的方式
在這裏插入圖片描述

  • 優點:
    • 可以避免萬向節死鎖現象
    • 只需要一個四維的四元數即可執行繞任意過原點的向量的旋轉 方便快遞
      某些實現下比旋轉矩陣的效率更高
    • 可以提供平滑插值
  • 缺點:比歐拉旋轉稍微複雜 因爲多了一個維度

歐拉角和四元數之間可以相互轉化

在Unity的編輯器中 爲了直觀地顯示旋轉 使用的是歐拉角表示法
在這裏插入圖片描述
而在代碼裏 爲了避免萬向節死鎖問題 使用的是四元數
因此 在編輯器中看到的角度和代碼中的角度是不一樣的

將四元數轉換爲歐拉角

eulerAngles返回四元數對應的歐拉角

// Unity在代碼中爲了避免萬向節死鎖問題 使用四元數來存放一個旋轉
// Unity編輯器中爲了直觀地顯示旋轉 顯示的是歐拉角
// eulerAngles可以將四元素轉換爲歐拉角
Vector3 e_degree = cube_trans.rotation.eulerAngles;
Debug.Log(e_degree);

將歐拉角轉換爲四元數

使用QuaternionEuler()函數 然後傳入一個三維向量對象 可以將歐拉角轉換爲四元數

// 將歐拉角轉換爲四元數 直接到了指定角度
cube_trans.rotation = Quaternion.Euler(new Vector3(0, 30, 60));

這個是直接到了指定角度 這是和Rotate()的區別

Rotate 歐拉角度

調用transform的Rotate()函數 然後傳入一個三維向量對象 同樣能夠旋轉指定的歐拉角度
在當前的基礎上再進行旋轉

// 直接旋轉歐拉角度 是在當前的基礎上再進行旋轉的
cube_trans.Rotate(new Vector3(0, 30, 60));

同樣的 Rotate不僅只能以自己旋轉 也支持傳入構造參數 改變參照物 能夠以世界空間和指定參照物來旋轉

實現一直旋轉

// 定義角速度
float w = 360;
// 角度=角速度×時間
float degree = w * Time.deltaTime;
// 旋轉
// cube_trans.Rotate(0, degree, 0);
// 將每個小變換組成一個大變換 因此不能用加法[+] 而是應該用乘法[*]
cube_trans.rotation = cube_trans.rotation * Quaternion.Euler(new Vector3(0, degree, 0));

🚩其它常用旋轉方法

在三維空間中 有個軸既垂直於初始位置 又垂直於結束位置 這個軸就是方向
初始位置和結束位置的角就是角度
方向 + 角度就能算出四元數 將四元數給物體 物體就能旋轉
在這裏插入圖片描述
Rotate(歐拉角,空間)
Rotate(方向,角度)

private Transform cube = transform.FindChild("Cube");
// 歐拉角旋轉
cube.Rotate(new Vector3(0, 45, 0));
// 旋轉向量+角度
cube.Rotate(new Vector3(0, 1, 0), 45);

Rotate是逆時針爲負 順時針爲正

使用RotateAround() 繞着某個旋轉
語法:RotateAround(要圍繞的點,方向,角度)

public class game_scene : MonoBehaviour {
    private Transform sphere;

	// Use this for initialization
	void Start () {
        sphere = transform.FindChild("Sphere");
	}
	
	// Update is called once per frame
	void Update () {
        // 角度=時間*速度
        float angle = Time.deltaTime * 180;
        // 要圍繞的中心點
        Vector3 pos = new Vector3(0, 0, 0);
        // 旋轉 RotateAround(要圍繞的點,方向,角度)
        sphere.RotateAround(pos, new Vector3(0, 1, 0), angle);
	}
}

LookAt() ★

使用LookAt()對準對應的方位(視線對準)
經常用與遊戲的尋路 移動等功能

頭頂方向
LookAt是跟着目標不停旋轉 因此需要旋轉角度和旋轉向量
旋轉向量在LookAt中 被稱作是頭頂方向
頭頂方向 即爲注視着目標點的時候 是繞着什麼軸旋轉的
默認以y軸(0,1,0)爲旋轉軸

有了目標點的位置 可以計算出旋轉角度
有了頭頂方向 能夠指名按照哪個向量進行旋轉

有了角度旋轉向量 那麼就能組成一個四元數 組成一個旋轉方位

void Update () {
	// 角度=時間*速度
	float angle = Time.deltaTime * 180;
	// 要圍繞的中心點
	Vector3 pos = new Vector3(0, 0, 0);
	// 旋轉 RotateAround(要圍繞的點,方向,角度)  
	sphere.RotateAround(pos, new Vector3(0, 1, 0), angle);
	
	// 鎖定目光
	cube.LookAt(sphere.position, new Vector3(0, 1, 0));
}

localEularAngles() 返回一個當前旋轉相對於父節點的歐拉角

🚩Quaternion四元素常用旋轉方法

LookRotation() 根據自己的上方向盯着前方
用法和LookAt類似 但LookAt使用會更加多

Quaternion rot= Quaternion.LookRotation(this.sphere.position,new Vector3(0,1,0));
cube.rotation = rot;

Angle(lhs,rhs) 計算兩個旋轉之間的夾角
用於計算兩個角之間的差值
FromToRotation(from,to) 獲得從初始向量旋轉到目標向量的旋轉四元數

// 獲得從初始向量旋轉到目標向量的旋轉四元數
Quaternion rot= Quaternion.FromToRotation(new Vector3(0, 0, 1), new Vector3(1, 0, 1));
cube.rotation = rot;

在這裏插入圖片描述

🚩Vector3

Vector3若表示三維座標 則表示一個立方體的點(帶有三條座標軸)
若表示向量 則表示從原點開始到結束點(x,y,z)的向量

向量的加減法

向量的加減法直接將每個分量加減即可
比如:

(x1,y1,z1)+(x2,y2,z2)=(x1+x2,y1+y2,z1+z2)
(x1,y1,z1)-(x2,y2,z2)=(x1-x2,y1-y2,z1-z2)

向量的縮放

向量乘以比例係數即可縮放
放大:(x1,y1,z1)*n=(x1n,y1n,z1n)(n>0)
縮小:(x1,y1,z1)*n=(x1n,y1n,z1n)(n<0)
同理 向量除以比例係數即可縮小

向量的比較

有(x1,y1,z1)和(x2,y2,z2)
若x1=y1,x2=y2,z1=z2那麼這兩個向量相等
只要有一個不等 那麼這兩個向量就不相等

單位向量

單位向量就是根號(x^2+y^2+z^2)=1的向量
Vector提供了一些單位向量:zero one forward right up

Debug.Log(Vector3.zero); // (0.0,0.0,0.0)
Debug.Log(Vector3.one); // (1.0,1.0,1.0)
Debug.Log(Vector3.forward); // (0.0,0.0,1.0)
Debug.Log(Vector3.right); // (1.0,0.0,0.0)
Debug.Log(Vector3.up); // (0.0,1.0,0.0)

因此:

float speed = 10;
float s = speed * Time.deltaTime;
cube.transform.position += new Vector3(0,0,s);

可改爲

float speed = 10;
float s = speed * Time.deltaTime;
cube.transform.position += Vector3.forward*s;

Lerp線性插值

Lerp兩個向量之間的線性插值

from+(to-from)*t

如下圖 若t=0 就在A點
若t=1 就在B點
若t=0.5 就在A點和B點的中間
在這裏插入圖片描述
所謂線性插值 這是一條直線

// from (0,0,0) to (10,0,0)
// Time.time爲從開始到現在所有delteTime的累計之和
cube.transform.position = Vector3.Lerp(Vector3.zero, Vector3.right * 10,Time.time);

Slerp球面插值

繞着球面以弧形移動
由於球面的圓心是(0,0,0) 因此不能從(0,0,0)開始移動

// 球面插值
// from (-10,0,0) to (10,0,0)
cube.transform.position = Vector3.Slerp(Vector3.right * -10, Vector3.right * 10, Time.time);

distance向量的距離

Distance()來求向量的距離
向量的距離就是兩個點之差求模
在這裏插入圖片描述

// Distance向量的距離
float len = Vector3.Distance(Vector3.right, Vector3.right * -1);
Debug.Log(len); // 2

Angle向量的角度

// Angle向量的角度
float degree = Vector3.Angle(Vector3.right, Vector3.right * -1);
Debug.Log(degree); // 180

Min Max 比較向量的模的大小

Vector3 max= Vector3.Max(Vector3.right, Vector3.right * 2);
Debug.Log(max);

Vector3 min = Vector3.Min(Vector3.right, Vector3.right * 2);
Debug.Log(min);

transform總共包括了四個邏輯

分別是:

  • 1、樹 - find get…
  • 2、旋轉 - lookAt…
  • 3、平移 - Translate…
  • 4.縮放 - rotate…

🚩獲取組件實例

每個gameObject下面都有一個transform
MonoBehavior組件也有一個transform指向gameObject
MonoBehavior還有一個數據成員gameObject指向gameObject
因此 圍繞着gameObject有一個組件transform 除此之外 還有一些必有的組件1,2…
同時 每個組件都有一個transform變量指向節點所對應的transform組件 包括其自己也有一個transform變量指向它自己

每個MonoBehavior組件都會拿到gameObject
同時 transform又是一個組件 因此也能拿到gameObject

總而言之 所有組件都會有transform和gameObject

  • 凡是組件 都會有一個gameObject成員可以獲得該組件實例所綁定的節點
  • 凡是組件 都會有一個transform成員可以獲得該組件實例所綁定的transform組件
  • 凡是GameObject 都會有一個transform成員指向其所掛載的Transform組件的實例

GetComponent() 獲取節點上所掛載的transform組件實例

只需要在泛型<>裏填入要獲取的組件的類型即可
獲取節點上所掛載的transform組件實例

// 獲取節點上所掛載的transform組件實例
// 會自動查找節點掛載的第一個Transform的組件實例
Transform t=gameObject.GetComponent<Transform>();
Debug.Log(t.name);

獲取節點上所掛載的所有爲該類型的組件

// 獲取節點上所掛載的所有爲該類型的組件
Transform[] tList = gameObject.GetComponents<Transform>();
for (int i=0;i<tList.Length;i++)
{
	Debug.Log(tList[i].name);
}

除了通過類型 還可以根據組件實例的名稱獲取 傳入字符串格式的名稱即可

// 獲取名爲cube的組件實例
Debug.Log(cube.gameObject.GetComponent("cube"));

上述代碼的特點 都是從gameObject開始查找的

也可通過組件 獲取當前組件所掛載的節點其它組件
其實只是封裝了一層 本質還是通過組件找到節點 然後通過節點找指定組件的 就不需要通過gameObject了

// 根據組件獲取當前組件所掛載的節點的其它組件
// 其實只是封裝了一層 本質還是通過組件找到節點 然後通過節點找指定組件的 就不需要通過gameObject了
this.GetComponent<Transform>();

如此 即可在一個組件裏調用其它另外的組件裏的方法了

將代碼中的參數綁定到編輯器

只需要加上public的數據成員 即可將該數據成員綁定到對應的編輯器上了
比如:
在這裏插入圖片描述

public class game_scene : MonoBehaviour {
    public int speed=10;

	// Use this for initialization
	void Start () {
      
	}
	
	// Update is called once per frame
	void Update () {
	
	}
}

然後就會識別到了:
在這裏插入圖片描述
同樣 在編輯器中進行修改 在代碼中也能拿到對應的值 (類似於Vue的雙向數據綁定…)


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章