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: 1346 | 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