Giới thiệu
Transform là một chức năng cần thiết để phát triển chò trơi trong Unity. Nó chịu trách nhiệm cho các chức năng quan trọng xuất hiện nhiều lần trong trò chơi như di chuyển, xoay, scale và nhóm. Transform luôn được thao tác khi di chuyển đối tượng trong quá trình phát triển trò chơi.
Component đặc biệt Transform
Đầu tiên cần nói đến chính là Transform là một component. Một component là một chức năng trong Unity. Transform là một component vì vậy bạn có thể coi nó như một chức năng tương tự như Camera hay Light.
Các chức năng của transform là Position, Rotation, Scale và Group
Nó có 3 điểm đặc biệt sau:
- Luôn được gán cho GameObject
- Không thể bị xoá khỏi GameObject
- Không cần GetComponent để có thể truy cập Transform
Transform luôn được gán cho GameObject
Không có GameObject nào mà không có Transform
Tất cả các GameObject đều có các chức năng cơ bản như di chuyển và xoay do Transform thực hiện
Không thể xóa Transform
Như đã nói ở trên Transform luôn tồn tại trong GameObject vì vậy không có cách nào để loại bỏ Transform khỏi GameObject
Như thấy trong hình dưới đây không hề có menu “Remove Component”
Còn đối với các component bình thường ta có thể thấy menu “Remove Component” để xóa component khỏi GameObject
Nếu bạn cố gắng xóa component Transform bằng code thông qua phương thức Destroy
Một lỗi như sau sẽ hiện thị ra màn hình console
Nói cách khác không thể destroy component Transform
Không thể thêm nhiều hơn 1 Transform
Không thể thêm Transform nhiều hơn 1 lần. hãy thêm bằng cách nhấn vào nút Add Component
Như bạn có thể thấy ở trên Transform không tồn tại trong danh sách hiển thị ra. Vì nó là component không bao giờ được thêm vào vì nó được gắn vào GameObject ngay từ đầu nên nó bị loại bỏ khỏi danh sách
Hãy thử thêm bằng cách sử dụng đoạn code sau
Một thông báo sẽ được hiển thị ra console khi đoạn code trên được thực thi nói cho bạn biết rằng không thể thêm component Transform vì nó đã tồn tại trước đó trong GameObject rồi
GetComponent là không bắt buộc để có thể sử dụng Transform
Để lấy component chúng ta thường sử dụng GetComponent nhưng Transform là component đặc biệt và nó không cần phải sử dụng GetComponent để có thể sử dụng được. Hãy xem đoạn code sau:
Notes
RectTransform là Transform nhưng sử dụng cho UI (user interface).
Nó chịu trách nhiệm về các chức năng như position, rotation, scale, group trong canvas
Bản thân RectTransform là kế thừa từ Transform nên nó có thể coi là Transform nhưng có thêm các chức năng bổ sung phục vụ cho UI
Các cách cơ bản để sử dụng Transfrom
- Thay đổi vị trí của Transform bằng position hoặc localPosition
- Thay đổi góc xoay của Transform bằng eulerAngles
- Thay đổi tỉ lệ của Transform bằng localScale
- Gom nhóm Transform bằng SetParent
Thay đổi vị trí của Transform bằng localPosition / position
Trong quá trình phát triển trò chơi, bạn có thể muốn thay đổi vị trí của nhân vật của mình. Thay đổi vị trí là nhiệm vụ của Transform. Bạn có thể thực hiện bằng cách thay đổi giá trị của thuộc tính position trong inspector
Bạn có thể thay đổi các tọa độ x, y, z
Sử dụng position hoặc localPosition trong code như thế nào? hãy xem ví dụ di chuyển tọa độ x của Transform từ -2 thành 2 sau 1 giây
Bạn có thể thấy rằng tọa độ X đã thay đổi sau 1 s
Sự khác biệt giữa position và localPosion là gì?
Chúng trông có vẻ giống nhau nhưng thật ra hoàn toàn khác nhau. Bạn cần hiểu chính xác điểm khác biệt giữa chúng để vận dụng trong thực tế
Đầu tiên bạn có thể hiểu là
- position : là tọa độ tuyệt đối
- localPosition : là tọa độ tương đối
Tọa độ tuyệt đối “position” là gì?
trong unity tọa độ biểu diễn dưới dạng xyz có trục x, trục y, trục z và gốc tọa độ. Gốc tọa độ là điểm có tọa độ xyz (0, 0, 0)
Tọa độ tuyệt đối là tọa độ dựa trên gốc tọa độ, trong hình dưới đây nhân vật của bạn được đặt ở vị trí cách gốc tọa độ 2 đơn vị trên trục x
Ví dụ nhân vật ở tọa độ (1, 0, 2)
Tọa độ tuyệt đối còn được gọi là “global position”
Tọa độ tương đối “localPosition” là gì?
Tọa độ tương đối là tọa độ liên quan đến hệ thống phân cấp
Hệ thống phân cấp thể hiện như hình minh họa sau đây
Để giải thích localPosition chúng ta sẽ sử dụng mối quan hệ parent-child đơn giản sau đây làm ví dụ
Đầu tiên hãy đặt vị trí parent trong hierarchy ở tọa độ (0, 1, 0)
Bây giờ chúng ta hãy di chuyển vị trí trục x của child đến vị trí (2, 0, 0)
Vị trí tương đối là vị trí so với tọa độ của parent, như ví dụ trên thì
- localPosition của nhân vật sẽ là (2, 0, 0)
- position của nhân vật sẽ là (2, 0, 0) + (0, 1, 0) = (2, 1, 0)
Phân biệt position và localPosition
Như đã giải thích về tọa độ tương đối và tọa độ tuyệt đối, nhưng làm thế nào để sử dụng chúng đúng cách.
Với hầu hết các nhu cầu bình thường chỉ sử dụng localPosition là đủ. Nếu transform hierarchy rất sâu (transform lồng nhau rất nhiều cấp) và bạn muốn di chuyển child hierarchy sang tọa độ tuyệt đối (global position) hãy sử dụng position
Chú ý răng việc sử dụng positon và localPosition cùng nhau trong trường hợp không cần thiết sẽ gây nhầm lẫn và khó hiểu. Vì vậy nên sử dụng localPosition cho hầu hết các thao tác thay đổi vị trí.
Đơn vị trong Unity là mét
Như tiêu đề đơn vị sử dụng trong Unity là mét
Nếu bạn đặt tọa độ là (3, 0, 0) nhân vật sẽ có khoảng cách với gốc tọa độ là 3m trên trục X.
Sử dụng đồng nhất đơn vị là điều quan trọng. Các model tạo bằng các phần mềm 3D có thể thêm vào unity và đơn vị của phần mềm 3D không phải lúc nào cũng là mét. hãy kiểm tra và chắc chắn nó được chuyển đổi thành mét để mọi tính toán của bạn sau này được đồng nhất và chính xác.
Vector3 là gì?
Vector3 là dữ liệu chứa 3 số bạn có thể truy cập nó với 3 thuộc tính x, y, z. Bạn có thể sử dụng nó để biểu diễn véc tơ trong không gian 3 chiều, điểm, tọa độ
Vector3 cũng có sẵn các chức năng hữu ích như các phép toán số học, hay tính khoảng cách giữa hai điểm. Lưu ý rằng Vector3 chứa các số thập phân, nếu bạn chỉ muốn xử lý số nguyên bạn có thể sử dụng Vector3Int
Di chuyển vị trí bằng phương thức Translate
Phương thức Translate cũng có thể thay đổi vị trí của Transform, tham số truyền vào là khoảng cách cần di chuyển đến.
Đoạn code trên sẽ cộng thêm (1f, 0.5f, 0f) vào tọa độ tương đối.
Nếu bạn muốn Translate bằng tọa độ tuyệt đối (global position) bạn có thể chỉ định đối số thứ 2 của phương thức Translate là Space.World
Thay đổi góc xoay bằng eulerAngles
Bạn có thể nhìn thấy thuộc tính Rotation trong cửa sổ Inspector
Bạn có thể xoay theo trục x, y, hoặc z
Thay đổi góc với localEulerAngles / eulerAngles
Cũng tương tự như localPosition / position các phép quay cũng có phép quay dựa trên gốc và phép quay dựa theo parent
Chạy thử đoạn mã trên bạn sẽ thấy nhân vật xoay một góc 45 độ theo trục z
localEulerAngles và eulerAngles xoay transform bằng cách thay đổi các giá trị vector3
Thay đổi góc với localRotation / rotation
Ngoài eulerAngle bạn có thể sử dụng rotation để thay đổi góc, rotation sẽ sử dụng Quaternion thay vì Vector3.
Quaternion được sử dụng để biểu diễn các phép quay.
Theo dõi đoạn code sau:
Để hiểu được quaternion, bạn cần có một số kiến thức toán học nhất định chúng ta sẽ tìm hiểu về nó chi tiết trong một bài viết cụ thể hơn.
Thay đổi tỉ lệ bằng localScale
Bạn có thể nhìn thấy thuộc tính Scale trong cửa sổ Inspector
Bạn có thể thay đổi tỉ lệ theo trục x, y, hoặc z
“Enable constrained proportions”: sẽ khiến cho thay đổi x, y, z biến đổi theo cùng tỉ lệ
Ví dụ:
Khác với position và rotation scale chỉ có một loại localScale mà thôi
Sử dụng setParrent để thay đổi hệ thống phân cấp
Cùng với position, rotation và scale một tính năng quan trọng khác của Transform là gom nhóm
Bạn có thể nhóm các đối tượng bằng cách kéo thả GameObject trong inspector
Ngoài ra bạn có thể sử dụng phương thức SetParent
Giờ thì gameObject sẽ là con của “_parent” sau khi hàm Start được thực thi.
Hãy chú ý đến tham số thứ 2 của phương thức SetParent nó rất quan trọng, giá trị mặc định là true nếu bạn không chỉ định.
- true : positon, rotation, scale được thay đổi để đối tượng giữ nguyên vị trí ở global position như trước
- false :
Để làm rõ hành vi của nó chúng ta xem xét ví dụ sau đây
ban đầu cả 3 đối tượng không ràng buộc với nhau không phải là cha con
thay đổi gameObject C thành con của A với tham số thứ 2 là true
Như bạn thấy vị trí của C được thay đổi thành (3.5, 1.5, 0) để global position của nó được giữ nguyên như ban đầu
thay đổi gameObject C thành con của A với tham số thứ 2 là false
localPosition của C lúc này bằng với position lúc trước của nó, lúc này C sẽ kế thừa tọa độ (6.5, -1.5, 0) của A
nói cách khác C được đặt tại vị trí (10, 0, 0) so với (6.5, -1.5, 0)
Đối số thứ 2 của SetParent thường được chỉ định là false, true hiếm khi được sử dụng
Nếu bạn truyền null vào SetParent. Điều này đồng nghĩa với việc loại bỏ parent của gameObject lúc này nó sẽ trở thành một đối tượng ngoài cùng trong hierarchy
|
|
Bạn có thể tham khảo thêm tài liệu của Unity về SetParent ở đây
Truy cập đối tượng parent
Bạn có thể truy cập đối tượng cha gần nhất của transform dựa vào thuộc tính parent
Giả sử ta có hệ thống hierarchy như sau
Đoạn code sau đây được gắn vào gameObject Child
Kết quả hiện thị ra màn hình console sẽ là “ParentB”
Parent trả về null
Nếu đối tượng của bạn đang ở gốc của hierarchy thuộc tính parent sẽ trả về kết quả null
vì vậy bạn nên kiểm tra kết quả với null trước khi sử dụng
Truy cập root
Đôi khi bạn muốn truy cập đối tượng root từ rất sâu trong hệ thống hierarchy. Trong trường hợp đó bạn có thể sử dụng thuộc tính root
Kết quả hiện thị ra màn hình console sẽ là “Root”.
Phụ lục
Phương thức LookAt
Trong quá trình phát triển trò chơi, bạn cần làm cho đối tượng của mình đối mặt với một hướng cụ thể. Ví dụ, trường hợp kẻ thù bắn đạn vào bạn. Kẻ thù sẽ đối mặt với bạn và bắn. Thoạt nghe có vẻ khó nhưng thực hiện rất dễ
Bạn cũng có thể truyền vector3 làm đối số cho phương thức LookAt
|
|
Transform animation
Animation của Transform thường được sử dụng trong suốt quá trình phát triển trò chơi của bạn. Dưới đây chúng ta cùng tìm hiểu một số cách thể thực hiện transform animation.
Sử dụng Update
Hãy xem xét đoạn code mô tả chuyển động đều (vận tốc không đổi) sau. Nhân vật sẽ di chuyển từ trái sang phải sau đó di chuyển ngược lại
Kết quả là bạn có được một chuyển động như dưới đây:
Sử dụng AnimationClip
Bạn cũng có thể tạo animation một cách trực quan bằng công cụ Animation có sẵn của Unity
Sử dụng Tween
Ngoài ra bạn cũng có thể sử dụng Tween (ví dụ ở đây sử dụng DOTween nó là một plugin của bên thứ 3). Giúp bạn tạo animation chỉ với một vài dòng code.
Và đây là kết quả
Bạn có thể thấy đoạn code sử dụng Dotween ngắn hơn và dễ hiểu hơn nhiều so với đoạn code viết trong hàm Update
Để biết thêm về những gì DOTween có thể làm được bạn có thể xem tài liệu trên trang chủ của DOTween
Xoay quanh trục hoặc tọa độ được chỉ định
Đôi khi bạn muốn xoay đối tượng xung quanh các trục tọa độ, trong trường hợp này bạn có thể sử dụng RotateAround
Sau đây chúng ta sẽ thử xoay nhân vật xung quanh trục z
Gắn đoạn code sau vào nhân vật
Sau khi chạy bạn sẽ có chuyển động giống như thế này:
Truy cập vào các đối tượng con
Foreach
Giả sử bạn có hệ thống hierarchy giống như sau:
Nếu bạn muốn duyệt toàn bộ các đối tượng con cấp 1 của root bạn có thể sử dụng foreach
Như thế này:
Dưới đây là kết quả in ra console
Thoạt nhìn transform không phải một array hay list nên tại sao nó có thể sử dụng được với foreach?
Vì transform kế thừa interface IEnumerable nên nó có thể duyệt được bằng foreach.
GetChild
Ngoài cách này bạn có thể sử dụng phương thức GetChild tham số truyền vào là index của đối tượng con mà bạn muốn lấy.
Lưu ý rằng ví dụ trên Root có 4 đối tượng con nên đối tượng con cuối cùng sẽ có index là 3 (tương ứng với childCount - 1) nếu bạn cố gắng lấy các đối tượng có index lớn hơn 3 một ngoại lệ sẽ được ném ra
Lấy toàn bộ các đối tượng con
Bằng cách sử dụng GetComponentsInChildren bạn có thể lấy được tất cả cấu trúc hierarchy bao gồm cả bản thân nó (self)
Ví dụ áp dụng với cấu trúc hierarchy sau
Kết quả hiển thị ra console:
Nếu bạn chỉ muốn nhận các đối tượng con mà loại bỏ chính bản thân mình hãy làm như sau
IsChildOf
Kiểm tra xem đối tượng Transform đã cho có phải là parent hay không?
Lúc này bạn có thể sử dụng IsChildOf cú pháp khá đơn giản như sau
Lưu ý rằng IsChildOf vẫn trả về true khi kiểm tra với chính bản thân nó
LossyScale
Đôi khi bạn muốn lấy scale chính xác của một đối tượng nằm sâu bên trong hierarchy
Như bạn thấy localScale của cả 2 Hina đều là 1 nhưng kích thước thật sự của chúng khác nhau. Bạn có thể dễ dàng lấy tỉ lệ chính xác bằng cách sử dụng lossyScale
Performance
Transform được sử dụng xuyên suốt quá trình phát triển trò chơi và có mặt ở khắp nơi trong code của bạn. vì vậy một thay đổi nhỏ về cách sử dụng transform cũng sẽ ảnh hưởng đến hiệu suất tổng thể của cả trò chơi
Cache transform
Cách mà bạn vẫn thường sử dụng để truy cập thuộc tính transform thường là gameObject.transform hoặc transform.
Nhìn thì có vẻ chúng ta đang truy cập trực tiếp nó giống như bạn gọi tới GetComponent
Vì vậy bạn luôn GetComponent mỗi lần bạn truy cập vào transform, sẽ không có vấn đề gì nếu tần suất truy cập thấp. Tuy nhiên đôi khi bạn sẽ quên mất nó và sử dụng bên trong Update nơi mọi thứ cập liên tục mỗi frame. Lúc này CPU của bạn luôn phải GetComponent để biết được đối tượng transform mặc dù hai đứa đã làm quen với nhau từ frame trước đó.
trong trường hợp này lưu vào bộ nhớ cache là một giải pháp tuyệt vời
Ví dụ khi không sử dụng bộ nhớ đệm mã của bạn trông giống như thế này
Khi sử dụng bộ nhớ đệm
Sau khi transform được vào cache ở hàm Start, khi bạn truy cập biến _cacheTransform bạn sẽ có nó ngay lập tức mà không cần tốn bất kỳ chi phí phát sinh thêm nào.
Thực tế là transform và gameObject.transform không sử dụng GetComponent
Tuy nhiên cho dù nó được tối ưu cỡ nào đi nữa nó vẫn chậm hơn so với việc truy cập từ bộ nhớ đệm.
Tóm lại cách tốt nhất để truy cập transform đó là lưu nó vào bộ nhớ đệm sau đó truy cập nó từ bộ nhớ đệm.
Thay vị trí và góc xoay cùng lúc
Trong trường hợp bạn thay đổi position và rotation cùng một lúc, bạn có thể sử dụng SetPositionAndRotation thay vì viết riêng và cập nhật riêng biệt từng thuộc tính nó sẽ mang lại hiệu suất tốt hơn
Performance cho hierarchy
Nếu độ sâu của hierarchy cố định, bạn có thể chỉ định độ tối đa thông qua hierarchyCapacity để có thể mong đợi cải thiện hiệu suất
Ví dụ bạn chỉ định độ sâu tối đa là 10
Nếu vượt quá 10 sẽ xuất hiện cảnh báo và trò chơi vẫn chạy
Theo như Unity nói nó có thể giảm mức sử dụng bộ nhớ và cải thiện hiệu suất của Transform.SetParent và Object.Destroy Nhưng với độ sâu 10 hay 20 chúng không có gì khác nhau
Bạn có thể xem thêm tài liệu về hierarchyCapacity trên trang chủ Unity
Hoặc một bài blog nói về tối ưu hierarchy của unity
Performance giữa position và localPosition
Theo dõi đoạn code sau đây
Kiểm tra trong hai trường hợp có độ sâu hierarchy khác nhau ta có kết quả sau:
Với độ sâu là 6:
localPosition mất 56,57 ms trong khi đó positon là 81,71ms
Với độ sâu là 3
localPosition mất 57,22 ms trong khi đó positon là 67.77ms
Điều này có nghĩa là độ sâu càng lớn position càng chậm do phải tính toán nhiều transform
Thông số hiện thị trong inspector của Transform
Position hiển thị trong Inspector là tọa độ tương đối (tọa độ local) Rotation hiển thị trong Inspector là góc so với parent, mặc dù ghi là rotation nó là giá trị hiển thị của EulerAngle không phải của Quaternion
Performance giữa GetComponent vs transform
transform nhanh hơn so với GetComponent
Không di chuyển Transform trong FixedUpdate
Đơn giản là vì FixedUpdate không gọi liên từng frame như Update mà nó được giọi cố định sau mỗi 0.02 giây.
Giả sử ta có FPS (frame per sec) là 30 nghĩa là màn hình sẽ được cập nhật 30 lần mỗi giây. Trong trường hợp trò chơi có FPS là 30 thì mỗi frame sẽ là 0.03 giây. Khi đó Update sẽ được gọi sau mỗi 0.03 giây. Còn với FixedUpdate nó gọi sau mỗi 0.02 giây vậy nếu Update gọi được 2 lần thì FixedUpdate đã được gọi 3 lần
Bạn có thể xem chỉ số FixedTimeStep trong ProjectSetting
Vì FixedUpdate trục thời gian khác với FPS nên nếu di chuyển Transform với FixedUpdate có thể sẽ dẫn đến chuyển động không mong muốn. Đôi khi bạn tính toán quá trình cần được di chuyển bởi một frame lại chạy hai lần và quá trình di chuyển sẽ không đồng bộ.
Hãy chỉ sử dụng FixedUpdate cho các tính toán vật lý