IV. Giao tiếp với các DLL
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:
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ã:
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;
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
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 {
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); }
Chương 4: Cài đặt
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, qng đườ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.
Chương 4: Cài đặt
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à qn 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
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
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 tố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 qng đườ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 tố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
Chương 4: Cài đặt
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 đã hồ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.