Ứng dụng trí tuệ nhân tạo trong xây dựng game

Giảsửtừnút bắt đầu bđ, muốn tìm một đường đi đi đến nút cần đến (nút kt). Và các nút vlà các chướng ngại vật: Ở đây, mỗi nút trung gian (nhưcác nút 0, 1 7), sẽchứa 2 giá trịlà chi phí đường đi (waycost), và chi phí toàn bộnút (nodecost). Mỗi nút có một nút cha, và một nút cha có 8 nút con (trong trường hợp bình thường), nằm xung quanh nút này. Giảsửmột nút có chi phí đường đi là waycost, thì các nút con của nó sẽcó waycostcon =waycostcha +1;

pdf120 trang | Chia sẻ: haianh_nguyen | Lượt xem: 1362 | Lượt tải: 0download
Bạn đang xem trước 20 trang tài liệu Ứng dụng trí tuệ nhân tạo trong xây dựng game, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
bar này, chúng ta có thể thay đổi mạng lưới skin. Một vài hàm sửa đổi các mặt – như xóa hoặc di chuyển các hình tam giác - chỉ làm việc trên skin loại MD2. 68 Chương 3: Game Engine – Cách sử dụng MED Select: Chế độ chọn đối tượng, kéo rê thành một hộp bao quanh các đỉnh hoặc mặt muốn chọn. Move: Di chuyển các đỉnh hoặc mặt đang chọn. Rotate: Quay các mặt hoặc đỉnh đang chọn. Nó sẽ quay quanh một điểm mà chúng ta đã nhấp lúc đầu, kéo rê chuột xuống/lên để quay theo cùng/ngược chiều kim đồng hồ. Scale: Thay đổi kích thướt của các mặt hoặc đỉnh đang chọn. Rê lên/xuống để thay đổi kích thướt lớn lên/ nhỏ xuống. Restrict: Hạn chế sự di chuyển, thay đổi kích thướt, hoặc phản chiếu theo trục X và Y. Mirror: Phản chiếu các đỉnh được chọn về trục X hoặc Y. Weld: Kết hợp các định được chọn thành một đỉnh là trung bình của nó. Delete: Xóa khối hoặc mặt được chọn. 2.2.3. Toolbar Paint Trình vẽ skin là công cụ vẽ hình 3D cơ sở, để thêm các kết cấu đến các mô hình. Thông thường, chúng ta chỉ sử dụng các chương trình vẽ ngoài để tạo ra một skin, trình vẽ này có tất cả các hàm cơ bản có thể nhanh chóng tạo hoặc thay đổi skin của mô hình. Nó được kích hoạt khi nhấp vào cửa sổ vẽ hình 3D. Chúng ta có thể quay hình bằng con trượt và vẽ trực tiếp vào skin. Position: Khi được kích hoạt, chúng ta có thể quay hoặc phóng to, thu nhỏ mô hình bằng chuột. Face: Khi được kích hoạt, chỉ những mặt mới có thể được nhìn thấy và thay đổi. 69 Chương 3: Game Engine – Cách sử dụng MED Pen: Vẽ/Tô mô hình với màu đang chọn. Spray: Chưa có giá trị. Fill: Tô tất cả các mặt với màu đang chọn. Palette: Để chọn màu và các chế độ vẽ khác. Chọn 4 màu từ bảng màu. Chọn màu kích hoạt bằng cách nhấp vào nó. Pen Size: Thiết lập kích thướt của bút vẽ theo số điểm. Opacity: Điều chỉnh độ mờ màu của ảnh. 100 là màu thuần nhất, 0 là hoàn toàn trong suốt. 70 Chương 3: Game Engine – Cách sử dụng SED III. SED, C-Script editor 1. Giao diện sử dụng Đây là giao diện sử dụng của C-Script: Ở bên trái chúng ta có thể thấy danh sách các biến, danh sách môi trường. Dĩ nhiên, chúng ta có thể di chuyển cửa sổ này và đặt nó ở phía bên phải nếu muốn. Ở phía bên dưới, chúng ta có cửa sổ chú thích các lệnh, cung cấp tất cả các hướng dẫn nằm trong tập tin help liên quan đến lệnh mà bạn đang viết. Nó cũng hiển thị kết quả kiểm tra cấu trúc mà nó kiểm tra trong lần biên dịch gần nhất. Nếu muốn debug, có cửa sổ xem biến hoặc thực thể trong tab Watch. Nếu đặt con trỏ chuột nằm trên toolbar, sẽ hiện lên một lời chỉ dẫn để mô tả những gì chúng ta sẽ thực hiện. 71 Chương 3: Game Engine – Cách sử dụng SED 2. Soạn thảo Đây là phần quan trọng nhất của hướng dẫn sử dụng SED, nó sẽ giới thiệu về cách soạn thảo nhanh với những phím tắt. 2.1. Lệnh Insert Nếu muốn thêm một lệnh chỉ cần ấn phím [CTRL+SPACE]. Nó đưa ra một danh sách các lệnh từ dữ liệu các lệnh và cả các hàm hoặc hành động từ tập tin wdl. Đánh kí tự đầu tiên của lệnh muốn thêm vào và danh sách sẽ cuộn đến lệnh đó, cũng có thể đi lên hoặc xuống danh sách bằng ấn phím mũi tên [UP/DOWN] hoặc phím [PAGE UP / PAGE DOWN]. Chúng ta cũng có thể ấn [CTRL+SPACE] ngay khi mới bắt đầu viết lệnh. Danh sách sẽ được cuộn đến một lệnh trước con trỏ và khi đó ấn [ENTER] được chèn lệnh vào. 2.2. Dòng chú thích Dòng chú thích rất cần thiết khi người biên soạn muốn bỏ qua một vài đoạn mã trong engine. Để có một dòng chú thích, chọn nó và ấn nút “//” xanh trên toolbar. Hoặc có thể ấn lệnh [CTRL+ALT+C]. Lệnh này sẽ chèn thêm dấu “//” trước dòng được chọn. Để bỏ một dòng chú thích, chọn nó và ấn nút “//” đỏ trên toolbar. Hoặc có thể ấn lệnh [CTRL+SHIFT+C]. Lệnh này sẽ xóa dấu “//” trước dòng được chọn. 2.3. Nhảy đến một đoạn mã 72 Chương 3: Game Engine – Cách sử dụng SED Nhảy đến một đoạn mã sẽ tiết kiệm được rất nhiều thời gian, nếu chúng ta viết một hàm có tên là “myfunction”. Rồi khi mã nguồn nhiều lên và chúng ta muốn quay trở lại và thay đổi nó, lúc này chúng ta sẽ nhảy đến đoạn mã đó. Mặc định, nó nằm phía bên phải, và chứa tất cả các hàm, hành vi, chuỗi … Chỉ cần mở phần function trong cửa sổ code jumber và nhấp vào myfunction. Con trỏ sẽ nhảy tới hàm myfunction. 2.4. Sử dụng danh sách các thành phần Chúng ta biết rằng danh sách môi trường chứa tất cả các nhân tố trong mã nguồn, như tập tin wdl, pcx được đưa vào. Để mở những tập tin này, vào element list và nhấp vào mục cần thiết. Nếu nó là một tập tin wdl nó sẽ được mở trong SED, còn không nó sẽ được mở trong một ứng dụng thích hợp. 2.5. Kiểm tra cú pháp Đây là cách để kiểm tra xem có lỗi trong mã nguồn hay không. Để có thể phát hiện ra đúng các lỗi trong lúc viết mã, nên chạy Test Run trước khi kiểm tra cú pháp. Tuy nhiên, hàm Test Run sẽ tự động kiểm tra cú pháp sau khi engine được thực thi. Để kiểm tra cú pháp nhanh chóng, chúng ta có thể ấn phím F5 để chạy engine và ấn F6 để kiểm tra cú pháp. 2.6. Soạn thảo thông minh Trong khi đang viết mã chúng ta nên chú ý hàm tự động thụt đầu dòng, nó sẽ tự động thuộc đầu dòng khi ấn phím [ENTER], cũng khi ấn phím [ENTER] và nếu một biểu tượng “{” được đặc trên một dòng thì biểu tượng “}” sẽ được được thêm tự động. Một tab sẽ được thêm vào để thụt lề đoạn mã giữa các dấu ngoặc. Cũng giống như vậy, khi một biểu tượng “(” được thêm vào, thì một biểu tượng “)” sẽ tự động thêm vào. 73 Chương 3: Game Engine – Cách sử dụng SED Tất cả các cửa sổ trong SED là lưu động, có nghĩa là chúng nó có thể được di chuyển và được lưu trữ lại. Sử dụng khả năng này và điều chỉnh màu sắc để có được một giao diện tốt. Một số phím tắt nên nhớ: [Alt+Left] để nhảy đến vị trí cuối cùng trong cửa sổ soạn thảo. [Ctrl+F6] chuyển đổi giữa các tập tin mã nguồn đang mở. [F1] mở tập tin giúp đỡ và chuyển đến lệnh hiện thời mà con trỏ đang chi vào. 3. Cấu hình Đây là hộp thoại cấu hình, nó nằm ở vị trí Options->Configuration. Paths Thư mục 3D GameStudio – thư mục chứa tập tin acknex.exe. Environment Tabsize: kích thướt của tab trong mã nguồn, mặc định là 3. Show hints while writing: Mặc định, hiển thị lời chỉ dẫn trong khi viết hàm. Load included files: Mở các tập tin được bao gồm trong tập tin wdl khi mở tập tin này, mặc định là tắt. Start with a blank script: Mặc định bắt đầu với một tập tin trống rỗng. Auto Indent: Giống như trong thực đơn Options, tự động thụt lề khi ấn phím enter 74 Chương 3: Game Engine – Cách sử dụng SED Only use these extensions with the element list: đánh “wdl,pcx” để chỉ có các tập tin wdl và pcx trong danh sách thành phần. Update watches every X ms: Cập nhật cửa sổ sau X giây, nếu giá trị này thấp, nó sẽ được cập nhật nhanh hơn nhưng nó có thể làm cho quá trình gỡ lỗi chậm xuống. Nếu xem các giá trị không thay đổi thường xuyên, hãy chọn giá trị này càng lớn càng tốt. Back up: Enable backup when opening files: Nếu tùy chọn này được chọn, một bản sao sẽ được tạo ra mỗi khi một đoạn mã được mở. Nó sẽ được lưu trữ trong thư mục backup, hãy nhìn vào thư mục này, dung lượng của nó sẽ ngày càng tăng lên nhưng tùy chọn này nên được chọn. 4. Thực đơn 4.1. Thực đơn File Đây là thực đơn File, hầu hết mọi lệnh ở đây đều chuẩn ngoại trừ lệnh Commands Database. 75 Chương 3: Game Engine – Cách sử dụng SED 4.2. Thực đơn Edit Đây là thực đơn edit, chứa các lệnh chuẩn và các lệnh: Find In Files: Tìm đoạn văn bản trong các tập tin wdl trong một thư mục. Indent All: Thụt lề trong tất cả các tập tin wdl, để cho đoạn mã được dễ nhìn hơn. Comment Line : Tất cả các dòng được chọn đều trở thành các dòng chú thích, hoặc chỉ chuyển dòng hiện tại thành dòng chú thích nếu không chọn dòng nào. Uncomment Line : Xóa các dấu “//” trước các dòng được chọn, hoặc xóa dấu “//” trước dòng hiện tại nếu không chọn dòng nào. Insert Date: Chèn ngày hiện thời vào mã nguồn, Bookmarks: Đánh dấu một dòng để sau này có thể di chuyển đến dòng đó được dễ dàng hơn. 4.3. Thực đơn Options Đây là thực đơn Options, dưới đây là phần diễn giải các tùy chọn: Auto Indent: Tự động thụt lề khi nhấn phím enter. Show Line Numbers: Hiển thị số dòng ở phía bên trái, chúng ta có thể tìm thấy số dòng hiển thị ở phía dưới của SED. Highlight Selected Line: Làm nổi bật dòng được chọn bằng màu được chọn trong hộp thoại Customize Colors. 76 Chương 3: Game Engine – Cách sử dụng SED Show Toolbar: Hiển thị toolbar nếu nó đã bị tắt. Show Command Help: Hiển thị Command Help nếu nó đã bị tắt. Show Variable List: Hiển thị danh sách biến nếu nó đã bị tắt. Configuration: Hiển thị hộp thoại cấu hình. Font: Thay đổi Font. 4.4. Thực đơn Tools Đây là thực đơn tools, chứa các lệnh sau: Add Comment: Để cho người dùng có thể nhập dòng chú thích. To-Do List: Mang đến một danh sách có ghi tất cả những công việc bạn sẽ thực hiện. Color Picker: Để chọn một màu và giá trị RGB sẽ được đưa vào đoạn mã. Insert Image: Để chọn một ảnh và thêm nó và mã nguồn. 4.5. Thực đơn Debug Đây là thực đơn debug, nó chứa các lệnh sau: Syntax Check: Kiểm tra lỗi cú pháp trong lần chạy cuối cùng. Test Run: Khởi động engine sử dụng đường dẫn đã được cấu hình. Hãy chắc chắn những thiết lập này đều chính xác. Nếu engine được khởi động, nó sẽ tiếp túc chạy tập tin wdl. Step over: Bỏ qua các dòng tiếp theo và tiến tới vị trí gỡ lỗi hiện tại. Toggle Breakpoint: Chỉ được thực hiện khi engine được khởi động. 77 Chương 3: Game Engine – Cách sử dụng SED Goto current debugposition: Nhảy tới vị trí gỡ lỗi hiện tại. Ignore further breakpoints: Tiếp tục engine và bỏ qua các điểm dừng tiếp theo. Add watch: Thêm biến vào cửa sổ watch. Edit watch: Thay đổi giá trị được chọn trong cửa sổ watch trong lúc run-time. Delete watch: Xóa biến được chọn trong cửa sổ watch. 78 Chương 3: Game Engine – Cách viết một DLL IV. Giao tiếp với các DLL DLL là một thư viện ngoài chứa các hàm có sẵn hoặc do người dùng viết. DLL A6 có thể được sử dụng như phần mở rộng đến engine và ngôn ngữ C-Script. Để tạo ra một plugin A6, SDK (hệ thống phát triển mã nguồn) và ngôn ngữ lập trình như Visual C++ cần phải có. Plugin DLL được dùng để thêm nhiều tác động mới, như tạo ra trí thông minh cho một thực thế, chỉ lệnh C-Script mới, cũng như có thể viết toàn bộ chương trình bằng C++ hoặc Delphi thay vì viết bằng C-Script. Theo lý thuyết, mọi thứ: engine vật lý, một engine 3D khác hoặc ngay cả một ngôn ngữ viết kịch bản có thể được thêm vào engine theo cách này. Bởi vì plugin DLL làm việc với tất cả các phiên bản. 1. Bắt đầu với SDK Khi tạo một dự án mới (chọn File->New Project), VC++ sẽ đề nghị chọn một dự án mẫu. Hãy chọn Win32 Dynamic-Link Library, khi ứng dụng Win32 được hiển thị, chọn Simple DLL. VC++ sẽ tạo ra một dự án DLL. Tập tin main.cpp chứa mã nguồn như sau: //plugin.cpp : Defines the entry point for the DLL application. #include "stdafx.h" BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { return TRUE; } Chép tất cả các tập tin SDK vào thư mục được tạo ra bởi VC++. Bây giờ, đê biên dịch một plugin DLL A6, chúng ta phải liên kết thư viện a5dll.lib (một tập tin trong SDK) đến dự án (Project Properties -> Linker Input -> Additional Dependencies) và include tập tin a5dll.h và a5funcs.h đến tập tin main.cpp. 3 tập tin này là những tập tin cần thiết để tạo một DLL A6. 79 Chương 3: Game Engine – Cách viết một DLL Thư viện a5dll.lib chứa đựng một vài hàm cần thiết cho một DLL, như các hàm: a5dll_getwdlobj, a5dll_getwdlfunc, vả a5dll_getscript. Chúng ta sẽ xem những hàm này sau. Tuy nhiên DLL phải gọi ít nhất một trong những hàm này để chắc chắn rằng thư viện được liên kết đúng. Để làm điều này, thêm hàm theo sau: // để chắc chắn rằng thư viện được liên kết đúng void force_library(void) { a5dll_getwdlobj(""); } Bây giờ, chúng ta có thể thêm một hàm nào đó đến DLL, mà nó có thể được gọi trong một script, hoặc bởi hàm main của game. Để được engine chấp nhận, tất cả các hàm phải có loại là DLLFUNC fixed function(...), như thế này: // trả về giá trị x * 2n DLLFUNC fixed ldexpc(fixed x,fixed n) { return (FLOAT2FIX(FIX2FLOAT(x)*pow(2.0,FIX2FLOAT(n)))); } Hàm này trả về một biểu thức số học với các đối số của nó. DLLFUNC không phải là một phần của C++ - đây chỉ quy ước để định nghĩa một hàm DLL xuất. fixed là loại biến số của A6 và C-Script. Cả hai đều được định nghĩa trong a5dll.h với các hàm chuyển đổi sau: #define DLLFUNC extern "C" __declspec(dllexport) typedef long fixed; // fixed point 22.10 number format used by C-Script inline fixed INT2FIX(int i) { return i<<10; } inline int FIX2INT(fixed x) { return x>>10; } inline double FIX2FLOAT(fixed x) { return ((double)x)/(1<<10); } inline fixed FLOAT2FIX(double f) { return (fixed)(f*(1<<10)); } Engine sẽ đưa qua và nhận lấy tất cả đều là số - tọa độ, biến … đều được biểu diễn theo loại fixed. Vì thế, hãy chuyển đổi bất kì số nào thành fixed trước khi đưa nó trở lại engine, giống như trong ví dụ trên. 80 Chương 3: Game Engine – Cách viết một DLL Hãy dịch DLL - giả định rằng tên nó là plugin.dll - và chép nó thư mục work. Để gọi hàm ldexpc, chúng ta phải làm 2 việc: khai báo hàm và mở DLL. Việc đầu tiên được thực hiện bằng tuyên bố dllfunction trong script: dllfunction ldexpc(x,n); // khai báo một hàm dll Với khai báo này, hàm ldexpc sẽ được biết trong script. Trước khi gọi nó, chúng ta phải mở DLL. Hãy đặt nó trong hàm main của script: function main() { ... dll_open("plugin.dll"); và đừng quên đóng DLL trước khi thoát khỏi game: ... dll_close(dll_handle); exit; Sau hoàn thành, chúng ta có thể gọi chỉ lệnh thêm vào trong C-Script: ... x = ldexp(y,n); // tính x = y * 2n ... Để có thể debug DLL trong VC++, thiết lập lệnh trong Project Properties - > Debug đến đường dẫn của tập tin exe của engine ( ví dụ như "C:\program files\gstudio\bin\acknex.exe), đối số lệnh chỉnh đến tập tin script và tham số dòng lệnh (như “mygame.wdl -wnd") và thư mục làm việc đến thư mục game (như "C:\program files\gstudio\mygame"). Trong Project Properties -> General, thiết lập thư mục output và immediate là “.” (dấu chấm, có nghĩa là thư mục đang làm việc) để engine có thể tìm thấy tập tin ngay lập tức. Như vậy, chúng ta có thể biên dịch và debug bằng cách đặt các điểm dừng như thông thường. Đây chỉ là những kiến thức cơ bản để viết một plugin DLL. Vẫn còn nhiều thứ để học. Còn các phương pháp để trao đổi dữ liệu với engine sẽ được mô tả trong phần tiếp theo. Tất cả các hàm DLL có thể được khai báo và gọi đến trong script giống như những hàm trong C-Script, sau khi kích hoạt DLL bằng hàm dll_open và dll_close được mô tả trong phần hướng dẫn script. 81 Chương 3: Game Engine – Cách viết một DLL 2. Sử dụng đối tượng C-Script trong một DLL Chúng ta đã biết được cách thêm một chỉ lệnh C-Script mới, còn bên trong DLL chúng ta có thể truy cập biến, đối tượng và hàm như thế nào? Chúng ta có một vài hàm thư viện để làm điều này. Tất cả các hàm thư viện được cung cấp bởi SDK đều bắt đầu bằng a5dll_. Trong đó, hàm thường sử dụng nhất là: long a5dll_getwdlobj(char *name); Hàm này trả về địa chỉ của một đối tượng hoặc biến có tên được đưa. Nó có thể sử dụng để đọc hoặc ghi bất kì một đối tượng C-Script bên trong một DLL. Nếu đối tượng không tồn tại, nó sẽ trả về NULL và sẽ xuất hiện một lỗi. Thí dụ một hàm DLL mà truy cập đối tượng trong C-Script. // thêm giá trị được đưa vào một vector trong C- Script fixed AddToVector(fixed value) { // lấy địa chỉ của một biến static fixed *myvector = (fixed *)a5dll_getwdlobj("myvector"); // thêm cùng giá trị đến 3 thành phần của vector myvector[0] += value; myvector[1] += value; myvector[2] += value; return value; } Vì thế, chúng ta có thể sử dụng hàm này để lấy một con trỏ chỉ đến một đối tượng bất kì trong C-Script, không kể nó được định nghĩa trước bởi engine hoặc do chúng ta định nghĩa. Trong trường hợp, nó là một vector được định nghĩa trong C-Script như sau: var myvector[3] = 1,2,3; 82 Chương 3: Game Engine – Cách viết một DLL và hàm a5dll_getwdlobj sẽ trả về một con trỏ đến vector này, cũng chính là một mảng các số fixed. Tại sao chúng ta lại không chuyển sang số kiểu fixed?. Bởi vì value có kiểu là fixed và chúng ta có thể cộng hai số kiểu fixed giống như 2 số nguyên. Nhưng chúng ta không thể chia hoặc nhân 2 số này. Như vậy, bây giờ chúng ta đã biết cách đọc và thay đổi các biến trong C- Script, nhưng với những đối tượng phức tạp như panel, view, entity thì sao? Hãy nhìn vào tập tin a5dll.h – chúng ta sẽ tìm thấy tất cả các cấu trúc được định nghĩa trong C-Script, như A4_STRING, A4_ENTITY, A4_PARTICLE, A4_BMAP, A4_TEX … Tất cả những cấu trúc này bắt đầu với A4_ ( ngay cả trong phiên bản A6). Chúng ta sẽ nhận được một con trỏ chỉ đến cấu trúc như với các biến. // phóng to, thu nhỏ camera view fixed ZoomIn(fixed value) { static A4_VIEW *camera = (A4_VIEW *)a5dll_getwdlobj("camera"); return (camera->arc -= value); } camera là một khung nhìn (view) đã được định nghĩa trước. Bởi vì a5dll_getwdlobj trả về một số nguyên kiểu long, chúng ta có thể ép nó đến kiểu mong muốn, như toán tử (A4_VIEW *) ở đây. Trong a5dll.h, chúng ta có thể thấy các khai báo cấu trúc, với cấu trúc A4_VIEW chứa đựng tất cả các tham số mà chúng ta sử dụng trong C-Script, như arc, ambient…Và một khi có được con trỏ chúng ta có thể thay đổi và đọc tất cả chúng. 3. Sử dụng các hàm API Truy cập các hàm trong C-Script tương tự như truy cập đối tượng C-Script, bằng hàm: long a5dll_getwdlfunc(char *name); 83 Chương 3: Game Engine – Cách viết một DLL Hàm này trả về địa chỉ của một hàm trong C-Script có tên là name. Nó có thể được sử dụng để gọi một hàm engine trong DLL. Không phải tất cả các hàm trong C-Script đều có giá trị trong DLL. Nếu chỉ lệnh không có giá trị bởi vì nó không được hiểu trong DLL (như hàm wait() hoặc INKEY()), NULL sẽ được trả về cùng với một lỗi. Thí dụ một hàm DLL cho một thực thể AI sử dụng các hàm C-Script để quét môi trường xung quanh thực thể. // trả về khoảng cách từ thực thể đến một chướng ngại vật phía trước fixed DistAhead(long p_ent) { if (!my) return 0; // lấy lại con trỏ đến thực thể được đưa A4_ENTITY *ent = (A4_ENTITY *)p_ent; // lấy địa chỉ của các biến và hàm static wdlfunc2 vecrotate = (wdlfunc2)a5dll_getwdlfunc("vec_rotate"); static wdlfunc3 c_trace = (wdlfunc3)a5dll_getwdlfunc("c_trace"); fixed target[3] = { FLOAT2FIX(1000.0),0,0 }; //vector đích // quay vector đích hướng đến thực thể (*vecrotate)((long)target,(long)&(ent->pan)); // thêm vị trí của thực thể vào vector đích target[0] += ent->x; target[1] += ent->y; target[2] += ent->z; // truy tìm theo một dòng giữa thực thể và đích, // và trả về kết quả fixed tracemode = INT2FIX(TRMF_IGNORE_ME + TRMF_IGNORE_PASSABLE + TRMF_USE_BOX); return (*c_trace)((long)&(ent- >x),(long)target,tracemode); } Chúng ta sẽ xem chi tiết đoạn mã: wdlfunc2 vecrotate = (wdlfunc2)a5dll_getwdlfunc("vec_rotate"); 84 Chương 3: Game Engine – Cách viết một DLL wdlfunc2 là một quy ước loại của con trỏ chỉ đến các chỉ lệnh trong C-Script nhận hai tham số. Bởi vì tất cả các chỉ lệnh trong C-Script nhận 1, 2, 3 hoặc 4 đối số, nên có 4 định nghĩa trong a5dll.h: typedef fixed (*wdlfunc1)(long); typedef fixed (*wdlfunc2)(long,long); typedef fixed (*wdlfunc3)(long,long,long); typedef fixed (*wdlfunc4)(long,long,long,long); Một khi, chúng ta nhận con trỏ đến chỉ lệnh đó, nó được đề nghị để lấy lại con trỏ đến tất cả các chỉ lệnh được sử dụng trong hàm khởi động, chúng ta sẽ gọi nó như thế này: (*vecrotate)((long)target,(long)&(ent->pan)); Có một ít khác so với việc thường gọi một hàm trong C++. Tuy nhiên, nó khá dễ hiểu: Chúng ta có một con trỏ hàm, vì thế để gọi nó, chúng ta phải sử dụng (*…). Và đối số được đưa luôn luôn là kiểu fixed hoặc long. Chúng ta đã ép kiểu nó thành kiểu long thay cho kiểu fixed, đó chỉ là sự thỏa thuận rằng chúng ta đang đưa một tham số con trỏ. Tất cả các chỉ lệnh vector đều đòi hỏi một con trỏ các số fixed. Nếu chỉ lệnh không đòi hỏi một vector hoặc một giá trị mà là cái gì đó phức tạp hơn như là một thực thể. Chúng ta chỉ cần đưa một con trỏ A4_ENTITY được ép sang kiểu long. Nhưng nếu nó đòi hỏi một chuỗi thì sao – chúng ta sẽ sử dụng con trỏ kiểu A4_STRING hoặc chỉ cần đưa char*? Chúng ta phải sử dụng A4_STRING. Trong thí dụ ackdll.cpp, chúng ta có thể tìm thấy cách để đưa một hằng chuỗi vào một chỉ lệnh trong C-Script. long pSTRING(char* chars) { static A4_STRING tempstring; static char tempchar[256]; strncpy(tempchar,chars,255); tempstring.chars = tempchar; tempstring.link.index = 4<<24; 85 Chương 3: Game Engine – Cách viết một DLL return (long)&tempstring; } //thí dụ được ra một chuỗi để tạo một thực thể DLLFUNC fixed create_warlock(long vec_pos) { static wdlfunc3 ent_create = (wdlfunc3)a5dll_getwdlfunc("ent_create"); return (*ent_create)(pSTRING("warlock.mdl"),vec_pos,0); } Một vài chỉ lệnh không thể được gọi trực tiếp từ một DLL. Tuy nhiên, chúng có thể được thi hành trực tiếp bằng cách gọi một script thực hiện những chỉ lệnh đó. Script có thể được gọi từ DLL bằng hàm sau: long a5dll_getscript(char *name); Hàm này trả về địa chỉ của một hàm script được định nghĩa bởi người dùng có tên được đưa. Nó có thể được dùng để gọi một hàm hoặc hành động trong C- Script được định nghĩa bởi người dùng bên trong một DLL. Nếu hàm không tìm thấy, NULL sẽ được trả về và phát sinh ra một lỗi. fixed a5dll_callscript(long script,long p1=0,long p2=0,long p3=0,long p4=0); fixed a5dll_callname(char *name,long p1=0,long p2=0,long p3=0,long p4=0); Hàm này gọi một hàm script được định nghĩa bởi người dùng có địa chỉ hoặc tên được đưa. 4 tham số được đưa có thể là một số kiểu fixed, mảng, hoặc một con trỏ đến một đối tượng C-Script. Nếu hàm yêu cầu ít hơn 4 tham số, các tham số còn lại đặt là 0. Thí dụ một hàm DLL gọi một hàm đã được định nghĩa trong C-Script: DLLFUNC fixed WDLBeep(fixed value) { // lấy con trỏ hàm static long beeptwice = a5dll_getscript("beeptwice"); // gọi hàm 86 Chương 3: Game Engine – Cách viết một DLL return a5dll_callscript(beeptwice,0,0,0,0); } Hàm DLL này sẽ yêu cầu hàm trong C-Script sau sẽ được gọi: function beeptwice() { beep; beep; } 4. Lập trình một game trong C++ Sử dụng đối tượng A4_ENTITY, một DLL có thể thực thi một hàm AI phức tạp, mà nó sẽ rất khó để viết trong C-Script. Ngay cả toàn bộ game đều có thể được viết trong một DLL. Thí dụ sau chỉ ra cách thay đổi tham số thực thể trong một hàm DLL. // lăn tròn thực thể được đưa một góc 180 độ DLLFUNC fixed FlipUpsideDown(long entity) { if (!entity) return 0; // lấy con trỏ đến thực thể được đưa A4_ENTITY *ent = (A4_ENTITY *)entity; // gán góc lăn tròn của thực thể đến 180 độ ent->roll = FLOAT2FIX(180); return 0; } Hàm này sẽ được gọi trong C-Script bằng hàm FlipUpsideDown(my). Để điều khiển toàn bộ thực thể trong một DLL, giả sử chúng ta viết toàn bộ một game bằng ngôn ngữ C++, một hành động giả trong C-Script có thể được gắn vào thực thể như sau: var appdll_handle; dllfunction dll_entmain(entity); dllfunction dll_entevent(entity); function main() { // Mở DLL appdll_handle = dll_open("myapp.dll"); ... } action myent_event { dll_handle = appdll_handle; 87 Chương 3: Game Engine – Cách viết một DLL dll_entevent(my); // hàm DLL này điều khiển tất cả các sự kiện của thực thể } action myentity { my.event = myent_event; while(1) { dll_handle = appdll_handle; dll_entmain(my); // hàm DLL này điều khiển thực thể wait(1); } } 88 Chương 4: Cài đặt 89 Chương 4 CÀI ĐẶT Game được tạo thành từ các thành phần: các khối và các thực thể. Các khối sẽ được kết hợp lại trong lúc thiết kế bằng WED để tạo ra một khung cảnh môi trường tĩnh trong game. Các thực thể: sprite (núi, mặt trời, mây…), mô hình (bao gồm cây trồng, xe của người chơi và các xe tự điều khiển…), terrain (tạo ra một địa hình có hình dạng phức tạp). Các thành phần tĩnh hoặc không cần xử lý hoặc đòi hỏi xử lý rất ít trong lúc viết mã. Do đó lúc cài đặc chủ yếu tập trung vào hai đối tượng quan trọng là chiếc xe do người chơi khiển khiển và những chiếc xe tự chuyển động theo một hướng nào đó. I. Cài đặt cho người chơi 1. Chuyển động vật lý a. Gia tốc, quán tính và lực ma sát Giả định rằng, người chơi di chuyển thẳng về phía trước, quãng đường mà nó đi được tăng lên theo vận tốc và thời gian: ds=v*dt v là tốc độ đo bằng quants (khoảng 1 inch) trên tick(khoảng 1/16s). ds quãng đường đi được đo bằng quants. dt thời gian để người chơi đi được quãng đường ds, do bằng tick. Nếu muốn thay đổi vận tốc của người chơi, chúng ta cần phải tác dụng một lực lên người chơi, lực càng lớn làm cho vận tốc người chơi thay đổi càng Chương 4: Cài đặt 90 nhanh. Theo vật lý học, thì khi một vật thay đổi vận tốc, thì nó sẽ bị một sự kháng cự nào đó. Đó chính là quán tính, phụ thuộc vào khối lượng m của một vật. Với cùng một lực tác dụng, thì vật có khối lượng càng lớn có sự thay đổi vận tốc càng nhỏ. Ta đã biết được các công thức: a - gia tốc – sự thay đổi của vận tốc trên 1 tick f - lực tác dụng m - khối lượng của vật. Chúng ta sẽ xem xét 3 loại lực tác dụng. - Lực đẩy (propelling force): Lực này dùng để thay đổi vận tốc người chơi, được tạo ra khi ấn phím. Trong một game đơn giản, lực này sẽ tùy thuộc vào khối lượng của người chơi. Đối với người chơi có khối lượng càng lớn thì lực tác dụng đến nó càng lớn. p=p*m - Lực thứ hai là lực hấp dẫn, có thể là một lực hút người chơi đến một hướng nào đó, hoặc là trọng lực của trái đất. Lực hấp dẫn thay đổi từ nơi này đến nơi khác. Trong game, độ lớn của lực này cũng tùy thuộc vào khối lượng của người chơi. Điều này rất dễ thấy trong trường hợp lực này là trọng lực: d=d*m - Lực thứ 3 tác dụng lên người chơi là lực ma sát. Lực này làm cản trở hoạt động của người chơi, liên tục làm giảm tốc độ của anh ta. Không giống như vật lý học, là lực hãm bao gồm lực ma sát và những thành phần suy giảm, chúng ta sử dụng lực giả tạo, nó sẽ tăng với khối lượng người chơi (sức ép lên mặt đất) và tốc độ: r= - f*m*v r: lực ma sát. dv = a*dt a=f/m Chương 4: Cài đặt 91 f: hệ số ma sát. v: vận tốc. m: khối lượng. Hệ số của lực ma sát tùy thuộc vào bề mặt mà người chơi đang di chuyển trên đó: tuyết có hệ số ma sát thấp hơn đá. Trong không trung, hệ số ma sát hầu như bằng 0. Dấu trừ chỉ rằng lực lực có chiều ngược hướng với vận tốc của người chơi. Khối lượng của người chơi là m, cũng là một thành phần trong phương trình, bởi vì trong game, nếu trọng lượng của người chơi lớn thì lực ma sát ép vào bề mặt càng lớn. Như vậy, 3 lực: lực đẩy f, lực hấp dẫn d và lực giảm tốc độ r đều làm thay đổi vận tốc của người chơi. dv phải được thêm vào vận tốc sau mỗi trạng thái (frame). p, d và f là các lực đẩy, lực hấp dẫn, lực ma sát. dt là khoảng thời gian mà vận tốc thay đổi - ở đây nó là thời giữa hai trang thái, bởi vì chúng ta đang tính vận tốc mới sau mỗi trạng thái. Chúng ta sẽ làm cho tất cả các lực cân xứng với trọng lượng, vì thế yếu tố khối lượng sẽ không còn cần thiết nữa, nó không còn có tác dụng gì trong chuyển động vật lý nữa, chúng ta sẽ loại nó ra khỏi phương trình. Chúng ta sẽ viết mã nguồn cho công thức cuối cùng như sau: dv = a * dt dv = (p + d + r) / m * dt dv = (p + d - f * v) * dt Chương 4: Cài đặt 92 Lực tác dụng là 10, lực ma sát là 0.7 trong cả lúc quay và lúc di chuyển. Chúng ta sử dụng các skill (biến có sẵn của thực thể), để lưu giữ vận tốc hiện tại của thực thể, bởi vì cần biết vận tốc của người chơi ở trạng thái trước để tính toán lực tác dụng hiện tại vào người chơi. Chúng ta đã sử dụng skill14 để cất giữ vận tốc góc, và skill11 để lưu vận tốc di chuyển về phía trước. time là dt trong công thức tính quãng đường đi được, vì thế người chơi sẽ di chuyển cùng vận tốc trên mọi PC, nó hầu như độc lập với tốc độ khung! Chú ý rằng, chúng ta đã thay đổi công thức gốc, theo lý thuyết thì công thức tính sự thay đổi của vận tốc trên một chu kỳ trạng thái là: Nó sẽ được viết lại trong script như sau: var force[3]; var dist[3]; action move_me { while (1) { force.PAN = -10 * KEY_FORCE.X; // tính lực quay my.SKILL14 = TIME*force.PAN + max(1- TIME*0.7,0)*my.SKILL14; // vận tốc quay my.PAN += TIME * my.SKILL14; // quay người chơi force.X = 10 * KEY_FORCE.Y; // tính toán lực di chuyển my.SKILL11 = TIME*force.X + max(1-TIME*0.7,0)*my.SKILL11; // tính vận tốc dist.X = TIME * my.SKILL11; // quãng đường đi được dist.Y = 0; dist.Z = 0; move_mode = ignore_passable + glide; ent_MOVE(dist,nullvector); // di chuyển người chơi move_view(); // di chuyển camera theo người chơi wait(1); } } v -> v + dv v -> v + (p - f * v) * d Chương 4: Cài đặt 93 my.SKILL11 = my.skill11 + (force.X - 0.7 * my.SKILL11) * time; và được thay đổi từng bước như sau: my.SKILL11 = my.skill11 + TIME * force.X - time * 0.7 * my.SKILL11; my.SKILL11 = TIME * force.X + (1-time*0.7) * my.SKILL11; Cuối cùng cho ra một kết quả khá phức tạp: my.SKILL11 = TIME * force.X + max(1-TIME*0.7,0) * my.SKILL11; Như vậy, chúng ta đã hoàn thành script cho công thức tính vận tốc trên. Nhưng chúng ta sử dụng hàm max ở đây để làm gì. Max(a.b) so sánh hai giá trị a và b, và trả về giá trị nào lớn hơn. Khi sử dụng max(1-TIME*0.7,0), nó sẽ chỉ cho ra kế quả dương, chúng ta phải làm như vậy, bởi vì trong các máy tính có tốc độ khung quá thấp, và giá trị time quá lớn – TIME*0.7 có thể sẽ lớn hơn 1, cho ra kết quả âm. Điều này làm cho vận tốc bị đảo chiều, và người chơi sẽ di chuyển ngược ra sau. b. Rơi từ trên xuống Trong khi sự di chuyển theo chiều ngang của người chơi chủ yếu được tác dụng từ các lực do người sử dụng ấn phím, thì sự di chuyển theo chiều dọc là do tác dụng của trọng lực tác dụng vào người chơi. Nếu người chơi đang đứng ở trên mặt đất, nó sẽ không cần di chuyển theo chiều dọc. Nhưng nếu người chơi bỗng nhiên rơi vào không trung – như nếu nó tiến đến một vực thẳm, thì nó nhất định phải rơi xuống. Để xác định người chơi có ở trạng thái này hay không, cần phải xác định người chơi cách đất bao nhiêu. Chúng ta sẽ sử dụng lệnh trace: Chương 4: Cài đặt 94 Lệnh trace trả về khoảng cách đến chướng ngại vật đầu tiên mà một tia giữa 2 điểm (2 vector được đưa). Chúng ta đang sử dụng điểm trung tâm của người chơi (my.x) là điểm thứ nhất, và điều chỉnh điểm temp để nó là điểm thứ 2 nằm phía dưới người chơi khoảng 4000 quant. Bằng cách sử dụng chế độ use_box, chúng ta đang sử dụng tia truy tìm có một bề dày là vỏ bao bên ngoài người chơi, thông thường có đường kính là 32 quant – nó cũng trả về khoảng cách đến điểm dưới cùng của hộp bao bên ngoài người chơi, không phải là trung tâm action move_me { while (1) { force.PAN = -10 * KEY_FORCE.X; // tính lực quay my.SKILL14 = TIME*force.PAN + max(1-TIME*0.7,0)*my.SKILL14; // tính vận tốc quay my.PAN += TIME * my.SKILL14; // quay người chơi force.X = 10 * KEY_FORCE.Y; my.SKILL11 = TIME*force.X + max(1-TIME*0.7,0)*my.SKILL11; // tính vận tốc tiến về trước dist.X = TIME * my.SKILL11; // quãng đường đi được về phía trước dist.Y = 0; vec_set(temp,my.x); temp.z -= 4000; // một vị trí nằm dưới người chơi 4000 quants // chọn các chế độ truy tìm các thực thể và bề mặt từ vị trí của người chơi trace_mode = ignore_me+ignore_sprites+IGNORE_MODELS+USE_BOX; dist.z = -trace(my.x,temp); // trừ khoảng cách theo chiều dọc move_mode = ignore_passable + glide; ent_MOVE(dist,nullvector); // di chuyển người chơi move_view(); // di chuyển camera wait(1); } } Chương 4: Cài đặt 95 của người chơi. Nếu chúng ta xem điểm dưới cùng của hộp bao bên ngoài là chân của người chơi, thì lệnh trace sẽ tính chân người chơi đang cách mặt đất là bao nhiêu. Nếu chân của nó đang ở dưới nền đất, lệnh trace sẽ trả về kết quả âm. Vì thế dist.z được gán bằng phủ định của khoảng cách trả về từ lệnh trace. Theo phương pháp này, người chơi có thể đi bộ giữa những vùng có độ cao khác nhau, như lên hoặc xuống cầu thang. Chú ý để cho đơn giản chúng ta dùng vector khoảng cách tương đối (dist) để di chuyển người chơi theo hướng Z. Thông thường ở đây, nên sử dụng khoảng cách tương đối. Nhưng người chơi sẽ điều chỉnh đến các độ cao khác nhau ngay lập tức. Cách di chuyển này rất không tự nhiên. Để cho sự di chuyển được tự nhiên hơn, chúng ta sẽ tìm các lực tác động đến người chơi theo hướng thẳng đứng: - Khi người chơi là một chiếc máy bay thì lệnh trace trả về một giá trị lớn hơn 0 - chỉ những lực hấp dẫn và ma sát mới tác dụng lên nó. Lực hấp dẫn ở đây là trọng lực mà chúng ta đã đề cập, nó kéo người chơi xuống phía dưới. - …cho đến khi người chơi tiến đến mặt đất hoặc chìm vào nó - lệnh trace sẽ trả về một giá trí nhỏ hơn hoặc bằng 0. Trong trường hợp này thì lực đàn hồi sẽ tác động. Đây là loại lực mới, nó càng mạnh khi người chơi càng chìm sâu trong nền đất. Nó tạo ra cho người chơi một gia tốc hướng lên. Hơn nữa gia tốc của người chơi sẽ tăng đáng kể khi người chơi tiến vào mặt đất. Sử dụng kĩ thuật này, và tùy theo trung tâm của thực thể người chơi, mà chân của thực thể có thể đi xuyên qua bề mặt rắn, không thể qua được. Trong thế giới thực loại bề mặt này dĩ nhiên không bao giờ đi qua được, nhưng với đầu gối của người chơi, nó sẽ tạo ra cùng tác dụng. Vì thế sự xuất hiện của lực đàn hồi là kết quả từ việc nhún của đầu gối - nếu người chơi là một động cơ – thì đó là bộ giảm xóc. Cũng tương tự chúng ta có thể giải thích sự tăng lên của Chương 4: Cài đặt 96 lực ma sát trên nền đất bằng lực ma sát được tạo ra bởi lực nhún của người chơi hoặc bộ giảm xóc của xe. var friction; action move_me { while (1) { force.PAN = -10 * KEY_FORCE.X; // tính lực quay my.SKILL14 = TIME*force.PAN + max(1-TIME*0.7,0)*my.SKILL14; // vận tốc quay my.PAN += TIME * my.SKILL14; // quay người chơi vec_set(temp,my.x); temp.z -= 4000; // một vị trí nằm dưới người chơi 4000 quants // chọn các chế độ truy tìm các thực thể và bề mặt từ vị trí của người chơi trace_mode = ignore_me+ignore_sprites+IGNORE_MODELS+USE_BOX; result = trace(my.x,temp); // subtract vertical distance to ground if (RESULT > 5) // có phải đang ở trong không gian? { force.X = 0; // không có lực đẩy force.Y = 0; force.Z = -5; // trọng lực friction = 0.1; // lực ma sát của không khí } Chương 4: Cài đặt 97 Bằng cách sử dụng lệnh if với kết quả truy tìm được, chúng ta có thể xác định người chơi đang ở trong không khí hoặc cách mặt đất 5 quant. Trong trường hợp đầu tiên, các lực được tạo ra từ bàn phím sẽ không có tác dụng, mà người chơi sẽ rơi xuống dưới tác dụng của trọng lực. Trong trường hợp sau, xuất hiện lực đàn hồi tương ứng với độ sâu khi thực thể chìm vào mặt đất, sẽ đẩy người chơi ra khỏi mặt đất. Chúng ta đang sử dụng nhiều skill của thực thể, skill13 sẽ lưu vận tốc theo phương thẳng đứng hiện tại của thực thể. 2. Cách di chuyển camera theo người chơi 2.1. Tầm nhìn của người thứ nhất else // đang ở trên mặt đất { force.X = 10 * KEY_FORCE.Y; // lực quay force.Y = 0; force.Z = -0.5 * RESULT; // lực nẩy friction = 0.7; // lực ma sát dưới nền đất } my.SKILL11 = TIME*force.X + max(1-TIME*friction,0)*my.SKILL11; // tính vận tốc tiến về phía trước my.SKILL13 = TIME*force.Z + max(1-TIME*friction,0)*my.SKILL13; // tính vận tốc theo phương thẳng đứng dist.X = TIME * my.SKILL11; // quãng đường tiến về phía trước dist.y = 0; dist.Z = TIME * my.SKILL13; // quãng đường rơi xuống move_mode = ignore_passable + glide; ent_MOVE(dist,nullvector); // di chuyển người chơi move_view(); // di chuyển camera theo wait(1); } } Chương 4: Cài đặt 98 Hãy đặt viết mã nguồn như sau: Và gọi hàm init_camera trong hàm main sau khi gọi hàm level_load. Lưu và chạy ứng dụng. Khi đó camera sẽ di chuyển theo người chơi. Chúng ta đã định nghĩa một khung nhìn mới. Hãy tượng tượng một khung nhìn trong game. Nó chỉ là một cửa sổ. Chúng ta có thể xác định vị trí và kích thướt của cửa sổ bằng cách điều chỉnh các thuộc tính pos_x, pos_y, size_x, size_y. Như trên, chúng ta đã định nghĩa, góc trên bên trái của khung nhìn trùng với góc trên bên trái của màn hình và có kích thướt của nó cũng bằng kích thướt của màn hình. view 1st_person { layer = 1; pos_x = 0; pos_y = 0; } function init_cameras() { camera.visible = off; 1st_person.size_x = screen_size.x; 1st_person.size_y = screen_size.y; 1st_person.genius = player; 1st_person.visible = on; } function update_views() { 1st_person.x = player.x; 1st_person.y = player.y; 1st_person.z = player.z; 1st_person.pan = player.pan; 1st_person.roll = player.roll; 1st_person.tilt = player.tilt; } Chương 4: Cài đặt 99 Hàm update_views, sẽ được cập nhật sau mỗi trạng thái và thay đổi tọa độ x, y, z của camera trong game. Ở đây, chúng ta thay đổi nó đến tọa độ của người chơi, vì thế làm cho camera luôn luôn ở bên trong người chơi. Đoạn mã vẫn chưa được hoàn hảo. Ví dụ, nếu camera được đặt ở điểm trung tâm của mô hình người chơi, không ở trên đầu, là nguyên nhân làm cho khung nhìn đặt quá gần nền đất. Như vậy chúng ta chưa thể nhìn lên hoặc xuống. Chúng ta định nghĩa thêm một biến ở đầu đoạn mã. var eye_height = 20; và thay vì 1st_person.z = player.z; chúng ta viết lại là: 1st_person.z = player.z + eye_height; Nó sẽ điều chỉnh camera lên cao hơn 20 quant. Nếu giá trị này chưa được tốt, có thể điều chỉnh lại trong lúc chơi game. Bây giờ, chúng ta muốn người chơi có thể nhìn lên hoặc nhìn xuống. Để thực hiện được, chúng ta cần các biến sau: var tilt_1st = 0; var cam_turnspeed = 2; var max_tilt_1st = 40; và thay đổi dòng mã: 1st_person.tilt = player.tilt; thành: 1st_person.tilt = player.tilt + tilt_1st; và thêm các hàm sau: Chương 4: Cài đặt 100 Những hàm này phải được gọi trong lúc chơi game. Ví dụ, chúng ta có thể định nghĩa như thế này: on_pgup = look_up; on_pgdn = look_down; Hãy lưu lại và chạy thử. Không tồi, nhưng chưa có tác dụng như mong muốn. Nếu ấn một phím và giữ nó, thì khung nhìn chỉ di chuyển một ít và sẽ không thay đổi nữa. Chúng ta phải ấn nó nhiều lần để khung nhìn có thể di chuyển và điều này quá chậm chạp. Chúng ta sẽ thay đổi nó như thế nào? Thay đổi định nghĩa on_pageup và on_pagedown như sau: on_pgup = handle_pageup; on_pgdn = handle_pagedown; Và định nghĩa 2 hàm sau: function look_up() { if (tilt_1st < max_tilt_1st) { tilt_1st += cam_turnspeed; } } function look_down() { if (tilt_1st > -max_tilt_1st) { tilt_1st -= cam_turnspeed; } } Chương 4: Cài đặt 101 Theo cách này các hàm của chúng ta sẽ được gọi trong mỗi trạng thái trong khi các phím này đang được ấn. Nếu camera quay quá nhanh hoặc quá chậm, có thể điều chỉnh giá trị cam_turnspeed. Nếu phạm vi của khung nhìn quá nhỏ và để người chơi có thể thay đổi góc nhìn nhiều hơn, hãy thay đổi biến max_tilt_1st. Giá trị 90 ở đây có nghĩa là: người chơi có thể nhìn thẳng lên hoặc xuống. Giá trị trên 90 tức là người chơi có thể nhìn ngược ra sau. 2.2 Quay tự do tầm nhìn của người thứ 3 Bây giờ chúng ta muốn tạo ra một khung nhìn có thể quay của người thứ 3. Có hai cách thực hiện: cách thứ nhất là tạo ra một camera ở bên ngoài người chơi và không quay, ngay cả khi nó đi, luôn luôn đối diện với nó từ một hướng nào đó. Cách thứ 2 là tạo ra một camera có thể rẽ khi người chơi rẽ, vì thế nó sẽ luôn nằm ở đằng sau người chơi (hoặc trước, hoặc bên cạnh …). Chúng ta sẽ làm cho camera này có thể rẽ và phóng to thu nho tự do. Để bắt đầu, chúng ta sẽ tạo ra một khung nhìn của người thứ 3 luôn luôn đối mặt với function handle_pageup() { while (key_pgup) { look_up(); wait(1); } } function handle_pagedown() { while (key_pgdn) { look_down(); wait(1); } } Chương 4: Cài đặt 102 người chơi từ một hướng và những thay đổi khi muốn khung nhìn quay cùng với thực thể. Định nghĩa khung nhìn thứ 2 như sau: Chúng ta chưa làm cho nó có thể nhìn thấy được. Thay vì, chúng ta sẽ có thể thay đổi camera trong lúc chơi game. Chúng ta sẽ định nghĩa thủ tục chuyển đổi camera ngay bây giờ: view 3rd_person { layer = 1; pos_x = 0; pos_y = 0; } Thêm dòng sau vào bên trong hàm "init_cameras": ... 3rd_person.size_x = screen_size.x; 3rd_person.size_y = screen_size.y; ... var cam_mode = 0; // 0 cho người thứ nhất, 1 cho người thứ 3 function toggle_cams() { if (cam_mode == 0) { // Thay đổi đến người thứ 3 1st_person.visible = off; 3rd_person.visible = on; cam_mode = 1; } else { // thay đổi đến người thứ nhất 3rd_person.visible = off; 1st_person.visible = on; cam_mode = 0; } } on f8 = toggle cams; Chương 4: Cài đặt 103 Như vậy, bằng cách nhấn f8 ta có thể chuyển đổi giữa các khung nhìn. Tuy nhiên, nó chưa có tác dụng, bởi vì các tọa độ x, y, z của người thứ 3 chưa được định nghĩa. Chúng ta sẽ định nghĩa ngay giờ bằng cách thay đổi hàm "update_views". Nhưng chúng ta sẽ định nghĩa một camera quay quanh người chơi như thế nào? Nó nên nằm trên cùng một mặt phẳng, vì thế giá trị z là giống nhau. Nhưng chúng ta sẽ tính giá trị x và y như thế nào? Chúng ta cần một chút toán học. Tưởng tượng người chơi được quan sát từ phía trên. Cách tốt nhất là lấy một tờ báo và đặt một điểm trên nó, ở một nơi nào đó, để chỉ ra vị trí của người chơi được thấy từ phía trên. Vẽ một đường tròn xung quanh điểm này. Đây là vòng tròn mà camera sẽ di chuyển trên đó. Vòng tròn này có một bán kính nào đó. Giả sử trên vòng tròn có một điểm là điểm gốc, khi đó chúng ta có thể xác định một điểm P trên vòng tròn bằng một cung giữa đường thẳng vẽ từ gốc đến tâm và từ P đến tâm của đường tròn. Như vậy, khi có khoảng cách đến người chơi (bán kính của đường tròn) và một góc trong mặt phẳng thì vị trí của camera sẽ được xác định. Chương 4: Cài đặt 104 Lưu mã nguồn và chạy nó. Chọn f8 để chuyển đổi giữa các khung nhìn. Nên xem người chơi trong khung nhìn của người thứ 3. Khi di chuyển, camera sẽ luôn luôn di chuyển cùng khoảng cách với người chơi, và sẽ không thay đổi vị trí khi người chơi rẽ. Nếu thay đổi giá trị của biến dist_planar thì camera sẽ tiến lại gần hoặc lùi ra xa. Nếu thay đổi góc cam_angle thì nó sẽ quay xung quanh người chơi, và luôn luôn đối diện với người chơi. Tuy nhiên vẫn còn vấn đề khi người chơi ở gần các bức tường. Camera sẽ thường xuyên đi xuyên qua các bức tường, và các chướng ngại vật sẽ chắn tầm nhìn của người chơi. Chúng ta sẽ giải quyết vấn đề này trong phần sau. Bây giờ chúng ta sẽ thay đổi mã nguồn một ít…chúng ta muốn di chuyển camera lên phía trên nhưng vẫn đối diện với người chơi. Và cũng cần phải giữ cùng var dist_planar = 300; // khoảng cách đến người chơi var cam_angle = 0; function update_views() { if (cam_mode == 0) { 1st_person.x = player.x; 1st_person.y = player.y; 1st_person.z = player.z + eye_height; 1st_person.pan = player.pan; 1st_person.roll = player.roll; 1st_person.tilt = player.tilt + tilt_1st; } else { 3rd_person.x = player.x - cos (cam_angle) * dist_planar; 3rd_person.y = player.y - sin (cam_angle) * dist_planar; 3rd_person.z = player.z; 3rd_person.pan = cam_angle; 3rd_person.roll = 0; 3rd_person.tilt = 0; } } Chương 4: Cài đặt 105 khoảng cách đến người chơi, vì thế sự di chuyển không còn theo đường tròn nữa mà theo hình cầu. Chúng ta sẽ thực hiện điều này như thế nào? Hãy tưởng tượng người chơi được nhìn từ bên cạnh. Nếu camera nằm trên mặt phẳng XY của người chơi. Hãy vẽ một đường thẳng từ nó đến người chơi, chúng ta sẽ nhận được một góc giữa mặt phẳng XY và đường này. Góc này sẽ xác định chính xác vị trí của người chơi. Hình sau đây sẽ biểu diễn vị trí của camera trong trường hợp này: Lúc này chúng ta cần chỉnh lại mã nguồn như sau: Chương 4: Cài đặt 106 Bằng cách thay đổi giá trị của các biến tilt_3rd, cam_angle, dist_total, chúng ta có thể di chuyển camera tự do xung quanh người chơi. Nó sẽ được di chuyển trên một mặt cầu với bán kính có thể được thay đổi bằng cách thay đổi giá trị của biến. 2.3 Cách để cho camera tránh chạm vào tường Ý tưởng là gửi một tia từ người chơi đến vị trí mới của camera, nếu thấy có một chướng ngại vật nằm trên tia này. Khi đó, khoảng cách giữa người chơi và camera cần được thay đổi… bằng khoảng cách giữa người chơi và chướng ngại vật. Thêm những dòng sau vào hàm update_views: var dist_total = 300; // thay đổi giá trị này để tiến lại gần hoặc ra xa thực thể var tilt_3rd = 0; function update_views() { ... else { dist_planar = cos (tilt_3rd) * dist_total; 3rd_person.x = player.x - cos (cam_angle) * dist_planar; 3rd_person.y = player.y - sin (cam_angle) * dist_planar; 3rd_person.z = player.z + sin (tilt_3rd) * dist_total; 3rd_person.pan = cam_angle; 3rd_person.roll = 0; 3rd_person.tilt = - tilt_3rd; } Chương 4: Cài đặt 107 Hàm này xác định vị trí của camera một cách hoàn toàn giống như cách trên, nó chỉ sử dụng một khoảng cách mới đến người chơi, đó là khoảng cách từ người chơi đến chướng ngại vật. Nó sẽ thay đổi từ từ để ngăn chặn việc camera nằm bên trong bức tường. Để đặt camera sau người chơi, chúng ta thay đổi những dòng: 3rd_person.x = player.x - cos (cam_angle) * dist_planar; 3rd_person.y = player.y - sin (cam_angle) * dist_planar; thành: function update_views() { ... 3rd_person.roll = 0; 3rd_person.tilt = - tilt_3rd; validate_view(); } ... } hàm trên sẽ được định nghĩa như sau: var dist_traced; function validate_view() { my = player; trace_mode = ignore_me + ignore_passable; dist_traced = trace (player.x, 3rd_person.x); if (dist_traced == 0) { return; } // không chạm vào bất kì chướng ngại vật nào if (dist_traced < dist_total) { dist_traced -= 5; // Di chuyển ra ngoài bức tường dist_planar = cos (tilt_3rd) * dist_traced; 3rd_person.x = player.x - cos (cam_angle) * dist_planar; 3rd_person.y = player.y - sin (cam_angle) * dist_planar; 3rd_person.z = player.z + sin (tilt_3rd) * dist_traced; } } Chương 4: Cài đặt 108 3rd_person.x = player.x - cos (cam_angle + player.pan) * dist_planar; 3rd_person.y = player.y - sin (cam_angle + player.pan) * dist_planar; chúng ta chỉ cần thêm góc quay của thực thể (player.pan) vào cam_angle. II. Xe tự động Tránh chướng ngại vật trên đường đi Giả sử người chơi muốn di chuyển từ một điểm bắt đầu đến điểm kết thúc. Tất nhiên nếu giữa nó không có chướng ngại vật nào hết thì rất đơn giản, chỉ cần cho nó di chuyển theo một đường thẳng đi qua điểm bắt đầu và điểm kết thúc. Ở đây chúng ta đặc biệt chú ý đến trường hợp: giữa 2 điểm bắt đầu và kết thúc có các chướng ngại vật: là các chiếc xe khác hoặc là các chướng ngại vật tĩnh. Hình 1 Chương 4: Cài đặt 109 Ví dụ ở đây: để người chơi di chuyển đến một vị trí nào đó ta có thể nhấp chuột đến vị trí cần người chơi cần đến: Lúc này điểm bắt đầu là vị trí của người chơi, điểm kết thúc là vị trí nút nhất của chuột. Có 8 hướng khác nhau xung quanh một người chơi, mỗi một hướng khác nhau ta xem đó là một nút. Một nút có thể có độ rộng khác nhau tùy thuộc vào chúng ta muốn chọn bao nhiêu. Hình 2 Nếu một nút chứa một chướng ngại vật thì ta xem nút đó là nút không có giá trị, sẽ không được chọn trong lúc tìm đường đến đích. Hình 3 3 4 5 kt 2 v v 1 v v bđ v V Chương 4: Cài đặt 110 Giả sử từ nút bắt đầu bđ, muốn tìm một đường đi đi đến nút cần đến (nút kt). Và các nút v là các chướng ngại vật: Ở đây, mỗi nút trung gian (như các nút 0, 1 … 7), sẽ chứa 2 giá trị là chi phí đường đi (waycost), và chi phí toàn bộ nút (nodecost). Mỗi nút có một nút cha, và một nút cha có 8 nút con (trong trường hợp bình thường), nằm xung quanh nút này. Giả sử một nút có chi phí đường đi là waycost, thì các nút con của nó sẽ có waycostcon=waycostcha+1; Trong đó nút bắt đầu không có nút cha và ta cho waycost của nó là 0. Ở đây ta sử dụng một heuristic, đó là khoảng cách từ nút đó đến nút đích (nút kt), ta kí hiệu là goal_dist, thì chi phí của một nút sẽ là: nodecost=waycost+goal_dist Như vậy thuật toán sẽ gồm các bước sau: Với thuật toán như vậy, trong hình 3 để tìm đường đi từ điểm bắt đầu (bd) đến điểm kết thúc (kt), ta sẽ đi qua con đường đi qua các nút 1, 2, 3, 4, 5. Bước 1: Mở nút bắt đầu Bước 2: - Chọn trong danh sách các nút mở ra một nút có nodecost nhỏ nhất. - Nếu không còn nút nào nữa thì kết thúc thuật toán. Bước 3: - Nếu nút này là nút đích thì thuật toán kết thúc. - Nếu không thì: + Tạo ra các nút con từ nút này, với điều kiện các nút con không phải là nút chứa các chướng ngại vật. + Đóng nút này lại và mở tất cả các nút con được tạo ra. Bước 4: Chương 4: Cài đặt 111 TÀI LIỆU THAM KHẢO [1] Hướng dẫn có sẵn trong chương trình 3D Game Studio. [2] Các tài liệu hướng dẫn trên trang web: www.3DGameStudio.com. [3] Bạch Hưng Khang, Hoàng Kiếm, Trí tuệ nhân tạo, các phương pháp và ứng dụng, Nhà xuất bản Khoa học và Kỹ thuật Hà Nội - 1999

Các file đính kèm theo tài liệu này:

  • pdfCNTT1007.pdf
Tài liệu liên quan