Post date: Jul 10, 2011 7:42:54 AM
Do trình độ còn hạn chế nên bài viết chưa thể đưa ra định nghĩa một các trọn vẹn ngữ nghĩa. Tuy nhiên, ta có thể hiểu nôm na, animation là các hoạt hình, các chuyển động, các biến đổi về mặt hình ảnh của các đối tưởng trong game. Ví dụ animation sau:
Để thực hiện các animation trong game, ta có thể áp dụng 1 trong 2 cách:
Trong đó, cách vẽ animation theo frame là phổ biến. Các chuyển động được tách thành các bức ảnh rời rạc, khi các bức ảnh được vẽ/xóa liên tục, sẽ tạo nên cảm giác chuyển động:
Trong hình trên, chuyển động của quả bóng được chia thành 6 frame, khi 6 frame được vẽ liên tục với một tốc độ cao nhất định (frame per second - fps), sẽ cho ta cảm giác animation. (Xem thêm frame-rate). Thông thường tốc độ fps của animtion cũng bằng fps của game.
Cũng như animation, cũng khá khó để đưa ra một định nghĩa với sprite. Theo định nghĩa từ wiki, "sprite là một hình ảnh 2 chiều hoặc animation tích hợp vào một cảnh lớn hơn". Quá trừu tượng và khó hiểu.
Theo một định nghĩa khác tương đối dễ hiểu hơn, "sprite là các đối tượng đồ họa tương tác được trong game, như NPC (Non primary character), MC (main character), các object động (Dynamic object), ...". Như vậy, sprite là một khái niệm trừu tượng chỉ chung có nhiều loại đối tượng khác nhau trong game.
Một cách hiểu khác, Sprite là thành phần đồ họa của các đối tượng trong game, chứ không phải là bản thân các đối tượng đó, và mở rộng cho tất cả các đối tượng động và tĩnh. Như vậy, sprite có thể xem như là cơ sở dữ liệu đồ họa cho tất cả các đối tượng trong game. Một sprite chứa nhiều animation của cùng một đối tượng, hoặc các đối tượng được gom nhóm theo một qui ước nào đó. Cách hiểu này thân thuộc, và rõ ràng hơn cho việc lập trình.
Từ cách định nghĩa thứ 3, ta có thể hình dung được một sprite trong game gồm các thành phần:
Ví dụ về sprite sheet
Để thiết lập cấu trúc dữ liệu cho sprite, ta cần thống nhất một số định nghĩa sau:
Với SpriteDB, ta có các quan hệ sau giữa các thành phần thông tin Bitmap, Frame và Animation:
Ta phân rã quan hệ nhiều nhiều Frame - Animation thành quan hệ một nhiều bằng việc sinh bảng trung gian, gọi là KeyFrame. KeyFrame bao gồm các thông tin: FrameID, OffsetX, OffsetY, nLoop (số lần lặp liên tục của một frame trong animation)
Thông tin về các thành phần trong SpriteDB:
Tuy nhiên, để thuận tiện cho quá trình coding, vai trò của KeyFrame sẽ trở nên không rõ ràng và không tách biệt với Animation. Mỗi Animation sẽ chứa riêng nó một danh sách các keyframe
Sprite structure
struct SSpriteDBHeader { __UINT16 Version; __UINT8 NumberOfImages; __UINT16 NumberFrames; __UINT16 NumberAnimations; __UINT8 Fps; }; struct SSpriteDBBitmap { __UINT16 ID; char Name[128]; }; struct SSpriteDBFrameRectImg { __UINT16 ImgID; __UINT16 OffsetX; __UINT16 OffsetY; __UINT16 Width; __UINT16 Height; __UINT8 Reserved[30]; }; struct SSpriteDBFrameRectColor { __UINT16 Width; __UINT16 Height; __UINT8 Alpha; __UINT8 Red; __UINT8 Green; __UINT8 Blue; __UINT8 Reserved[32]; }; struct SSpriteDBFrame { __UINT16 ID; __UINT16 Type; union { SSpriteDBFrameRectImg RectImgFrame; SSpriteDBFrameRectColor RectColorFrame; __UINT8 Reserved[40]; }; }; struct SSpriteDBAnimKeyFrame { __UINT16 FrameID; __UINT16 OffsetX; __UINT16 OffsetY; __UINT16 Loop; }; struct SSpriteDBAnimation { __UINT16 ID; char Name[128]; CList* KeyFrameList; };
Thông tin của Sprite có thể được lưu trữ dưới dạng raw text file , xml, file binary, excel, .... Để thuận tiện, ta có thể sử dụng ví dụ dưới dạng raw text file
///////////////////////////////////////////// // Header // /////////////////////////////////////////////////////// VERSION 1 IMAGES 1 FRAMES 17 ANIMS 2 FPS 10 /////////////////////////////////////////////////////// // image // /////////////////////////////////////////////////////// IMG 0 smurf_sprite.tga /////////////////////////////////////////////////////// // frame // /////////////////////////////////////////////////////// // ID Type IMG OffsetX OffsetY W H // for type = 0 - image rect FRAME 0 0 0 0 0 128 128 FRAME 1 0 0 0 128 128 128 FRAME 2 0 0 0 256 128 128 FRAME 3 0 0 0 384 128 128 FRAME 4 0 0 128 0 128 128 FRAME 5 0 0 128 128 128 128 FRAME 6 0 0 128 256 128 128 FRAME 7 0 0 128 384 128 128 FRAME 8 0 0 256 0 128 128 FRAME 9 0 0 256 128 128 128 FRAME 10 0 0 256 256 128 128 FRAME 11 0 0 256 384 128 128 FRAME 12 0 0 384 0 128 128 FRAME 13 0 0 384 128 128 128 FRAME 14 0 0 384 256 128 128 FRAME 15 0 0 384 384 128 128 // ID Type W H Alpha Red Green Blue // for type = 0 - color rect FRAME 16 1 100 100 255 255 128 128 /////////////////////////////////////////////////////// // anim // /////////////////////////////////////////////////////// ANIM XYZ // FrameID OffsetX OffsetY nFrames KEYFRAME 0 0 0 1 KEYFRAME 4 0 0 1 KEYFRAME 8 0 0 1 KEYFRAME 12 0 0 1 KEYFRAME 1 0 0 1 KEYFRAME 5 0 0 1 KEYFRAME 9 0 0 1 KEYFRAME 13 0 0 1 KEYFRAME 2 0 0 1 KEYFRAME 6 0 0 1 KEYFRAME 10 0 0 1 KEYFRAME 14 0 0 1 KEYFRAME 3 0 0 1 KEYFRAME 7 0 0 1 KEYFRAME 11 0 0 1 KEYFRAME 15 0 0 1 ENDANIM ANIM ABC // FrameID OffsetX OffsetY nFrames KEYFRAME 0 0 0 1 KEYFRAME 4 0 0 1 KEYFRAME 8 0 0 1 KEYFRAME 12 0 0 10 ENDANIM
class CSpriteDB { SSpriteDBHeader m_Header; CList<SSpriteDBBitmap*> m_Bitmaps; CList<SSpriteDBFrame*> m_Frames; CList<SSpriteDBAnimation*> m_Anims; };
Để tiến hành việc load sprite cũng như hiện trị sprite, ta cần các thực hiện các bước bổ sung sau:
Do cần phải thực hiện việc load file text, nên công đoạn đầu tiên cần chuẩn bị là thực hiện các lớp chuyên phục vụ cho quá trình đọc file text và xử lý chuỗi.
Trong phần trước, ta đã thiết kế lớp hình ảnh (CImage). Và theo những gì đã trình bày ở trên, sprite không thể thiếu hình ảnh. Do hình ảnh là nguồn tài nguyên chiếm một lượng lớn không gian lưu trữ và được sử dụng thường xuyên, từ đó nãy sinh nhu cầu cần có cơ chế quản lý hình ảnh tốt và thuận tiện.
Cơ chế quản lý đơn giản nhất là bảng tra (LUT - Lookup table). Các hình ảnh được tạo ra sẽ được định danh và lưu trữ trong bảng tra. Khi cần sử dụng một hình ảnh bất kỳ, người dủng chỉ việc tra thông tin trong bảng và sử dụng resource cần thiết
Từ ý tưởng trên, ta tiến hành thiết kế một lớp "bảng tra" tổng quát, dựa trên cấu trúc map của STL.
CLut
template<typename element_type> class CLut { struct ltstr { bool operator()(const char* s1, const char* s2) const { return strcmp(s1, s2) < 0; } }; public: CLut(); virtual ~CLut(); void AddItem(const char * name, element_type ele); element_type RemoveItem(const char * name); bool GetElement(const char * name, element_type& out); int GetCount(); void DeallocateElementPointer(); void Clear(); protected: map<const char *, element_type, ltstr> m_List; };
CResourceLookupTable
namespace GameTutor { template <class ResourceType> class CResourceLookupTable { public: CResourceLookupTable(); virtual ResourceType* Get(const char* name); virtual void Add(const char* name, ResourceType* item); virtual ~CResourceLookupTable(); virtual void Free(const char* name); virtual void Clean(); protected: CLut<ResourceType*> *m_LookupTable; }; }
CImageManager
namespace GameTutor { class CImageManager: public CSingleton<CImageManager>, public CResourceLookupTable<CImage> { friend class CSingleton<CImageManager>; protected: CImageManager() {} public: template <class IODriver> void AddImage(const char* name, bool isForceBindGPU) { if (!Get(name)) { CReaderStream<IODriver> *F = new CReaderStream<IODriver>(name); if (F->GetStatus() == ESTREAM_OPEN) { char ext[3]; memcpy(ext, name + (strlen(name) - 3), 3); Str_ToLower(ext, 3); if (memcmp("tga", ext, 3) == 0) { CImage *re = CImage::LoadTGA(F); this->Add(name, re); if (isForceBindGPU) { re->BindGPU(); } } } F->Close(); SAFE_DEL(F); } } virtual ~CImageManager() {} }; }
Từ thời điểm này, việc tạo/hủy image nên được thực hiện thông qua CImageManager, thay vì cấp phát trực tiếp từ lớp CImage
Lớp CSprite được thiết kế như trình bày ở trên. Ngoài ra, CSpriteDB đồng thời cung cấp các hàm load file. Ví dụ: AddSpriteDBFromTextFile cho phép load thông tin sprite từ file text. Hàm này thực hiện với sự hổ trở của 2 lớp CStringBufferStream và CToken trong việc tách thông tin từ chuỗi.
Việc load ảnh cho sprite được thực hiện thông qua CImageManager
CSpriteDB
class CSpriteDB { friend class CSprite; public: template <class IODriver> static CSpriteDB* ImportFromTextFile(const char *file) { CReaderStream<IODriver> F(file); if (F.GetStatus() == ESTREAM_ERROR) { return 0; } CStringBufferStream BStream(&F); CStringBufferStream *txtStream = &BStream; CSpriteDB* re = 0; int numLines = txtStream->GetNumOfLine(); if (!numLines) { return 0; } re = new CSpriteDB(); CToken *tok; SSpriteDBAnimation *anim = 0; for (int i = 0; i < numLines; i++) { tok = txtStream->GetLineToken(i); int numOfToken = tok->GetTokenCount(); if (numOfToken>0) { if (tok->IsTokenEqual(0, "VERSION")) { re->m_Header.Version = tok->GetTokenValueAsInt(1); } else if (tok->IsTokenEqual(0, "IMAGES")) { re->m_Header.NumberOfImages = tok->GetTokenValueAsInt(1); } else if (tok->IsTokenEqual(0, "FRAMES")) { re->m_Header.NumberFrames = tok->GetTokenValueAsInt(1); } else if (tok->IsTokenEqual(0, "ANIMS")) { re->m_Header.NumberAnimations = tok->GetTokenValueAsInt(1); } else if (tok->IsTokenEqual(0, "FPS")) { re->m_Header.Fps = tok->GetTokenValueAsInt(1); } else if (tok->IsTokenEqual(0, "IMG")) { SSpriteDBBitmap *bitmap = new SSpriteDBBitmap(); bitmap->ID = tok->GetTokenValueAsInt(1); tok->GetToken(2, bitmap->Name); re->m_Bitmaps.AddItem(bitmap); CImageManager::GetInstance()->AddImage<IODriver>(bitmap->Name, true); } else if (tok->IsTokenEqual(0, "FRAME")) { SSpriteDBFrame *frame = new SSpriteDBFrame(); frame->ID = tok->GetTokenValueAsInt(1); frame->Type = tok->GetTokenValueAsInt(2); if (frame->Type == ESPRFRAME_TYPE_RECT_IMG) { frame->RectImgFrame.ImgID = tok->GetTokenValueAsInt(3); frame->RectImgFrame.OffsetX = tok->GetTokenValueAsInt(4); frame->RectImgFrame.OffsetY = tok->GetTokenValueAsInt(5); frame->RectImgFrame.Width = tok->GetTokenValueAsInt(6); frame->RectImgFrame.Height = tok->GetTokenValueAsInt(7); } else if (frame->Type == ESPRFRAME_TYPE_RECT_COLOR) { frame->RectColorFrame.Width = tok->GetTokenValueAsInt(3); frame->RectColorFrame.Height = tok->GetTokenValueAsInt(4); frame->RectColorFrame.Alpha = tok->GetTokenValueAsInt(5); frame->RectColorFrame.Red = tok->GetTokenValueAsInt(6); frame->RectColorFrame.Green = tok->GetTokenValueAsInt(7); frame->RectColorFrame.Blue = tok->GetTokenValueAsInt(8); } re->m_Frames.AddItem(frame); } else if (tok->IsTokenEqual(0, "ANIM")) { SSpriteDBAnimation *a = new SSpriteDBAnimation; a->KeyFrameList = new CList<SSpriteDBAnimKeyFrame*>(); tok->GetToken(1, a->Name); anim = a; } else if (tok->IsTokenEqual(0, "KEYFRAME")) { if (anim) { SSpriteDBAnimKeyFrame* kf = new SSpriteDBAnimKeyFrame; kf->FrameID = tok->GetTokenValueAsInt(1); kf->OffsetX = tok->GetTokenValueAsInt(2); kf->OffsetY = tok->GetTokenValueAsInt(3); kf->Loop = tok->GetTokenValueAsInt(4); anim->KeyFrameList->AddItem(kf); } } else if (tok->IsTokenEqual(0, "ENDANIM")) { if (anim) { re->m_Anims.AddItem(anim); anim = NULL; } } } } F.Close(); return re; } virtual ~CSpriteDB() { m_Anims.BeginTravel(); while (!m_Anims.IsEndOfTravel()) { SSpriteDBAnimation *anim = m_Anims.Travel(); anim->KeyFrameList->DeallocateElementPointer(); anim->KeyFrameList->Clear(); SAFE_DEL(anim->KeyFrameList); } m_Anims.DeallocateElementPointer(); m_Anims.Clear(); m_Frames.DeallocateElementPointer(); m_Frames.Clear(); m_Bitmaps.DeallocateElementPointer(); m_Bitmaps.Clear(); } protected: CSpriteDB(){} SSpriteDBHeader m_Header; CList<SSpriteDBBitmap*> m_Bitmaps; CList<SSpriteDBFrame*> m_Frames; CList<SSpriteDBAnimation*> m_Anims; };
Lớp CSprite cung cấp các phương thức cơ bản cho việc cập nhật và hiển thị animation của sprite. Các phương thức bao gồm:
Cũng như Image, SpriteDB cũng là một đối tượng cần được quản lý để thuận tiện sữ dụng cũng như quản lý. Việc load/remove/sử dụng spriteDB nên thông qua lớp này.
CSpriteDBManagement
class CSpriteDBManagement: public CSingleton<CSpriteDBManagement>, public CResourceLookupTable<CSpriteDB> { friend class CSingleton<CSpriteDBManagement>; protected: CSpriteDBManagement(){} public: template <class IODriver> void AddSpriteDBFromTextFile(const char* name) { if (!Get(name)) { CReaderStream<IODriver> *F = new CReaderStream<IODriver>(name); if (F->GetStatus() == ESTREAM_OPEN) { CSpriteDB *re = CSpriteDB::ImportFromTextFile<IODriver>(name); this->Add(name, re); } F->Close(); SAFE_DEL(F); } } };
State CStatePoster được hiệu chỉnh để demo cho cách sử dụng sprite:
Demo
#include "CStatePoster.h" CStatePoster::CStatePoster(): CState() { } void CStatePoster::Init() { Log("State Poster: Init"); //init 2D graphics CGraphics2D::GetInstance()->Reset(); //Load sprite and add to SpriteDB manager CSpriteDBManagement::GetInstance()->AddSpriteDBFromTextFile<CFileWin32Driver>("test.txt"); //Create a sprite, use sprite data stored in CSpriteDBManagement spr = new CSprite(CSpriteDBManagement::GetInstance()->Get("test.txt")); // set sprite animation, loop forever spr->SetAnimation("XYZ", -1); // set sprite position spr->SetPosition(SPosition2D<__INT32>(250, 10)); } void CStatePoster::Update() { spr->UpdateAnimation(CFpsController::GetInstance()->GetFrameDt()); } void CStatePoster::Render() { CGraphics2D::GetInstance()->Clear(SColor<float>(0, 0, 0, 1)); spr->RenderAnimation(CGraphics2D::GetInstance()); CGraphics2D::GetInstance()->Flush(); } void CStatePoster::Exit() { Log("State Poster: Exit"); SAFE_DEL(spr); CSpriteDBManagement::GetInstance()->Free("test.txt"); }
SGameConfig
typedef struct _SGameConfig { __UINT16 iWidth; __UINT16 iHeight; bool isUseFullScreen; const char *strTitle; IVideoDriver *pVideoDriver; CGame* pGame; }SGameConfig;
Các hàm cần Video driver làm tham số sẽ sử dụng trực tiếp Video Driver từ config thông qua biến toàn cục Configuation (được thiết lập bởi StartApp)
Download