Nội dung chính
Game Developer hiện đang là một ngành với nhu cầu tuyển dụng cao, đặc biệt là với sự phát triển của ngành công nghiệp trò chơi điện tử trong những năm gần đây. Nếu bạn là một lập trình viên được các doanh nghiệp tuyển chọn cho buổi phỏng vấn sắp tới, đừng bỏ qua danh sách 20+ câu hỏi phỏng vấn Game Developer mà có thể bạn sẽ gặp.
Đọc bài viết sâu để hiểu rõ những câu hỏi phỏng vấn Game Developer theo các mục:
- Câu hỏi phỏng vấn Game Developer cơ bản
- Câu hỏi phỏng vấn Game Developer nâng cao
Trước khi cùng ITviec “đào sâu” các câu hỏi phỏng vấn Game Developer phổ biến, hãy cùng “ôn tập” lại với các bài viết thuộc chủ đề lập trình game sau đây:
- Lập trình game nên học ngôn ngữ nào?
- Học lập trình game: Lộ trình và Tài liệu học lập trình game 2024
- Cách lập trình Game: Hướng dẫn lập trình 3 game đơn giản
- Lập trình game là gì: Tổng quan 7 giai đoạn lập trình game
Câu hỏi phỏng vấn Game Developer cơ bản
Bạn đã ứng dụng vật lý trong môi trường trò chơi như thế nào?
Ứng dụng vật lý trong trò chơi không chỉ tạo ra sự chân thực mà còn nâng cao trải nghiệm chơi game. Việc mô phỏng các hiện tượng vật lý phức tạp giúp người chơi cảm thấy như họ đang tương tác với một thế giới sống động và có tính liên kết chặt chẽ với thực tế. Để ứng dụng, bạn cần:
- Gán khối lượng và tính toán vận tốc:
Mỗi vật thể trong trò chơi có thể được gán khối lượng, cho phép chúng phản ứng theo cách tương tự như trong thực tế.
Các phép toán được thực hiện tương tự như trong vật lý thế giới thực, bao gồm tính toán vận tốc và các yếu tố khác.
- Áp dụng định luật vật lý:
Các định luật vật lý, chẳng hạn như định luật vạn vật hấp dẫn của Newton, được áp dụng trong môi trường ảo.
Để làm điều này, lập trình viên gán các giá trị cho các tham số trong công thức như khối lượng, khoảng cách, lực, và hằng số hấp dẫn.
- Tương tác và phản ứng:
Kết quả của các công thức này được liên kết trực tiếp với các đối tượng trong trò chơi. Mỗi hành động của người chơi sẽ tạo ra một phản ứng cụ thể trong môi trường trò chơi.
Ví dụ, khi một hộp rơi từ một tòa nhà, nó sẽ có giá trị trọng lượng gán sẵn. Lực hấp dẫn của hành tinh cũng được tính toán để xác định cách thức hộp rơi.
- Tác động của hành động:
Để hộp rơi, cần có một hành động tác động như một luồng không khí hoặc một lực đẩy. Những hành động này được lập trình và có các công thức riêng, hoạt động trong công cụ vật lý của trò chơi.
- Đánh giá chất lượng công cụ vật lý:
Một công cụ vật lý được coi là “tốt” nếu các phương trình và tương tác được triển khai chính xác. Nếu các đối tượng phản ứng theo cách người chơi mong đợi và đáng tin cậy, công cụ đó sẽ được đánh giá cao.
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 di chuyển vật thể trong game như thế nào?
Tuỳ vào công cụ lập trình mà cách bạn di chuyển vật thể trong game cũng sẽ khác nhau. Với Unity, bạn có thể sử dụng phương pháp MoveTowards như sau:
Bước 1 – Tính toán vị trí: Đầu tiên, xác định vị trí hiện tại của vật thể (current) và vị trí mục tiêu (target). Tính toán khoảng cách giữa hai điểm này mà không vượt quá một khoảng cách tối đa được chỉ định (maxDistanceDelta).
Bước 2- Sử dụng MoveTowards: Gọi hàm MoveTowards để di chuyển đối tượng từ vị trí hiện tại về phía vị trí mục tiêu. Hàm này sẽ tính toán vị trí mới của đối tượng dựa trên khoảng cách tối đa cho phép (maxDistanceDelta) mà bạn đã xác định.
Bước 3 – Cập nhật vị trí mỗi khung hình: Cập nhật vị trí của đối tượng mỗi khung hình bằng cách sử dụng giá trị trả về từ hàm MoveTowards. Nếu vị trí hiện tại đã gần vị trí mục tiêu hơn khoảng cách tối đa cho phép, hàm sẽ trả về vị trí mục tiêu.
Bước 4 – Đảm bảo di chuyển mượt mà: Để đảm bảo rằng tốc độ di chuyển của đối tượng không phụ thuộc vào tốc độ khung hình, bạn nên nhân giá trị maxDistanceDelta với Time.deltaTime (hoặc Time.fixedDeltaTime trong vòng lặp FixedUpdate).
Một cách đơn giản khác để di chuyển đối tượng trong Unity là thay đổi trực tiếp vị trí của nó. Bạn chỉ cần thiết lập thuộc tính position trong thành phần Transform của đối tượng để đưa nó đến một vị trí mới trong không gian 3D.
Ví dụ: // Di chuyển đối tượng đến vị trí (10, 0, 5)
transform.position = new Vector3(10, 0, 5);
Ngoài ra, bạn cũng có thể di chuyển đối tượng theo một hướng cụ thể bằng cách cộng thêm một vectơ vào vị trí hiện tại của nó.
Ví dụ: // Di chuyển đối tượng lên 2 đơn vị theo trục Y
transform.position += new Vector3(0, 2, 0);
Phương pháp này rất hữu ích khi bạn muốn di chuyển đối tượng một khoảng cách cụ thể.
Những mô hình nào được sử dụng để kiếm tiền trong kinh doanh trò chơi?
Câu hỏi này có thể được trả lời dựa trên mô hình kinh doanh của công ty về trò chơi mà các developer đã từng làm việc. Tuy vậy, đây là một số mô hình kiếm tiền phổ biến nhất khi kinh doanh trò chơi:
- Đăng ký (Hàng tháng/Hàng năm): Người chơi trả phí đăng ký để có quyền truy cập vào trò chơi, thường bắt đầu với một phiên bản miễn phí trước khi yêu cầu thanh toán để tiếp tục.
- Trò chơi Freemium: Trò chơi miễn phí với các hạn chế. Người chơi có thể mua phiên bản cao cấp để mở khóa tính năng hoặc cấp độ.
- Hộp trò chơi: Bán trò chơi dưới dạng hộp với một khoản phí cố định để sở hữu và chơi suốt đời.
- Quảng cáo trong trò chơi: Đặt quảng cáo trong trò chơi để kiếm doanh thu. Quảng cáo có thể là biểu ngữ, xen kẽ, hoặc video, thường xuất hiện dưới dạng phần thưởng trong trò chơi.
- Cửa hàng tài sản trò chơi: Bán các vật phẩm hoặc hình đại diện trong trò chơi. Nhiều trò chơi sử dụng tiền ảo có thể được đổi lấy tiền thật.
- Phân phối trò chơi: Kiếm tiền bằng cách phân phối trò chơi qua các cổng thông tin hoặc trang web, giúp tăng lưu lượng truy cập.
- Chơi để kiếm tiền (P2E): Mô hình mới kết hợp blockchain và NFT. Người chơi đầu tư tiền điện tử để nâng cấp và có thể giao dịch trong trò chơi. Đưa game lên các nền tảng phát hành (Apple Store, GG Play Store, Steam…)
Ngoài ra, bạn cũng nên tham khảo thêm về cách hoạt động một số plugin thứ 3 vì nó cũng phục vụ cho mục đích kinh doanh của trò chơi. Chẳng hạn như Unity Ads và AdMob để hiển thị quảng cáo trong trò chơi. Hay IAP (In-App Purchases) cho phép người chơi mua vật phẩm trực tiếp trong game và PlayFab để quản lý các dịch vụ trực tuyến. Những công cụ này giúp doanh nghiệp kiếm thêm thu nhập từ quảng cáo và mua sắm trong trò chơi.
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ó rất nhiều dạng cấu trúc dữ liệu được sử dụng trong lập trình game. Tuỳ vào mục đích và ngôn ngữ lập trình, game developer sẽ phải lựa chọn cho mình loại phù hợp nhất. Tuy nhiên, có rất nhiều dạng cấu trúc dữ liệu có thể sử dụng đa mục đích khá phổ biến như:
Dynamic Array (Mảng động):
Mảng động hay danh sách mảng là mảng có khả năng thay đổi kích thước. Khi vượt quá kích thước ban đầu, mảng sẽ tự động mở rộng và sao chép các phần tử sang một mảng mới lớn hơn. Cấu trúc này lưu trữ số lượng phần tử hiện có để theo dõi kích thước thực sự của mảng. Mảng động hữu ích khi cần lưu trữ danh sách có kích thước thay đổi.
Linked List (Danh sách liên kết):
Danh sách liên kết là cấu trúc dữ liệu trong đó mỗi phần tử trỏ đến phần tử tiếp theo trong danh sách. Phần tử cuối cùng không trỏ đến phần tử nào khác. Danh sách liên kết hữu ích khi cần thường xuyên thêm hoặc xóa phần tử, vì không cần phải di chuyển dữ liệu như trong mảng động.
Doubly Linked List (Danh sách liên kết kép):
Danh sách liên kết kép tương tự như danh sách liên kết, nhưng mỗi phần tử không chỉ trỏ đến phần tử tiếp theo mà còn trỏ đến phần tử trước đó. Điều này giúp việc tìm kiếm và xóa phần tử nhanh hơn, tuy nhiên sẽ tốn thêm bộ nhớ.
Hashtable (Bảng băm):
Bảng băm là cấu trúc dữ liệu ánh xạ các khóa với các giá trị. Đây là cách hiệu quả để tìm kiếm thông tin dựa trên khóa tùy ý (như ID người chơi). Ví dụ, bảng băm có thể lưu trữ thông tin người chơi với ID người chơi là khóa và thông tin chi tiết về người chơi là giá trị.
Đồ thị:
Đồ thị là cấu trúc dữ liệu mà các nút liên kết với nhau nếu có mối quan hệ. Đồ thị có thể có hướng (nút A trỏ đến nút B, nhưng không ngược lại) hoặc vô hướng (cả hai nút đều trỏ đến nhau). Đồ thị mạnh mẽ khi áp dụng trong game, chẳng hạn như tìm đường A* để AI có thể tìm ra cách đi từ điểm này đến điểm khác một cách tối ưu.
Dữ liệu trong game được lưu trữ như thế nào?
Dữ liệu game không được lưu trực tiếp trên máy chủ trò chơi mà được lưu trữ trên các máy tính chạy cơ sở dữ liệu như Microsoft SQL, MySQL, hoặc MongoDB. Cách lưu trữ này đảm bảo dữ liệu của bạn luôn sẵn sàng, bất kể cách bạn kết nối với trò chơi. Khi chơi, các máy chủ trò chơi sẽ kết nối với các máy tính cơ sở dữ liệu này để lưu trữ và truy xuất dữ liệu của bạn.
Tương tự, mỗi công cụ lập trình game như Unity hay Unreal Engine đều có hệ thống cơ sở dữ liệu riêng. Khi bạn liên kết các tài khoản trên các nền tảng khác nhau, bạn đang cung cấp thông tin để các nền tảng này có thể nhận diện và đồng bộ hóa tài khoản của bạn. Nhờ đó, dữ liệu giữa các nền tảng có thể được chia sẻ và duy trì nhất quán.
Ngoài ra, với đa số game offline, dữ liệu được lưu trữ local ngay tại thiết bị hoặc được tải về từ server sau khi cài đặt game và cũng được lưu trữ tại bộ nhớ local.
Một số thuật toán phổ biến được sử dụng trong lập trình game là gì?
- A*: A* (phát âm là “A star”) là một trong những thuật toán quan trọng nhất trong lập trình game, đặc biệt dùng để tìm đường đi giữa hai điểm trong một đồ thị hoặc trên bản đồ. Nếu trò chơi của bạn có các tác nhân cần điều hướng trong một thế giới ảo, bạn sẽ cần sử dụng hoặc biến thể của A* để tìm đường hiệu quả.
- Thuật toán Blind search: Tìm kiếm theo chiều rộng (BFS) và tìm kiếm theo chiều sâu (DFS) là các thuật toán tìm kiếm mù, kiểm tra mọi khả năng có thể xảy ra từ một điểm bắt đầu cho đến khi tìm thấy đích. Chúng có thời gian thực hiện O(V+E), trong đó V là số lượng đỉnh và E là số lượng cạnh của đồ thị.
- Thuật toán Flood Fill: Đây được sử dụng để xác định các vùng được kết nối trong một không gian, thường được áp dụng trong việc tạo địa hình và tô màu. Nó giúp tạo nội dung theo thủ tục và nâng cao chất lượng hình ảnh trong game.
- Thuật toán 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) là nền tảng cho việc phát hiện và xử lý va chạm giữa các thực thể trong game, đảm bảo sự tương tác vật lý chân thực và hấp dẫn.
- Thuật toán Dijkstra: Dijkstra là thuật toán cơ bản để tìm đường ngắn nhất giữa các điểm trong đồ thị có trọng số. Nó giúp tối ưu hóa điều hướng và quản lý tài nguyên trong trò chơi, mang lại trải nghiệm chơi game mượt mà hơn.
Và một số các thuật toán khác:
- Greedy Best-First Search: Một thuật toán tìm đường nhanh, ưu tiên những nút có chi phí thấp nhất.
- Heap Priority Queue: Sử dụng để quản lý các hàng đợi ưu tiên trong game, giúp thực hiện các thao tác ưu tiên một cách hiệu quả.
- MinMax và NegaMax: Được sử dụng trong các trò chơi đối kháng để tìm nước đi tốt nhất, thường đi kèm với thuật toán Alpha-Beta Pruning để cắt giảm không gian tìm kiếm, tối ưu hóa quá trình ra quyết định.
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ì?
Dưới đây sẽ là câu trả lời cho 2 công cụ lập trình game thông dụng nhất:
Unity:
- Awake(): Hàm này được gọi khi script instance được tải. Đây là nơi bạn thường khởi tạo các biến hoặc thiết lập trạng thái ban đầu của đối tượng.
- OnEnable(): Được gọi ngay sau Awake() và mỗi khi đối tượng trở lại trạng thái kích hoạt (enabled).
- Start(): Được gọi 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(): Được gọi một lần trong mỗi frame. Đây là nơi bạn viết các lệnh liên tục thay đổi theo thời gian như di chuyển nhân vật.
- FixedUpdate(): Được gọi với tần suất cố định, thường dùng cho các tính toán vật lý.
- LateUpdate(): Được gọi mỗi frame sau khi tất cả các hàm Update() đã chạy. Dùng để xử lý logic cần được thực hiện sau khi các đối tượng khác đã cập nhật.
- OnDisable(): Được gọi khi đối tượng bị vô hiệu hóa (disabled).
- OnDestroy(): Được gọi khi đối tượng bị hủy (destroyed).
Unreal Engine:
- Constructor: Được gọi khi đối tượng được khởi tạo, trước khi bất kỳ thuộc tính nào được gán giá trị.
- PreInitializeComponents(): Được gọi trước khi các thành phần của Actor được khởi tạo.
- PostInitializeComponents(): Được gọi sau khi tất cả các thành phần của Actor đã được khởi tạo.
- BeginPlay(): Được gọi khi trò chơi bắt đầu hoặc khi đối tượng được kích hoạt lần đầu tiên.
- Tick(float DeltaTime): Được gọi mỗi frame, tương tự như Update() trong Unity. Đây là nơi xử lý các lệnh cần thực thi liên tục.
- EndPlay(const EEndPlayReason::Type EndPlayReason): Được gọi khi trò chơi kết thúc hoặc khi đối tượng bị hủy.
- Destructor: Được gọi khi đối tượng bị phá hủy, sau khi EndPlay() đã chạy.
Câu hỏi phỏng vấn Game Developer nâng cao
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ì?
Design Pattern (Mẫu Thiết Kế) là những giải pháp tái sử dụng được thiết kế để giải quyết các vấn đề phổ biến mà các lập trình viên thường gặp phải trong quá trình thiết kế và phát triển phần mềm. Lưu ý, đây không phải là một giải pháp cụ thể hay một đoạn mã có thể áp dụng trực tiếp, mà là một dạng mô tả, một khuôn mẫu về cách tiếp cận để giải quyết một vấn đề nào đó.
Một số mẫu thiết kế lập trình phổ biến mà các developer có thể trả lời khi được hỏi phỏng vấn:
- Observer (Mẫu Quan Sát): Đây là một mẫu thiết kế cơ bản nhưng rất hữu ích, giúp tránh hiện tượng “God Class” – khi tất cả logic đều tập trung trong một lớp duy nhất.
- State Machine (Máy Trạng Thái): Đây là một trong những mẫu thiết kế quan trọng nhất. Một trạng thái có thể biểu thị một màn hình trong menu, hành vi của AI, hoặc trạng thái của kỹ năng.
- Command (Mẫu Lệnh): Mẫu Lệnh giúp đóng gói các hành động và kiểm soát thời điểm thực thi chúng. Điều này đặc biệt hữu ích trong việc sắp xếp các chuyển động của nhân vật trong trò chơi, chẳng hạn như quay lại hoặc tiếp tục di chuyển đến một điểm khác.
- Entity-Component (Thực Thể-Thành Phần): Đây là một mô hình phổ biến trong các công cụ trò chơi. Thế giới được cấu trúc thành một mảng (hoặc cây) thực thể, mỗi thực thể lại bao gồm một tập hợp các thành phần. Mỗi thành phần biểu thị một chức năng hoặc thuộc tính, chẳng hạn như hình dạng va chạm, kịch bản điều khiển hành động, hoặc dữ liệu mô tả thuộc tính.
- Singleton (Đơn): Là một mẫu thiết kế đảm bảo rằng một lớp chỉ có duy nhất một thể hiện trong suốt quá trình hoạt động của ứng dụng. Mẫu này khá tiện vì chỉ cần một đối tượng duy nhất để quản lý các nguồn tài nguyên hoặc điều phối các hành động.
Các mẫu thiết kế khác:
- Prototype (Nguyên mẫu): Dùng để nhân bản đối tượng.
- Adapter (Bộ chuyển đổi): Chuyển đổi giữa các giao diện khác nhau (ví dụ: asList, toString).
- Chain Of Responsibility (Chuỗi Trách Nhiệm): Dùng để ghi nhật ký hoặc xử lý yêu cầu.
- Factory (Nhà Máy): Dùng để tạo ra các đối tượng hoặc ánh xạ hành động.
- Proxy (Ủy quyền): Đại diện cho một đối tượng khác để kiểm soát truy cập.
- Filter (Bộ Lọc): Sử dụng tiêu chí để lọc dữ liệu hoặc yêu cầu.
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.
Dưới đây là giải thích ngắn gọn và dễ hiểu về từng nguyên tắc đã được sắp xếp lại để tạo thành từ viết tắt SOLID, bạn nên tham khảo khi trả lời câu hỏi phỏng vấn Game Developer:
- Single Responsibility Principle (SRP – Nguyên tắc Trách nhiệm Duy nhất): Mỗi lớp hoặc mô-đun chỉ nên có một lý do để thay đổi, tức là nó chỉ nên chịu trách nhiệm cho một phần chức năng cụ thể của hệ thống. Điều này giúp tránh việc một thay đổi nhỏ có thể ảnh hưởng đến nhiều phần khác nhau của hệ thống.
- Open/Closed Principle (OCP – Nguyên tắc Mở-Đóng): Các thực thể phần mềm (như lớp, mô-đun) nên được mở để mở rộng nhưng đóng để sửa đổi. Nghĩa là, bạn có thể mở rộng chức năng của một lớp mà không cần phải thay đổi mã nguồn hiện tại của nó. Điều này thường được thực hiện thông qua abstraction (trừu tượng hóa).
- Liskov Substitution Principle (LSP – Nguyên tắc Thay thế Liskov): Các đối tượng của kiểu con phải có thể thay thế cho các đối tượng của kiểu cha mà không làm thay đổi tính đúng đắn của chương trình. Nếu một kiểu con phá vỡ chức năng của kiểu cha, thì nó vi phạm nguyên tắc này.
- Interface Segregation Principle (ISP – Nguyên tắc Phân tách Giao diện): Nên sử dụng nhiều giao diện đặc thù thay vì một giao diện chung. Điều này giúp tránh việc các lớp phải triển khai những phương thức không cần thiết chỉ vì chúng nằm trong một giao diện lớn.
- Dependency Inversion Principle (DIP – Nguyên tắc Đảo ngược Phụ thuộc): Các mô-đun cấp cao không nên phụ thuộc vào các mô-đun cấp thấp; thay vào đó, cả hai nên phụ thuộc vào các trừu tượng. Đồng thời, các trừu tượng không nên phụ thuộc vào các chi tiết, mà các chi tiết nên phụ thuộc vào các trừu tượng. Nguyên tắc này giúp hệ thống trở nên linh hoạt hơn và dễ dàng thay đổi hoặc mở rộng.
Đọc thêm: SOLID là gì? 5 phút hiểu ngay cách áp dụng chi tiết những nguyên tắc SOLID
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 phổ biến, trong đó dữ liệu và hành vi được tổ chức thành các đơn vị gọi là đối tượng. Các đối tượng này đại diện cho các khái niệm trừu tượng hoặc thực tế và tương tác với nhau thông qua các giao diện được xác định trước.
Nói một cách đơn giản, trong OOP, các chương trình được xây dựng từ các đối tượng, từ đó giúp dễ dàng tái sử dụng mã, tăng năng suất, nâng cao bảo mật và đơn giản hóa việc bảo trì.
OOP giúp lập trình dễ dàng hơn nhờ các lợi ích sau:
- Cho phép tái sử dụng mã: Lập trình viên có thể sử dụng lại mã thông qua kế thừa, giúp tiết kiệm công sức và thời gian phát triển.
- Tăng năng suất: OOP cho phép sử dụng các gói phần mềm hiện có, như Python, giúp xây dựng chương trình nhanh hơn.
- Tăng cường bảo mật: Cơ chế ẩn dữ liệu và trừu tượng hóa trong OOP giúp bảo vệ dữ liệu, đảm bảo rằng người dùng chỉ có thể truy cập thông tin cần thiết.
- Dễ bảo trì: OOP thúc đẩy thiết kế mô-đun, làm cho mã dễ hiểu và dễ bảo trì hơn trong các dự án lớn.
Tính chất 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 xoay quanh khái niệm “đối tượng”, trong đó các đối tượng là thể hiện của lớp, đóng gói dữ liệu và hành vi thành một đơn vị độc lập. Các tính chất cơ bản của OOP bao gồm:
- Encapsulation (Đóng gói): Đóng gói là việc gộp dữ liệu và các phương thức thao tác trên dữ liệu đó vào trong một lớp duy nhất. Điều này giúp tổ chức mã một cách gọn gàng và có thể tái sử dụng. Ví dụ, trong phát triển trò chơi, lớp nhân vật có thể đóng gói các thuộc tính như vị trí, sức khỏe và các hành động như di chuyển, tấn công.
- Inheritance (Kế thừa): Kế thừa cho phép tạo lớp mới dựa trên lớp hiện có, kế thừa các thuộc tính và phương thức từ lớp cha. Điều này tạo điều kiện cho việc xây dựng hệ thống phân cấp và tái sử dụng mã. Chẳng hạn, lớp kẻ thù chung có thể được kế thừa để tạo các loại kẻ thù cụ thể như yêu tinh hoặc rồng, với các tính năng chung và bổ sung thêm các đặc điểm riêng.
- Polymorphism (Đa hình): Đa hình cho phép các đối tượng thuộc các loại khác nhau được xử lý như các đối tượng của cùng một loại chung, tạo sự linh hoạt và khả năng mở rộng cho hệ thống. Trong trò chơi, đây là một phương thức chung có thể xử lý nhiều đối tượng khác nhau, chẳng hạn như nhân vật, kẻ thù, và vật phẩm.
- Abstraction (Trừu tượng hóa): Trừu tượng hóa là việc đơ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 đặc điểm cốt lõi và che giấu các chi tiết phức tạp. Điều này giúp tạo ra mã dễ hiểu và dễ bảo trì. Ví dụ, một công cụ vật lý trong trò chơi có thể cung cấp các giao diện đơn giản cho các nhà phát triển mà không cần phải bận tâm đến các phép tính phức tạp bên trong.
Đọc thêm: OOP là gì? 4 đặc tính cơ bản của OOP
Phân biệt Abstract và Interface
Abstract (Lớp trừu tượng):
- Chức năng: Abstract 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 đè. Nó có thể chứa mã thực thi và cả phương thức trừu tượng (phương thức không có thân).
- Sử dụng: Nếu bạn muốn chia sẻ mã giữa các lớp có liên quan chặt chẽ hoặc muốn khai báo các trường không tĩnh và không phải là trường cuối cùng, abstract là lựa chọn phù hợp. Nó cũng hữu ích khi bạn cần định nghĩa các phương thức với quyền truy cập không chỉ là “public” (ví dụ: “protected” hoặc “private”).
- Kế thừa: Một lớp chỉ có thể mở rộng (kế thừa) từ một abstract duy nhất.
Nên sử dụng Abstract trong trường hợp:
- Khi bạn dự định cập nhật lớp cơ sở trong suốt vòng đời của dự án.
- Khi bạn muố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ẽ.
Interface (Giao diện):
- Chức năng: Interface chỉ định nghĩa các phương thức mà bất kỳ lớp nào triển khai nó đều phải tuân thủ, nhưng không chứa bất kỳ mã thực thi nào. Nó chỉ là một cam kết về những gì một lớp phải cung cấp.
- Sử dụng: Interface thích hợp khi bạn muốn định nghĩa hành vi mà các lớp không liên quan sẽ triển khai, hoặc khi bạn muốn tận dụng khả năng đa kế thừa. Đây là lựa chọn tốt khi bạn không cần xử lý một 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 lợi thế của đa kế thừa mà không gặp phải vấn đề “diamond (kim cương)”.
Nên sử dụng Interface trong trường hợp:
- Khi bạn không cần xử lý một hệ thống phân cấp phức tạp.
- Khi bạn cần nhiều kế thừa và muốn tránh các vấn đề liên quan đến kế thừa abstract.
Bạn làm việc với các API như thế nào?
Để triển khai API, hai ứng dụng được tích hợp phải tuân theo các quy tắc và giao thức đã được thiết lập để có thể giao tiếp hiệu quả. Trong mối quan hệ máy khách với máy chủ, cả hai bên đều phải thực hiện đúng trách nhiệm của mình. Khi các developer phát triển API, họ cần hiểu rõ về mục tiêu và cách khách hàng sẽ gửi yêu cầu để nhận phản hồi.
Đây là câu trả lời tham khảo cho phần câu hỏi phỏng vấn Game Developer này.
Để làm việc với các 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: Xác định rõ mục tiêu của API, như tăng doanh thu, cải thiện hiệu quả hoạt động, hoặc tạo giá trị từ dữ liệu hiện có.
- Xác định nguồn dữ liệu và sơ đồ API: Mô hình hóa dữ liệu và xác định các hoạt động cần thiết để tương tác với các nguồn dữ liệu. Tài liệu API cần mô tả rõ các tham số, tiêu đề, và định dạng dữ liệu.
- Chọn giải pháp API phù hợp với hạ tầng hiện có: Đảm bảo API tích hợp tốt với mạng lưới và tài nguyên phần mềm hiện có, có thể cần tư vấn từ chuyên gia tích hợp.
- Xác định yêu cầu API: Làm rõ các yêu cầu và kỳ vọng từ API, đảm bảo chúng gắn liền với chiến lược kinh doanh ban đầu.
- Chọn kiến trúc trao đổi dữ liệu API: REST API thường được sử dụng do tính linh hoạt và hiệu quả, dễ sử dụng, nhanh, và có thể xử lý nhiều định dạng dữ liệu khác nhau.
- Chọn phương thức xác thực API: API có thể sử dụng khóa API hoặc OAuth 2.0 cho các ứng dụng cần bảo mật cao.
- Phát triển tài liệu API: Sử dụng các công cụ ghi chép để tài liệu API dễ hiểu và cập nhật khi có thay đổi.
- Quản lý phiên bản API: Khi có phiên bản mới, cần thông báo rõ ràng và cập nhật tài liệu tương ứng.
- Triển khai và phát triển API: Sử dụng các công cụ và quy trình để tự động triển khai API, giảm thiểu rủi ro và thời gian phát triển.
- Giám sát API: Đầu tư vào phần mềm giám sát để đảm bảo API luôn hoạt động ổn định, theo dõi lỗi và hiệu suất liên tục.
Bộ dọn rác (GC – Garbage Collector) trong engine của bạn hoạt động như thế nào?
Bộ dọn rác (Garbage Collector – GC) là một tính năng trong các ngôn ngữ lập trình như C# và Java, giúp tự động quản lý bộ nhớ. Khi các đối tượng không còn được chương trình sử dụng, GC sẽ tự động giải phóng bộ nhớ đã cấp phát cho những đối tượng đó, để không gian này có thể được sử dụng cho các lần phân bổ trong tương lai.
Chẳng hạn, GC trong Java là một quy trình tự động, giúp giải phóng bộ nhớ mà không cần lập trình viên phải tự tay xóa các đối tượng không còn cần thiết. GC được tích hợp trong Java Virtual Machine (JVM) và mỗi JVM có thể triển khai phiên bản GC riêng, nhưng tất cả đều phải tuân thủ các tiêu chuẩn JVM. GC hoạt động bằng cách quét bộ nhớ heap, đánh dấu các đối tượng không thể truy cập, và sau đó loại bỏ chúng bằng cách nén lại bộ nhớ.
Với Unity, GC hoạt động bằng cách tự động thu hồi bộ nhớ từ các đối tượng mà ứng dụng của bạn không còn sử dụng.
Khi một tập lệnh cố gắng phân bổ bộ nhớ trên heap nhưng không đủ không gian trống, Unity sẽ kích hoạt GC. GC kiểm tra tất cả các đối tượng trong heap và xác định những đối tượng nào không còn được tham chiếu. Những đối tượng này sẽ bị xóa để giải phóng bộ nhớ.
GC tiếp tục xử lý các yêu cầu tương tự cho đến khi không còn đủ bộ nhớ trống để phân bổ. Để làm điều này, GC sẽ tìm kiếm tất cả các biến tham chiếu đang hoạt động, đánh dấu các khối bộ nhớ mà chúng gọi là “trực tiếp.” Các khối không được đánh dấu sẽ được coi là trống và sẵn sàng cho các phân bổ sau này.
Quá trình này giúp quản lý bộ nhớ hiệu quả, đảm bảo ứng dụng của bạn hoạt động mượt mà mà không cần can thiệp thủ công vào việc giải phóng bộ 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:
Các thiết bị khác nhau có thể chạy trò chơi với tốc độ khung hình khác nhau (30, 60, hoặc 120 FPS). Nếu bạn chỉ di chuyển vật thể theo một khoảng cố định mỗi khung hình mà không tính đến thời gian thực giữa các khung hình, vật thể sẽ di chuyển nhanh hơn trên những thiết bị có tốc độ khung hình cao, làm trò chơi không công bằng.
- Chuyển động mượt mà hơn:
Khi nhân với Time.deltaTime, chuyển động của vật thể sẽ mượt mà và đều đặn hơn. Nếu tốc độ khung hình giảm, chuyển động của vật thể vẫn sẽ ổn định, mặc dù có thể không còn mượt mà như trước.
- Tính nhất quán trên các thiết bị:
Nhờ việc sử dụng Time.deltaTime, trò chơi của bạn có thể giữ được sự nhất quán về trải nghiệm trên mọi thiết bị, dù là máy tính cấu hình mạnh hay điện thoại cấu hình yếu. Sự khác biệt duy nhất là tốc độ khung hình, còn tốc độ di chuyển sẽ giống nhau.
Tổng kết Câu hỏi phỏng vấn Game Developer
Trên đây là tổng hợp 20 câu hỏi phỏng vấn Game Developer phổ biến. Tuy vậy, tuỳ vào tính chất dự án và yêu cầu của doanh nghiệp mà nhà tuyển dụng sẽ cần hỏi thêm về kinh nghiệm, dự án thực tế cũng như các kỹ năng mềm cần thiết. Vì vậy, ngoài việc tham khảo những câu hỏi phỏng vấn Game Developer kể trên, bạn cũng nên dành thời gian nghiên cứu và tìm hiểu mô tả công việc để có thể trang bị kiến thức tốt hơn. Tốt hơn hết, bạn có thể thảo luận với nhà tuyển dụng về việc cần chuẩn bị những gì để buổi phỏng vấn được diễn ra mượt mà nhất.