基於Qt有限狀態機器人工智慧的一種實現及改進方法,qt人工智慧改進
基於Qt有限狀態機器人工智慧的一種實現及改進方法
人工智慧在今年是一個非常火的方向,當然了,不僅僅是今年,它一直火了很多年,有關人工智慧的一些演算法層出不窮。人工智慧在很多領域都有應用,就拿我熟悉的遊戲領域來說吧,一些尋路演算法,比如說A*演算法(我的《十日驅鬼記》就曾經使用了A*演算法進行尋路),還有一些進階的演算法,比如說決策樹等,都在遊戲中得以了廣泛的應用。我目前想製作的項目和人工智慧也有一定的關係,因此,我這個月開始學習搭建一些簡單的人工智慧架構。
蔣彩陽原創文章,首發地址:http://blog.csdn.net/gamesdev/article/details/46628447。歡迎同行前來探討。
Qt為了更加方便地在既有的GUI介面上增添更加複雜的邏輯,在4.6的時候引入了有限狀態機器這個概念。有限狀態機器指的是以限定個數的狀態進行相互轉換,而形成的一種有機的整體,它在遊戲中用得也非常多,我以前在製作遊戲項目的時候也見過自己製作有限狀態機器來處理複雜邏輯的。因此我開始重新拾起有限狀態機器,看看能不能更深入地挖掘它的內容。
如果你和我一樣瞭解了QML的用法,那麼一定會有印象,Qt 將有限狀態機器模組移植到了QML環境中來了。要使用QML的有限狀態機器,需要來一句“import QtQml.StateMachine 1.0”這樣的聲明。Qt的文檔非常豐富,在介紹有限狀態機器的時候甚至專門有一個章節,叫做“The Declarative State Machine Framework”,來介紹它的用法。如果大家還對QML的有限狀態機器不是很熟悉的話,還是看看這篇Qt協助文檔吧!
Qt的有限狀態機器,分為兩個重要的內容。一個是“State”,指的是具體的某個狀態,另外一個則是“Transition”,指的是兩個狀態之間的具體的轉換。我在使用的時候發現,QML提供的有限狀態機器,只提供了SignalTransition以及TimeoutTransition這樣的轉換,並沒有像Qt那樣提供很多實用的Transition。剛開始嘗試簡單的時候,覺得還好,但是想到以後的狀態機器異常複雜,一旦涉及到的狀態千變萬化,就可能要寫很多的狀態,實在是不方便。我拿我正在製作的項目打比方吧:
是一個非常簡單的有限狀態機器,它只有入口,沒有出口,並且只有三個狀態。除了初始狀態s1之外,只是在s2和s3之間做切換。在圖中,方框表示狀態,箭頭表示一個轉換(transition)。那麼不包括開始那個箭頭,我們這裡總共出現了6個狀態,也是3×2個狀態。用QML代碼錶示的話,是這個樣子:
QtObject{ id: root signal output( string text ) property string input property var stateMachine: StateMachine { running: true initialState: s1 State { id: s1 onEntered: output( "你好,歡迎來到人工智慧測試平台。" ) SignalTransition { targetState: s2 signal: root.inputChanged guard: root.input == "我喜歡你。" } SignalTransition { targetState: s3 signal: root.inputChanged guard: root.input != "我喜歡你。" } } State { id: s2 onEntered: output( "我也喜歡你。" ) SignalTransition { targetState: s2 signal: root.inputChanged guard: root.input == "我喜歡你。" } SignalTransition { targetState: s3 signal: root.inputChanged guard: root.input != "我喜歡你。" } } State { id: s3 onEntered: output( "我剛來到這個世界,還不太懂人類的語言,能夠教教我嗎?" ) SignalTransition { targetState: s2 signal: root.inputChanged guard: root.input == "我喜歡你。" } SignalTransition { targetState: s3 signal: root.inputChanged guard: root.input != "我喜歡你。" } } }}
這僅僅是針對一個最小可執行檔有限狀態機器而言,諸如Galgame這樣的遊戲,它的分支情況是非常多的,而且如果知道乘法原理的話,當x和y非常大的時候,產生的轉換(Transition)的個數也是非常驚人的。究其原因,是因為SignalTransition必須依附於State類作為它的sourceState。因此我們必須想辦法縮小規模才行。
因此我在研究Qt的有限狀態機器機制。幸運的是,在強大的Qt下,有限狀態機器的各個部分也是可以定製的。QState的祖先類是QAbstractState,QTransition的祖先類是QAbstractTransition,它們都是一定規模的抽象類別,我們是需要實現它們的少數方法,就可以結合Qt的有限狀態機器做自己的處理了。於是我製作了一個名為AIState的狀態類,它的作用是:
1、 儲存它所發出的所有轉換(Transition)的引用,當此狀態啟用時,統一讓所有轉換都為它服務。
同樣地,我製作了一個名為ConditionalTransition的類,它有以下幾個特性:
1、 可以設定sourceState,讓其與State脫離父子關係,即可以定義在任何需要的位置;
2、 提供觸發的條件屬性;
3、 向狀態機器發送自訂的事件。
下面是它們的代碼:
AIState.h
#ifndef AISTATE_H#define AISTATE_H#include <QState>#include <QQmlListProperty>#include "ConditionalTransition.h"class AIState : public QState{ Q_OBJECT Q_PROPERTY( QQmlListProperty<ConditionalTransition> conditions READ conditions )public: explicit AIState( QState* parent = Q_NULLPTR ); QQmlListProperty<ConditionalTransition> conditions( void );private slots: void onActiveChanged( bool active );protected: QList<ConditionalTransition*> m_conditions;};#endif // AISTATE_H
AIState.cpp
#include "AIState.h"AIState::AIState( QState* parent ): QState( parent ){ connect( this, SIGNAL( activeChanged( bool ) ), this, SLOT( onActiveChanged( bool ) ) );}QQmlListProperty<ConditionalTransition> AIState::conditions( void ){ return QQmlListProperty<ConditionalTransition>( this, m_conditions );}void AIState::onActiveChanged( bool active ){ // 將原來transition的sourceState設定為AIState。 foreach ( ConditionalTransition* condition, m_conditions ) { condition->setSourceState( active? this: Q_NULLPTR ); }}
ConditionalTransition.h
#ifndef CONDITIONALTRANSITION_H#define CONDITIONALTRANSITION_H#include <QObject>#include <QObjectList>#include <QEvent>#include <QAbstractTransition>#include <QQmlListProperty>class ConditionalTransition: public QAbstractTransition{ Q_OBJECT Q_PROPERTY( QState* sourceState READ sourceState WRITE setSourceState NOTIFY sourceStateChanged ) Q_PROPERTY( bool when READ condition WRITE setCondition NOTIFY conditionChanged )public: explicit ConditionalTransition( QState* sourceState = Q_NULLPTR ); inline bool condition( void ) { return m_condition; } void setCondition( bool condition ); void setSourceState( QState* state );signals: void sourceStateChanged( void ); void conditionChanged( void );protected: virtual bool eventTest( QEvent* event ); virtual void onTransition( QEvent* event ); bool m_condition;};class ConditionalEvent: public QEvent{public: explicit ConditionalEvent( bool condition, ConditionalTransition* sourceTransition ): QEvent( QEvent::Type( ConditionalType ) ), m_condition( condition ), m_sourceTransition( sourceTransition ) { } inline bool condition( void ) { return m_condition; } inline ConditionalTransition* sourceTransition( void ) { return m_sourceTransition; } enum Type { ConditionalType = QEvent::User + 2079 };private: bool m_condition; ConditionalTransition* m_sourceTransition;};#endif // CONDITIONALTRANSITION_H
ConditionalTransition.cpp
#include <QStateMachine>#include "ConditionalTransition.h"ConditionalTransition::ConditionalTransition( QState* sourceState ): QAbstractTransition( sourceState ){ m_condition = false;}void ConditionalTransition::setCondition( bool condition ){ m_condition = condition; emit conditionChanged( ); if ( condition && sourceState( ) != Q_NULLPTR && sourceState( )->active( ) && machine( )->isRunning( ) ) { // 只允許狀態機器正在運行並且源狀態被啟用的向狀態機器發送事件 machine( )->postEvent( new ConditionalEvent( condition, this ) ); }}void ConditionalTransition::setSourceState( QState* state ){ if ( sourceState( ) == state ) return; setParent( state ); emit sourceStateChanged( );}bool ConditionalTransition::eventTest( QEvent* event ){ bool ret = false; if ( event->type( ) == QEvent::Type( ConditionalEvent::ConditionalType ) ) { // 如果當前條件為真,並且源轉換為其本身,那麼通過,執行轉換 ConditionalEvent* ce = static_cast<ConditionalEvent*>( event ); ret = ce->sourceTransition( ) == this; } return ret;}void ConditionalTransition::onTransition( QEvent* event ){ Q_UNUSED( event );}
接著將這幾個類註冊到QML環境中,就可以在QML中定義這些類的執行個體了。
StateMachine{ id: machine running: true initialState: s1 StateSettings { id: settings } property alias inputWord: settings.inputWord property alias outputWord: settings.outputWord Condition { id: c2 objectName: "c2" when: inputWord.indexOf( "我喜歡你" ) != -1 targetState: s2 } Condition { id: c3 objectName: "c3" when: inputWord.indexOf( "我喜歡你" ) == -1 targetState: s3 } AIState { id: s1 objectName: "AI:s1" conditions: [ c2, c3 ] onEntered: outputWord = "你好,歡迎來到人工智慧測試平台。" } AIState { id: s2 objectName: "AI:s2" conditions: [ c2, c3 ] onEntered: outputWord = "我也喜歡你。" } AIState { id: s3 objectName: "AI:s3" conditions: [ c2, c3 ] onEntered: outputWord = "我剛來到這個世界,還不太懂人類的語言,能夠教教我嗎?" }}
下面用狀態機器的圖來分析一下:
紅色代表的是處於啟用的狀態,綠色則是處於啟用的狀態所擁有的轉換。結合上面的QML代碼我們可以知道,程式中總共只定義了兩個轉換,並且轉換定死的是targetState,而不是綁在了sourceState上,這麼做可以把狀態和轉換進行解耦。比以前的實現少用了四個轉換。如果有限狀態機器大起來了,這樣的效率提升是非常可觀的。
示範程式的運行:
原始碼:這裡