Unity là công cụ lập trình rất phổ biến mà gần như các lập trình viên game đều đã từng trải nghiệm hoặc sử dụng một cách thông thạo. Vì vậy, nếu bạn đang ứng tuyển cho vị trí Unity Game Developer hoặc một vị trí Game Developer yêu cầu sử dụng Unity, sau đây là tổng hợp các câu hỏi phỏng vấn Unity kèm câu trả lời chi tiết mà bạn nên làm quen.

Bài viết tổng hợp câu hỏi phỏng vấn Unity sau đây chia thành các phần:

  • Câu hỏi phỏng vấn Unity về lập trình
  • Câu hỏi phỏng vấn Unity cơ bản

Unity là gì?

Unity là một công cụ phát triển game (game engine) mạnh mẽ và linh hoạt, được sử dụng để tạo ra các trò chơi điện tử và ứng dụng tương tác trên nhiều nền tảng khác nhau. Được phát triển bởi Unity Technologies, Unity cung cấp một môi trường phát triển tích hợp (IDE) cho phép các nhà phát triển thiết kế, lập trình, và triển khai các sản phẩm của họ một cách dễ dàng và hiệu quả.

Một trong những điểm mạnh của Unity là khả năng hỗ trợ đa nền tảng, cho phép Game Developer sản xuất game trên nhiều hệ điều hành và thiết bị, bao gồm PC, console, di động, và thực tế ảo (VR).

Unity cũng nổi bật với giao diện người dùng thân thiện và dễ sử dụng, cùng với một kho tài nguyên phong phú, bao gồm các mô hình 3D, âm thanh, và các công cụ hỗ trợ khác.

Cộng đồng người dùng Unity rất lớn và năng động, với nhiều diễn đàn, tài liệu hướng dẫn, và khóa học trực tuyến, giúp người mới bắt đầu có thể nhanh chóng làm quen và phát triển kỹ năng của mình. Unity cũng thường xuyên cập nhật và cải tiến, cung cấp các tính năng mới và sửa lỗi để đáp ứng nhu cầu ngày càng cao của ngành công nghiệp game.

Trước khi đi vào chi tiết các câu hỏi phỏng vấn Unity, bạn có thể tham khảo chi tiết về Unity qua các bài viết sau:

Câu hỏi phỏng vấn Unity về lập trình

OOP là gì? Tại sao nó giúp lập trình dễ dàng hơn?

Lập trình hướng đối tượng (OOP) là một mô hình phát triển phần mềm, trong đó dữ liệu và hành vi được tổ chức thành các đối tượng. Các đối tượng này đại diện cho các khái niệm hoặc thực thể và tương tác với nhau qua các giao diện được xác định trước.

OOP giúp việc lập trình game dễ dàng hơn nhờ khả năng:

  • Tái sử dụng mã: Cho phép sử dụng lại mã qua kế thừa, tiết kiệm thời gian phát triển.
  • Tăng năng suất: Dễ dàng sử dụng các gói phần mềm có sẵn, giúp xây dựng chương trình nhanh hơn.
  • Bảo mật: Cơ chế ẩn dữ liệu bảo vệ thông tin, chỉ cho phép truy cập cần thiết.
  • Dễ bảo trì: Thiết kế mô-đun giúp mã dễ hiểu và bảo trì trong các dự án lớn.

4 thuộc tính của OOP là gì?

Lập trình hướng đối tượng (OOP) là một mô hình lập trình tập trung vào “đối tượng”, trong đó các đối tượng là phiên bản của lớp, chứa cả dữ liệu và các hành vi liên quan. OOP giúp tổ chức mã dễ hiểu và dễ quản lý hơn nhờ vào bốn đặc tính chính:

  • Đóng gói (Encapsulation): Đóng gói kết hợp dữ liệu và các phương thức thao tác trên dữ liệu đó vào một lớp duy nhất. Điều này giúp tổ chức mã ngăn nắp và dễ tái sử dụng. Ví dụ, trong phát triển trò chơi, một lớp nhân vật có thể bao gồm thuộc tính như vị trí và sức khỏe, cùng với các hành động như di chuyển hay tấn công.
  • Kế thừa (Inheritance): Kế thừa cho phép bạn tạo ra các lớp mới từ một lớp có sẵn, tái sử dụng các thuộc tính và phương thức của lớp cha. Điều này giúp giảm sự trùng lặp mã. Ví dụ, một lớp “kẻ thù” chung có thể được kế thừa để tạo ra các loại kẻ thù cụ thể như yêu tinh hoặc rồng, với các đặc điểm chung và thêm các thuộc tính riêng.
  • Đa hình (Polymorphism): Đa hình cho phép các đối tượng khác nhau được xử lý như thể chúng thuộc về một loại chung, giúp mã linh hoạt hơn. Trong trò chơi, một phương thức có thể xử lý nhiều loại đối tượng khác nhau như nhân vật, kẻ thù và vật phẩm, giúp đơn giản hóa logic.
  • Trừu tượng hóa (Abstraction): Trừu tượng hóa giúp đơn giản hóa các hệ thống phức tạp bằng cách chỉ tập trung vào các chi tiết quan trọng và che giấu các phần phức tạp. Điều này giúp mã dễ hiểu và bảo trì. Ví dụ, một công cụ vật lý trong trò chơi có thể cung cấp giao diện đơn giản cho các nhà phát triển mà không cần phải quan tâm đến các phép tính phức tạp bên trong.

Phân biệt Abstract và Interface

Lớp trừu tượng (Abstract Class) nên được sử dụng khi bạn dự định cập nhật lớp cơ sở trong suốt vòng đời của dự án hoặc khi bạn cần xây dựng một hệ thống phân cấp các đối tượng có liên quan chặt chẽ.

  • Chức năng: Lớp trừu tượng cho phép bạn tạo các phương thức mà các lớp con có thể triển khai hoặc ghi đè, và có thể chứa cả phương thức đã có mã thực thi lẫn phương thức trừu tượng (chưa có thân).
  • Sử dụng: Khi bạn muốn chia sẻ mã giữa các lớp có liên quan chặt chẽ hoặc cần khai báo các biến không tĩnh và không phải hằng số. Abstract class cũng hữu ích khi bạn muốn định nghĩa các phương thức với mức truy cập không chỉ là “public” (ví dụ như “protected” hay “private”).
  • Kế thừa: Một lớp chỉ có thể kế thừa từ một lớp trừu tượng duy nhất.

Đối với giao diện (Interface), bạn có thể sử dụng khi bạn không cần xử lý một hệ thống phân cấp phức tạp và bạn muốn tận dụng đa kế thừa và tránh các vấn đề phức tạp từ kế thừa lớp trừu tượng.

  • Chức năng: Giao diện chỉ định nghĩa các phương thức mà mọi lớp triển khai nó phải tuân theo, nhưng không chứa bất kỳ mã thực thi nào. Nó chỉ là một cam kết về các hành vi mà lớp cần phải có.
  • Sử dụng: Giao diện thích hợp khi bạn muốn định nghĩa các hành vi chung mà các lớp không liên quan có thể triển khai, hoặc khi bạn cần hỗ trợ đa kế thừa. Interface không tạo ra cấu trúc phân cấp phức tạp.
  • Kế thừa: Một lớp có thể triển khai nhiều interface cùng lúc, cho phép tận dụng đa kế thừa mà không gặp vấn đề “diamond problem” (vấn đề kim cương trong đa kế thừa).

Nêu nguyên tắc SOLID

Nguyên tắc SOLID là tập hợp các hướng dẫn giúp tổ chức và kết nối các hàm, cấu trúc dữ liệu thành các lớp một cách hợp lý, nhằm tạo ra các mô-đun phần mềm dễ hiểu, dễ thay đổi, và có thể tái sử dụng trong nhiều loại hệ thống khác nhau.

  • Single Responsibility Principle (SRP): Mỗi lớp chỉ nên chịu trách nhiệm cho một nhiệm vụ cụ thể, giúp giảm rủi ro khi thay đổi mã.
  • Open/Closed Principle (OCP): Các lớp nên mở để mở rộng nhưng đóng để chỉnh sửa, nghĩa là có thể thêm tính năng mới mà không thay đổi mã nguồn hiện tại.
  • Liskov Substitution Principle (LSP): Các đối tượng con có thể thay thế cho đối tượng cha mà không làm thay đổi tính đúng đắn của hệ thống.
  • Interface Segregation Principle (ISP): Nên sử dụng nhiều giao diện nhỏ thay vì một giao diện lớn, giúp các lớp không phải thực hiện các phương thức không cần thiết.
  • Dependency Inversion Principle (DIP): Các mô-đun cấp cao không nên phụ thuộc vào mô-đun cấp thấp; cả hai nên phụ thuộc vào các trừu tượng, giúp hệ thống dễ thay đổi và mở rộng.

Coroutines có phải đa luồng không?

Coroutines không phải là đa luồng. Chúng hoạt động dựa trên cơ chế đa nhiệm hợp tác, giúp tiết kiệm tài nguyên hơn so với đa luồng. Điều này có nghĩa là:

  • Không có chuyển đổi ngữ cảnh giữa các luồng.
  • Không chặn hệ thống.
  • Không sử dụng các lệnh gọi hệ thống phức tạp hoặc xử lý các phần quan trọng.

Nói cách khác, coroutines giúp thực hiện nhiều tác vụ đồng thời mà không thực sự chạy song song. Chúng chỉ hoạt động trên một luồng duy nhất, đạt được tính đồng thời mà không cần tạo ra nhiều luồng.

Tuy nhiên, trong một số ngôn ngữ hỗ trợ cả coroutines và đa luồng, một coroutine có thể tạm ngừng trong một luồng và sau đó tiếp tục chạy trên một luồng khác. Điều này thường được kết hợp với nhóm luồng (thread pool), nơi một số lượng luồng nhỏ hơn sẽ xử lý hàng nghìn coroutines. Các luồng sẽ chia sẻ hàng đợi công việc và khi một coroutine bị chặn, luồng đó sẽ lấy một coroutine khác từ hàng đợi để tiếp tục thực hiện.

Nhờ vậy, CPU vẫn hoạt động hiệu quả mà không cần quá nhiều chuyển đổi ngữ cảnh giữa các luồng, vì coroutines có thể chuyển đổi trạng thái hàng nghìn lần mà không tốn quá nhiều tài nguyên.

Một số cấu trúc dữ liệu phổ biến được sử dụng trong lập trình game là gì?

Có nhiều cấu trúc dữ liệu được sử dụng trong lập trình game, và mỗi loại có đặc điểm riêng phù hợp với các mục đích cụ thể. Tuy nhiên, một số cấu trúc dữ liệu đa dụng và thường gặp trong game development gồm:

  • Dynamic Array (Mảng động): Mảng động có khả năng thay đổi kích thước linh hoạt. Khi mảng đầy, nó tự động mở rộng và sao chép dữ liệu sang một mảng mới lớn hơn. Mảng động hữu ích cho việc lưu trữ danh sách có kích thước thay đổi, ví dụ khi số lượng vật phẩm hoặc nhân vật trong game có thể thay đổi liên tục.
  • Linked List (Danh sách liên kết): Trong danh sách liên kết, mỗi phần tử chứa dữ liệu và một con trỏ trỏ đến phần tử tiếp theo. Danh sách liên kết rất hữu dụng khi cần thêm hoặc xóa phần tử thường xuyên mà không phải dịch chuyển nhiều dữ liệu như trong mảng.
  • Doubly Linked List (Danh sách liên kết kép): Giống như danh sách liên kết, nhưng mỗi phần tử trỏ đến cả phần tử trước và sau nó. Điều này giúp tăng hiệu quả khi duyệt hoặc xóa phần tử ở giữa danh sách, tuy nhiên nó tiêu tốn nhiều bộ nhớ hơn.
  • Hashtable (Bảng băm): Bảng băm lưu trữ dữ liệu theo cặp khóa-giá trị. Nó rất hiệu quả cho việc tìm kiếm thông tin nhanh chóng dựa trên khóa, chẳng hạn như tìm kiếm thông tin người chơi theo ID trong game.
  • Graph (Đồ thị): Đồ thị bao gồm các nút liên kết với nhau, có thể có hướng (chỉ có một chiều kết nối) hoặc vô hướng (kết nối hai chiều). Đồ thị hữu dụng trong các bài toán như tìm đường A*, nơi AI phải tìm con đường ngắn nhất từ một điểm đến điểm khác trong game.

Một số thuật toán phổ biến được sử dụng trong lập trình game là gì?

Có nhiều thuật toán được sử dụng trong lập trình game nhằm tối ưu hóa các nhiệm vụ như tìm đường, phát hiện va chạm, và ra quyết định. Một số thuật toán phổ biến gồm:

  • A* (A star): Đây là một trong những thuật toán quan trọng nhất trong game, thường được dùng để tìm đường giữa hai điểm trên bản đồ hoặc trong đồ thị. A* đảm bảo tìm đường hiệu quả và tối ưu, đặc biệt khi các nhân vật cần điều hướng trong thế giới ảo.
  • Blind Search (Tìm kiếm mù): Bao gồm hai thuật toán chính là tìm kiếm theo chiều rộng (BFS) và tìm kiếm theo chiều sâu (DFS). Cả hai đều kiểm tra mọi khả năng từ điểm xuất phát đến khi tìm thấy đích, thích hợp cho các tình huống cần khám phá tất cả các khả năng trên một đồ thị.
  • Flood Fill: Thuật toán này được dùng để xác định các khu vực được kết nối trong một không gian, phổ biến trong việc tạo địa hình hoặc tô màu bản đồ. Flood Fill hỗ trợ tạo nội dung game tự động và cải thiện hình ảnh.
  • Collision Detection (Phát hiện va chạm): Các thuật toán như Định lý trục phân tách (SAT) và Gilbert-Johnson-Keerthi (GJK) giúp phát hiện và xử lý va chạm giữa các đối tượng trong game. Đây là yếu tố cốt lõi để đảm bảo sự tương tác vật lý chân thực, mang lại trải nghiệm chơi game sống động.
  • Dijkstra: Thuật toán này giúp tìm đường ngắn nhất trong đồ thị có trọng số. Nó được sử dụng để điều hướng nhân vật hoặc quản lý tài nguyên trong game, giúp tối ưu hóa quá trình tìm đường.
  • Greedy Best-First Search: Đây là một thuật toán tìm đường nhanh, ưu tiên khám phá các nút có chi phí thấp nhất trước, giúp tăng tốc độ tìm kiếm đường đi.
  • Heap Priority Queue: Sử dụng để quản lý các hàng đợi ưu tiên trong game, giúp các thao tác xử lý thứ tự ưu tiên diễn ra hiệu quả, ví dụ như xử lý các sự kiện trong game theo mức độ quan trọng.
  • MinMax và NegaMax: Được áp dụng trong các trò chơi đối kháng, hai thuật toán này giúp tính toán nước đi tốt nhất. Kết hợp với thuật toán Alpha-Beta Pruning, nó giúp giảm thiểu không gian tìm kiếm, từ đó tối ưu hóa quá trình ra quyết định.

Tại sao code di chuyển phải nhân với Time.DeltaTime?

  • Đảm bảo không phụ thuộc vào tốc độ khung hình: Mỗi thiết bị có thể chạy trò chơi ở các tốc độ khung hình khác nhau (30, 60, hoặc 120 FPS). Nếu bạn di chuyển vật thể theo một khoảng cách cố định mỗi khung hình mà không tính đến thời gian giữa các khung hình, vật thể sẽ di chuyển nhanh hơn trên các thiết bị có FPS cao. Điều này khiến trò chơi không nhất quán và không công bằng.
  • Chuyển động mượt mà hơn: Nhân với Time.deltaTime giúp điều chỉnh chuyển động dựa trên thời gian thực, làm cho vật thể di chuyển mượt mà hơn, bất kể khung hình thay đổi. Dù tốc độ khung hình có giảm, chuyển động của vật thể vẫn ổn định và không bị giật.
  • Tính nhất quán trên mọi thiết bị: Nhờ Time.deltaTime, trò chơi sẽ giữ được tốc độ di chuyển nhất quán trên mọi thiết bị, bất kể cấu hình mạnh hay yếu. Sự khác biệt duy nhất là tốc độ khung hình, còn tốc độ di chuyển của vật thể sẽ không bị ảnh hưởng.

Nêu một số kinh nghiệm làm việc với shader của bạn

Đây là câu hỏi phỏng vấn Unity thiên về kinh nghiệm làm việc cá nhân. Tuy nhiên, để trả lời được câu hỏi này, các developer cần hiểu shader là gì, từ đó rút ra cách áp dụng shader trong lập trình game. 

Shader là các tập lệnh nhỏ chứa các phép tính toán học và thuật toán nhằm tính toán màu sắc của từng pixel dựa trên ánh sáng và cấu hình vật liệu. Khi làm việc với Shader Graph trong Unity, trước tiên bạn cần tạo một Shader Graph Asset, xuất hiện dưới dạng shader thông thường. Để tạo, bạn vào menu tạo trong cửa sổ Project, chọn “Shader” và sau đó có thể tạo một Shader Graph Asset loại PBR hoặc Unlit.

Có nhiều cách khác nhau để viết shader, trong đó phổ biến nhất là sử dụng HLSL để viết shader đỉnh (vertex shader) và shader mảnh (fragment shader). Unity cũng hỗ trợ các shader kiểu cũ sử dụng lệnh ShaderLab cho mục đích tương thích ngược.

Để áp dụng shader cho tất cả các đối tượng trong cảnh, đặc biệt khi các đối tượng đã có sẵn vật liệu và kết cấu riêng, shader của bạn cần truy cập vào các thuộc tính của từng vật liệu đó (bao gồm các kênh kết cấu, pháp tuyến, tọa độ vị trí trong thế giới, v.v.). Thay vì thay thế từng vật liệu thủ công, bạn có thể tạo một Material mới, chọn shader mong muốn từ Shader Graph, và sau đó áp dụng Material đó cho các đối tượng trong cảnh.

Một số mẫu thiết kế lập trình (design pattern) mà bạn biết hoặc sử dụng là gì?

Mẫu thiết kế lập trình (Design Pattern) là các giải pháp đã được thử nghiệm, giúp giải quyết những vấn đề phổ biến mà lập trình viên thường gặp trong quá trình phát triển phần mềm. Thay vì là một đoạn mã cụ thể, chúng là cách tiếp cận chung để giải quyết một vấn đề.

Dưới đây là một số mẫu thiết kế lập trình phổ biến:

  • Observer (Mẫu Quan Sát): Mẫu này giúp quản lý việc cập nhật giữa các đối tượng mà không cần kết nối chúng chặt chẽ. Ví dụ, khi một đối tượng thay đổi, các đối tượng khác theo dõi sẽ tự động nhận thông tin thay đổi. Điều này tránh tình trạng một lớp chứa quá nhiều logic (God Class).
  • State Machine (Máy Trạng Thái): Mẫu này giúp quản lý các trạng thái khác nhau của một đối tượng, chẳng hạn như các màn hình trong menu, hành vi AI hoặc trạng thái kỹ năng trong trò chơi. Nó giúp chuyển đổi trạng thái dễ dàng và rõ ràng.
  • Command (Mẫu Lệnh): Mẫu này đóng gói các hành động thành đối tượng, cho phép bạn kiểm soát thời điểm và cách thức hành động được thực thi. Điều này hữu ích cho việc điều khiển chuyển động hoặc thao tác phức tạp trong trò chơi.
  • Entity-Component (Thực Thể-Thành Phần): Thường được dùng trong phát triển game, mẫu này chia thế giới thành các thực thể (entities), và mỗi thực thể được tạo từ các thành phần (components). Mỗi thành phần đại diện cho một chức năng hoặc thuộc tính, như hình dạng va chạm hoặc hành vi AI.
  • Singleton (Đơn): Mẫu này đảm bảo rằng một lớp chỉ có duy nhất một thể hiện trong suốt vòng đời của ứng dụng. Thường dùng để quản lý các tài nguyên dùng chung hoặc các công việc điều phối.

Các mẫu thiết kế khác:

  • Prototype (Nguyên mẫu): Dùng để tạo bản sao của các đối tượng một cách linh hoạt.
  • Adapter (Bộ chuyển đổi): Giúp chuyển đổi giữa các giao diện khác nhau, làm cho các lớp không tương thích có thể làm việc cùng nhau.
  • Chain of Responsibility (Chuỗi Trách Nhiệm): Cho phép một chuỗi các đối tượng xử lý yêu cầu, thường được dùng trong việc ghi nhật ký hoặc xử lý yêu cầu một cách tuần tự.
  • Factory (Nhà Máy): Tạo ra đối tượng mà không cần chỉ định lớp cụ thể, hữu ích trong việc quản lý các loại đối tượng khác nhau.
  • Proxy (Ủy quyền): Đại diện cho một đối tượng khác để kiểm soát hoặc tối ưu hóa việc truy cập.
  • Filter (Bộ Lọc): Dùng để lọc dữ liệu hoặc yêu cầu dựa trên tiêu chí nhất định.

Bạn làm việc với các API như thế nào?

Để làm việc với API, bạn có thể tuân theo các bước sau:

  • Hiểu mục tiêu và chiến lược API: Trước hết, bạn cần xác định rõ API được sử dụng để làm gì. Mục tiêu có thể là tăng doanh thu, cải thiện hiệu suất hệ thống, hoặc tận dụng dữ liệu có sẵn để tạo ra giá trị mới.
  • Xác định nguồn dữ liệu và cấu trúc API: Mô hình hóa dữ liệu là bước quan trọng để hiểu các loại dữ liệu mà API sẽ tương tác. Bạn cũng cần hiểu các phương thức mà API hỗ trợ (như GET, POST), các tham số cần truyền, và định dạng dữ liệu phản hồi.
  • Chọn giải pháp API phù hợp với hạ tầng: Đảm bảo rằng API bạn chọn có thể tích hợp tốt với hệ thống phần mềm và hạ tầng mạng hiện có. Trong một số trường hợp, bạn có thể cần tư vấn từ chuyên gia tích hợp để đảm bảo API hoạt động trơn tru.
  • Xác định yêu cầu của API: Làm rõ các yêu cầu cụ thể mà API cần đáp ứng, từ đó đảm bảo rằng API hoạt động theo đúng chiến lược và mục tiêu của dự án.
  • Chọn kiến trúc API phù hợp: REST API là loại thường được dùng nhất do sự linh hoạt, hiệu quả và dễ sử dụng. Nó có thể xử lý nhiều định dạng dữ liệu như JSON, XML, và phù hợp với nhiều trường hợp sử dụng khác nhau.
  • Chọn phương thức xác thực API: Bảo mật là yếu tố quan trọng khi làm việc với API. Bạn có thể sử dụng khóa API (API key) hoặc OAuth 2.0 để đảm bảo rằng chỉ các ứng dụng được cấp phép mới có thể truy cập.
  • Phát triển tài liệu API: Đảm bảo rằng tài liệu API được viết rõ ràng và cập nhật liên tục. Tài liệu này sẽ hướng dẫn cách thức sử dụng API, giúp các lập trình viên dễ dàng tích hợp.
  • Quản lý phiên bản API: Khi API được nâng cấp hoặc thay đổi, bạn cần thông báo về các phiên bản mới và cập nhật tài liệu cho phù hợp để người dùng dễ dàng chuyển đổi.
  • Triển khai API: Sử dụng các công cụ và quy trình tự động hóa để triển khai API, giúp giảm thiểu lỗi và đẩy nhanh thời gian phát triển.
  • Giám sát API: Đầu tư vào công cụ giám sát để đảm bảo API luôn hoạt động ổn định, phát hiện lỗi và theo dõi hiệu suất liên tục nhằm tối ưu hóa trải nghiệm người dùng.

Bạn sử dụng những phương pháp nào để tối ưu hóa hiệu suất trò chơi?

  • Object Pooling: Tái sử dụng các game object không hoạt động để giảm thiểu bộ nhớ và tổng số game object trong scene.
  • Nén ảnh: Sử dụng các chuẩn nén ASTC, ETC, DXT.
  • Giảm draw calls: Đóng gói sprite thuộc cùng 1 scene để giảm thiểu số lần vẽ trong 1 frame.
  • Không gọi vòng lặp trong Update, không GetComponent trong Update.
  • Sử dụng kỹ thuật LOD (Level of details) để tối ưu bộ nhớ cho việc hiển thị model 3D.
  • Cấu hình sẵn ánh sáng của game để giảm nhu cầu render ánh sáng.

Bạn đã ứng dụng vật lý trong môi trường trò chơi như thế nào?

Câu hỏi phỏng vấn Unity này cũng nên được trả lời dựa trên kinh nghiệm cá nhân đúc kết từ những dự án bạn đã làm trước. Tuy nhiên, đây là câu trả lời tham khảo bạn có thể học hỏi:

  • Áp dụng các định luật vật lý: Các định luật như trọng lực được tích hợp vào trò chơi. Ví dụ, khi một vật thể rơi, tôi sử dụng công thức dựa trên khối lượng và lực hấp dẫn để xác định cách nó rơi và va chạm với các vật khác.
  • Tương tác và phản ứng: Mỗi hành động của người chơi trong trò chơi sẽ tạo ra phản ứng dựa trên các công thức vật lý. Chẳng hạn, khi một hộp rơi từ tòa nhà, trọng lượng và lực hấp dẫn sẽ quyết định tốc độ rơi và va chạm với mặt đất.
  • Tác động từ hành động: Ngoài ra, các yếu tố như gió hoặc lực tác động từ người chơi cũng có thể ảnh hưởng đến cách vật thể di chuyển. Những yếu tố này được tính toán dựa trên các công thức vật lý, tạo nên trải nghiệm tương tác chân thực.
  • Đánh giá chất lượng của công cụ vật lý: Một công cụ vật lý tốt là khi các vật thể trong trò chơi phản ứng chính xác và tự nhiên với các lực tác động, giúp người chơi cảm nhận thế giới ảo một cách đáng tin cậy và thuyết phục.

Sau khi hoàn thành xong các câu hỏi phỏng vấn Unity thuộc chủ đề lập trình game kể trên, bạn có thể tham khảo tiếp Câu hỏi phỏng vấn Game Developer để tăng thêm sự tự tin cho buổi phỏng vấn sắp tới.

Câu hỏi phỏng vấn Unity cơ bản

Unity 3D là gì?

Unity 3D là một công cụ phát triển trò chơi (game engine) mạnh mẽ, giúp các nhà phát triển tạo ra trò chơi trên nhiều nền tảng, bao gồm máy tính, di động, console và thực tế ảo (VR). Được phát triển bởi Unity Technologies, Unity cung cấp một môi trường phát triển tích hợp (IDE) cùng nhiều tính năng hỗ trợ mạnh mẽ.

Tính năng nổi bật của Unity:

  • Đa nền tảng: Hỗ trợ triển khai trên nhiều nền tảng khác nhau, từ iOS, Android đến PC, Xbox và PlayStation.
  • Giao diện thân thiện: Môi trường kéo-thả (drag-and-drop) trực quan, giúp dễ dàng phát triển và chỉnh sửa.
  • Hỗ trợ 2D và 3D: Phát triển game 2D, 3D với nhiều công cụ tích hợp sẵn, từ tạo hình đến xử lý vật lý.
  • Asset Store: Thư viện tài nguyên phong phú, nơi người dùng có thể tìm kiếm và mua các công cụ, tài sản sẵn có để đẩy nhanh quá trình phát triển.
  • Cộng đồng và tài liệu hỗ trợ: Unity có một cộng đồng phát triển lớn và nguồn tài liệu hướng dẫn phong phú, hỗ trợ việc học hỏi và giải quyết vấn đề.
  • C# scripting: Sử dụng ngôn ngữ C# để lập trình, giúp xây dựng logic phức tạp và tạo ra các tính năng tùy chỉnh cho trò chơi.

Nêu tên một số component Unity 3D

  • Image: Thường được sử dụng để hiển thị các hình ảnh trên giao diện người dùng (như nút bấm, nền, hoặc bất kỳ yếu tố UI nào), và thường được gắn vào một đối tượng GameObject loại Canvas để thay đổi kích thước, màu sắc, hoặc điều chỉnh theo ý muốn.
  • Sprite Renderer: Hiển thị hình ảnh 2D (Sprite) trong môi trường 3D hoặc 2D của Unity. để đặt và quản lý các hình ảnh của nhân vật, đồ vật, hoặc bất kỳ yếu tố nào trong game, và cho phép tùy chỉnh Color, Flip, hoặc Sorting Layer.
  • Animator: Tạo và quản lý các hoạt cảnh cho đối tượng, kiểm soát các trạng thái hoạt động (như đi, chạy, nhảy) thông qua Animator Controller.
  • Rigidbody: Thêm tính chất vật lý (lực, trọng lực, và va chạm) vào đối tượng trong Unity. Khi một đối tượng có Rigidbody, nó sẽ được Unity tính toán các chuyển động vật lý như rơi, trượt, và tương tác với các vật khác một cách tự nhiên.
  • Collider: Mang khả năng va chạm cho đối tượng trong game. Có nhiều loại collider khác nhau, như Box Collider, Sphere Collider, và Mesh Collider, tùy vào hình dạng của đối tượng. 
  • Particle System: Tạo ra các hiệu ứng hình ảnh như lửa, khói, pháo hoa, hoặc bụi. Particle System giúp bạn kiểm soát các yếu tố như tốc độ, hướng, kích thước, và số lượng hạt, cho phép bạn tạo các hiệu ứng bắt mắt và sinh động cho trò chơi.
  • Audio Source: Dùng để phát âm thanh trong game, như nhạc nền, tiếng động của môi trường, hoặc hiệu ứng âm thanh của nhân vật. Audio Source cho phép bạn kiểm soát các thuộc tính của âm thanh như âm lượng, độ trễ, hoặc lựa chọn phát lặp lại.
  • Transform: Xác định vị trí, xoay và kích thước của đối tượng trong không gian 3D.

Sự khác biệt chính giữa UE4 và Unity 3D là gì?

Unreal Engine 4 và Unity 3D là hai công cụ lập trình game phổ biến nhất hiện nay. Tuy nhiên, mỗi công cụ đều có những tính chất khác nhau mà bạn cần chú ý để lựa chọn cho phù hợp, bao gồm:

Ngôn ngữ lập trình:

  • Unreal Engine sử dụng ngôn ngữ C++, là một ngôn ngữ mạnh mẽ và phức tạp hơn, phù hợp cho các dự án lớn và đòi hỏi nhiều tính năng cao cấp.
  • Unity 3D sử dụng ngôn ngữ C#, dễ học hơn và thường được coi là lựa chọn tốt hơn cho người mới bắt đầu thiết kế game.

Chất lượng đồ họa:

  • Unreal Engine nổi bật với khả năng cung cấp đồ họa chất lượng cao, chân thực, nên thường được sử dụng cho các dự án yêu cầu độ chi tiết và chân thực cao, như kết xuất 3D thời gian thực.
  • Unity cũng có đồ họa tốt nhưng không đạt tới mức độ chi tiết như Unreal, và thường phù hợp hơn cho các dự án game nhỏ hoặc tầm trung.

Chi phí:

  • Unreal Engine hoàn toàn miễn phí, nhưng bạn sẽ phải trả tiền bản quyền cho các dự án thương mại.
  • Unity có mô hình freemium, nghĩa là bạn có thể sử dụng phiên bản miễn phí, nhưng có thêm các gói trả phí cho các tính năng cao cấp.

Kho tài sản:

  • Unity có kho tài sản lớn hơn, cung cấp nhiều lựa chọn về các tài nguyên 3D, âm thanh, và các công cụ phát triển mà người dùng có thể mua hoặc tải về để tích hợp vào dự án.
  • Unreal Engine tuy có kho tài sản nhưng không phong phú bằng Unity.

Tóm lại, Unreal Engine phù hợp cho các dự án yêu cầu đồ họa cao cấp và tính năng mạnh mẽ, trong khi Unity dễ tiếp cận hơn và phù hợp với người mới bắt đầu cũng như các dự án quy mô nhỏ hơn.

Những lợi thế chính của Unity 3D là gì?

Có rất nhiều lý do để Unity 3D trở thành một trong những công cụ lập trình game lớn nhất hiện nay, trong đó bao gồm một số lợi ích mà nền tảng này mang lại cho các trò chơi điện tử:

  • Khả năng tương thích đa nền tảng: Unity cho phép nhà phát triển tạo ra trò chơi có thể chạy trên nhiều nền tảng khác nhau như PC, máy chơi game, thiết bị di động, và thậm chí cả các thiết bị thực tế tăng cường (AR) và thực tế ảo (VR). Điều này giúp tiết kiệm thời gian và công sức khi không cần phát triển riêng cho từng nền tảng, đồng thời mở rộng khả năng tiếp cận người chơi.
  • Môi trường phát triển trực quan và mạnh mẽ: Unity cung cấp một giao diện phát triển dễ dùng, phù hợp cả với người mới bắt đầu lẫn các nhà phát triển có kinh nghiệm. Với trình chỉnh sửa trực quan, bạn có thể thấy kết quả các thay đổi ngay lập tức, giúp tối ưu hóa quá trình phát triển.
  • Kho tài sản phong phú: Unity có một kho tài sản mở rộng chứa nhiều mô hình 3D, hiệu ứng âm thanh, hoạt ảnh và các công cụ do cộng đồng đóng góp. Điều này giúp tiết kiệm thời gian phát triển và nâng cao chất lượng trò chơi.
  • Khả năng tối ưu hóa hiệu suất tốt: Unity tích hợp nhiều công cụ giúp lập hồ sơ và tối ưu hóa, đảm bảo trò chơi hoạt động mượt mà trên nhiều thiết bị và nền tảng khác nhau.
  • Cộng đồng hỗ trợ mạnh mẽ: Với lượng người dùng đông đảo, cộng đồng Unity chia sẻ nhiều kiến thức, hướng dẫn và giải pháp, giúp các nhà phát triển dễ dàng học hỏi, khắc phục lỗi và phát triển trò chơi hơn.

Một số nhược điểm của Unity 3D là gì?

Tất nhiên, không công cụ nào hoàn hảo. Dù Unity 3D là một công cụ được ưa thích với nhiều lợi ích, nó cũng có những mặt trái mà các developer nên biết: 

  • Không hỗ trợ liên kết thư viện mã bên ngoài: Unity không hỗ trợ tích hợp trực tiếp các thư viện mã bên ngoài. Điều này buộc các nhà phát triển phải thêm các thư viện này theo cách thủ công, có thể tốn thời gian và phức tạp, đặc biệt đối với những dự án lớn hoặc nhiều dự án khác nhau.
  • Chi phí giấy phép cao: Mặc dù Unity có phiên bản miễn phí, nhưng các tính năng nâng cao và hỗ trợ chuyên nghiệp yêu cầu mua giấy phép có thể khá đắt đối với các công ty mới. Chi phí thêm cho các tính năng bổ sung như hỗ trợ khuôn mẫu hoặc giá đỡ có thể làm tăng tổng chi phí phát triển.
  • Tiêu thụ bộ nhớ cao: Trò chơi phát triển bằng Unity thường tiêu tốn nhiều bộ nhớ hơn so với một số công cụ khác. Điều này có thể gây ra lỗi hết bộ nhớ (OOM) trên các thiết bị có cấu hình thấp, đặc biệt là thiết bị di động.
  • Điều hướng mã nguồn chưa tối ưu: Tính năng tìm kiếm và điều hướng mã của Unity có thể không mạnh mẽ như mong đợi. Đôi khi, nhà phát triển phải tìm thủ công qua các thành phần liên quan, điều này có thể làm chậm tiến độ và tốn thời gian trong quá trình phát triển.

Fixed Timestep trong Unity 3D là gì?

Trong Unity 3D, “Fixed Timestep” là một khoảng thời gian cố định mà trong đó các phép tính vật lý và các sự kiện FixedUpdate() được thực hiện. Điều này đảm bảo rằng mô phỏng vật lý trong trò chơi diễn ra đồng đều và không bị ảnh hưởng bởi tốc độ khung hình của thiết bị.

Ví dụ, nếu ta đặt Fixed Timestep là 0.004 giây (tức là 4 mili giây), thì mỗi khi có một bước vật lý, nó sẽ xử lý một khoảng thời gian thực tế là 4 mili giây.

Xét một khung hình mất 10 mili giây để xử lý:

  • Trong khung hình đầu tiên, chương trình sẽ thực hiện hai bước vật lý, mỗi bước mất 4 mili giây, tổng cộng 8 mili giây.
  • 2 mili giây còn lại sẽ không đủ để bắt đầu bước vật lý thứ ba và sẽ được tích lũy cho khung hình tiếp theo.

Trong những khung hình sau, Unity sẽ cố gắng bắt kịp bằng cách thêm các bước vật lý để xử lý thời gian tích lũy này, đảm bảo rằng mô phỏng vật lý luôn đồng bộ với thời gian thực. 

Ví dụ, nếu có 2 mili giây được mang sang từ khung hình trước, và khung hình hiện tại cũng mất 10 mili giây, Unity sẽ xử lý ba bước vật lý (12 mili giây) và tích lũy 2 mili giây cho khung tiếp theo.

Tóm lại, Fixed Timestep trong Unity giúp đảm bảo mô phỏng vật lý xảy ra một cách ổn định và dự đoán được, bất kể biến động trong tốc độ xử lý của từng khung hình.

Bạn thực hiện việc kiểm tra va chạm trong Unity như thế nào? 

Trong Unity, kiểm tra va chạm là một phần quan trọng trong tương tác vật lý. Tuỳ vào tính chất của mỗi dựa án mà các phương pháp sử dụng cũng khác nhau. Dưới đây là các hình thức phổ biến để kiểm tra va chạm:

OnCollisionEnter / OnCollisionStay / OnCollisionExit: Dùng cho đối tượng có Collider và Rigidbody, bao gồm

  • OnCollisionEnter: Kích hoạt khi hai đối tượng bắt đầu va chạm.
  • OnCollisionStay: Kích hoạt khi va chạm liên tục diễn ra.
  • OnCollisionExit: Kích hoạt khi va chạm kết thúc.

Lưu ý: Ít nhất một trong hai vật thể phải chứa component Rigidbody.

Phương pháp chồng lấp (Overlap):

  • Sử dụng để kiểm tra va chạm trong một khu vực nhất định như hình cầu (OverlapSphere) hoặc viên nang (OverlapCapsule).
  • Phù hợp để phát hiện các đối tượng gần nhau mà không cần va chạm trực tiếp.

Raycasting: Sử dụng để bắn một tia từ một điểm xác định và phát hiện đối tượng bị tia này cắt ngang. Ví dụ: Physics.Raycast dùng để kiểm tra xem có vật cản trước camera hay không.

Rigidbody SweepTestAll: Dùng để kiểm tra các đối tượng va chạm dọc theo hướng di chuyển của một vật thể, giúp dự đoán va chạm tiềm năng.

Việc sử dụng các tính năng nâng cao này để kiểm tra sẽ đảm bảo tính toàn vẹn của trò chơi. Tuy vậy, trong Unity, khi kiểm tra va chạm, bạn cũng nên cân nhắc những yếu tố như chọn Collider phù hợp, hiệu suất, va chạm môi trường và địa hình để tránh lỗi cho những lần tiếp theo.

AssetBundle có vai trò gì trong Unity 3D?

AssetBundle trong Unity 3D đóng vai trò như một kho lưu trữ nội dung độc lập, được tải khi ứng dụng hoạt động, thay vì phải tích hợp sẵn vào ứng dụng chính. Điều này giúp tối ưu hóa hiệu suất bằng cách giảm bớt tài nguyên mạng và hệ thống cần thiết, cho phép người dùng chỉ tải và cài đặt phần nội dung cần thiết.

AssetBundle rất hữu ích trong việc quản lý nội dung có thể cập nhật hoặc thêm vào sau khi phát hành, như các mô hình liên quan đến sự kiện đặc biệt hoặc nội dung tải về. Ví dụ, một ứng dụng thực tế ảo cho phép người dùng trải nghiệm lái xe có thể không bao gồm tất cả các mẫu xe ngay từ đầu. Thay vào đó, người dùng chỉ tải mô hình xe mà họ muốn trải nghiệm, giảm dung lượng tải về và lưu trữ.

Ngoài ra, AssetBundle cũng hỗ trợ cập nhật tự động, cho phép tái sử dụng nội dung giữa các dự án, và thậm chí cập nhật các tài nguyên như logo hoặc video mà không cần cập nhật toàn bộ ứng dụng. Điều này có thể được thực hiện thông qua việc lưu trữ các AssetBundle trên một máy chủ từ xa và cho phép ứng dụng truy cập và cập nhật từ đó.

AssetBundle có thể được phân loại thành các biến thể, giúp quản lý các phiên bản tài nguyên khác nhau dành cho các nền tảng hoặc cấu hình cụ thể mà không cần tạo nhiều phiên bản của cùng một asset bundle. Ví dụ, bạn có thể có biến thể cho các tài sản được tối ưu hóa cho Windows và một biến thể khác cho macOS. Điều này cho phép tùy biến và quản lý hiệu quả các tài nguyên phù hợp với từng nền tảng cụ thể.

Đọc thêm về AssetBundle của Unity tại đây.

Bạn di chuyển vật thể trong Unity như thế nào?

  • Sử dụng hàm MoveTowards

Hàm này di chuyển một đối tượng từ vị trí hiện tại đến vị trí mục tiêu một cách mượt mà. Bạn cập nhật vị trí của đối tượng mỗi khung hình bằng vị trí mới được tính bởi MoveTowards. Tham số maxDistanceDelta kiểm soát tốc độ di chuyển. 

Nếu đối tượng ở gần mục tiêu hơn khoảng cách maxDistanceDelta, nó sẽ dừng tại mục tiêu mà không vượt qua. Để đảm bảo tốc độ không bị ảnh hưởng bởi tốc độ khung hình, hãy nhân maxDistanceDelta với Time.deltaTime (hoặc Time.fixedDeltaTime trong vòng lặp FixedUpdate).

  • Đặt trực tiếp vị trí đối tượng

Cách đơn giản nhất để di chuyển một đối tượng là đặt trực tiếp thuộc tính position của thành phần Transform của đối tượng. Cách này sẽ lập tức đưa đối tượng đến vị trí mới.

Ví dụ:

// Di chuyển đối tượng đến một vị trí cụ thể

transform.position = new Vector3(10, 0, 5);
  • Thêm một lượng di chuyển vào vị trí hiện tại: Bạn cũng có thể cộng thêm một giá trị vào vị trí hiện tại của đối tượng để di chuyển nó theo một hướng cụ thể.

Ví dụ:

// Di chuyển đối tượng lên trên 2 đơn vị

transform.position += new Vector3(0, 2, 0);
  • Tác động lực bằng AddForce.

Bộ dọn rác (GC – Garbage Collector) trong engine hoạt động như thế nào?

Bộ dọn rác (Garbage Collector – GC) là một tính năng tự động giúp quản lý bộ nhớ trong các ngôn ngữ lập trình như C# và Java. Nó chịu trách nhiệm giải phóng bộ nhớ của những đối tượng không còn được sử dụng, giúp ngăn chặn lãng phí bộ nhớ và đảm bảo chương trình vận hành hiệu quả.

Ví dụ, trong Unity, GC hoạt động như sau:

  • Khi bạn tạo đối tượng mới, bộ nhớ được cấp phát trên heap. Nếu heap không đủ chỗ trống để cấp phát thêm, Unity sẽ kích hoạt GC.
  • GC sẽ kiểm tra tất cả các đối tượng hiện có và xác định những đối tượng nào không còn được tham chiếu, tức là không còn được chương trình sử dụng.
  • Những đối tượng không còn tham chiếu sẽ được đánh dấu là không cần thiết và bộ nhớ của chúng sẽ được thu hồi để sử dụng cho các đối tượng khác trong tương lai.

Quá trình này giúp quản lý bộ nhớ một cách hiệu quả mà không cần lập trình viên phải tự tay xóa đối tượng không còn sử dụng, từ đó đảm bảo ứng dụng chạy mượt mà hơn.

Trong các engine như Unity, GC thường chạy khi bộ nhớ bị cạn kiệt, và nó sẽ lặp lại quá trình thu hồi cho đến khi có đủ bộ nhớ trống cho các phân bổ tiếp theo. Quá trình này là cần thiết để tối ưu hóa hiệu suất và tránh các vấn đề như tràn bộ nhớ (memory leaks).

Thứ tự chạy của các hàm mặc định trong công cụ mà bạn sử dụng là gì?

  • Awake(): Hàm này được gọi ngay khi một script được tải. Đây là nơi bạn khởi tạo biến và thiết lập trạng thái ban đầu cho đối tượng.
  • OnEnable(): Chạy ngay sau Awake() và mỗi khi đối tượng được kích hoạt trở lại.
  • Start(): Chạy trước khi frame đầu tiên bắt đầu, sau khi tất cả các đối tượng đã được khởi tạo.
  • Update(): Chạy mỗi frame. Đây là nơi bạn thực hiện các lệnh cần cập nhật liên tục như di chuyển hoặc thay đổi trạng thái.
  • FixedUpdate(): Chạy với tần suất cố định, thường dùng cho các tính toán liên quan đến vật lý.
  • LateUpdate(): Chạy sau khi tất cả các hàm Update() đã hoàn thành. Dùng để xử lý những lệnh cần chạy sau các đối tượng khác.
  • OnDisable(): Được gọi khi đối tượng bị vô hiệu hóa.
  • OnDestroy(): Được gọi khi đối tượng bị hủy.

Tổng kết câu hỏi phỏng vấn Unity

Trên đây là danh sách câu hỏi phỏng vấn Unity thông dụng nhất mà bạn có thể tham khảo. Tuy nhiên, để cho cuộc phỏng vấn được diễn ra mượt mà nhất, bạn nên dành nhiều thời gian nghiên cứu và tìm hiểu thêm về Unity. Đây là một công cụ với rất nhiều tính năng từ cơ bản đến nâng cao và được cập nhập liên tục, để hiểu cũng như thành thạo sử dụng Unity không phải là một quá trình ngắn và dễ dàng. 

Nên nhớ, nhà tuyển dụng sẽ kiểm tra kiến thức của bạn về nhiều khía cạnh của Unity mà buộc bạn phải thành thạo để gây ấn tượng với họ, vì vậy, hãy dành thời gian học thêm về Unity bạn nhé.