在上一课中,我们学习了给英雄增加血条和攻击,其实就是在状态层加入了血条,并添加了一个攻击按键。本篇将在前面的基础上添加敌人,并通过有限状态机(FSM)实现简单的AI。
开发环境
Win64 : vs2010
Cocos2d-x v3.4Final
TexturePackerGUI
MapEdit
代码A
角色Role
Enemy
创建一个继承基础Role类,作为敌人。
.h
typedef enum {
AI_IDLE = 0,
AI_PATROL,
AI_ATTACK,
AI_PURSUIT
}AiState;
作为AI类型。
其他的代码和Hero大同小异。
public:
Enemy();
~Enemy();
bool init();
void updateSelf();
CREATE_FUNC(Enemy);
CC_SYNTHESIZE(cocos2d::Vec2, m_moveDirection, MoveDirection);
CC_SYNTHESIZE(float, m_eyeArea, EyeArea);
CC_SYNTHESIZE(float, m_attackArea, AttackArea)
CC_SYNTHESIZE(AiState, m_aiState, AiState);
private:
void decide(const cocos2d::Vec2& target, float targetBodyWidth);
void execute(const cocos2d::Vec2& target, float targetBodyWidth);
unsigned int m_nextDecisionTime;
由于敌人是AI,我们要给它设定一些区域,视野,最大攻击判定区,AI状态等.
.cpp
init类似
Animation *idleAnim = this->createNomalAnimation("bear_idle_d.png", 3, 6);
this->setIdleAction(RepeatForever::create(Animate::create(idleAnim)));
...
然后是类似Hero的自更新函数updateSelf刷新自己状态:
void Enemy::updateSelf()
{
//this->execute(global->hero->getPosition() + global->hero->getBodyBox().actual.origin , global->hero->getBodyBox().actual.size.width);
this->execute(global->hero->getPosition(), global->hero->getBodyBox().actual.size.width);//对象坐标及body宽度
if(this->getCurrActionState() == ACTION_STATE_WALK)
{
Vec2 location = this->getPosition();
Vec2 direction = this->getMoveDirection();
Vec2 expectP = location + direction;
float maptileHeight = global->tileMap->getTileSize().height;
//if(expectP.y<0 || !global->tileAllowMove(expectP))
//这群笨蛋AI总跑出地图报错.后续再去改进吧
if(expectP.y < 0 || expectP.y > maptileHeight * 3 )
{
direction.y =0;
}
this->setFlippedX(direction.x < 0 ? true : false);
this->setPosition(location + direction);
this->updateBoxes();
this->setLocalZOrder(this->getPositionY());
}
if(this->getCurrActionState() == ACTION_STATE_NOMAL_ATTACK_A)
{
this->runNomalAttackA();
}
}
之后我们给它一个执行延时,这是为了防止过快判断,判读的太过频繁不止给CPU压力,还会使效果出现问题。比如我们的敌人死追着Hero不放,撞了墙还在向着Hero的方向执行动画等。
void Enemy::execute(const Vec2& target, float targetBodyWidth)
{
if(m_nextDecisionTime == 0)//lazy延时到0执行下一个动作判定
{
this->decide(target, targetBodyWidth);
}else {
-- m_nextDecisionTime;
}
}
接下来通过FSM设定敌人的AI:
void Enemy::decide(const Vec2& target, float targetBodyWidth)
{
//Vec2 location = this->getPosition()+ this->getBodyBox().actual.origin;//获得自己的身体中心的坐标
Vec2 location = this->getPosition();//获得脚下坐标
float distance = location.getDistance(target);//与对象Body的距离
distance = distance - targetBodyWidth / 2;//距离范围应该减去body宽度
bool isFlippedX = this->isFlippedX();
bool isOnTargetLeft = (location.x < target.x ? true : false);//方向判定
if((isFlippedX && isOnTargetLeft) || (!isFlippedX && !isOnTargetLeft)) {
this->m_aiState = CCRANDOM_0_1() > 0.5f ? AI_PATROL : AI_IDLE;
}else {
if(distance < m_eyeArea)
{
this->m_aiState = (distance < m_attackArea)&&((fabsf(location.y - target.y) < 15)) ? AI_ATTACK : AI_PURSUIT;
}else {
this->m_aiState = CCRANDOM_0_1() > 0.5f ? AI_PATROL : AI_IDLE;
}
}
switch(m_aiState)
{
case AI_ATTACK:
{
this->runNomalAttackA();
//this->attack();
this->m_nextDecisionTime = 50;
}
break;
case AI_IDLE:
{
this->runIdleAction();
this->m_nextDecisionTime = CCRANDOM_0_1() * 100;
}
break;
case AI_PATROL:
{
this->runWalkAction();
this->m_moveDirection.x = CCRANDOM_MINUS1_1();
this->m_moveDirection.y = CCRANDOM_MINUS1_1();
m_moveDirection.x = m_moveDirection.x > 0 ? (m_moveDirection.x + velocity.x) : (m_moveDirection.x -velocity.x);
m_moveDirection.y = m_moveDirection.y > 0 ? (m_moveDirection.y +velocity.y) : (m_moveDirection.y -velocity.y);
this->m_nextDecisionTime = CCRANDOM_0_1() * 100;
}
break;
case AI_PURSUIT:
{
this->runWalkAction();
this->m_moveDirection = (target - location).getNormalized();
this->setFlippedX(m_moveDirection.x < 0 ? true : false);
m_moveDirection.x = m_moveDirection.x > 0 ? (m_moveDirection.x +velocity.x) : (m_moveDirection.x -velocity.x);
m_moveDirection.y = m_moveDirection.y > 0 ? (m_moveDirection.y +velocity.y) : (m_moveDirection.y -velocity.y);
this->m_nextDecisionTime = 10;
}
break;
}
}
distance是为了判断这个敌人和目标Body之间的距离。
下面是几个AI的判断,里面用到几个宏是随机一个数,更真实的表现敌人。
Attack,攻击,每次攻击延时50
Idle,发呆,延时随机一个0-1的数字*100
Patrol,巡逻,延时时间也似随机出来的
Pursuit,追击,当发现Hero追击的判断
主要的AI是:
目标出现在正前方?(根据视野范围)发呆or巡逻
是否在攻击范围内?(根据攻击范围)追击or攻击
这个图片更好理解下。
接下来在GameLayer加入他们
我们每次加入多个敌人,方便起见需要使用数组来实现它(链表更好)
Game
GameLayer
.h
#include "Enemy.h"
...
void addEnemies(int number);
void updateEnemies(float dt);
__Array *m_pEnemies;
实现addEnemies,一次加入多个敌人通过这个数组创建。最后别忘了将这个数组注册到Global,下一章做攻击判断用。
.cpp
void GameLayer::addEnemies(int number)
{
m_pEnemies = __Array::createWithCapacity(number);
m_pEnemies->retain();
for(int i=0;i
{
Enemy *pEnemy = Enemy::create();
pEnemy->setPosition(Vec2( random(_visibleSize.width/2,_visibleSize.width) , 70 ));
pEnemy->runIdleAction();
pEnemy->setLocalZOrder(_visibleSize.height - pEnemy->getPositionY());
//属性设置
pEnemy->setVelocity(Vec2(0.5f, 0.5f));
pEnemy->setEyeArea(300);
pEnemy->setAttackArea(80);
pEnemy->setDamageStrenth(5);
pEnemy->setSumLifeValue(100);
pEnemy->setCurtLifeValue(m_pHero->getSumLifeValue());
m_pEnemies->addObject(pEnemy);
this->addChild(pEnemy,0);
}
global->enemies= m_pEnemies;
}
init中
1this->addEnemies(5);
结果图A
我们看到了几个敌人呆立在地图中.(截图帧数问题,刚好每次敌人都没动)
由于我们没有刷新敌人的状态,敌人是不会动的。
继续添加刷新敌人的代码:
void GameLayer::updateEnemies(float dt)
{
Ref *Obj = NULL;
Vec2 distance = Vec2::ZERO;
CCARRAY_FOREACH(m_pEnemies, Obj)//遍历所有的怪物
{
Enemy *pEnemy = (Enemy*)Obj;
pEnemy->updateSelf();//自更新状态
//如果死了就移除他们
if(pEnemy->getDeadAction()->isDone())
m_pEnemies->removeObject(pEnemy);
}
}
跟新update方法:
void GameLayer::update(float dt)
{
this->updateHero(dt);
this->updateEnemies(dt);
}
结果B
他们开始乱跑和执行攻击动画了
ok,我们已经达到了想要的效果。
结论
本片实现了添加多个敌人,并给敌人设定简单的AI让其何以自动随机巡逻或者追击或是攻击Hero。但是目前只有各种动画,没有实现攻击判定.。
下一章我们通过攻击判定,让Hero或者是Enemy受伤,生命到底还会死亡。具体的属性比如生命值,攻击力之类的,参阅上面的代码。