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 enum NINJA_STATE { INVALID, IDLE, FORWARD, BACKWARD }; class Ninja { public: Ninja(); ~Ninja(); // hàm này cần nhận scene manager để load model void Init(irr::scene::ISceneManager* smgr); // các hàm điều khiển nhân vật void Idle(); void Forward(float dt); void Backward(float dt); void Destroy(); private: // nhân vật giữ một con trỏ tới scene node irr::scene::IAnimatedMeshSceneNode* m_Node; // trạng thái hiện tại NINJA_STATE m_State; // tốc độ di chuyển float m_MovementSpeed; }; #endif // NINJA_H |
Hàm khởi tạo, hàm hủy và hàm Destroy:
Ninja::Ninja() : m_Node(NULL), m_State(INVALID), m_MovementSpeed(10.0f) { } void Ninja::Destroy() { m_Node->drop(); m_Node = NULL; } Ninja::~Ninja() { Destroy(); } |
Không có gì phức tạp ở đây …
Về model ninja mà chúng ta sẽ dùng để điều khiển, đây là một 3D model mình lấy từ website http://www.psionic3d.co.uk/. Các bạn có thể sử dụng model này vào bất cứ mục đích gì, và khi sử dụng vui lòng ghi credits đầy đủ và rõ ràng.
Animation Ranges của model Ninja:
1-14 Walk (normal)
15-30 Stealth Walk
32-44 Punch and swipe sword
45-59 Swipe and spin sword
60-68 Overhead twohanded downswipe
69-72 Up to block position (play backwards to lower sword if you want)
73-83 Forward kick
84-93 Pick up from floor (or down to crouch at frame 87)
94-102 Jump
103-111 Jump without height (for programmer controlled jumps)
112-125 High jump to Sword Kill (Finish em off move??)
126-133 Side Kick
134-145 Spinning Sword attack (might wanna speed this up in game)
146-158 Backflip
159-165 Climb wall
166-173 Death 1 – Fall back onto ground
174-182 Death 2 – Fall forward onto ground
184-205 Idle 1 – Breathe heavily
206-250 Idle 2
251-300 Idle 3
Bạn hãy tham khảo kỹ những con số này bởi irrlicht sẽ dùng nó để render các hành động tương ứng đã có sẵn sau khi load model.
Ý tưởng về điều khiển nhân vật ở trong bài này như sau:
Mỗi vòng lặp game kiểm tra input, nếu nút W được bấm, nhân vật tiến lại gần Camera và lùi ra xa nếu S được bấm. Nếu không nút nào được bấm, nhân vật sẽ đứng yên và chạy animation Idle.
Hàm Init của Ninja:
void Ninja::Init(irr::scene::ISceneManager* smgr) { irr::scene::IAnimatedMesh* mesh = smgr->getMesh("ninja.b3d"); if (!mesh) return; m_Node = smgr->addAnimatedMeshSceneNode(mesh); if (m_Node) { m_Node->setMaterialFlag(irr::video::EMF_LIGHTING, false); // vị trí mặc định của ninja, bạn hoàn toàn có thể thay đổi m_Node->setPosition(irr::core::vector3df(0, 0, 0)); // lúc khởi tạo mặc định ninja sẽ idle Idle(); } } |
Hàm Idle:
void Ninja::Idle() { // phải kiểm tra trạng thái // bởi nếu hiện tại vẫn đang Idle mà câu lệnh setFrameLoop được gọi, // animation sẽ luôn chạy lại từ đầu // hậu quả là bạn sẽ không thấy hành động nào diễn ra cả if (m_State != IDLE) { m_State = IDLE; m_Node->setFrameLoop(251, 300); } } |
Hàm Forward – tất nhiên là chúng ta sử dụng framerate independent movement, cần đến frame time:
void Ninja::Forward(float dt) { // nếu trạng thái hiện tại không phải là đang tiến về trước // đặt lại trạng thái và play animations // nếu lệnh if này sai tức là hiện tại nhân vật vẫn đang forward // vậy thì chỉ cần thay đổi vị trí của nó // lệnh play animation đã được thực hiện từ trước rồi // hàm setFrameLoop sẽ chạy hết animation và tự động lặp lại if (m_State != FORWARD) { m_State = FORWARD; m_Node->setFrameLoop(1, 13); } irr::core::vector3df pos = m_Node->getPosition(); // di chuyển theo trục Z pos.Z += m_MovementSpeed * dt; m_Node->setPosition(pos); } |
Hàm Backward tương tự như hàm Forward …
OK, vậy là API điều khiển nhân vật đã xong, tiếp đến chúng ta set up scene và test thử.
Bạn download một cái plane mình làm ở đây để tượng trưng cho mặt đất:
Tiếp đến là load Plane ở trong hàm main, hãy thử vận dụng các tọa độ để đặt ground và camera ở vị trí thích hợp để có thể quan sát.
Khởi tạo Ninja:
Ninja* ninja = new Ninja(); ninja->Init(smgr); |
Code điều khiển nhân vật:
while (...) { // tính thời gian đã trôi qua (delta time) if (receiver->IsKeyDown(irr::KEY_KEY_W)) ninja->Forward(dt); else if (receiver->IsKeyDown(irr::KEY_KEY_S)) ninja->Backward(dt); else ninja->Idle(); driver->beginScene(true, true, irr::video::SColor(255, 100, 101, 140)); smgr->drawAll(); driver->endScene(); } delete ninja; device->drop(); return 0; |
Extra:
Animation Blending
Như bạn có thể để ý trong ví dụ ở trên thì việc chuyển tiếp giữa các hành động rất đột ngột và gây mất tự nhiên khi nhân vật chuyển động, kỹ thuật Animation Blending sẽ giúp chúng ta giải quyết vấn đề này. Một số khái niệm cần thiết:
Skeletal Animations: Nhân vật có một khung xương và bề mặt ngoài của nó (mesh) sẽ chuyển động khi các xương ở bên trong chuyển động. Animation Blending chỉ hoạt động với loại animation này vì nguyên lý của nó là tính toán lại để tạo ra các animation chuyển tiếp từ frame này sang frame khác. Các file đồ họa hỗ trợ skeletal animations: .b3d, .X, .ms3d
Blending time/TransitionTime: thời gian chuyển tiếp, tức thay vì đột ngột thay đổi các chuỗi animation, mỗi lần thay đổi thì irrlicht sẽ chèn thêm một đoạn animation chuyển tiếp có độ dài bằng khoảng thời gian mà bạn chọn.
Cách sử dụng:
Bạn có thể dùng Animation Blending như sau – chỉ dành cho IAnimatedSceneNode được load từ các file có hỗ trợ Skeletal Animations.
Node->setTransitionTime(seconds); |
Và trước câu lệnh drawAll của SceneManager, bạn gọi:
Node->animateJoints(); |
Áp dụng vào bài này, các bạn có thể sử dụng như sau:
Mình viết thêm hàm Animate cho class Ninja:
class Ninja { public: Ninja(); ~Ninja(); // hàm này cần nhận scene manager để load model void Init(irr::scene::ISceneManager* smgr); // các hàm điều khiển nhân vật void Idle(); void Forward(float dt); void Backward(float dt); void Animate(); void Destroy(); private: // nhân vật giữ một con trỏ tới scene node irr::scene::IAnimatedMeshSceneNode* m_Node; // trạng thái hiện tại NINJA_STATE m_State; // tốc độ di chuyển float m_MovementSpeed; }; |
Sau đó trong hàm Init, đặt blending time cho nó:
void Ninja::Init(irr::scene::ISceneManager* smgr) { irr::scene::IAnimatedMesh* mesh = smgr->getMesh("ninja.b3d"); if (!mesh) return; m_Node = smgr->addAnimatedMeshSceneNode(mesh); if (m_Node) { m_Node->setMaterialFlag(irr::video::EMF_LIGHTING, false); m_Node->setPosition(irr::core::vector3df(0, 0, 0)); // lúc khởi tạo mặc định ninja sẽ idle Idle(); m_Node->setTransitionTime(0.2f); } } void Ninja::Animate() { m_Node->animateJoints(); } |
Trong vòng lặp rendering:
while (...) { // tính toán delta time if (receiver->IsKeyDown(irr::KEY_KEY_W)) ninja->Forward(dt); else if (receiver->IsKeyDown(irr::KEY_KEY_S)) ninja->Backward(dt); else ninja->Idle(); // gọi hàm animate ninja->Animate(); driver->beginScene(true, true, irr::video::SColor(255, 100, 101, 140)); smgr->drawAll(); driver->endScene(); } delete ninja; device->drop(); return 0; |
Bạn có thể xem video sau để thấy sự khác biệt khi sử dụng Animation Blending
(Tay cầm kiếm của ninja nâng lên và hạ xuống khi bắt đầu bước đi chứ ko dịch chuyển tức thời vị trí của tay như video trước, đây chính là những đoạn animation được irrlicht tính toán để thêm vào mỗi khi chuyển đổi animation)
Manh
August 26, 2015
Nhập môn Lập trình Game