无标题
Thiết kế Giao diện Điều khiển Hành động Của Nhân vật
Thiết kế Giao diện Điều khiển Hành động của Nhân vật
Thiết kế giao diện điều khiển hành động nhân vật
Gần đây trọng tâm công việc có chút thay đổi, tôi chuyển từ thiết kế server sang làm việc tại phía client.
Sau khi hoàn thành kiến trúc động cơ底层, nhóm client chỉ còn lại hai người toàn thời gian viết code - một người phụ trách động cơ 3D ở tầng C, người còn lại tập trung thiết kế giao diện ứng dụng cấp cao và hoàn thiện demo game dựa trên nền tảng đó.
Về mặt kỹ thuật, cả hai đều rất xuất sắc. Người có năng lực thiết kế, người có kỹ năng lập trình vững vàng. Thẩm mỹ code cũng rất đồng điệu, vì vậy chúng tôi hợp tác vô cùng ăn ý.
Điểm duy nhất chưa hài lòng là lập trình viên làm demo tiếp xúc game quá ít, dẫn đến việc tinh chỉnh cảm giác vận hành còn nhiều hạn chế. Thường xuyên phải trao đổi với策划 (nhà thiết kế hệ thống chiến đấu) để điều chỉnh. Tuy nhiên do策划 không hiểu rõ phương pháp triển khai phía code, kết quả cuối cùng vẫn chưa đạt yêu cầu. Nếu có thời gian đầy đủ, chắc chắn sẽ điều chỉnh ổn thỏa. Nhưng hiện tại tiến độ dự án vượt trước kế hoạch, tôi đành trực tiếp tham gia hỗ trợ để đẩy nhanh tiến độ phần này.
Tuần trước tôi dành vài ngày đọc kỹ toàn bộ code liên quan. Nhìn chung thiết kế khá ổn, nhưng khi sử dụng thực tế vẫn thấy vài điểm bất tiện.
Tôi mong muốn game có cảm giác vận hành mượt mà, cho phép người chơi linh hoạt điều khiển nhân vật thực hiện các hành động chiến đấu như né tránh, kết hợp kỹ năng… Khi viết nhiều đoạn code thử nghiệm để đạt được cảm giác mong muốn, tôi phát hiện một số vấn đề thiết kế.
Cảm giác vận hành không chỉ đơn thuần là kiểm soát thiết bị đầu vào. Nó bao gồm việc thu thập phân tích dữ liệu từ thiết bị đầu vào (bàn phím/chuột) và phản hồi hình ảnh qua thiết bị đầu ra (màn hình). Để có trải nghiệm tốt, cần kiểm soát chính xác phản hồi hình ảnh của từng hành động.
Ở đây, thiết kế giao diện điều khiển hành động nhân vật trong động cơ 3D đóng vai trò then chốt. Giao diện cần đơn giản dễ dùng nhưng vẫn đảm bảo độ ổn định.
Thực tế đến giai đoạn này, việc sử dụng động cơ 2D hay 3D không còn quan trọng. Điểm khác biệt nằm ở việc 3D có thêm một bậc tự do trong việc xác định vị trí không gian của sprite và điều khiển camera, khiến việc triển khai phức tạp hơn nhiều. Giữ giao diện đơn giản càng trở nên thách thức.
Hôm nay tôi sẽ tập trung phân tích phần đơn giản trước - cách điều khiển phát lại hành động nhân vật (áp dụng cho cả động cơ 2D).
Về bản chất, engine hình ảnh hoạt động bằng cách render từng frame sprite nhân vật. Thao tác render này cần được ẩn khỏi tầng ứng dụng. Động cơ 3D có khả năng biểu đạt cao hơn như tạo hoạt ảnh xương (skeletal animation), thực hiện chuyển tiếp hành động mượt mà. Những tính năng này nên được xử lý ở tầng render, tầng ứng dụng hầu như không cần quan tâm.
Trước đây, giao diện engine chúng tôi cung cấp phương thức thiết lập chuỗi hoạt ảnh hiện tại của nhân vật, với tham số lựa chọn phát lại số lần cụ thể hay lặp vô hạn. Nhưng khi suy nghĩ kỹ, thiết kế này không thực sự hợp lý.
Trong thực tế, khi hoàn thành phát lại hoạt ảnh theo số lần chỉ định, engine nên làm gì? Giữ hình ảnh sprite ở frame cuối cùng? Rõ ràng điều này không hợp lý. Nếu sprite đại diện cho sinh vật sống động, chúng luôn phải vận động trong trạng thái bình thường.
Giao diện cần được thiết kế chắc chắn đến mức dù người dùng gọi như thế nào cũng không thể gây ra trạng thái lỗi. Hình ảnh nhân vật đứng yên là một trạng thái lỗi điển hình.
Tôi từng cố gắng xây dựng một engine điều khiển chuỗi hoạt ảnh phức tạp, tin rằng nhiều người cũng từng làm như vậy. Đó là hỗ trợ phát lại danh sách hoạt ảnh theo thứ tự, cho phép gắn controller vào từng nút để quyết định phát lại đơn hay lặp, thậm chí controller có thể điều khiển thuộc tính nội tại của nhân vật. Danh sách này thậm chí có thể có cấu trúc dạng cây.
Ý tưởng này tôi từng triển khai vào năm 2001, nhưng phát hiện ra nó rất khó dùng. Nó vi phạm nguyên tắc KISS (Keep It Simple, Stupid). Càng cố gắng tích hợp nhiều tính năng, hệ thống càng khó mở rộng và kiểm soát chi tiết. Các lập trình viên sử dụng cũng gặp nhiều khó khăn. (Nhân tiện, năm ngoái tôi từ bỏ Boost Jam chuyển sang dùng GMake làm công cụ build cũng vì lý do tương tự)
Nếu suy nghĩ kỹ, mọi chuỗi hoạt ảnh điều khiển nhân vật thực chất đều có thể phân rã thành một “nguyên hoạt ảnh” gồm: chuỗi dẫn nhập (intro animation) dẫn vào trạng thái lặp vô hạn. Ví dụ hành động ngồi xuống: từ tư thế đứng thẳng trải qua quá trình ngồi xuống, cuối cùng giữ trạng thái ngồi liên tục. Hay như khi nhân vật đứng yên nhưng thực hiện các động tác hoa mỹ, sau khi hoàn thành cần trở về tư thế đứng cơ bản.
Việc衔接 (kết nối) giữa chuỗi dẫn nhập với trạng thái trước đó của nhân vật có thể giao cho tầng底层 engine xử lý. Mọi hành động phức tạp đều có thể phân rã như vậy. Ngay cả các trường hợp đặc biệt cũng có thể xử lý linh hoạt. Ví dụ giai đoạn lặp vô hạn cuối cùng có thể chỉ gồm một frame duy nhất, khiến nhân vật đứng yên.
Thiết kế như vậy không cần giao diện engine cung cấp tùy chọn phát lại một lần hay lặp vô hạn. Mỗi “nguyên hoạt ảnh” chỉ cần một lệnh phát lại duy nhất.
Vậy làm thế nào kiểm soát hoạt ảnh tương tác?
Cần thêm một tham số gọi hàm. Dù chuỗi hoạt ảnh có thể phát lại vô hạn, nhưng giai đoạn dẫn nhập phía trước chỉ xảy ra duy nhất một lần. Thiết kế dùng callback function thường rất khó coi. Giải pháp tinh tế hơn là cho phép chỉ định độ dài thời gian của giai đoạn dẫn nhập. Vì thời gian này thường gắn chặt với logic game, ví dụ động tác chém kiếm phải được kiểm soát chính xác bởi lập trình viên chứ không thể phụ thuộc vào việc artist tạo ra. Nhiều hoạt ảnh có độ dài cần điều chỉnh liên tục trong quá trình phát triển game, chúng ta không thể yêu cầu artist thay đổi mãi mãi. Vì vậy engine cần hỗ trợ nội bộ việc kéo giãn hoặc nén độ dài phát lại hoạt ảnh theo tham số runtime.
Ngay cả với hoạt ảnh lặp cũng cần kiểm soát chính xác độ dài mỗi chu kỳ. Ví dụ điển hình là hoạt ảnh chạy bộ. Khi thiết lập nhân vật di chuyển 10 mét trong 1 giây (tức 2 bước chân mỗi giây), cần kiểm soát chính xác mỗi chu kỳ chạy bộ kéo dài 0.5 giây. Kiểm soát như vậy sẽ tránh được hiện tượng nhân vật trượt trên mặt đất trong