AI algorithm demonstration in the game
Artificial intelligence is a branch with great potential. As the name suggests, it can simulate human behavior through computer commands, and artificial intelligence in the game is very diverse. For FPS, tab, RPG, Stg, ADV and other games, there are different artificial intelligence based on several theories: finite state machine, genetic algorithm, and neural network. Next I will use the most basic and commonly used AI algorithm in the game for demonstration.
My development environment is:
Windows: WindowsXP (SP3) + mingw4.4/mingw4.7 + qt4.8.3/qt5.0.1 + qtcreator2.6/qtcreator2.7
Linux: ubuntu12.10 + gcc4.7 + qt4.8.1/qt4.8.4/qt5.0.1 + qtcreator2.6/qtcreator2.7
Demo program: here
Source code: here
Note: Due to an unknown bug in qt5.0.1, the role cannot be moved after compilation, so use qt4.6 + for compilation.
The program is as follows:
Through the sub-window control options, we can set the control methods of the initial audio, mirror audio, and mirror audio. You can choose manual control or AI control. If you select manual control, you can press the "Up and down" key to control the movement of the role. If you select ai control, the role will move clockwise along the scene.
The entire project has a large amount of code. I will explain the content about AI and hope you can keep thinking about it along my train of thought.
First, how do I rotate the role clockwise along the window? A simple idea is: when the role is about to reach the top of the window, the role will move to the right; when the role is about to reach the right of the window, the role will move down, and so on. According to this idea, I wrote the following AI code:
if ( m_pCharacter->pos( ).y( ) > 20.0 ){qDebug( "AI go Up." );m_pCharacter->SetAnimation( Character::_Up_ );emit TriggerTransition( );}else if ( m_pCharacter->pos( ).x( ) < 608.0 ){qDebug( "AI go Right." );m_pCharacter->SetAnimation( Character::_Right_ );emit TriggerTransition( );}else if ( m_pCharacter->pos( ).y( ) < 340.0 ){qDebug( "AI go Down." );m_pCharacter->SetAnimation( Character::_Down_ );emit TriggerTransition( );}else if ( m_pCharacter->pos( ).x( ) > 0.0 ){qDebug( "AI go Left." );m_pCharacter->SetAnimation( Character::_Left_ );emit TriggerTransition( );}
This is also the first version of AI, but as shown in the qtmikusnake7_ver_1 application, it cannot achieve the expected effect, and the role has been spinning in the upper right corner. It seems that there is a problem with the first version. Where is the problem? This is because we give priority to the upward judgment, which leads the role to go down in the upper right corner and then go back to the upper right corner. After understanding this problem, the first idea is to add constraints for the upward judgment so that it can correctly go to the downward judgment in the upper right corner without turning back. The second version of AI is as follows:
if ( m_pCharacter->pos( ).y( ) > 20.0 && m_pCharacter->m_Direction != Character::_Down_ ){qDebug( "AI go Up." );m_pCharacter->SetAnimation( Character::_Up_ );emit TriggerTransition( );}else if ( m_pCharacter->pos( ).x( ) < 608.0 && m_pCharacter->m_Direction != Character::_Left_ ){qDebug( "AI go Right." );m_pCharacter->SetAnimation( Character::_Right_ );emit TriggerTransition( );}else if ( m_pCharacter->pos( ).y( ) < 340.0 && m_pCharacter->m_Direction != Character::_Up_ ){qDebug( "AI go Down." );m_pCharacter->SetAnimation( Character::_Down_ );emit TriggerTransition( );}else if ( m_pCharacter->pos( ).x( ) > 0.0 && m_pCharacter->m_Direction != Character::_Right_ ){qDebug( "AI go Left." );m_pCharacter->SetAnimation( Character::_Left_ );emit TriggerTransition( );}
For insurance purposes, constraints are added to each direction decision, that is, to determine the current direction. It is reasonable to say that the role is going up, so the current direction is definitely not going down, the role is going to the left, and the current direction is definitely not going to the right. All right, run the command and find that the effect is the same as that shown in qtmikusnake7_ver_2. The role moves up from the bottom right corner to the left. It seems to be another mistake.
Analyze the cause and find that the upward judgment constraint solves the problem that the upward judgment is still performed when the current direction is downward, however, when the current direction is left, the upward direction is higher than the left direction. It seems that we still need to further constrain the upward determination. According to the moving order of "Top right bottom left", we understand that the upward determination requires two constraints, and the right decision requires one constraint, however, the downward determination does not require additional constraints, and the left judgment does not require any additional constraints. The third version of AI below:
If (m_pcharacter-> pos (). Y ()> 20.0 & m_pcharacter-> m_direction! = Character: _ down _ & m_pcharacter-> m_direction! = Character: _ left _) {qdebug ("Ai go up. "); m_pcharacter-> setanimation (character: _ up _); emit triggertransition ();} else if (m_pcharacter-> pos (). X () <608.0 & m_pcharacter-> m_direction! = Character: _ left _) {qdebug ("Ai go right. "); m_pcharacter-> setanimation (character: _ right _); emit triggertransition ();} else if (m_pcharacter-> pos (). Y () <340.0) {qdebug ("Ai go down. "); m_pcharacter-> setanimation (character: _ down _); emit triggertransition ();} else if (m_pcharacter-> pos (). X ()> 0.0) {qdebug ("Ai go left. "); m_pcharacter-> setanimation (character: _ left _); emit triggertransition ();} else if (m_pcharacter-> pos (). X () = 0.0) // to prevent the role from being stuck in the lower left corner, determine {m_pcharacter-> setdirection (character: _ up _);}
Note the bottom judgment, because when the role is in the lower left corner, it will be stuck because it cannot meet these judgment conditions, therefore, we need to perform the "unlock" Operation-set the current direction to upward, which can satisfy the upward judgment. Below is the program qtmikusnake7_ver_3.
It seems that this problem has been solved successfully. However, I think this code is still too passive, because it is very passive to install patches one by one. We have to consider it from another angle. Imagine that if a statement can be "queued", it will be placed at the top when it is executed, and it will be at the end after execution, giving the next statement a chance. If so, we can queue up the top, right, bottom, and left statements in sequence, and run one statement in a round.
This is entirely possible! Recall it? This is not the classic queue structure in the data structure! However, how can we intelligently move statements to the backend for execution? Here, we need to use a class to encapsulate this statement. Because the statement format is quite organized: If condition then executes the statement. I encapsulated it like this:
class Clause: public QObject{public: Clause( Character* pParent = 0 ): QObject( pParent ), m_pCharacter( pParent ){ } virtual bool JudgeSentence( void ) = 0; virtual void Statement( void ) = 0;protected: Character* m_pCharacter;};
Then I set a queue with a ready-made qqueue in QT.
QQueue<Clause*> m_Clauses;
Then define the statement class to inherit the clause class:
class DirUpClause: public Clause{public: DirUpClause( Character* pParent = 0 ): Clause( pParent ) { } bool JudgeSentence( void ) { return m_pCharacter->pos( ).y( ) > 20.0; } void Statement( void ) { qDebug( "AI go Up." ); m_pCharacter->SetAnimation( Character::_Up_ ); }};class DirDownClause: public Clause{public: DirDownClause( Character* pParent = 0 ): Clause( pParent ) { } bool JudgeSentence( void ) { return m_pCharacter->pos( ).y( ) < 340.0; } void Statement( void ) { qDebug( "AI go Down." ); m_pCharacter->SetAnimation( Character::_Down_ ); }};class DirLeftClause: public Clause{public: DirLeftClause( Character* pParent = 0 ): Clause( pParent ) { } bool JudgeSentence( void ) { return m_pCharacter->pos( ).x( ) > 0.0; } void Statement( void ) { qDebug( "AI go Left." ); m_pCharacter->SetAnimation( Character::_Left_ ); }};class DirRightClause: public Clause{public: DirRightClause( Character* pParent = 0 ): Clause( pParent ) { } bool JudgeSentence( void ) { return m_pCharacter->pos( ).x( ) < 608.0; } void Statement( void ) { qDebug( "AI go Right." ); m_pCharacter->SetAnimation( Character::_Right_ ); }};
Finally, we can simply call the updated object status code.
foreach ( Clause* pClause, m_Clauses ){if ( pClause->JudgeSentence( ) ){pClause->Statement( );emit TriggerTransition( );break;}else{m_Clauses.dequeue( );m_Clauses.enqueue( pClause );break;}}
In the code above, the statement is executed when the condition is met. When the condition is not met, the statement is moved from the header of the queue to the end of the queue. In this way, although the Code will be much written, the idea is clear and plays an important role in more complex state maintenance. The execution efficiency is also relatively high, because there are fewer unnecessary judgments. Below is the program qtmikusnake7_ver_4.
The above algorithm is just a simple demonstration. There are also AI algorithms such as pursuit, autonomous avoidance, and path searching for role movements. For complex games, State maintenance is very complex and error-prone, this error is not as easy to perceive as program downtime. At this time, a specific position-the script designer is needed to solve this problem. Script designers face various types of data and fine-tune various program parameters through their own skilled scripting languages. A successful game always has the hard work of script designers. Therefore, all script designers are artists.
Note: The program qtmikusnake7 modifies the frame size of the previous version, so that the refresh will not be defective. the AI test code will also be removed from the next version.