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;
                
              
                                            
                                
            
 
            
                
120 trang | 
Chia sẻ: haianh_nguyen | Lượt xem: 1529 | Lượt tải: 0
              
            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:
CNTT1007.pdf