制作横版游戏KillBear第6课:添加敌人 简单AI实现

 在上一课中,我们学习了给英雄增加血条和攻击,其实就是在状态层加入了血条,并添加了一个攻击按键。本篇将在前面的基础上添加敌人,并通过有限状态机(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攻击

 

20150209212200223.jpg

这个图片更好理解下。

接下来在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

我们看到了几个敌人呆立在地图中.(截图帧数问题,刚好每次敌人都没动)

 

20150209213554341.gif

由于我们没有刷新敌人的状态,敌人是不会动的。

继续添加刷新敌人的代码:

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

他们开始乱跑和执行攻击动画了

 

20150209214144593.gif

ok,我们已经达到了想要的效果。

结论

       本片实现了添加多个敌人,并给敌人设定简单的AI让其何以自动随机巡逻或者追击或是攻击Hero。但是目前只有各种动画,没有实现攻击判定.。

       下一章我们通过攻击判定,让Hero或者是Enemy受伤,生命到底还会死亡。具体的属性比如生命值,攻击力之类的,参阅上面的代码。