Animation(動畫) 靜宜大學資工系 蔡奇偉 副教授 2006-2007
內容大綱 簡介 Motion Animation Sprite Animation
簡介 動畫是由隨時間而改變的圖片所產生 2D 遊戲動畫可由下列三種技巧產生: Motion Animation Sprite Animation Motion + Sprite Animation
Motion Animation 遊戲角色的位置隨時間而變。 玩家控制角色移動 自主性移動:遊戲角色按照既定路徑而移動, 不受外界因素的干擾(碰撞除外)。 被動性移動:遊戲角色的移動受到其他角色的 牽引,如追逐與逃脫。 如何設定移動路徑?
移動路徑 直線路徑 速率 (velocity) = 單位時間的位移向量 (dx, dy) 速度 (speed) = 單位時間的位移長度 = | (dx, dy) | 距離 (distance) = 速度 時間
多線段路徑 計算簡單 足夠密的多線段可以用來模擬曲線路徑
曲線路徑 explicit formula: 簡單曲線如二次錐線 parametric formula: 複雜曲線或 Bezier curves 足夠密的多線段
CPolylinePath private: CPoint *m_vertices; // 端點序列 int *m_durations; // 時間序列(單位 frame) int m_maxsize; // 序列最大長度 int m_size; // 序列使用長度 int m_index; // 目前線段的索引 CPoint m_startVertex; // 目前線段的起點 int dx, dy; // 目前線段的向量 CPoint m_currentPos; // 目前位置 int m_dfCount; // duration frame count int m_currentDur // current duration bool m_bCycle; // 是否循環
bool CPolylinePath::Alloc (int size) { m_vertices = new CPoint[size]; m_durations = new int[size-1]; if (m_vertices && m_durations) m_maxsize = size; m_size = 0; m_index = 0; return true; } else if (m_vertices) { delete [] m_vertices; m_vertices = NULL; if (m_durations) { delete [] m_durations; m_durations = NULL; m_maxsize = 0; return false;
CPolylinePath::CPolylinePath (int size) { Alloc(size); } CPolylinePath::CPolylinePath (CPoint *vertices, int *durations, int size, bool bCycle=false) if (Alloc(size)) Setup(vertices, durations, size, bCycle) CPolylinePath::~CPolylinePath () delete [] m_vertices; delete [] m_durations; CPolylinePath::OK () return m_size != 0;
bool CPolylinePath::Setup (CPoint *vertices, int *durations, int size, bool bCycle=false) { if (size > m_maxsize || size < 2) return false; int k; for (k = 0; k < size-1; k++) m_vertices[k] = vertices[k]; m_durations[k] = durations[k]; if (durations[k] <= 0) } m_size = size; m_bCycle = bCycle; EnterSegment(0) return true;
void CPolylinePath::GetCurretnPosition (CPoint &pos) { pos = m_currentPos; } bool CPolylinePath::IsStopped () return (m_index == m_size-2 && m_dfCount == m_currentDur && !m_bCycle); void CPolylinePath::EnterSegment (int n) m_index = n; m_startVertex = m_vertices[n]; dx = m_vertices[n+1].x - m_startVertex.x; dy = m_vertices[n+1].y - m_startVertex.y; m_currentPos = m_startVertex; m_currentDur = m_durations[n]; m_dfCount = 0;
bool CPolylinePath::NextPosition (CPoint &pos) { if (IsStopped()) { pos = m_currentPos; return false; } m_dfCount++; if (m_dfCount == m_currentDur) if (m_index < m_size -2) EnterSegment(m_index+1); else if (m_index == m_size -2) if (m_bCycle) EnterSegment(0); else m_currentPos = m_vertices[m_size -1]; else { m_currentPos.x = m_startVertex.x + m_dfCount*dx/m_currentDur; m_currentPos.y = m_startVertex.y + m_dfCount*dy/m_currentDur; return true;
CPolylineMotionObject 類別 class CPolylineMotionObject { public: CPolylineMotionObject() : m_path(0){} bool LoadImage (char *file) return m_image.LoadImage(file); } // 其他成員函式 private: CSurface m_image; CPolylinePath *m_path; };
bool CPolylineMotionObject::SetupPath ( CPoint *vertices, int *durations, int size, bool bCycle=false) { m_path = new CPolylinePath(vertices, durations, size, bCycle); if (m_path && m_path.OK()) return true; else delete m_path; return false; }
CPolylineMotionObject::FirstShow () { CPoint pos; m_path->GetCurretnPosition(pos); m_image.Show(pos); } CPolylineMotionObject::NextShow () m_path->NextPosition(pos);
class CMyGameApp : public CGameApp { public: CMyGameApp () : CGameApp() {} protected: virtual bool InitializeApp (); virtual bool AppMain (); private: CPolylineMotionObject m_ballObj; };
bool CMyGameApp::InitializeApp () { CPoint vertices[5]; int durations[4] = {50, 100, 50, 100}; vertices[0].Set(50, 50); vertices[1].Set(50, 400); vertices[2].Set(550, 400); vertices[3].Set(550, 50); vertices[4].Set(50, 50); m_ballObj.LoadImage ("ball.png"); m_ballObj.SetupPath(vertices, durations, 5, true); m_pScreen->Clear(); m_ballObj.FirstShow(); return true; }
bool CMyGameApp::AppMain () { m_pScreen->Clear(); m_ballObj.NextShow(); return true; } int main ( int argc, char* argv[] ) CMyGameApp myGameApp; myGameApp.InitializeSDL(); myGameApp.SetScreen(); myGameApp.SetCaption(WINDOW_TITLE); myGameApp.Run(); return EXIT_SUCCESS;
Sprite Animation Sprite 是什麼? Sprite Set Surface Play List Sprite Animation Implementation
Sprite 是什麼? Sprite 是遊戲畫面上會改變形狀的物件。 參見網站 http://www.videogamesprites.net/
Sprite Set Surface 為了減少繪圖頁的數量,我們通常把相關的 sprite 圖形擺在一張繪圖頁上,如下圖所示:
CSpriteSet 類別 class CSpriteSet : public CSurface { public: CSpriteSet () : CSurface() {} void Setup (char *file, int n, int m, CRect *pRects = NULL, bool bDisplayFormat = true); bool ShowSprite (int k, Sint16 x, Sint16 y); int SpriteWidth (int k); int SpriteHeight (int k); void GetSpriteRect (int k, CRect &rect); private: int m_numSprites; CRect *m_spriteRects; };
bool CSpriteSet::Setup (char *file, int n, int m, CRect *pRects, bool bDisplayFormat) { if (!LoadImage(file, bDisplayFormat)) return false; int count = n*m; m_spriteRects = new CRect[count]; if (m_spriteRects == NULL) int k; if (pRects) for (int k = 0; k < count; k++) m_spriteRects[k] = pRects[k]; else int w = Width() / m; int h = Height() / n; int i, j, x, y = 0; k = 0; for (i = 0; i < n; y+=h, i++) for (j = 0, x = 0; j < m; x+=w, j++) m_spriteRects[k++].Set(x, y, w, h); } return true;
bool CSpriteSet::ShowSprite (int k, Sint16 x, Sint16 y) { Show(m_spriteRects[k], x, y); } int CSpriteSet::SpriteWidth (int k) return m_spriteRects[k].w; int CSpriteSet::SpriteHeight (int k) return m_spriteRects[k].h; void CSpriteSet::GetSpriteRect (int k, CRect &rect) rect = m_spriteRects[k];
CPlayList 類別 class CPlayList { public: CPlayList(CSpriteSet *pSpriteSet); void Setup (int *sequence, int n, int delay = 1); bool ShowSprite (int k, Sint16 x, Sint16 y); bool Play (Sint16 x, Sint16 y); private: CSpriteSet *m_pSpriteSet; int m_numFrames; int *m_sequence; int m_currentFrame; int m_delay; int m_delayCount; bool m_bCycle; };
bool CPlayList::Setup (int *sequence, int n, int delay, bool bCycle) { if (n < 1) return false; if (m_sequence) delete [] m_sequence; m_sequence = new int[n]; if (!m_sequence) m_numFrames = n; for (int i = 0; i < n; i++) m_sequence[i] = sequence[i]; m_delay = delay; m_bCycle = bCycle; m_currentFrame = 0; m_delayCount = m_delay; return true; }
bool CPlayList::ShowSprite (int k, Sint16 x, Sint16 y) { return m_pSpriteSet->ShowSprite(m_sequence[k], x, y); } bool CPlayList::Play (Sint16 x, Sint16 y) bool bEnd = false; m_delayCount--; if (m_delayCount == 0) if (m_currentFrame == m_numFrames-1) { if (m_bCycle) m_currentFrame = 0; else bEnd = true; m_currentFrame++; m_delayCount = m_delay; ShowSprite (m_currentFrame, x, y); return bEnd;
class CMyGameApp : public CGameApp { public: CMyGameApp () : CGameApp() {} protected: virtual bool InitializeApp (); virtual bool AppMain (); private: CSpriteSet m_spriteset; CPlayList *m_pPlayList; };
bool CMyGameApp::InitializeApp () { int seq[] = {0, 1, 2}; m_spriteset.Setup("mariobros.png", 1, 3); m_spriteset.SetColorKey(0, 0, 0); m_pPlayList = new CPlayList(&m_spriteset); m_pPlayList->Setup(seq, 3, 5); return true; }
bool CMyGameApp::AppMain () { m_pScreen->Clear(); m_pPlayList->Play(200, 200); return true; } int main( int argc, char* argv[] ) CMyGameApp myGameApp; myGameApp.InitializeSDL(); myGameApp.SetScreen(); myGameApp.SetCaption(WINDOW_TITLE); myGameApp.Run(); return EXIT_SUCCESS;