Trong phần này, ta sẽ tiến hành một số thay đổi, cập nhật để chuẩn bị cho version mockup đầu tiên (còn nhiêu version mockup sau).
Các thay đổi được thực hiện:
Phần này cung cấp chức năng cho phép người dùng sử dụng phím để chơi game. Người dùng sẽ dùng 4 phím mũi tên trên bàn phím. 4 ô liền kề ô trống sẽ được di chuyển vào ô trống theo qui tắt như hình sau:
Trước tiên, ta xây định nghĩa hàm OnMoveSave trong CBoard, quản lý việc điều khiển bằng Key
void CBoard::OnMoveCell(__INT32 Dir)
{
int row = m_iCurrentBlankCell/m_iSize;
int col = m_iCurrentBlankCell%m_iSize;
switch (Dir)
{
case EKEY_WIN_UP:
if (row < m_iSize - 1)
{
row++;
}
break;
case EKEY_WIN_DOWN:
if (row > 0)
{
row--;
}
break;
case EKEY_WIN_LEFT:
if (col < m_iSize - 1)
{
col++;
}
break;
case EKEY_WIN_RIGHT:
if (col > 0)
{
col--;
}
break;
}
int index = GET_INDEX(row, col);
m_pBoardData[m_iCurrentBlankCell] = m_pBoardData[index];
m_pBoardData[index] = BLANK_CELL;
m_iCurrentBlankCell = index;
}
Biến m_iCurrentBlankCell lưu lại vị trí ô trống. Tùy vào Dir, các ô lận cận ô trống sẽ được chuyển vào ô trống.
Trong CStateIngame, hàm update được sửa đổi để tương tác với CBoard trong việc điều khiển bằng phím:
void CStateIngame::Update()
{
if (CControllerKeyManager::GetInstance()->WasKeyRelease(EKEY_SPACE))
{
m_pBoard->Shuffle2();
}
else if (CControllerKeyManager::GetInstance()->WasKeyRelease(EKEY_WIN_UP))
{
m_pBoard->OnMoveCell(EKEY_WIN_UP);
}
else if (CControllerKeyManager::GetInstance()->WasKeyRelease(EKEY_WIN_DOWN))
{
m_pBoard->OnMoveCell(EKEY_WIN_DOWN);
}
else if (CControllerKeyManager::GetInstance()->WasKeyRelease(EKEY_WIN_LEFT))
{
m_pBoard->OnMoveCell(EKEY_WIN_LEFT);
}
else if (CControllerKeyManager::GetInstance()->WasKeyRelease(EKEY_WIN_RIGHT))
{
m_pBoard->OnMoveCell(EKEY_WIN_RIGHT);
}
}
Các phím EKEY_WIN_UP, EKEY_WIN_DOWN, EKEY_WIN_LEFT, EKEY_WIN_RIGHT được bổ sung trong CControllerKeyManager_inc.h:
enum EKeyCode
{
EKEY_WIN_LEFT = (0x25)<<4,
EKEY_WIN_RIGHT = (0x27)<<4,
EKEY_WIN_UP = (0x26)<<4,
EKEY_WIN_DOWN = (0x28)<<4,
EKEY_WIN_A = (0x42)<<4,
EKEY_WIN_B = (0x43)<<4,
...
...
...
}
Mặc khác, cũng cần có sự đồng bộ giữa phím và touch, cũng như 2 phương thức sinh ngẫu nhiên bàn cờ:
void CBoard::Shuffle()
{
.....
for (int i = 0; i < m_iSizeSquare; i++)
{
if (m_pBoardData[i] == BLANK_CELL)
{
m_iCurrentBlankCell = i;
break;
}
}
}
void CBoard::Shuffle2()
{
......
m_iCurrentBlankCell = blankRow*m_iSize + blankCol;
SAFE_DEL(random);
}
void CBoard::OnTouch(__INT32 x, __INT32 y)
{
...
if (blank >= 0)
{
m_pBoardData[blank] = m_pBoardData[pos];
m_pBoardData[pos] = BLANK_CELL;
m_iCurrentBlankCell = pos;
}
}
Khung bao là hình chữ nhật bao quanh animation, dùng cho 2 mục đích:
Trong phạm bài viết này, khung bao được dùng để qui định kích thước.
Hiện tại, việc tính toán kích thước của cell thông qua define CELL_REGION. Điều này làm giảm tính linh hoạt vì mỗi khi thay đổi hình ảnh cell, ta lại phải thiết lập số này cho phù hợp. Với chức năng tính toán khung bao animation, ta sẽ tránh được việc này
Trước tiên, ta thêm tham số m_CurrentAnimationBoundary cho CSprite
SRect<__INT32> m_CurrentAnimationBoundary;
và thực hiện tính toán boundary này thực hiện khi update animation
void CSprite::UpdateAnimation(__UINT32 dt)
{
if (m_isEndOfAnimtion)
{
m_iElapsedTime = 0;
return;
}
m_isEndOfAnimtion = true;
if (m_pCurrentAnimation)
{
// calculate frame number
m_iElapsedTime += dt;
int frameNo = __INT32((m_iElapsedTime*m_iFps)/1000);
// if move to new frame
m_isEndOfAnimtion = false;
if (frameNo != m_iCurrentFrameNo)
{
m_isEndOfAnimtion = true;
m_iCurrentFrameNo = frameNo;
// find suitable key frame
if (m_pCurrentAnimation->KeyFrameList)
{
int nf = 0;
ForeachP(m_pCurrentAnimation->KeyFrameList, SSpriteDBAnimKeyFrame*, kf)
{
nf += kf->Loop;
if (nf > m_iCurrentAnimTotalFrame) {m_iCurrentAnimTotalFrame = nf;}
// if found
if (nf > m_iCurrentFrameNo)
{
//update boundary
m_CurrentAnimationBoundary.X = kf->OffsetX;
m_CurrentAnimationBoundary.Y = kf->OffsetY;
m_isEndOfAnimtion = false;
//check if new KF
if (m_pCurrentKeyFrame != kf)
{
m_pCurrentKeyFrame = kf;
m_pCurrentFrame = 0;
//link from KF to Frame
if (m_pCurrentKeyFrame)
{
int frameID = m_pCurrentKeyFrame->FrameID;
Foreach(m_SpriteData->m_Frames, SSpriteDBFrame*, frame)
{
if(frame && frame->ID == frameID)
{
//update boundary
if (frame->Type == ESPRFRAME_TYPE_RECT_IMG)
{
m_CurrentAnimationBoundary.W = frame->RectImgFrame.Width;
m_CurrentAnimationBoundary.H = frame->RectImgFrame.Height;
}
else if (frame->Type == ESPRFRAME_TYPE_RECT_COLOR)
{
m_CurrentAnimationBoundary.W = frame->RectColorFrame.Width;
m_CurrentAnimationBoundary.H = frame->RectColorFrame.Height;
}
m_pCurrentFrame = frame;
//link to Bitmap if any
m_pCurrentBitmap = 0;
if (m_pCurrentFrame && m_pCurrentFrame->Type == ESPRFRAME_TYPE_RECT_IMG)
{
Foreach(m_SpriteData->m_Bitmaps, SSpriteDBBitmap*, bitmap)
{
if (bitmap && bitmap->ID == m_pCurrentFrame->RectImgFrame.ImgID)
{
m_pCurrentBitmap = bitmap;
}
}; //Foreach
}
break;
}
}; //Foreach
}
}
break;
}//if (nf > m_iCurrentFrameNo)
}; //ForeachP
}//if (m_pCurrentAnimation->KeyFrameList)
}//if (frameNo != m_iCurrentFrameNo)
if (m_isEndOfAnimtion)
{
if (m_iLoopCounter != 0)
{
if (m_iLoopCounter > 0) m_iLoopCounter--;
m_iElapsedTime = Math_Min<__UINT64>(m_iElapsedTime - m_iCurrentAnimTotalFrame*m_iFps, 0);
m_iCurrentFrameNo = -1;
m_isEndOfAnimtion = false;
}
}
}//if (m_pCurrentAnimation)
}
Trong CSprite, ta bổ sung hàm để lấy thông tin animation hiện tại:
SRect<__INT32> GetBoundary() {return m_CurrentAnimationBoundary;}
Trong CCell, ta bổ sung 2 biến static
class CCell
{
public:
static __INT32 s_iCellWidth;
static __INT32 s_iCellHeight;
CCell(__INT32 ID);
virtual ~CCell();
void Render(CGraphics2D* g, int x, int y);
private:
__INT32 m_iID;
CSprite* m_pSpriteRef;
};
Giá trị này được thiết lập mỗi khi tạo mới 1 cell:
CCell::CCell(__INT32 ID): m_iID(ID),
m_pSpriteRef(0)
{
char spr_name [10];
char anim_name [10];
sprintf (spr_name, "CELL_%d", m_iID);
m_pSpriteRef = CSpriteManager::GetInstance()->Get(spr_name);
if (!m_pSpriteRef)
{
char ErrorMessage [100];
sprintf (ErrorMessage, "Sprite %s is not found", spr_name);
BREAK(ErrorMessage);
}
else
{
memset(anim_name, 0, 10);
sprintf (anim_name, "%d", ID);
m_pSpriteRef->SetAnimation(anim_name);
s_iCellWidth = m_pSpriteRef->GetBoundary().W;
s_iCellHeight = m_pSpriteRef->GetBoundary().H;
}
}
Từ lúc này ta thay thế CELL_REGION bằng 2 số tương ứng cho width và height:
#define CELL_WIDTH (CCell::s_iCellWidth)
#define CELL_HEIGHT (CCell::s_iCellHeight)
Ta bổ sung 3 file png là
Mỗi file ứng với một mode chơi (3x3, 4x4, 5x5)
(Lưu ý, cần chạy make data lại để có data mới cho game)
Trong hàm khởi tạo của state Ingame, ta load các resource này
#define MAX_CELL 25
#define SPR_CELL TOSTRING(cell.spr)
#define SPR_BOARD TOSTRING(board.spr)
void CStateIngame::Init()
{
char buffer [50];
for (int i = 0; i < MAX_CELL; i++)
{
memset(buffer, 0, 50);
sprintf (buffer, "CELL_%d", i+1);
CSprite *spr = CSpriteManager::GetInstance()->AddSprite<CFileWin32Driver>(buffer, SPR_CELL);
}
for (int i = E_BOARD_TYPE_3x3; i <= E_BOARD_TYPE_5x5; i++)
{
memset(buffer, 0, 50);
sprintf (buffer, "background_%dx%d.tga", i, i);
CImageManager::GetInstance()->AddImage<CFileWin32Driver>(buffer, true);
}
CText::GetInstance()->Load<CFileWin32Driver>("VN");
CText::GetInstance()->SetFont(0);
CGraphics2D::GetInstance()->Reset();
m_pBoard = new CBoard(E_BOARD_TYPE_4x4);
m_pBoard->SetPosition(21, 81);
}
Và sử dụng trong CBoard (dùng m_pImgBoard để lưu lại pointer đến hình cần thiết)
CBoard::CBoard(E_BoardType size): m_iSize(size),
m_iSizeSquare(size*size),
m_Cells(0),
m_iCurrentBlankCell(size*size-1)
{
m_Cells = new CCell*[m_iSizeSquare - 1];
for (int i = 0; i < m_iSizeSquare - 1; i++)
{
m_Cells[i] = new CCell(i+1);
}
m_pBoardData = new __INT32[m_iSizeSquare];
for (int i = 0; i < m_iSizeSquare; i++)
{
m_pBoardData[i] = i;
}
m_Region.X = 0;
m_Region.Y = 0;
m_Region.W = size*CELL_WIDTH;
m_Region.H = size*CELL_HEIGHT;
char buffer [50];
memset(buffer, 0, 50);
sprintf (buffer, "background_%dx%d.tga", size, size);
m_pImgBoard = CImageManager::GetInstance()->Get(buffer);
}
Ở bước render, ta vẽ các thông tin bao gồm các cells, background:
void CBoard::Render(CGraphics2D* g)
{
//Render cell
int x0 = m_Region.X;
int y0 = m_Region.Y;
int x = x0;
int y = y0;
int index = 0;
for (int i = 0; i < m_iSize; i++)
{
for (int j = 0; j < m_iSize; j++)
{
int spr_id = m_pBoardData[index++];
if (spr_id < m_iSizeSquare - 1)
{
m_Cells[spr_id]->Render(g, x, y);
}
x += CELL_WIDTH;
}
y += CELL_HEIGHT;
x = x0;
}
//Render Board
if (m_pImgBoard)
{
g->DrawImage(SPosition2D<__INT32>(0, 0), m_pImgBoard);
}
}
Download