GPBasics 05: Căn bản về điều khiển nhân vật

Chào các bạn, trong tutorial này chúng ta sẽ làm việc với một số vấn đề trong điều khiển nhân vật.

Chúng ta sẽ làm quen với khái niệm mới: Framerate independent movement. Trước khi đọc bài này thì chúng ta phải sử dụng các kiến thức về game loop (tài liệu về phần này trên internet rất nhiều nên mình không viết lại ở đây).

Như bạn đã biết rằng game loop luôn chạy trong một game, và vòng lặp game có thể chạy nhanh hay chậm tùy vào các thành phần bạn viết cho game, nếu các thành phần càng nhiều và càng phức tạp thì game loop sẽ chạy chậm hơn, và cũng phụ thuộc vào game play. Ví dụ như người chơi của bạn đến một màn chơi có 20 monster, có lúc số monster đó lại tăng lên 50 hoặc hơn, thì vòng lặp sẽ chậm đi, nếu như bạn đang code điều khiển nhân vật như thế này:

if (nút A được bấm)
    vị trí nhân vật += x; // x ở đây là một lượng nào đó tùy bạn định nghĩa đơn vị tính trong hệ tọa độ

Những game đơn giản thì có thể sẽ không nhận thấy được ảnh hưởng, nhưng khi code những game thật sự, nó sẽ là vấn đề gây ra việc game của bạn sẽ trông giật cục và điều đó hoàn toàn không phải do máy tính của người chơi quá … “cùi”.

Tại thời điểm t1 của game, với một lượng x quái vật, vòng lặp game của bạn chạy A lần trong 1 giây. Thời điểm t2, lượng quái vật ít đi, bằng x – x’, vòng lặp của bạn chạy nhanh hơn, A + A’ lần 1 giây. Ví dụ rằng người dùng giữ phím để di chuyển trong một khoảng thời gian. Trong một giây câu lệnh thay đổi vị trí ở trường hợp 2 chạy nhiều lần hơn => nhân vật đi được dài hơn, ngược lại, khi vòng lặp chậm đi, nhân vật của bạn đi được ngắn hơn mặc dù điều chúng ta cần là nhân vật phải đi được các khoảng dài bằng nhau nếu thời gian bấm nút như nhau. Framerate independent movement là điều bạn cần làm để giải quyết trường hợp này, hành vi của nhân vật game sẽ không bị lúc nhanh lúc chậm, lúc dài lúc ngắn chỉ vì FPS (frames per second) thay đổi. Mình nói thêm một chút về FPS, số đo này có ý nghĩa về performance của game, nếu FPS cao đồng nghĩa bạn có thể mở rộng game của bạn ra nhiều hơn, nếu FPS thấp trong khi goal của bạn chưa đạt được thì bạn cần cân nhắc đến việc thay đổi một lượng lớn code của bạn, để đảm bảo được FPS ít nhất là 30 trong mọi trường hợp, bởi đó là con số chuẩn tương xứng giữa mắt người và màn hình máy tính để cảm nhận được các chuyển động trơn tru, nếu dưới 30 FPS tức là bạn đã hoàn toàn fail. Tuy vậy, không thể lấy FPS là số đo duy nhất cho performance, hãy dùng con số chính xác hơn đó là thời gian thực thi một frame (miligiây). Sau đây là một vài con số có thể sẽ giúp ích cho bạn:

Thời gian trung bình 1 frame:

13ms: game của bạn có thể chạy trên nền tảng thực tế ảo

16ms: có thể lock framerate để game của bạn luôn chạy ở 60FPS

30ms: có thể lock framerate để game của bạn luôn chạy ở 30FPS

Nếu game của bạn performance kém, ít nhất hãy đảm bảo nó vẫn xấp xỉ xung quanh con số FPS 60.

Quay lại với framerate independent, đơn giản là bạn chỉ cần tính toán ra vòng lặp trước đó chạy với thời gian bao lâu (tức bằng với thời gian thực ở ngoài đời đã trôi qua bao lâu) để tính toán thay đổi của các game object. Điều này hợp lý bởi quãng đường đi được sẽ bằng thời gian trôi qua nhân với vận tốc, vòng lặp có chạy nhanh chậm bao nhiêu, quãng đường nhân vật của bạn đi được bảo toàn do bạn đã thêm yếu tố thời gian vào code.

Tính thời gian đã trôi qua mỗi vòng lặp (giả mã):

time_type time = GetTime(); // bạn có thể dùng bất cứ hàm thời gian nào đủ chính xác đến ms
// suy ra rằng thư viện ctime không thể dùng được do chỉ chính xác đến giây
// có thể dùng hàm std::clock() (hãy đọc kỹ tài liệu của hàm này bởi trên các hệ điều hành chuẩn 
// POSIX sẽ có điều khác biệt khi sử dụng)
// hoặc các hàm time của Win32 API: GetTickCount, QueryPerformanceCounter, TimeGetTime
// game Doom 3 sử dụng hàm TimeGetTime cho bản windows
 
while (condition)
{
    frameTime = GetTime() - time;
    time = GetTime();
 
    game update ...
}

Ví dụ áp dụng trong Irrlicht:

irr::u32 time = device->getTimer()->getTime();
 
// hàm getTime trong Timer của Irrlicht trả về miligiây, chúng ta lựa chọn frame time tính theo giây
// suy ra SPEED có ý nghĩa 1 đơn vị 1 giây.
const float SPEED = 1.0f;
 
while (device->run())
{
    float frameTime = (device->getTimer()->getTime() - time) / 1000.f;
    time = device->getTimer()->getTime();
 
    if (receiver->IsKeyDown(irr::KEY_SPACE))
    {
        // nếu space được bấm, di chuyển dòng chữ đi lên
        irr::core::vector3df pos = text->getPosition();
        pos.Y += SPEED * frameTime;
        text->setPosition(pos);
    }
 
    driver->beginScene(true, true, irr::video::SColor(255, 100, 101, 140));
 
    smgr->drawAll();
 
    driver->endScene();
}

 

Comments

Related Posts

GPBasics 06: Thực hành điều khiển nhân vật

Chào các bạn, hôm nay chúng ta sẽ cùng thực hành về điều khiển nhân vật, kết quả của bài này các bạn có thể xem tại video: Đầu tiên các bạn tạo một class mới như sau: #ifndef NINJA_H #define NINJA_H   #include <irrlicht.h>   // liệt kê các trạng thái của nhân vật […]

Read More

GPBasics 04: Irrlicht Text

1. Irrlicht Text Scene Node Irrlicht Text Scene Node là một Scene Node giúp bạn render các text trong 3D World. Để khởi tạo nó, bạn cần load một Font cho text. Thành phần này nằm trong GUI nên bạn cần phải thao tác qua Irrlicht GUI Environment. irr::gui::IGUIEnvironment* gui = device->getGUIEnvironment();   // font […]

Read More