Code game là phải nhỏ và nhanh
Mỗi nền tảng phát triển
game đều có những hạn chế về năng lực xử lý của CPU(bộ xử lý trung tâm) và GPU
(bộ xử lý đồ hoạ) .Đặc biệt trong các cảnh game 3D với độ chi tiết cao thì nhu
cầu xử lý sẽ càng cao . Khi nhu cầu xử lý này trở nên quá tải thì hiệu năng
game sẽ giảm và gây cảm giác khó chịu cho người dùng như là load game lâu, game
chạy chậm và bị giật hoặc nghiêm trọng hơn thì có thể bị treo hệ thống .Quy
trình tối ưu hoá là chuỗi các quyết định hay thoả hiệp giữa việc duy trì hiệu
năng game và chất lượng đồ hoạ. Vậy chúng ta cần phải tối ưu những gì và cụ thể
như thế nào? Well, hãy đọc hết bài này đi nhé! J
1. Hạn chế dùng Update
Hàm update được gọi mỗi lần cho 1 frame(khung hình). Và trong toàn
bộ các object có hàm update thì nó gọi khá là nhiều, ngoài ra còn có FixUpdate
và LateUpdate cũng được gọi thường xuyên. Chức năng này được gọi đến để cập
nhật các hành vi và sự kiện theo thời gian, nên dùng trong trường hợp cần
thiết, nếu lạm dụng update thì sẽ rất ảnh hưởng đến performance. Khi
bạn gọi một hàm khác từ trong hàm Update() hay FixedUpdate() thì hàm bạn gọi được
chạy từ lúc bắt đầu cho tới lúc kết thúc một frame, ở tốc độ 60fps thì
điều này có nghĩa là hàm đó sẽ được gọi 60 lần trên giây, thấy sự lãng phí ở
đây chưa? Vậy điều đầu tiên cần nhớ đó là hạn chế sử dụng tính toán và gọi hàm
từ Update nếu bạn không muốn game của mình chạy ỳ ạch và ngốn hết tài nguyên hệ
thống. Để thay thế thì có nhiều cách, như ta có thể dùng Coroutine hoặc InvokeRepeat
-
Corooutine có
thể tạm ngừng việc thực thi của nó trong một frame rồi lấy lại và tiếp tục thực
thi trong frame tiếp theo qua bất kỳ độ dài frame nào . Thử so sánh một hàm chạy
60 lần trên giây và 10 lần trên giây mà hiệu năng game vẫn vậy thì bạn chọn
cách nào , rõ ràng trong trường hợp này dùng Coroutine sẽ tăng hiệu năng lên 6
lần.
-
InvokeRepeat dùng để gọi hàm sau một số giây
nhất định, gần giống với Corooutine,
cụ
thể khác nhau ở những điểm nào mình sẽ có một bài viết phân tích sau J
2. Object pooling
Object
pooling lấy việc tối ưu hoá mô hình làm sẵn . Việc tạo thể hiện và huỷ đối tượng
cũng chiếm một chi phí tính toán đáng kể . Thay vì tạo thể hiện và huỷ hết đối
tượng này đến đối tượng khác thì một mảng các mô hình đối tượng game làm sẵn sẽ
được tạo ra dưới dạng "pool" (bể chứa) để có thẻ lấy ra đối tượng khi
cần rồi trả lại đối tượng khi không sử dụng nữa. Nhân nói về tạo và hủy, theo
mình thì nên hạn chế sử dụng hàm Destroy() theo giây. Mặc dù đây là sự cải tiến
để tránh việc có quá nhiều đối tượng trong chương trình , nhưng Unity vẫn phải
theo dõi mỗi đối tượng và kiểm tra vòng đời của chúng cho tới khi nào hết thời
gian . Một giải pháp khác là hàm Destroy() sẽ sử dụng khi đối tượng chạm vào một
vùng trigger nào đó mà ta chỉ định thay vì việc phải kiểm tra liên tục.
5.Sử dụng Static Batching
Đối với các object tĩnh, ví dụ như bàn, background tĩnh,… không di
chuyển trong suốt quá trình chơi game, thì ta có thể chọn vào Static:
Unity sẽ tối ưu hóa, giảm số draw calls và tăng hiệu suất làm việc
hơn.
6. Tối ưu hoá với Mecanim
Dưới đây là một số lưu ý để dùng Mecanim hiệu quả hơn .
- Triển khai AI Layer để điều khiển Animator . Bạn
có thể dùng AI Layer để cung cấp các lời gọi callback đơn giản cho hàm
OnStateChange(), OnTransitionBegin , ...
- Sử dụng tag state để dễ dàng ánh xạ máy trạng
thái AI với máy trạng thái Mecanim.
- Sử dụng đường cong phụ trợ để mô phỏng Events .
- Sử dụng đường cong phụ trợ để đánh dấu
animation(ví dụ như kết hợp với việc ánh xạ mục tiêu)
- Tối ưu hoá thời gian chạy :
- Luôn tối ưu hoá animation bằng cách thiết lập mục
Culling Mode của animator về thành Based on Renderers , và vô hiệu hoá
thuộc tính Update when offscreen của trình render lưới da (skinned mesh
renderer) . . Theo đó thì cách animation sẽ không phải cập nhật khi nhân
vật chưa hiện ra .
7. Lưu đệm các tìm kiếm component
Kỹ thuật viết script này thường hiệu quả với các script được dùng
thường xuyên ,nhưng đổi lại đòi hỏi viết nhiều mã hơn một chút . Hàm
GetComponent() là một ví dụ về tìm kiếm (lookup) . Việc tìm component trong đối
tượng game sẽ tiêu tốn thời gian và ảnh hưởng tới hiệu năng . Ý tưởng ở đây là
tìm tham chiếu một lần duy nhất rồi lưu đệm hoặc lưu tham chiếu trong một biến
private để sẵn sàng sử dụng trong các script về sau . Nói cách khác hãy tránh
dùng hàm GetComponent() trong hàm Update() hoặc hàm FixedUpdate() bất cứ nơi
nào có thể. Ví dụ:
Thay vì gọi trực tiếp :
gameObject.getComponent<Rigidbody2D>() liên tục mỗi khi cần dùng đến, ta
dùng:
Rigidbody2D mRigidbody2D;
Void Start() {
mRigidbody2D = gameObject.getComponent<Rigidbody2D>();
}
Bây giờ, nếu muốn sử dụng thì chỉ cần dùng mRigidbody2D.
Đặc biệt đối với transform ( và một số thuộc tính tương tự, như
audio…) cũng nên dùng cách này để tránh tham chiếu trực tiếp component của
objects.
Void Start(){
mTransform = transform;
}
Void Update(){
mTransform.localPosition = mPosition;
}
Điều này cũng đúng khi dùng Hash ID khi làm việc với animator, thông thường bạn sẽ code theo kiểu như thế này:
- Luôn tối ưu hoá animation bằng cách thiết lập mục
Culling Mode của animator về thành Based on Renderers , và vô hiệu hoá
thuộc tính Update when offscreen của trình render lưới da (skinned mesh
renderer) . . Theo đó thì cách animation sẽ không phải cập nhật khi nhân
vật chưa hiện ra .
Điều này cũng đúng khi dùng Hash ID khi làm việc với animator, thông thường bạn sẽ code theo kiểu như thế này:
Animator anim;
//...
anim.SetBool("isRun",true);
anim.SetFloat("speed",5f);
...
Hãy thử tưởng tượng khi làm một game lớn với số lượng animation cực khủng, thì việc truy cập thông qua các chuỗi string như thế này sẽ ảnh hưởng tới performance. Hoặc là khi muốn đổi tên 1 chuỗi string, bạn phải tìm và đổi rất nhiều nơi, và thường gặp lỗi chính tả khi viết các chuỗi string. Unity cung cấp cho chúng ta Hash id, chuyển các chuỗi string về kiểu int trong Animator.
public int isRun;
void Awake(){
isRun = Animator.StringtoHash("isRun");
}
//.......
anim.SetBool(Hash.isRun,true);
Bạn nên để tất cả các hash id này vào 1 class để dễ dàng quản lý.
P/s: Đối với tag và layer cũng là các chuỗi string, trong quá trình code, bạn nên tập trung vào 1 class khai báo quản lý. Và cũng nên khai báo 1 class để chứa và quản lý các hằng số trong game, dễ quản lý và bảo trì.
.
ReplyDelete