GPBasics 03: Irrlicht Event Receiver

Bài này chúng ta sẽ học cách xử lý input của người dùng.

Để tạo ra một đối tượng nhận input, bạn thừa kế đổi tượng IEventReceiver.

Đối tượng này chỉ có nhiệm vụ nhận event, mọi xử lý thì tùy vào bạn muốn nó làm gì thì sẽ code thêm vào.

class MyEventReceiver : public irr::IEventReceiver
{
public:
    virtual bool OnEvent(const irr::SEvent& event);
};

Rất đơn giản, chỉ cần thừa kế và override method OnEvent thì đối tượng của bạn đã có thể làm việc.

SEvent là một struct (chữ S chính là viết tắt của struct) chứa các event xảy ra trong một chương trình sử dụng Irrlicht:

SEvent bao gồm

SGUIEvent: thông tin về các event về giao diện 2D ví dụ như button click, check box,….

SJoystickEvent: các event về tay cầm chơi game

SKeyInput: input bàn phím

SLogEvent: về những thông tin được log

SMouseEvent: các event về chuột như click, di chuyển, nút lăn,…

SUserEvent: event của user

Vậy chúng ta có thể thấy rằng từ SEvent chúng ta có thể kiểm soát rất nhiều khía cạnh để xử lý input. Cách sử dụng Event Receiver mà bạn đã thừa kế như sau:

Bạn tạo ra object và truyền con trỏ của nó vào hàm createDevice;

MyEventReceiver* receiver = new MyEventReceiver();
 
irr::IrrlichtDevice* device = irr::createDevice(DeviceType, WindowSize, BitDepth, 
                                    FullScreen, StencilBuffer, VSync, 
                                    receiver);

Tức là bạn phải tạo ra Reciever và truyền cho Irrlicht khi tạo Device.

Bạn cũng có thể dùng receiver nếu như nó khởi tạo sau Irrlicht Device.

Trường hợp đó bạn có thể gọi hàm setEventReceiver từ bất cứ nơi nào của chương trình:

devicePtr->setEventReceiver(EventReceiverPtr);

Việc khởi tạo một Event Receiver đến đây đã xong, chúng ta sẽ đi vào phần mở rộng class của bạn để xử lý những event mong muốn.

1. Keyboard Events

Ý tưởng của việc xử lý keyboard event là thay vì mỗi khi bấm nút chương trình lại chạy một đoạn code nào đó, bạn nên lưu lại trạng thái của tất cả các phím rồi sau đó kiểm tra nút có được bấm hay không, không thể làm rối code lên bằng cách chèn luôn vào EventReceiver của chúng ta được.

Thay vì

if (nút A được bấm)
{
    do something
}

ở trong class Event Receiver, chúng ta có thể tạo ra một method kiểm tra xem nút có được bấm hay không, hệ quả là code xử lý nó sẽ nằm ở bên ngoài tạo ra sự linh hoạt và logic khi làm việc với input của người dùng.

Trên ý tương này, chúng ta sẽ viết class như sau:

class MyEventReceiver : public irr::IEventReceiver
{
public:
    MyEventReceiver()
    {
        // hàm khởi tạo đặt tất cả các trạng thái về false, tức không nút nào được bấm
        // u32 : unsigned int 32 bit
        for (irr::u32 i = 0; i < irr::KEY_KEY_CODES_COUNT; ++i)
            KeyStates[i] = false;
    }
 
    virtual bool OnEvent(const irr::SEvent& event);
 
    // method trả về trạng thái của nút bấm, để object bên ngoài gọi nó và xử lý input
    // các EKEY_CODE là enum của Irrlicht đặt ra
    bool IsKeyDown(irr::EKEY_CODE keyCode) const
    {
        // mỗi key code thực chất là một giá trị unsigned int, 
        // key code = chỉ số của nó ở trong mảng, nơi lưu trạng thái của chính phím đó 
        return KeyStates[keyCode];
    }
 
private:
    // mảng lưu trạng thái của các nút trên bàn phím
    // irr::KEY_KEY_CODES_COUNT = tổng số phím mà Irrlicht định nghĩa
    bool KeyStates[irr::KEY_KEY_CODES_COUNT];
};

Việc tiếp theo đó là mỗi khi có event (hàm OnEvent được gọi), bạn kiểm tra xem nút nào được bấm và đặt trạng thái vào mảng KeyStates.

bool MyEventReceiver::OnEvent(const irr::SEvent& event)
{
    // kiểm tra xem có phải key event hay là các event khác như mouse, gui,...
    // event.KeyInput chứa thông tin của nút vừa được bấm hoặc thả ra
    // dễ dàng thấy được PressedDown là một giá trị boolean, 
    // nếu như key được bấm, nó sẽ là true và nếu event là key này được thả ra, nó mang giá trị false
    if (event.EventType == irr::EET_KEY_INPUT_EVENT)
        KeyStates[event.KeyInput.Key] = event.KeyInput.PressedDown;
 
    return false;
}

Chúng ta cùng thử xem Object này có hoạt động tốt không nhé. Ví dụ trường hợp này mình muốn model biến mất khi bấm nút Spacebar.

while (device->run())
{
    if (receiver->IsKeyDown(irr::KEY_SPACE))
        node->setVisible(false);
    else
        node->setVisible(true);
 
 
    driver->beginScene(true, true, irr::video::SColor(255, 100, 101, 140));
 
    smgr->drawAll();
 
    driver->endScene();
}

2. Mouse Events

Cũng tương tự keyboard events, chúng ta sẽ mở rộng class như sau:

class MyEventReceiver : public irr::IEventReceiver
{
public:
    MyEventReceiver()
    {
        // hàm khởi tạo đặt tất cả các trạng thái về false, tức không nút nào được bấm
        // u32 : unsigned int 32 bit
        for (irr::u32 i = 0; i < irr::KEY_KEY_CODES_COUNT; ++i)
            KeyStates[i] = false;
    }
 
    virtual bool OnEvent(const irr::SEvent& event);
 
    // method trả về trạng thái của nút bấm, để object bên ngoài gọi nó và xử lý input
    // các EKEY_CODE là enum của Irrlicht đặt ra
    bool IsKeyDown(irr::EKEY_CODE keyCode) const
    {
        // mỗi key code thực chất là một giá trị unsigned int, 
        // key code = chỉ số của nó ở trong mảng, nơi lưu trạng thái của chính phím đó 
        return KeyStates[keyCode];
    }
 
    bool LeftClick() const
    {
        return leftClick;
    }
 
    bool RightClick() const
    {
        return rightClick;
    }
 
private:
    // mảng lưu trạng thái của các nút trên bàn phím
    // irr::KEY_KEY_CODES_COUNT = tổng số phím mà Irrlicht định nghĩa
    bool KeyStates[irr::KEY_KEY_CODES_COUNT];
 
    bool leftClick;
    bool rightClick;
 
    // lưu vị trí hiện tại của con trỏ chuột
    // tọa độ cửa sổ là số nguyên nên chúng ta dùng position2di
    irr::core::position2di mousePosition;
};

Hàm OnEvent sẽ cập nhật trạng thái của các thông tin input mà object của chúng ta lưu trữ, để tiện cho bên ngoài truy cập khi cần.

bool MyEventReceiver::OnEvent(const irr::SEvent& event)
{
    // kiểm tra xem có phải key event hay là các event khác như mouse, gui,...
    // event.KeyInput chứa thông tin của nút vừa được bấm hoặc thả ra
    // dễ dàng thấy được PressedDown là một giá trị boolean, 
    // nếu như key được bấm, nó sẽ là true và nếu event là key này được thả ra, nó mang giá trị false
    if (event.EventType == irr::EET_KEY_INPUT_EVENT)
        KeyStates[event.KeyInput.Key] = event.KeyInput.PressedDown;
    else if (event.EventType == irr::EET_MOUSE_INPUT_EVENT)
    {
        leftClick = event.MouseInput.isLeftPressed();
        rightClick = event.MouseInput.isRightPressed();
 
        mousePosition.X = event.MouseInput.X;
        mousePosition.Y = event.MouseInput.Y;
    }
 
    return false;
}

Trên đây là 2 ví dụ về xử lý input bàn phím và chuột với irrlicht, các bạn hoàn toàn có thể mở rộng nhiều hơn nữa, ví dụ như middle click, hay là nút cuộn của chuột,…dựa vào các ý tưởng mà chúng ta đã đi qua ở trên.

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 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 […]

Read More