【参赛作品】分享Qt下人工智能的演示
通过子窗口的控制选项,我们可以设置我们的初音ミク、镜音リン和镜音レン的控制方法,可以选择人工控制也可以选择AI控制。如果选择人工控制,那么可以通过按下“上下左右”键控制角色的移动,如果选择AI控制,那么角色会沿着场景作顺时针移动。
整个项目的代码量较多,我将选择有关人工智能的内容进行讲解,也希望大家沿着我的思路思考下去。
首先如何让角色沿着窗口作顺时针旋转?一个简单的想法就是:当角色将要达到窗口顶端那么角色将往右移动;角色将要到达窗口右端那么角色将往下移动,以此类推。按照这样的思路,我写了这样的AI代码:
这也是我AI的第一个版本,但是正如QtMikuSnake7_ver_1应用程序截图中所示,它并不能达到应有的效果,角色一直在右上角打转。看来第一个版本有问题。问题在哪儿呢?这是由于我们将向上的判定优先于向下的判定,导致了角色在右上角处转至下后又转回了右上角。了解了这个问题之后第一个想法就是为向上判定添加约束条件,使其能够在右上角处正确地转至向下判定而不会折返。下面是AI的第二个版本:
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( );
}
为了保险,按照这种思路,将每一个方向判定都添加了约束条件,即判定当前的方向是何方向。按理说角色要往上走,那么当前的方向就肯定不是往下走,角色要往左走,当前的方向就肯定不是往右走。好了,运行一下,结果发现如QtMikuSnake7_ver_2所示的效果一样,角色在右下角处至左移了一格就往上走了。看来又是一次失误。

分析原因,发现向上判定的约束虽然解决了当前方向向下时仍进行向上判定的问题,可是未解决当前方向向左时仍然出现向上判定优先于向左判定的情况。看来还是需要再对向上判定进行进一步约束,按照“上右下左”的移动顺序,我们了解向上判定需要两个约束,向右判定需要一个约束,而向下判定不需要额外的约束,向左判定就更不需要了。下面AI的第三个版本:
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 )// 为防止角色在左下角卡死设立的判断
{
m_pCharacter->SetDirection( Character::_Up_ );
}
注意到最下面一个判断,因为角色走在左下角的时候会因为都满足不了这些判定条件而陷入“卡死”状态,所以我们要进行“解锁”操作——将当前的方向设为向上,这样又可以满足向上的判定了。下面是程序QtMikuSnake7_ver_3的截图。

似乎这个问题圆满地解决了。但是我觉得这个代码还是写得太被动了,因为这些代码都是出了问题而一个一个地打补丁打上去的,非常被动。我们得换一个角度考虑。试想,如果一条语句能够“排队”,当让它执行的时候它排在最前面,执行完毕后它轮到最末尾,给下一条语句机会,要是这样的话,我们可以让上、右、下、左四条语句依次排队,一条语句一条语句地轮着运行。
这完全有可能实现!回想起来了吗?这不就是数据结构中经典的队列结构!可是,怎样才能让语句智能地移动到后面执行呢?这里需要使用一个类对这条语句进行封装。因为语句的格式相当有条理:if 判定条件 then 执行语句。我是这样封装的:
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;
};
随后我设定一个队列,在Qt中有个现成的QQueue。
QQueue<Clause*> m_Clauses;
接着进行语句类的定义,让其继承Clause类:
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_ );
}
};
最后我们在更新对象状态代码中进行一个简单地调用就可以了。
上面的算法仅仅是一个很简单的演示,对于角色的运动还有诸如追击、自主规避、寻路等AI算法,对于复杂的游戏,状态的维护非常复杂且容易出错,而这个错误又不像程序宕机那样容易觉察,这时需要一个特定的职位——脚本设计师来解决此类问题。脚本设计师面对着各类的数据,通过自身熟练的脚本语言来对程序的各类参数进行微调,一款成功的游戏总有脚本设计师付出的辛勤汗水。所以说脚本设计师都是艺术家。
后记:程序QtMikuSnake7对上一个版本的帧框大小进行了修正,刷新不会出现残影的现象,有关AI测试代码也将在下一个版本中被去除。 Qt 游戏 人工智能
[解决办法]
顶一个!
以后发一个60分的。
[解决办法]
挺好的,自己实践的东西才是自己的,看起来不错……
[解决办法]
楼主貌似很强大,没有毕业就学的如此之深
[解决办法]
学习了。。。qt很强大
[解决办法]
雨天和撒发饿
[解决办法]
挺好的,看起来不错。
[解决办法]
[解决办法]
迷糊了,这就是人工智能 ?
[解决办法]
顶楼主 曾经迷恋的AI,离我越来越远。。
[解决办法]
学习了,了解了什么是人工智能!
[解决办法]
[解决办法]
又长见识了。。Good
[解决办法]
看了这么lz的文章和这么多评论,我觉得首先lz的文章通俗易懂,那些说简单的,要是直接给你看源代码,不添加任何提示,你还愿意看吗?能让读者感到简单的文章也正说明lz的文章是好文章。
此外lz的资源制作得也很到位,我也下载了其它人的代码,不是写得乱七八糟的就是从老外那儿扒来的,lz的代码规范,感觉很舒服。
最后我觉得lz的创新能力很强,想当年我毕业的时候就会一点C语言,结合生产制作出软件完全不行。lz的博客让我感到自愧不如。
最后我希望lz继续努力,发布更多更好的原创文章。