9.6.1. Sử dụng biến tĩnh
Đối với ngôn ngữ Javascript, chúng ta cần phải xác định rõ kiểu của biến đang sử dụng để hệ thống không phải tốn nhiều thời gian xử lý.
Trang 69 CHƯƠNG 9. CĂN BẢN VỀ LẬP TRÌNH TRÊN UNITY
Với đoạn mã trên biến foo là loại biến động, việc gọi phương thức DoSomeThing() sẽ tốn nhiều thời gian do kiểu của biến chưa được xác định và hệ thống Unity sẽ làm thay chúng ta điều đó.
Với đoạn mã đã được sửa như trên, xác định chính xác kiểu của biến là một trong những cách tốt nhất để tối ưu hóa cho ngôn ngữ Javascript.
Chú ý: Để chắc rằng chúng ta không sử dụng bất kỳ một biến động nào, sử dụng từ khóa “#pragma strict”, hệ thống Unity sẽ tự động báo lỗi nếu trong script tồn tại bất kỳ biến nào không có kiểu xác định. Vì vậy sử dụng ngôn ngữ C# cũng là một trong những cách tối ưu hóa hữu hiệu.
9.6.2. Sử dụng các thành phần của GameObject thông qua biến tĩnh
Nhưđã tìm hiểu ở các mục trên, bản thân một GameObject luôn được tích hợp sẵn các thành phần để lưu trữ thông tin của chúng trong không gian 3D, ở đây chúng ta sẽ lấy ví dụ cụ thể với thành phần transform.
Mỗi khi chúng ta gọi một thành phần bất kỳ của GameObject, hệ thống Unity sẽ tốn thời gian để duyệt qua các thành phần được tích hợp trong GameObject để cuối cùng trả về kết quả thích hợp, để tiết kiệm khoản thời gian đó nên sử dụng thành phần của GameObject thông qua biến tĩnh. Đoạn mã trên được sửa lại như sau:
Chú ý: Tuy thời gian để hệ thống truy xuất đến các thành phần là không nhiều, nhưng với một số lượng lớn code thì chúng ta sẽ tiết kiệm được một khoản thời gian lớn, vì vậy đây cũng là một cách tối ưu hóa hữu hiệu.
9.6.3. Sử dụng mảng tĩnh
Các lớp ArrayList hay Array thì rất dễ sử dụng, chúng ta có thể dễ dàng thêm một phần tử vào mảng và sử dụng các phương thức, nhưng chi phí phải trảđể hệ thống thực hiện điều đó là rất cao. Thay vì vậy, việc sử dụng các mảng được cấu trúc sẵn sẽ giảm bớt công việc cho hệ thống, vì các phần tử trong mảng có cùng kiểu và độ dài của mảng đã được xác định từ trước, chúng ta chỉ mất thời gian để xác định độ dài mảng cần thiết, bù lại chúng ta tiết kiệm được một khoản chi phí khá lớn.
Ví dụ:
Chú ý: Hạn chế sử dụng for, foreach, nên sử dụng while để đạt được tốc độ tối ưu.
Trang 71 CHƯƠNG 9. CĂN BẢN VỀ LẬP TRÌNH TRÊN UNITY
9.6.4. Hiệu chỉnh tần số sử dụng phương thức
Đặt vấn đề: Khi người chơi ở quá xa, kẻđịch sẽở trạng thái ngủ tạm thời.
Code xử lý:
Giải thích: Đoạn mã trên sẽ xét khoảng cách giữa người chơi và kẻđịch tại mỗi frame, nhưng điều đó là không cần thiết, trung bình một giây hệ thống chạy từ 30 đến 40 khung hình, chi phí bỏ ra quá lớn nhưng hiệu quả lại không cao.
9.6.5. Yield và Coroutine
Các Coroutine cho phép chúng ta đình trệ hay làm trễ việc thực thi một đoạn mã hoặc một phương thức, áp dụng để xử lý vấn đề nêu trên.
Giải thích: Việc thực thi vòng lập while sẽ bị chậm lại 2 giây sau mỗi lần lặp do tác động của yield, việc xét khoảng cách giữa người chơi và kẻđịch sẽđược thực thi 2 giây 1 lần, tiết kiệm được nhiều chi phí và giảm nhẹ công việc cho hệ thống.
Chú ý: Đối với C#, phương thức sử dụng yield phải thuộc kiểu IEnumerator, riêng với Javascript thì không, ví dụ minh họa:
Trang 73 CHƯƠNG 10. LẬP TRÌNH GIẢI QUYẾT CÁC VẤN ĐỀ TRONG GAME FPS
CHƯƠNG 10. LẬP TRÌNH GIẢI QUYẾT CÁC VẤN ĐỀ
TRONG GAME FPS
10.1. MÔ PHỎNG NHÂN VẬT DƯỚI GÓC NHÌN THỨ NHẤT 10.1.1. First Person Controller Prefab 10.1.1. First Person Controller Prefab
Thể loại game FPS là rất phổ biến hiện nay, vì vậy hầu hết các game engine đều có các gói tài nguyên hỗ trợ developer giả lập “trạng thái người chơi”, ở đây chúng ta chú ý đến đối tượng First Person Controller.
10.1.2. Phân tích cấu trúc FPC
First Person Controller(FPC) là một đối tượng đã được Unity đóng gói sẵn các script và các Component khác để hổ trợ developer, cơ bản cấu tạo của FPC gồm 3 phần chính: First Person Controller, Graphics và Main Camera.
a. Đối tượng 1: First Person Controller
Là cha của cả nhóm đối tượng, được tích hợp các script quan trọng để xử lý các vấn đề cơ bản của game PFS như di chuyển, ngồi xuống, nhảy, xoay camera …v…v.
Hình 10.1. First Person Controller.
- Thành phần FPSWalker (Script): Được viết bằng Javascript, cho phép người chơi di chuyển thông qua input, có các biến được public ra bên ngoài như Speed, Jump Speed, Gravity để thuận tiện cho việc tùy chỉnh từ bên ngoài.
- Thành phần Character Controller: Là một mạng lưới bao phủ lên đối tượng, có nhiệm vụ kiễm tra các tương tác vật lý và hỗ trợ di chuyển cho đối tượng trong không gian 3D, bao gồm các thuộc tính sau:
Trang 75 CHƯƠNG 10. LẬP TRÌNH GIẢI QUYẾT CÁC VẤN ĐỀ TRONG GAME FPS
Hình 10.2. Character Controller. • Height: Độ cao của mạng lưới bao quanh đối tượng. • Radius: Độ rộng của mạng lưới bao quanh đối tượng.
• Slope Limit: Độ dốc giới hạn của địa hình mà đối tượng có thể tiếp tục di chuyển được.
• Step Offset: Giống như khi đang đi lên một cầu thang, đây là thông số về độ cao giới hạn của bậc thang mà đối tượng có thể bước lên.
• Skin Width: Khi đối tượng đột ngột va chạm với một đối tượng khác ở tốc độ cao, hay bị kẹt trong một bức tường, dựa vào thông số này hệ thống sẽ ngừng việc kiểm tra va chạm. Nên để thông số này bằng 10% bán kính nhân vật.
• Min Move Distance: Input còn phụ thuộc vào độ nhạy của bàn phím hay con chuột của người chơi, vì vậy đây là giá trị di chuyển nhỏ nhất ứng với giá trị input nhỏ nhất.
• Center: Tâm của mạng lưới bao quanh đối tượng.
- Thành phần Mouse Look (Script): Được viết bằng C#, là một hệ thống quản lý việc xoay của đối tượng quanh một trục cho trước.
• Axes: Thuộc tính cho phép nhập các trục X, Y hoặc MouseX, MouseY để kiểm soát việc xoay của đối tượng.
• Minimum X / Maximum X: Giá trị của thuộc tính này sẽ giới hạn độ xoay của đối tượng, mặc định là -360 đến 360 độ.
• Minimum Y / Maximum Y: Tương tự như trên.
b. Đối tượng 2: Graphics
Đối với thể loại game FPS, người chơi không bao giờ nhìn thấy toàn bộ cơ thể của họ, đối tượng này giúp developer dễ dàng nhận biết nó trong thế giới 3D, ngoài ra đối tượng này còn là vị trí thích hợp để chứa các model của nhân vật, khi game được chạy, đối tượng này sẽđược tắt render để hiện lên các model của nhân vật.
Hình 10.3. Đối tượng Graphics.
Thành phần Mesh Filter và Mesh Renderer: Trong ví dụ trên thành phần mesh filter mang tên Poly Surface 2, bản thân nó là nơi chứa dữ liệu về các lưới đồ họa(mesh) của model, trên thực tế một model nhân vật hoàn chỉnh sẽ bao gồm nhiều bộ phận, mỗi bộ phận được lưu trữ trong một mesh filter và được vẽ nên bởi một mesh renderer. Ngoài việc quyết định một mesh filter có được vẽ lên hay không, mesh renderer còn thể hiện được chất liệu và đổ bóng.
10.1.3. Đối tượng 3: Main Camera
Trong FPC prefab, camera được đặt ở vị trí tương tự như tầm mắt của con người và bị điều khiển bởi script, cho phép người chơi nhìn xung quanh trong khi đang di chuyển hay đứng yên.
Trang 77 CHƯƠNG 10. LẬP TRÌNH GIẢI QUYẾT CÁC VẤN ĐỀ TRONG GAME FPS
- Thành phần Camera: Là thành phần chính tạo nên tầm nhìn của người chơi trong thế giới 3D.
Hình 10.4. Thành phần Camera.
- Clear Flags: Cho phép camera render các vật liệu chỉ định, ở đây mặc định là loại Skybox.
- Back Ground Color: Nếu thế giới 3D của chúng ta không có một bầu trời, thì sẽđược thay thế bằng màu của thuộc tính này.
- Normalized View Port Rect: Thuộc tính này dùng để hiệu chỉnh vùng nhìn của camera so với vùng nhìn thật sự mà developer cho phép, hay giữa các camera với nhau. Ví dụ như trong game đua xe, chúng ta muốn có một màn hình để đua xe, một màn hình thể hiện thông số của xe và một màn hình để làm bản đồ, điều đó có thểđược tùy chỉnh tại đây.
- Near Clip Plane / Far Clip Plane: Độ gần nhất và xa nhất của tầm mắt, xem ảnh minh họa sau để hiểu rõ thêm.
Hình 10.5. Near Clip Plane và Far Clip Plane.
- Orthographic và Orthographic Size: Một cách dễ hiểu đây là thuộc tính thay đổi phong cách nhìn từ camera.
- Depth: Dùng để thiết lập độưu tiên giữa các camera.
- Culling Mask: Là một bộ lọc các đối tượng trong tầm nhìn của camera thông qua thuộc tính layer của đối tượng, bằng thuộc tính này chúng ta có thể tùy chỉnh cho camera chỉ nhìn thấy các đối tượng thuộc các layer chỉ định.
10.2. TƯƠNG TÁC 10.2.1. Collision Detection 10.2.1. Collision Detection
Trong bất kỳ một game engine nào, khi một đối tượng bị va chạm thì mọi thông tin của đối tượng va chạm đều được lưu trữ lại tại thời điểm đó. Ví dụ, khi một viên đạn chạm vào người chơi, ta sẽ có được các thông tin như vị trí va chạm và truy xuất được đến các thành phần của đối tượng va chạm. Để có thể kiểm tra va chạm giữa các đối tượng thì bản thân mỗi đối tượng phải được tích hợp thành phần Rigidbody hoặc Collider.
Unity cung cấp một số phương thức để bắt các sự kiện va chạm như OnCollisionEnter, OnCollisionStay, OnCollisionExit. Ví dụ: Khi một đối tượng bị va chạm sẽ nổ tung.
Trang 79 CHƯƠNG 10. LẬP TRÌNH GIẢI QUYẾT CÁC VẤN ĐỀ TRONG GAME FPS
Giải thích: Khi một chiếc xe bị va chạm, ta sẽ hủy chiếc xe “nguyên vẹn” ban đầu, thay vào đó là model chiếc xe bị vỡ nát và hiệu ứng cháy nổ, model thay thếở ví dụ trên là explosionPrefab.
10.2.2. Vấn đề trễ khung hình
Đặt vấn đề: Điều cơ bản trong thể loại game bắn súng là xử lý vấn đề bắn, vậy làm sao để giả lập được một viên đạn?
Hướng giải quyết tạm thời: Tạo một model viên đạn có chứa script xét va chạm, truyền tốc độ lớn cho model này khi người chơi ra lệnh bắn, kiểm tra va chạm giữa viên đạn và đối tượng va chạm còn lại đểđược kết quả mong muốn.
Vấn đề phát sinh: Để giả lập được một viên đạn bay với tốc độ gần như thực tế thì vận tốc trung bình của model viên đạn phải vào khoảng 500 feet trên giây. Nếu frame rate của game vào khoảng 25 khung hình mỗi giây có nghĩa là mỗi khung hình viên đạn di chuyển được 20 feet. Vấn đề xảy ra khi đối tượng bị va chạm bởi viên đạn có đường kính khoảng 2 feet, có nghĩa là viên đạn sẽ rất dễ “không xét va chạm” ở các vị trí tại 5 feet và 25 feet.
Hình 10.6. Bỏ lỡ khung hình.
10.2.3. Ray castting
Thay vì cố gắng giả lập một viên đạn như thật, ta có thể sử dụng kỹ thuật Ray Castting, bằng cách vẽ một tia cùng phương và chiều với model súng chúng ta sẽ dễ dàng kiểm tra kết quả va chạm. Vì trên thật tế tốc độ của một viên đạn là quá nhanh để mắt thường có thể nhìn thấy được, nên Ray Castting hoàn toàn là một giải pháp hữu hiệu.
Hình 10.7. Ray castting.
Giới thiệu phương thức:
static bool Raycast(Vector3 origin, Vector3 direction, out RaycastHit hitInfo, float distance, int layerMark).
Trang 81 CHƯƠNG 10. LẬP TRÌNH GIẢI QUYẾT CÁC VẤN ĐỀ TRONG GAME FPS
• origin: Điểm bắt đầu của tia. • direction: Hướng của tia.
• hitInfo: Thông tin về vật va chạm được lưa trữ dưới lớp RaycastHit • distance: Độ dài của tia.
• layerMark: Các layer mà tia sẽ bỏ qua sự va chạm.
10.2.4. Một số vấn đềđược giải quyết với Raycast a. Vấn đề 1 a. Vấn đề 1
Đặt vấn đề: Khi nào enemy phát hiện được người chơi?
Hướng giải quyết: Vẽ một tia từ enemy đến người chơi, nếu đối tượng bị va chạm không phải người chơi thì trả về false và ngược lại thì trả về true.
Code xử lý:
b. Vấn đề 2
Đặt vấn đề: Khi nào enemy bắn trúng người chơi? Giải quyết tương tự vấn đề 1.
10.3. QUẢN LÍ VŨ KHÍ
Trong một game FPS, vũ khí là một thành phần quan trọng gắn liền với người chơi. Cụ thể những vú khí này có thể là súng, lựu đạn hay dao, … Một nhân vật trong game (đặc biệt là nhân vật do người chơi điều khiển) có thể mang cùng lúc nhiều vũ khí, vì vậy chúng ta cần xây dựng một hệ thống để quản lí chúng, bao gồm những vú khi nào đang được mang theo và thông tin trạng thái của chúng (số lượng băng đạn cùng số lượng đạn của súng, …).
10.3.1. Weapon Slot
Một nhân vật được mang theo một vũ khí chính và một vũ khí phụ, vũ khí nào đang được dùng (đang được cầm trên tay), tựđộng xử lí việc thay đổi vũ khí khi người chơi scroll chuột – đó là các chức năng của script Weapon Slot.
Thuộc tính Kiểu Giải thích
PrimarySlot GameObject Mô hình vũ khí chính. SecondarySlot GameObject Mô hình vũ khí phụ.
PrimarySelected bool Vũ khí chính có đang được sử dụng? SecondarySelected bool Vũ khí phụ có đang được sử dụng?
Trang 83 CHƯƠNG 10. LẬP TRÌNH GIẢI QUYẾT CÁC VẤN ĐỀ TRONG GAME FPS
Phương thức ChangeWeapon trên thực hiện các xử lí khi người chơi đổi vũ khí.
10.3.2. Weapon Info
Weapon Info là script gắn liền với mỗi vũ khí, nó nắm giữ các thông tin của vũ khí và thực hiện gửi các thông tin này đến các script khác như script xử lí HUD thông tin về súng, script Weapon Slot, script xử lí bắn, …
Thuộc tính Kiểu Giải thích
Range float Tầm sát thương của vũ khí. RateOfFire float Tần số nhảđạn của súng. ReloadTime float Thời gian thay đạn. Damage float Mức sát thương của súng. ClipCount int Tổng số băng đạn.
TotalAmmo int Tổng sốđạn.
BulletPerClip int Sốđạn tối đa trong một băng. CurrentClipAmo int Sốđạn trong băng đạn đang dùng. ShootSound AudioClip Âm thanh bắn.
ReloadSound AudioClip Âm thanh thay đạn. OutOfAmmoSound AudioClip Âm thanh hết đạn.
ShootTime float Thời gian bắn (thời gian của hoạt cảnh bắn) WalkTime float Thời gian đi (thời gian của hoạt cảnh đi) RunTime float Thchạờy) i gian chạy (thời gian của hoạt cảnh
10.4. TRÍ TUỆ NHÂN TẠO 10.4.1. Hệ thống tìm đường
Hiện nay hầu hết các 3D game engine đều có hỗ trợ xử lý vấn đề tìm đường cho AI (trí tuệ nhân tạo), thuật toán thông dụng nhất được sử dụng là A*, nhưng ở đây chúng ta không đề cập đến A* là thuật toán thế nào, mà sẽ trã lời câu hỏi: “Làm sao áp dụng được thuật toán A* vào game 3D?”.
Hiện nay thuật toán A* trở nên rất phổ biến, các đoạn mã nguồn về thuật toán có thểđược dễ dàng tìm thấy trên mạng, vậy công việc chúng ta phải làm là thiết lập được thông tin ban đầu cho các Node.
a. Way Point
Hình 10.8. A* trong 2D và 3D
Liên hệ từ thuật toán A* cơ bản, các Waypoint đóng vai trò tương tự như các node, sẽ chứa thông tin về PreviousNode và NextNode, trong không gian 3D các
Trang 85 CHƯƠNG 10. LẬP TRÌNH GIẢI QUYẾT CÁC VẤN ĐỀ TRONG GAME FPS
waypoint đơn thuần chỉ là GameObject cơ bản chứa script và không được render khi game chạy.
Hình 10.9. AstarPathFinding 3.0.8.2 • Root: Danh sách các waypoint.
• Recursive: Nếu như Root rỗng, các đối tượng có Tag được gán trùng với thuộc tính.
• Tag: Tag của waypoint.
• Max Distance: Khoảng cách lớn nhất mà waypoint này có thể đi đến waypoint khác.
• Max Distance (axis aligned): Giống với thuộc tính trên nhưng có thể giới hạn cụ thể các hướng.
• Raycast: Kiểm tra va chạm giữa 2 waypoint, nếu giữa 2 waypoint này có một đối tượng khác thì sự liên kết sẽ bị xóa bỏ.