QTE為Quick Time Event的簡(jiǎn)寫,多見于主機(jī)游戲中,通常以在規(guī)定時(shí)間內(nèi)根據(jù)提示完成(一系列)按鍵操作的形式出現(xiàn)。就表現(xiàn)上看,其實(shí)是過場(chǎng)動(dòng)畫的一種分解觸發(fā)機(jī)制,用于表現(xiàn)看似復(fù)雜實(shí)則固定的動(dòng)作或者場(chǎng)景效果,一方面在消除了單純過場(chǎng)乏味感的同時(shí)增強(qiáng)了游戲的互動(dòng)性,另一方面還降低了表現(xiàn)復(fù)雜效果的開發(fā)難度。


圖1 QTE in GOD OF WAR III
目前MMORPG的一個(gè)開發(fā)趨勢(shì)就是融入主機(jī)游戲中的一些元素,提升網(wǎng)游在傳統(tǒng)游戲性之外的可玩性。本文主要根據(jù)自己在項(xiàng)目中開發(fā)的QTE系統(tǒng),介紹一個(gè)相對(duì)比較簡(jiǎn)單的原型框架。
一、分析
以一個(gè)限時(shí)按鍵的QTE來(lái)分析,其中包括的元素有:時(shí)間、鍵、結(jié)果。時(shí)間與鍵可看作Event的約束條件,它們共同決定該Event是否達(dá)成。而結(jié)果可作為一條消息發(fā)送給關(guān)心者,然后由后者決定與之相關(guān)的表現(xiàn)。

圖2
鼠標(biāo)作為PC上重要的外設(shè),當(dāng)然會(huì)被用于QTE,其形式可能會(huì)包括:搖晃、按鍵、滾輪滾動(dòng)等等。Event是否達(dá)成的判斷流程,與限時(shí)按鍵大同小異。
有時(shí)候Designer可能會(huì)設(shè)計(jì)一系列Event,增加操作難度,這時(shí)就需要考慮將多個(gè)Event串聯(lián)起來(lái),只有所有Event達(dá)成時(shí),該QTE才能算達(dá)成。因此,在圖1之上,需要加入另一個(gè)判斷邏輯。

圖3
二、實(shí)現(xiàn)
通過分析,不難得出一個(gè)典型的QTE系統(tǒng)的核心數(shù)據(jù)結(jié)構(gòu):Event鏈表。此外,為了將結(jié)果反饋給QTE請(qǐng)求者,還應(yīng)該提供一個(gè)通用的通知機(jī)制。在這個(gè)原型框架中,通過加入一個(gè)中間層來(lái)隔離異構(gòu)的請(qǐng)求者,實(shí)現(xiàn)了回調(diào)機(jī)制。

圖4
QTEFunctor作為處理Event的仿函數(shù)基類,給出了各種QTE的處理接口。
Code Snippet
- class QTEFunctor
- {
- public:
- QTE_TYPE m_type;
- protected:
- float _fTimer;
- float _fDuration;
- unsigned int _uValue; ///< condition
- int _iRetValue; ///< returned value
- public:
- virtual QTEFUNCTOR_RETVAL operator() (float) = 0;
- unsigned int GetValue() { return _iRetValue; }
- };
-
- class MouseShakeFunctor : public QTEFunctor
- {
- private:
- CPoint _point;
- public:
- MouseShakeFunctor(const QTECondition&);
- ~MouseShakeFunctor() {}
- QTEFUNCTOR_RETVAL operator() (float);
- };
-
- class KeyDownFunctor : public QTEFunctor
- {
- public:
- KeyDownFunctor(const QTECondition&);
- ~KeyDownFunctor() {}
- QTEFUNCTOR_RETVAL operator() (float);
- };
QTEDelegateBase提供了回調(diào)函數(shù)的統(tǒng)一接口,為兼容類函數(shù)和非類函數(shù),分別派生出了QTETypeDelegate和QTENoTypeDelegate。
Code Snippet
- class QTEDelegateBase
- {
- public:
- virtual void Call(QTEManager::QTE_TYPE , int) = 0;
- virtual ~QTEDelegateBase() {}
- protected:
- QTEDelegateBase() {}
- };
-
- /// non-class method callback
- typedef void (*CallbackFuncPtrType)(QTEManager::QTE_TYPE , int); ///< FAILED: second parameter <=0, OK : >0 (may include additional meanings)
- class QTENoTypeDelegate : public QTEDelegateBase
- {
- public:
- QTENoTypeDelegate(CallbackFuncPtrType func) : _pCallbackFunc(func) {}
- ~QTENoTypeDelegate() {}
- void Call(QTEManager::QTE_TYPE , int) {}
- private:
- CallbackFuncPtrType _pCallbackFunc;
- };
-
- /// class method callback
- template<class C>
- class QTETypeDelegate : public QTEDelegateBase
- {
- public:
- QTETypeDelegate(C* , void (C::*)(QTEManager::QTE_TYPE , int));
- ~QTETypeDelegate() {}
- void Call(QTEManager::QTE_TYPE , int);
- private:
- C* _pObj;
- void (C::*_pCallbackFunc)(QTEManager::QTE_TYPE , int);
- };
-
- template<class C>
- QTETypeDelegate<C>::QTETypeDelegate(C* pObj , void (C::*func)(QTEManager::QTE_TYPE , int))
- : _pObj(pObj) , _pCallbackFunc(func)
- {
-
- }
-
- template<class C>
- void QTETypeDelegate<C>::Call(QTEManager::QTE_TYPE type , int value)
- {
- (_pObj->*_pCallbackFunc)(type , value);
- }
QTEManager中采用了vector容器來(lái)保存待處理的多個(gè)Event鏈,并提供申請(qǐng)QTE、取消QTE的接口,以及Event是否達(dá)成的邏輯更新。
Code Snippet
- void QTEManager::RequestQTE(QTEDelegateBase* pQTE , std::vector<QTECondition>& condList)
- {
- std::vector<QTECondition>::iterator iter = condList.begin();
- QTELinkNode* pNode = NULL;
- QTELinkNode* pNextNode = NULL;
-
- for(; iter != condList.end(); ++iter)
- {
- if(pNextNode)
- {
- pNextNode->pNext = new QTELinkNode;
- pNextNode = pNextNode->pNext;
- }
- else
- {
- pNode = new QTELinkNode;
- pNextNode = pNode;
- }
-
- switch(iter->type)
- {
- case QT_MOUSE_SHAKE:
- pNextNode->pDelegate = pQTE;
- pNextNode->pFunctor = new MouseShakeFunctor(*iter);
- break;
- case QT_KEY_DOWN:
- pNextNode->pDelegate = pQTE;
- pNextNode->pFunctor = new KeyDownFunctor(*iter);
- break;
- case QT_KEY_PRESS:
- pNextNode->pDelegate = pQTE;
- pNextNode->pFunctor = new KeyPressFunctor(*iter);
- break;
- }
- }
-
- if(pNode)
- _RequestList.push_back(pNode);
- }
-
- void QTEManager::CancelQTE(QTEDelegateBase* pQTE)
- {
- QTEListType::iterator iter = _RequestList.begin();
-
- for(; _RequestList.end() != iter; ++iter)
- {
- if(pQTE == (*iter)->pDelegate)
- {
- (*iter)->pDelegate->Call((*iter)->pFunctor->m_type , -1 , 0); ///< failed
-
- QTELinkNode* pNode = *iter;
- /// delete the rest node
- while(pNode)
- {
- *iter = pNode->pNext;
- delete pNode;
- pNode = *iter;
- }
- _RequestList.erase(iter);
- break;
- }
- }
- }
Code Snippet
- #define STEP_OK_BASE 1000
- #define SUCCESS_BASE 2000
-
- void QTEManager::Update(float dt)
- {
- QTEListType::iterator iter = _RequestList.begin();
- for(; iter != _RequestList.end();)
- {
- QTEFUNCTOR_RETVAL ret = (*iter)->pFunctor->operator ()(dt);
- if(QTEFR_FAILED == ret)
- {
- (*iter)->pDelegate->Call((*iter)->pFunctor->m_type , -1); ///< failed
-
- QTELinkNode* pNode = *iter;
- /// delete the rest event
- while(pNode)
- {
- *iter = pNode->pNext;
- delete pNode;
- pNode = *iter;
- }
- iter = _RequestList.erase(iter);
- }
- else if(QTEFR_OK == ret)
- {
- /// get next event
- if((*iter)->pNext)
- {
- QTELinkNode* pNode = *iter;
- pNode->pDelegate->Call(pNode->pFunctor->m_type ,
- STEP_OK_BASE + pNode->pFunctor->GetValue()); ///< step ok
- *iter = (*iter)->pNext;
- delete pNode;
- ++iter;
- }
- else
- {
- (*iter)->pDelegate->Call((*iter)->pFunctor->m_type ,
- SUCCESS_BASE + (*iter)->pFunctor->GetValue()); ///< finally ok
- delete *iter;
- iter = _RequestList.erase(iter);
- }
- }
- else if(QTEFR_PENDING == ret)
- {
- (*iter)->pDelegate->Call((*iter)->pFunctor->m_type ,
- (*iter)->pFunctor->GetValue()); ///< continue progressing
- ++iter;
- }
- }
- }