Unity là một công cụ tuyệt vời và đơn giản, có thể dùng cho nhiều platform lập trình. Nguyên lý làm việc rất dễ hiểu, và nhìn vào là biết tạo sản phẩm ngay. Tuy nhiên, nếu không để ý, tiến độ của bạn sẽ chậm lại khi tiến dần vào giai đoạn prototype và final release. hy vọng bài viết này sẽ giúp bạn vượt qua nhiều vấn đề thường gặp và cách tránh lỗi trong một project. Đặc biệt, bài viết này tập trung vào lập trình ứng dụng 3D (nhưng vẫn có thể áp dụng được với 2D).

Unity là một công cụ tuyệt vời và đơn giản, có thể dùng cho nhiều platform lập trình

Common Unity Mistake #1: Đánh giá thấp khâu planning trong project

Với mỗi project, việc xác lập một số quyết định quan trọng trước khi bắt đầu design và programing là rất quan trọng. Vì product marketing là một phần cốt yếu, bạn cũng cần biết về mô hình kinh doanh của ứng dụng nữa. Bạn còn phải biết bạn sẽ release ứng dụng lên platform nào. Bạn cũng phải đặt thông số thiết bị được hỗ trợ tối thiểu (Bạn sẽ hỗ trợ các thiết bị lỗi thời hay chỉ tập trung vào các cấu hình mới?) để hình dung tài nguyên hiệu suất và đồ họa bạn có thể đưa vào ứng dụng.

Từ góc nhìn kỹ thuật, bạn cũng cần phải xác lập trước workflow tạo asset và model. Đồng thời cung cấp chúng cho programmer, và chú ý kỹ đến các công việc lặp đi lặp lại vì model sẽ cần thay đổi và cải tiến. Hơn nữa, phải định trước frame rate mong muốn và ngân sách tối đa, để họa sĩ 3D biết mình phải làm gì, độ phân giải tối đa của model là bao nhiêu, và bao nhiêu LOD phải làm. Bạn còn phải xác định cách thống nhất đơn vị tính toán cho cả project.

Việc thiết kế các cấp độ cực kỳ quan trọng về sau này, cách phân cấp sẽ ảnh hưởng mạnh đến hiệu suất làm việc. Đừng đi theo tầm nhìn thiếu thực tế. Bạn sẽ muốn tự hỏi mình “mục tiêu này có khả thi hay không?” Đừng phí tài nguyên cho những mục tiêu khó lòng hoàn thành được.

Common Unity Mistake #2: Làm Việc Với Model Chưa Tối Ưu

Hãy chuẩn bị kỹ tất cả model, luôn sẵn sàng sử dụng chúng trong mọi trường hợp mà không cần điều chỉnh thêm. Một model tốt cần đáp ứng một số yêu cầu.

Bạn cần thiết đặt đơn vị đo cho phù hợp. Đôi khi, ta không thể dùng đơn vị trực tiếp từ 3D modeling software vì những ứng dụng này sử dụng hệ đơn vị khác đi. Để đảm bảo chính xác, hãy thiết đặt hệ số tỷ lệ trong model import setting (0,01 với 3dsMax và Modo. 1,0 với Maya), và đôi khi sau thay đổi setting tỷ lệ, bạn phải re-import objects. Những setting này phải đảm bảo bạn có thể dùng ngay tỷ lệ 1,1,1 cơ bản, với hành vi cố định và không gặp bất cứ vất đề physics nào. Bạn nên áp dụng quy định này cho tất cả subproject trong model, chứ không chỉ riêng với project chính. Khi bạn cần điều chỉnh chiều của object, hãy để ý đến các ứng dụng thiết kế 3D khác nữa chứ không riêng gì Unity. Tuy nhiên, bạn có thể thử nghiệm với tỷ lệ trong Unity để tìm được giá trị phù hợp.

Xem xét functionality của object và dynamic part kỹ lưỡng giúp bạn chia model rõ ràng hơn. Càng ít suboject càng tốt. Tách các phần của object để đề phòng khi bạn cần đến chúng, ví dụ như, để di chuyển và xoay, cho mục đích animation, hay có các tương tác khác. Mỗi object và suboject của nó phải được xác định và quanh quanh trục đúng cách, sao cho phù hợp với chức năng chính. Object chính phải có trục Z chỉ về phía trước và trục quay phải ở cuối object. Sử dụng càng ít tài liệu cho object càng tốt.

Tất cả assets phải có tên riêng, tên này miêu tả type và chức năng. Tiếp tục áp dụng lên tất cả project.

Common Unity Mistake #3: Xây Dựng Cấu Trúc Code Liên Đới

Rất dễ prototype và implement functionality trong Unity. Bạn có thể drag và drop reference sang các object khác, xác định tất cả object và thành tố trong scene. Tuy nhiên, cách này cũng khá nguy hiểm. Bên cạnh các vấn đề thấy rõ về hiệu suất (tìm một object trong hierachy và truy cập vào các thành phần có overhead), và viết code hoàn toàn liên hệ với nhau còn nguy hiểm hơn. Hay phụ thuộc vào các hệ thống khác và script độc lập với ứng dụng, hay scene và scenario hiện tại. Hãy tìm một cách thức “module” hơn và tạo các part tái sử dụng được, để dùng trong các part khác của ứng dụng, hoặc share khắp cả portfolio ứng dụng. Build framework và library sau Unity API giống như cách bạn xây dựng knowledge base.

Có nhều cách đảm bảo điều này. Bản thân Unity component system là điểm bắt đầu khá tốt. Khi một số component nhất định giao tiếp với các hệ thống khác của ứng dụng, có thể xuất hiện một vài rắc rối. Với cách làm này, bạn có thể dùng interface để làm các part của hệ thống abstract (trừu tượng hơn) và tái sử dụng dễ hơn. Thay vào đó, bạn có thể dùng cách (dựa theo event) để react với một event cụ thể từ outside scope, bằng việc tạo messaging system hoặc register trực tiếp vào part của system khác dưới dạng listener. Cách đúng nhất sẽ là tách gameObject property khỏi program logic (hoặc ít nhất đại loại như model-controller principle), vì rất khó xác định được object nào đang điều chỉnh transform property của nó, như vị trí hoặc góc quay. Nó phải là trách nhiệm của riêng controller.

Hãy cố viết document thật rõ ràng. Viết như kiểu bạn sẽ quay lại code sau “”nhiều năm vắng bóng” vậy, và bạn sẽ cần bắt kịp nhanh chóng đấy. Nhưng cũng đừng nên viết rõ quá mức cần thiết. Nhiều khi, tên class, method và property phù hợp là quá đủ.

Common Unity Mistake #4: Lãng phí hiệu suất

Sẽ không bao giờ có dòng điện thoại, consoles, máy tính nào cao cấp đến mức không cần quan tâm về hiệu suất. Tối ưu hiệu suất luôn luôn cần thiết, và đây chính là nền tảng tạo khác biệt giữa game hay ứng dụng của bạn với của đối thủ. Vì khi bạn tiếp kiệm được hiệu suất của phần này, bạn có thể đẩy mạnh thêm ở phần khác.

Chúng ta có thể tối ưu ở rất nhiều mặt. Cả bài viết này cùng lắm chỉ nêu lên được những mặt cơ bản nhất thôi. Chí ít, tôi có thể giúp bạn chia thành một số loại chính.

Update Loops

Không được dành quá nhiều tài nguyên vào Update loop, thay vào đó, hãy dùng caching. Một ví dụ điển hình là access tới component hoặc các object khác trong một scence hoặc các tính toán cường độ cao trong script. Nếu có thể, cache mọi thứ vào method `Awake()`, hoặc thay đổi cấu trúc sang hướng thiên về event hơn để kích hoạt đúng khi cần thiết.

Instantiations

Với các object thường được instantiate (như đạn trong game FPS), tạo một pool được initalize trước, và chí việc chọn ra object đã initialie khi cần và kích hoạt. Sau đó, thay vì hủy khi không cần nữa, hãy deactivate và trả lại pool.

Rendering

Dùng occlusion culling hoặc kỹ thuật LOD để giới hạn các phần được render trong scene. Hãy dùng các model đã tối ưu để có thể kiếm soát số lượng vertex trong scene. Hãy chú ý, số lượng vertex không chỉ là về số lượng vertice trên chính model, mà còn bị ảnh hưởng bởi nhiều thứ như normal (hard edges), UV coordinate (UV seams) và màu vertex. Hơn nữa, số lượng dynamic light trong một scene sẽ ảnh hưởng mạnh mẽ đến hiệu suất tổng quan, vậy nên bạn hãy cố chuẩn bị trước mọi thứ nếu có thể.

Draw Calls

Hãy tìm cách giảm số lượng draw count. Trong Unity, bạn có thể giảm draw calls bằng cách sử dụng static batching với object đứng yên và dynamic batching với object di động. Tuy nhiên, bạn phải chuẩn bị scene và model của bạn trước (object đã batch phải chia sẻ cùng tài nguyên), và việc batch của dynamic object chỉ làm việc với các model phân giải thấp. Thay vào đó, bạn cũng có thể kết hợp mesh bằng script vào `Mesh.CombineMeshes` thay vì sử dụng batching, nhưng bạn phải cẩn thận để không tạo object quá lớn, từ đó không thể tận dụng được view frustum culling trên một vài platform. Nhìn chung, chìa khóa nằm ở việc sử dụng càng ít tài nguyên càng tốt, và share khắp cả scene. Đôi khi, bạn phải tạo nhiều atlas từ texture để có thể share một tài nguyên giữa nhiều object khác nhau. Một lời khuyên hữu ích: bạn cũng nên dùng phiên bản scene lightmaps textures có độ phân giải cao hơn (không phải generated resolution, mà là texture output resolution) để giảm số lượng của chúng khi bạn đang bake light trong các môi trường rộng lớn hơn.

Overdraw Problems

Đừng dùng transpatent texture khi không cần thiết, vì sẽ gây nhiều vấn đề về fill-rate. Tất nhiên, bạn vẫn có thể dùng được với geometry từ xa hoặc phức tạp, như cây/bụi. Khi bạn cần dùng đến transparent texture, nên dùng alpha blende shader thay cho shader với alpha testing (hoặc shader for mobile platform). Nhìn chung để xác định được những vấn đề này, hãy cố hạ độ phân giải của ứng dụng xuống. Nếu hiệu quả, có vẻ như bạn đang gặp vấn đề fill-rate rồi, bạn cần phải tối ưu shader hơn nữa. Nếu không, đây cũng có thể là vấn đề về bộ nhớ.

Shaders

Hãy tối ưu shader để tăng hiệu suất. Giảm số pass, sử dụng biến với độ chính xác thấp hơn,, thay thế các tính toán phức tạp với pre-generated lookup texture.

Luôn luôn dùng profiler để xác định ta bị trì trệ chỗ nào. Về phần render, bạn cũng có thể dùng Frame Debugger, công cụ sẽ giúp bạn biết được mọi thứ làm việc ra sao khi decompose rendering process với nó.

Common Unity Mistake #5: Làm ngơ các vấn đề lẻ tẻ

Bạn cần nhận biết rằng, trái với sự thật rằng bản thân Garbage Collector (GC) giúp chúng ta rất nhiều, chúng ta vẫn phải tự để ý một số thứ. Dùng GC không giải quyết được mọi thứ. Nhìn chung, chúng ta nên tránh cấp phát bộ nhớ không hợp lý để tránh GC quá tải làm giảm hiệu suất với framerate spike. Lý tưởng mà nói, không nên xảy ra bất cứ sự cấp pháp bộ nhớ mới nào ở mỗi frame. Tuy nhiên, ta phải làm sao đây? Thực sự phụ thuộc vào cấu trúc ứng dụng, nhưng vẫn có một số quy luật bạn có thể tuân theo:

  • Tránh cấp phát không cần thiết trong update loop.
  • Dùng struct làm property container đơn giản, vì chúng không được phân phối trong heap.
  • Preallocate array hoặc list hoặc các tập hợp object khác, thay vì tạo trong update loop.
  • Tránh dùng mấy thứ mono rắc rối (như LINQ expressions hoặc foreach loops) vì Unity đang dùng phiên bản Mono cũ hơn, ít tối ưu hơn.
  • Cache string trong method `Awake()` hoặc tron event.
  • Nếu cần update string property trong update loop, hãy dùng StringBuilder object thay cho string.
  • Dùng profiler để xác định vấn đề tìm ẩn.

Common Unity Mistake #6: Tối ưu việc sử dụng bộ nhớ ở lúc cuối project

Bạn phải để ý đến mức độ sử dụng bộ nhớ tối thiểu của ứng dụng ngay từ đầu project, đừng nên chờ đến pre-release mới tối ưu, khi đó sẽ phức tạp hơn nhiều. Trên thiết bị mobile, khâu này lại càng quan trọng hơn nữa, vì ta rất thiếu tài nguyên trên nền tảng này. Hơn nữa, bộ cài chỉ cần tăng 100MB thôi, ta cũng đã mất kha khá khách hàng rồi đấy. Một phần là vì cước phí 3G, một phần là vì lý do tâm lý. Sẽ tốt hơn nhiều nếu bạn không lãng phí tài nguyên của điện thoại khách hàng , và họ sẽ có khuynh hướng mua và tải ứng dụng của bạn khi kích thước ứng dụng gọn gàng hơn.

Để tìm lý do tiêu tốn tài nguyên, các bạn có thể sử dụng editor log khi bạn thấy (sau mỗi lần build) kích thước của tài nguyên được chia thành các nhóm riêng biệt, như audio, texture, và DLL. Để vận hành tốt hơn, có nhiều phần mở rộng cho editor trên Unity Asset Store (công cụ cung cấp tóm tắt chi tiết cùng tài nguyên và file kèm theo trong filesystem của bạn. Thực ra mức độ tiêu thụ bộ nhớ cũng có thể được theo dõi trong profiler, nhưng theo tôi, bạn vẫn nên test khi được kết nối với build trên target platform vì sẽ có nhiều mâu thuẫn khi test trên editor hoặc bất cứ môi trường nào khác target platform.

Nguyên nhân lớn nhất làm thâm hụt bộ nhớ chính là texture. Nên dùng compressed texture vì chúng chiếm ít bộ nhớ hơn. Làm tất cả texture theo kiểu vuông, và lý tưởng nhất, độ dài hai bên POT, nhưng cần để ý, Unituy cũng có thể tự động scale NPOT texture thành POT. Và texture có thể nén được khi ở dạng POT. Đôi khi, bạn thậm chí có thể dùng texture alpha channel để tìm thêm thông tin về shader để tiếp kiệm không gian và hiệu suất. Và tất nhiên, bạn nên tái sử dụng texture khi có thể cho ra kết quả đồ họa khá tốt. Với các thiết bị cũ, bạn có thể giảm độ phân giải của texture trong Quality Setting. Dùng format audio nén để có audio clip dài hơn, như background music chẳng hạn.

Khi phải làm việc với nhiều platform, độ phân giải hoặc localization, các bạn có thể sử dụng asset bundle cho nhiều nhóm texture với những thiết bị và người dùng khác nhau. Từ internet, ta có thể tải những asset bundle này sau khi cài đặt ứng dụng. Theo đó, bạn có thể vượt quá ngưỡng 100MB khi download tài nguyên ngay trong game.

Common Unity Mistake #7: Các lỗi physics thường gặp

Đôi khi, khi chuyển object trong scene, chúng ta không nhận ra rằng object có collider bên trên và thay đổi vị trí của nó sẽ buộc engine phải tính toán lại cả thế giới vật lý. Trong trường hợp đó, bạn nên thêm component `Rigidbody` (bạn có thể set thành non-kinematic nếu bạn không muốn liên quan đến ngoại lực).

Để thay đổi vị trí của object với `Rigidbody`, luôn luôn set `Rigidbody.position` khi một vị trí mới không tuân theo vị trí trước đó, hay `Rigidbody.MovePosition` khi đây là chuyển động liên tục, tính đến cả interpolation. Khi điều chỉnh, luôn áp dụng operation trong hàm `FixedUpdate`, chứ không phải trong `Update`. Từ đó ta có thể đảm bảo hành vi vật lý nhất quán.

Nếu có thể, sử dụng primituve collider trên gameObject, như sphere (khỗi cầu), hộp, trụ, mà không phải là mesh collider. Bạn có thể compose collider cuối cùng từ một hoặc nhiều collider trên. Physics (tương tác vật lý) có thể gây giảm hiệu suất của ứng dụng, vì nó gây quá tải CPU, và xung đột giữa các primitive collider có thể tính toán nhanh hơn nhiều. Bạn cũng có thể điều chỉnh Fixed Timestep setting trong Time manager để giảm tần số fixed update của physics khi độ chính xác của tương tác vật lý không còn cần thiết nữa.

Common Unity Mistake #8: Test thủ công tất tần tật

Sẽ có lúc bạn cần test functionality thử công trong playmode vì: vừa vui vừa tự chủ. Nhưng chơi càng nhiều, sự hứng thú của bạn càng ít đi. Ứng dụng ngày càng phức tạp, lập trình viên càng phải làm nhiều công việc tẻ nhạt để đảm bảo ứng dụng chạy đúng như dự kiến. Đây có lẽ là khâu tồi tệ nhất trong cả quá trình phát triển, vì tính chất thụ động lặp đi lặp lại của mình. Hơn nữa, vì bạn chán, nên có thể bạn sẽ sơ ý, và sự sơ ý này để bug lọt qua cả quy trình testing.

Thật may mắn, Unity có công cụ giúp bạn test tự động. Với thiết kế cấu trúc và code phù hợp, bạn có thể sử dụng unit test để test các isolated functionality, hoặc thậm chí là integration test để test những scenario phức tạp hơp. Bạn có thể không cần try-and-check nhiều khi log data thật và so sánh chúng với trạng thái mong muốn.

Testing thủ công tuy có thể được giảm bớt, nhưng vẫn không cách nào thay thế được bằng test tự động. Để làm quá trình test thủ công “dễ chịu” hơn nữa, các bạn có thể xác định test sence cần tập trung, hoặc thêm cheat vào game để vào trạng thái mong muốn dễ dàng hơn.

Common Unity Mistake #9: Cho rằng Unity Asset Store Plugins sẽ giải quyết mọi vấn đề

Tin tôi đi, không đúng đâu. Khi làm việc với một vài vị khách hàng, tôi thường hay sa vào thói quen dùng asset store plugin cho mọi thứ như hồi còn “trẻ trâu”. Tôi không nói rằng plugin nào cũng vô dụng, nhưng lại có quá nhiều plugin trong đó, và rất khó để ta lựa chọn. Và với mỗi project, sự nhất quán vô cùng quan trọng. Ta có thể phá vỡ sự nhất quán này khi sử dụng quá nhiều plugin không phù hợp với nhau.

Mặt khác, với những functionality mất nhiều thời gian để đưa vào ứng dụng, bạn hoàn toàn có thể sử dụng những sản phẩm đã qua kiểm chứng từng Unity Asset store để tiếp kiệm hàng đồng thời gian. Tuy nhiên, hãy chọn lựa thật kỹ, tìm đến những plugin uy tín để tránh bug cho sản phẩm của bạn.

Common Unity Mistake #10: Không mở rộng các chức năng cơ bản của Unity

Không như nhiều bạn tưởng, môi trường Unity Editor vẫn chưa đủ để test game và thiết kế level cơ bản, và mở rộng Editor chưa hẳn đã là phí thời gian. Tiềm năng mở rộng to lớn của Unity đến từ khả năng thích nghi với các tình huống cụ thể phù hợp với nhiều project khác nhau. Việc mở rộng vừa có thể cải thiện trải nghiệm làm việc với Unity, hoặc tăng tốc mạnh mẽ cả workflow.

Lời kết

Tôi hy vọng bài viết này sẽ hữu ích cho các bạn trong quá trình thực hiện project của mình. Điều bạn luôn luôn phải nhớ là sự “nhất quán” trong cả project để mọi người trong team hiểu rõ về project, và giải quyết vấn đề chính xác hơn.

Posted on Techtalk via toptal

Advertisements