You are on page 1of 18

项目的战斗场景开发

服务器战斗场景开发
第一节 加载战场
注:相似步骤很多,整个过程代码量大
1.修改英雄位置信息 2.添加英雄(创建英雄模型) 3.英雄模型显示到场景中
battle中:
//1.实现对英雄处理等待的功能
checkLoadingTimeOut(tUTCMilsec: number) {
//不是loading就返回
if (BattleStateEnum.Loading != this._battleState) {
return;
}
//bIfAllPlayerConnect:所有玩家都连接为true
let bIfAllPlayerConnect = true;
//如果时间未到
if (tUTCMilsec - this._battleStateTime < ConstLoadTime) {
//遍历所有玩家
for (let i = 0; i < ConstMaxUserInBattle; i++) {
//若有非空类玩家与没有连接的玩家
if (this._battleUserInfo[i]._user && !this._battleUserInfo[i]._ifLoadComplete) {
bIfAllPlayerConnect = false; //返回true
break;
}
}
}
//玩家没有连接进来
if (!bIfAllPlayerConnect) {
return;
}
//遍历所有玩家
for (let i = 0; i < ConstMaxUserInBattle; i++) {
if (this._battleUserInfo[i].ifNoGameUnit()) {
continue;//不是玩家退出循环,不用设置位置
}
do {
//获取当前用户与id
let piUser = this._battleUserInfo[i]._user;
let heroTypeId = this._battleUserInfo[i]._heroTypeID;
//id是否有效判断
if (heroTypeId < 1) {
heroTypeId = ConfigManager.getInstance().getUserHeroIdByMapId(this.getBattleMapId());
}
//获取当前用户的证明信息
let eGOCamp = <GameObjectCampEnum>this._battleUserInfo[i]._grpID;
//设置接口类型变量
let shbp = <IHeroBornPos>{};
//配置中读取出生地位置
let bFlag = ConfigManager.getInstance().getHeroBornConfig(this.getBattleMapId(), eGOCamp, shbp);
if (!bFlag) { //判断是否得到配置信息(程序严谨性)
break;
}
//获取英雄位置
let pcBornPos = shbp._pos;
if (Utils.isZero(pcBornPos)) { //不成功则退出
break;
}
//3vs3或5vs5的情况,疏散英雄所在位置
let bornAngle = (Math.floor(i / 2) % 3) * (3.1415926 * 2 / 3);//角度
//向量值
let bornMoveVec = new THREE.Vector3(Math.cos(bornAngle), 0, Math.sin(bornAngle));
bornMoveVec.multiplyScalar(200);//出生的移动向量值放大200倍
pcBornPos.add(bornMoveVec);//出生位置在原基础上加上移动位置
//方位
let cDir = shbp._dir;
//判断是否为0向量
if (Utils.isZero(cDir)) {
break;
}
//创建英雄
let pcHero: HOKHero = null;
//判断是否为AI,为AI则创建AI英雄,否则创建用户的英雄
if (this._battleUserInfo[i]._ifAI) {
pcHero = this.addHero(heroTypeId, pcBornPos, cDir, null, this._battleUserInfo[i]._guid, eGOCamp, this._battleUser
} else { //普通用户创建英雄
pcHero = this.addHero(heroTypeId, pcBornPos, cDir, piUser, piUser.getGUID(), eGOCamp, this._battleUserInfo[i]._po
this._battleHero++; //自增,方便下一个用户
}
if (!pcHero) {
break;
}
//赋值
this._battleUserInfo[i]._hero = pcHero;
pcHero.syncCpToCl(); //将英雄信息发给客户端
this.broadTipsByType(TipsTypeEnum.ObjAppear); //广播可见
this.syncBattleDelayTime(piUser);
} while (false); //只有false结束
}
//战斗信息遍历发送给客户端
for (let i = 0; i < ConstMaxUserInBattle; i++) {
if (!this._battleUserInfo[i]._user) {
continue;
}
//战斗信息广播
this.battleHeroInfo(this._battleUserInfo[i]._user);
}
//遍历所有玩家添加到战场
for (let i = 0; i < ConstMaxUserInBattle; i++) {
if (this._battleUserInfo[i].ifNoGameUnit()) {
continue;
}
if (this._battleUserInfo[i]._user) {
let cTempUserMap = new Array<HOKUser>();//所有用户添加到cTempUserMap中
cTempUserMap.push(this._battleUserInfo[i]._user);//发送可见消息
if (this._battleUserInfo[i]._hero != null) {
this._battleUserInfo[i]._hero.onAppearInSight(cTempUserMap);
//添加到视野中
this.addUserToSight(this._battleUserInfo[i]._user, this._battleUserInfo[i]._hero.getCampID());
}
}
}
this.setBattleState(BattleStateEnum.Playing);//进入到playing状态

第二节 数据容错处理

在configmanager.ts中,因代码量实在过多故建议直接在项目中查阅

第三节 加载英雄
//添加英雄到战场
// eOT: ObjectTypeEnum 枚举id rsMasterGUID:房主id eCampID: GameObjectCampEnum证明id
addHero(eOT: ObjectTypeEnum, crsPos: THREE.Vectors3, crsdir: THREE.Vectors3, piWatchUser: HOKUser, rsMasterGUID: number, eCampID: G
//获取英雄配置信息
let cpsHeroCfg = ConfigManager.getInstance().getHeroConfig(Number.parseInt(<any>eOT));
if (!cpsHeroCfg) {
return null;
}
//如果是ai或用户存在
if (false == ifAI && !piWatchUser) {
return null; //返回空
}

let sObjGUID = this.generateObjGUID();


//new对象,玩家用户创建英雄
let pcHero = new HOKHero(sObjGUID, eCampID, eOT, rsMasterGUID, this, this._battleManager, ifAI);
//加载英雄配置信息
let n32Res = pcHero.loadHeroConfig(cpsHeroCfg);
if (n32Res != PreDefineErrorCodeEnum.eNormal) {
pcHero = null;
return null;
}
//可见用户存在
if (piWatchUser) {
//重生,死亡状态复活
pcHero.changeUserHeroRebornTimes(piWatchUser.getHeroRebornTimes());
}
//设置拥有者
pcHero.setUser(piWatchUser);
//座位号
pcHero.setSeatID(seat);
//开始动作
pcHero.beginActionFree(false);
//出生位置
pcHero.setHeroBornPos(crsPos);
//进入战斗中
//enterBattle封装好战斗功能
let rn32RetFlag = this.enterBattle(pcHero, crsPos, crsdir);
if (rn32RetFlag != PreDefineErrorCodeEnum.eNormal) {
pcHero = null;
return null;
}
pcHero.loadPassitiveSkill();//加载技能
pcHero.resetAI();//重置ai
return pcHero;
}

第四节 进入战场
proto文件下创建BattleProto.ts文件,并在hok.cof中添加此文件
battle中:

BattleProto.ts:

BattleManager.ts中:

第五节 显示英雄
本节主要是调试英雄显示错误
存储的英雄是未定义的对象,new出来问题解决
在battle中:
//playTimeOut中参数为传递时间与时间差
playTimeOut(tUTCMilsec: number, tTickSpan: number): void {
if (BattleStateEnum.Playing != this._battleState) {
return ; //处于playing才继续
}
//遍历所有玩家
for (let i = 0; i < ConstMaxUserInBattle; i++) {
//判断是否已经加载完整的场景与当前英雄是否为空
if (this._battleUserInfo[i]._ifLoadComplete == false && this._battleUserInfo[i]._hero != null
&& this._battleUserInfo[i]._hero.ifAI() == false && this._battleUserInfo[i]._offLineTime != 0
//时间超过30s
&& tUTCMilsec - this._battleUserInfo[i]._offLineTime > 30000) {
//替换为机器人ai,即为用户掉线后机器人补上
this.resetAIAtOnUserOffline(this._battleUserInfo[i]._hero);
}
}
//调用移动管理方法监控
this._moveManager.onSchedule(tUTCMilsec, tTickSpan);
//技能特效监控
this._effectManager.onSchedule(tUTCMilsec, tTickSpan);
//被动技监控
this._passiveSkillManager.onSchedule(tUTCMilsec, tTickSpan);
this._sightManager.onSchedule(tUTCMilsec, tTickSpan);
//对玩家对象也要进行监控
for (let gameObject of this._gameObjectMap.values()) {
gameObject.onSchedule(tUTCMilsec, tTickSpan);
}
//判断死亡状态,如果已死则移除死亡对象
for (let [key, gameObject] of this._gameObjectMap.entries()) {
if (gameObject.getExpire()) {
this.removeGameUnit(gameObject);//移除
this._gameObjectMap.delete(key);//删除map中的对象
continue;
}
}
//遍历所有延时信息,发送给客户端
for (let item of this._dealySendMsgSet) {
//判断是否超时
if (tUTCMilsec - item._beginSendTime >= item._delayTime) {
//获取所有用户
for (let user of item._users) {
//发送消息
user.postMessageById(item._n32MsgID, item._msgStr);
}
//集合中删除信息
this._dealySendMsgSet.delete(item);
continue;
}
}
}

在定时器中调用此方法
this.playTimeOut(utcMilsec, tickSpan);

第六节 英雄AI移动(本节开始均为高能)
四层调用!
注意:别忘记消息驱动一定需要请求函数,位于proto中
battleproto中:

aihero中:
battle中movemanager中:

battle中:

hokhero.ts中:
battlemanager中:

第七节 停止移动
类似移动原理,层层调用
battleproto.ts中:

aihero中:

battle中:

movemanager中:
battle中hokhero中

battlemanager中:

第八节 A星算法寻找路径 定时器实现


寻路区域位于astarregion文件中
findpathinfo.ts已经定义好寻路结果的数据结构
在astar中完成寻路算法

第九节 A星算法寻找路径
//寻路函数,将寻路参数传递进来
findPath(findPathInfo: FindPathInfo): number {
//如果是无效值
if (!findPathInfo._pathBuff || findPathInfo._pathNodeCount) {
return AllErrorCodeEnum.eEC_NullPointer;//返回当前点
}
this._runTimeID++;//移动的id值改变,实现自增过程
//获取起始逻辑点
let beginRegion = this.getRegion(findPathInfo._startPoint.x, findPathInfo._startPoint.z);
if (!beginRegion) { //判断该点是否为有效点
return AllErrorCodeEnum.eEC_InvalidRegionID;
}
//获取目标点
let targetRegion = this.getRegion(findPathInfo._targetPoint.x, findPathInfo._targetPoint.z);
//判断起始点与目标点是否一样
if (!targetRegion || beginRegion == targetRegion) {
findPathInfo._pathNodeCount = 0;//寻路节点设置为0
return PreDefineErrorCodeEnum.eNormal;
}
this._openRegionBuffBTree.clear();//开始列表中所有数据清空
//开始设置为默认状态
beginRegion._gValue = 0;
beginRegion._hValue = 0;
beginRegion._nodeState = NodeStateEnum.Open;//默认开启状态
this._openRegionBuffBTree.push(beginRegion);//起始点添加到开启列表中
//寻路属性
let aroundRegion: AStarRegion = null;
let findEndRegion: AStarRegion = null;
let n32TestRegionTimes = 0;
let n32MaxOpenNode = 0;
let n32MinDist = -1;
let nearestRegion: AStarRegion = null;
let aroundRegionList = new Array<AStarRegion>(ConstAroundAStarRegionNum);
let un32StepSize = 10;
let intDirToCurRegion: IntDirEnum;
let un16CurFValue = 0;
let un16NewGValue = 0;
let un16TarHValueInX = 0;
let un16TarHValueInZ = 0;
let un16Mini = 0;
let un16Max = 0;
let un16NewHValue = 0;
let un16NewFValue = 0;
let ifHasDynBlock = false;
//开始寻路
while(true) {
//步骤:
//寻找到开启列表中f值最低的部分称为当前格,将其切换到关闭列表中,对相邻的8个格子依次进行判断;
//判断是不是可以通过,或已经在关闭列表中则进行省略,不计算。对于不在开启列表的格子添加进去,并作为周围格的父节点,
//计算f值,z值和h值。对于已经在开启列表中的,用z值作为参考新路径的值来判断是否有更低的z值,将会出现更好的路径,
//判断完结束寻找,将格子添加到关闭列表,表明路径已经找到。
let curRegion = this._openRegionBuffBTree.popMiniFValue();
if (!curRegion) { //判断当前格子存在
break;
}
if (10000 < n32TestRegionTimes) {//测试格子超过1w次退出循环
break;
}
//获取周围格子的8个格子
this.getAroundRegion(curRegion, aroundRegionList);
//8个格子存放到aroundRegion中
for (let i = 0; i < ConstAroundAStarRegionNum; i++) {
n32TestRegionTimes++;//次序更改
aroundRegion = aroundRegionList[i];
if (!aroundRegion) { //判断格子存在
continue;
}
//判断格子是否为目标点,是则寻路结束
if (aroundRegion == targetRegion) {
aroundRegion._parent = curRegion;//当前格子设置为父节点
//格子状态设置为关闭
aroundRegion._nodeState = NodeStateEnum.Close;
//成为最后一个格子
findEndRegion = aroundRegion;
break;//结束
}
//格子被关闭,走不通或者已经走过的
if (NodeStateEnum.Close == aroundRegion._nodeState) {
continue;
}
//是否产生碰撞
ifHasDynBlock = this.checkAStarCollider(aroundRegion._indexX, aroundRegion._indexZ, findPathInfo._dynamicBlockList);
if (ifHasDynBlock) {
continue;
}
//格子是否与目标格子相邻
let dist = Math.abs(aroundRegion._indexX - findPathInfo._targetPoint.x) + Math.abs(aroundRegion._indexZ - findPathInfo._targetPoin
//最小值小于0或者比相邻距离大
if (n32MinDist < 0 || n32MinDist > dist) {
n32MinDist = dist; //将相邻距离赋给最小值
nearestRegion = aroundRegion;
}
//判断是否走到目标格子
if (false == findPathInfo._ifToTarget) {
//判断是否相邻
if (dist <= 2) {
//设置为父节点
aroundRegion._parent = curRegion;
//关闭状态
aroundRegion._nodeState = NodeStateEnum.Close;
//最后一次的格子
findEndRegion = aroundRegion;
break;
}
}
//找到z值,默认为10
un32StepSize = 10;
//判断是否为斜线
intDirToCurRegion = this.getRegionDir(curRegion, aroundRegion);
if (intDirToCurRegion % 2 == 1) {
un32StepSize = 14; //若为斜线改为14
}
//当前为z值+h值
un16CurFValue = aroundRegion._gValue + aroundRegion._hValue;
//z值是改变的,计算新的z值
un16NewGValue = curRegion._gValue + un32StepSize;
//目标h值先设置为0
un16TarHValueInX = 0
//目标点与当前点的判断,预测h值
if (targetRegion._indexX > aroundRegion._indexX) {
un16TarHValueInX = targetRegion._indexX - aroundRegion._indexX;
} else {
//判断x方向
un16TarHValueInX = aroundRegion._indexX - targetRegion._indexX;
}
//判断z的与x一样
un16TarHValueInZ = 0
if (targetRegion._indexZ > aroundRegion._indexZ) {
un16TarHValueInZ = targetRegion._indexZ - aroundRegion._indexZ;
} else {
un16TarHValueInZ = aroundRegion._indexZ - targetRegion._indexZ;
}
//获取最小h值
un16Mini = Math.min(un16TarHValueInX, un16TarHValueInZ);
//获取最大h值
un16Max = Math.max(un16TarHValueInX, un16TarHValueInZ);
//新的h值算法
un16NewHValue = un16Mini * 14 + (un16Max - un16Mini) * 10;
//最新f值
un16NewFValue = un16NewGValue + un16NewHValue;
//当前f值是否为0或最新的f值是不是比当前f值要小
if (0 == un16CurFValue || un16CurFValue > un16NewFValue) {
//当前的值赋给下一个父节点
aroundRegion._gValue = un16NewGValue;
aroundRegion._hValue = un16NewHValue;
aroundRegion._parent = curRegion;
//非开放状态需要改变状态
if (NodeStateEnum.Open != aroundRegion._nodeState) {
aroundRegion._nodeState = NodeStateEnum.Open;
//可抖数量设为0
aroundRegion._aroundGOCount = 0;
//为了修改当前h值判断是否为0
if (0 < aroundRegion._aroundGOCount) {
aroundRegion._hValue += aroundRegion._aroundGOCount * un32StepSize;
}
//将此路点添加到开启列表中
this._openRegionBuffBTree.push(aroundRegion);
//判断当前节点数与最大节点数
if (n32MaxOpenNode < this._openRegionBuffBTree._curRegionNum) {
//修改最大开启节点数的数值
n32MaxOpenNode = this._openRegionBuffBTree._curRegionNum;
}
}
}
}
//最后一个节点存在,结束循环
curRegion._nodeState = NodeStateEnum.Close;
if (findEndRegion) {
break;
}
}
}

第十节 A星算法保存路径
接上节在astar中完成寻路算法后,继续补充
//平滑算法实现英雄移动
//算法的主要思想是,不断从两头检查两个点中间经过的所有节点,判断其是否有经过阻挡点。如果没有,则删除这两个点中间的所有节点
if (findPathInfo._ifFliter && findPathInfo._pathNodeCount > 1) {
//fliterArray存储要筛选的所有节点
let fliterArray = new Array<THREE.Vector3>(32);
//遍历整个数组,为数组中的每个对象创建新的对象
for (let index=0; index<32; index++) {
fliterArray[index] = new THREE.Vector3();
}
//给数组中元素赋值
let len = findPathInfo._pathNodeCount + 1;
//第一个元素是起点位置,获取到路径节点的节点数,当前的路径节点数不可以超过总节点数
fliterArray[0].copy(findPathInfo._startPoint);
//所有缓冲区中的节点缓存到数组中
for (let i = 1; i < len; i++) {
fliterArray[i].copy(findPathInfo._pathBuff[i - 1]);
}
//计算两个节点之间是否有阻挡点
let i = 0;
let j = len - 1;

while (j - i > 1) {
while (j - i > 1) {
let ifConnect = true;
//检查阻挡,计算距离
let differenceX = fliterArray[j].x - fliterArray[i].x;
let differenceY = fliterArray[j].z - fliterArray[i].z;
let stepNum = Math.floor(Math.pow(differenceX * differenceX + differenceY * differenceY, 0.5));
let stepX = differenceX / stepNum;
let stepY = differenceY / stepNum;
for (let stepIndex = 1; stepIndex < stepNum - 1; stepIndex++) {
let tempX = Math.floor(fliterArray[i].x + stepIndex * stepX);
let tempY = Math.floor(fliterArray[i].z + stepIndex * stepY);
if (this.checkAStarCollider(tempX, tempY, findPathInfo._dynamicBlockList)
|| this.checkAStarCollider(tempX, tempY + 1, findPathInfo._dynamicBlockList)
|| this.checkAStarCollider(tempX + 1, tempY, findPathInfo._dynamicBlockList)
|| this.checkAStarCollider(tempX + 1, tempY + 1, findPathInfo._dynamicBlockList)
) {
ifConnect = false;
break;
}
}

if (ifConnect) {
let removeLen = j - i - 1;
for (let index=0; index<len-j; index++) {
fliterArray[i+1 +index].copy(fliterArray[j +index]);
}
len -= removeLen;
break;
}
else {
j--;
}
}
i++;
j = len - 1;
}
//返回最终计算结果
for (let i = 1; i < len; i++) {
findPathInfo._pathBuff[i - 1].x = fliterArray[i].x;
findPathInfo._pathBuff[i - 1].z = fliterArray[i].z;
}
//赋值到缓冲区中,节点数值会少一个
findPathInfo._pathNodeCount = len - 1;
}
return PreDefineErrorCodeEnum.eNormal;//返回
}
}

第十一节 机器人AI移动
aihero中:

battle中:

movemanager中:
//3.请求朝指定位置移动,具体实现
askStartMoveToTar(moveObject: IMoveObject, pos: THREE.Vector3, ifMoveToBlackPoint: boolean, ifFliter: boolean): boolean {
let now = BattleManager.getUTCMiliSecond();//获取开始时间
if (now - moveObject.getLastFailStartTime() < 150) {
//同一个单位的最短尝试时间为150;
return false;//失败
}
if (!this.askStartCheck(moveObject)) {
moveObject.setLastFailStartTime(now);
return false;
}
//依靠目标移动
moveObject.setMoveType(MoveObjectMoveTypeEnum.Tar);
if (moveObject.getMoveStatus() == MoveObjectStatusEnum.Stand) {
//获取周围的动态碰撞数据,必须先判断是否处于站立状态
let dynamicBlockSet = new Array<number>();
let dynBlockLength = 0;
dynBlockLength = this.findDynamicBlock(moveObject, dynamicBlockSet, dynBlockLength);
//长度不一致返回错误
if (dynBlockLength && dynamicBlockSet.length!=dynBlockLength) {
myLogger.error('有错误了');
}
//准备寻路的基础数据
let nowVec = moveObject.getColVector();
let startX = this.getRegionIndexX(nowVec.x);
let startZ = this.getRegionIndexY(nowVec.z);
let targetX = this.getRegionIndexX(pos.x);
let targetZ = this.getRegionIndexY(pos.z);
//动态区域长度为0,没有阻挡
let ifNeedFind = false;
if (dynBlockLength == 0) {
//没有阻挡,尝试读取缓存数据
let cachePath = this.getPathByCache(this.getPathID(this.getRegionIDINT(startX, startZ), this.getRegionIDINT(targetX, targetZ)));
if (cachePath) {
//有缓存的值,直接使用
let pathList = moveObject.getPathCell();
for(let index=1;index<ConstMaxOrderPathNode;index++) {
if (index>cachePath.length) {
break;
}
pathList[index].copy(cachePath[index-1]);
}
moveObject.setPathLength(1);
for (let i=0; i<ConstMaxOrderPathNode-1; i++) {
if (cachePath[i].x == 0 || cachePath[i].z == 0 || i>=cachePath.length) {
moveObject.setPathLength(i + 1);
break;
}
}
} else {
//没有缓存的值
ifNeedFind = true;
}
}

if (dynBlockLength > 0 || ifNeedFind) {


//如果有动态阻挡,或者找不到缓存,则还是需要寻找
let aStartInfo = new FindPathInfo();
//设置起点
aStartInfo._startPoint.setX(startX);
aStartInfo._startPoint.setZ(startZ);
//设置终点
aStartInfo._targetPoint.setX(targetX);
aStartInfo._targetPoint.setZ(targetZ);
moveObject.getPathCell()[0] = aStartInfo._targetPoint;
//设置阻挡
aStartInfo._dynamicBlockList = dynamicBlockSet;
//设置其他属性
aStartInfo._ifToTarget = ifMoveToBlackPoint;
aStartInfo._ifFliter = ifFliter;
//初始化接收用的数组
aStartInfo._pathBuff.length = 0;
for (let index=1; index<ConstMaxOrderPathNode; index++) {
aStartInfo._pathBuff.push(moveObject.getPathCell()[index]);
}
aStartInfo._pathNodeCount = aStartInfo._pathBuff.length;
this._aStar.findPath(aStartInfo);
//如果寻路失败,返回
if (aStartInfo._pathNodeCount <= 0) {
moveObject.setLastFailStartTime(now);
return false;
}
moveObject.setPathLength(aStartInfo._pathNodeCount + 1);
if (dynBlockLength == 0 && aStartInfo._pathNodeCount > 0) {
//没有动态阻挡,把本次寻路缓存
this.savePathToCache(this.getPathID(this.getRegionIDINT(startX, startZ), this.getRegionIDINT(targetX, targetZ)), aStartInfo._pat
}
}

//设置移动参数
moveObject.setStartMoveTime(now);//移动时间
moveObject.setMoveStep(1); //步长
this.setNextMovePoint(moveObject); //下一个移动点

//判断动态碰撞
moveObject._ifStart = true;//是否开始移动设置为true
now = BattleManager.getUTCMiliSecond();//获取时间
moveObject.setStartMoveTime(now);//重新设置开始移动的时间
moveObject.calculateStepMoveTarget(now + 100);
//如果下一个100毫秒后会阻挡,返回。正常情况下,
//这里返回的概率应该很低。如果有游戏对象在地图上卡住,优先检查这里,看是否寻到的路直接被阻挡
if (!this.testNextStepMove(moveObject, false, true)) {
moveObject.stop(now, false);
moveObject._ifStart = false;
return false;
}
moveObject._ifStart = false;//方便下次移动重新计算
moveObject.onStartMove(moveObject.getDir());

return true;
}
moveObject.setLastFailStartTime(now);//移动失败时间
return false;
}
}

第十二节 自动攻击
英雄ai是通过消息驱动,机器人ai是通过行为树
在aihero中:
//1.自动攻击状态判断
askStartAutoAttack(): number {
//先判断完所有异常情况
if (this.ifPassitiveState()) { //判断是否可控制
return PreDefineErrorCodeEnum.eNormal;
}
//已经在自动攻击则返回
if (this._ifAutoAttack) {
return PreDefineErrorCodeEnum.eNormal;
}
//若处于手动攻击也不能停止
if (this._ifMoveDir) {
return PreDefineErrorCodeEnum.eNormal;
}
//是否被限制移动
if (this._masterGU.getFPData(EffectCateEnum.Restrain) > 0) {
return PreDefineErrorCodeEnum.eNormal;
}
//其他状况则直接返回
let rst = this.checkAttackTarget();
if (PreDefineErrorCodeEnum.eNormal != rst) {
return rst;
}
//停止其他技能
if (!this.stopAllSkill()) {
return AllErrorCodeEnum.eEC_AbsentOrderPriority;
}
//停止手动移动
this._ifMoveDir = false;
//停止站立攻击
this._ifStandAttack = false;
//自动攻击开启
this._ifAutoAttack = true;
//攻击状态修改
this._attackState = AttackStateEnum.Pursue;
//最后朝对象移动的时间,也就是从头开始
this._lastCheckMoveTarTime = 0;
//调用ai中的attackSchedule
this.attackSchedule(BattleManager.getUTCMiliSecond(), 0);
return PreDefineErrorCodeEnum.eNormal;
}

在ai中:
//2.实现自动攻击功能
attackSchedule(tUTCMilsec: number, tickSpan: number): number {
if (!this._attackSkill) { //检查当前技能
return AllErrorCodeEnum.eEC_AbsentOrderPriority;
}
if (this.ifPassitiveState()) {//检查ai状态是否移动
if (this.ifMoving()) {

this._masterGU.getCurBattle().askStopMoveObjectTar(this._masterGU);
}
//结束当前技能
this._attackSkill.end();
return AllErrorCodeEnum.eEC_AbsentOrderPriority;
}
//若敌方已经死亡,攻击停止
if (!this.getAttackGU() || this.getAttackGU().isDead() || this.getAttackGU().getGOActionStateInfo()._OAS == GOActionStateEnum.eOA
this.setAttackGU(null);//攻击目标设置为空
if (this.ifMoving()) {
this._masterGU.getCurBattle().askStopMoveObjectTar(this._masterGU);
}
//结束移动,释放技能
his._attackState = AttackStateEnum.Pursue;
this._attackSkill.end();
return AllErrorCodeEnum.eEC_TargetIsDead;
}
//检查是否在施法距离内
if (AttackStateEnum.Pursue == this._attackState) {
if (this._masterGU.ifInReleaseSkillRange(this.getAttackGU(), this._attackSkill._config)) {
//停止英雄移动
this._attackState = AttackStateEnum.UseSkill;
this._masterGU.getCurBattle().askStopMoveObjectTar(this._masterGU);
this._masterGU.getCurBattle().askStopMoveObjectDir(this._masterGU);
} else {
//未在距离内判断是否需要重新开始或者移动
let lastCheckTickSpan = tUTCMilsec - this._lastCheckMoveTarTime;
//检查时间差超过500毫秒则开始移动
if (500 <= lastCheckTickSpan) {
//判断当前位置与目标位置是否相同
if (this._moveTarPos.x != this.getAttackGU().getCurPos().x || this._moveTarPos.z != this.getAttackGU().getCurPos().z) {
//不相等则把目标位置数值赋值给当前位置
this._moveTarPos = this.getAttackGU().getCurPos();
this.moveToTar(this._moveTarPos, false, 0);
}
//修改上次移动的时间
this._lastCheckMoveTarTime = tUTCMilsec;
}
//若停止移动状态则要重新启动移动
if (false == this._ifMoving) {
this.moveToTar(this._moveTarPos, false, tUTCMilsec);
}
}
}
/**释放技能状态 */
if (AttackStateEnum.UseSkill == this._attackState) {
if (this.ifMoving()) {
//停止移动再释放技能
this._masterGU.getCurBattle().askStopMoveObjectTar(this._masterGU);
this._masterGU.getCurBattle().askStopMoveObjectDir(this._masterGU);
}
if (this._attackSkill) { //判断是否处在施法状态
if (!this._attackSkill._ifRunning) {
if (this._masterGU.ifInReleaseSkillRange(this.getAttackGU(), this._attackSkill._config)) {
//在当前距离内开始施法
this._attackSkill._tarGU = this.getAttackGU();
let rst = this._attackSkill.start();
//判断施法是否成功
if (PreDefineErrorCodeEnum.eNormal != rst) {
return rst;
}
} else { //施法距离内朝施法对象移动
this._attackState = AttackStateEnum.Pursue;
return PreDefineErrorCodeEnum.eNormal;
}
} else { //已经在释放技能中,则调用技能心跳释放技能
//onSchedule:在skill文件中
let rst = this._attackSkill.onSchedule(tUTCMilsec, tickSpan);
//判断是否成功释放
if (PreDefineErrorCodeEnum.eNormal != rst) {
//未成功释放则设置未true状态
if (SkillStateEnum.End != rst && this._masterGU.getGOActionStateInfo()._OAS != GOActionStateEnum.eOAS_Free) {
this._masterGU.beginActionFree(true);
}
this._attackSkill.end();//结束释放
return rst;
}
}
}
}
return PreDefineErrorCodeEnum.eNormal;
}

第十三节 技能释放
skill文件中skill.ts
/**从这开始... */
//技能释放分为6个状态:等待,吟唱 ,前摇,引导,后摇,结束
//主要思想为改变技能状态与改变技能状态时间
//只要在ai于aihero,btreenormal中分别调用onSchedule方法即可
//自动攻击功能完善
onSchedule(tUTCMilsec: number, tTickSpan: number): number {
//获取当前技能状态
let heartBeatStartState = this._skillState;
//rst设置返回值
let rst = Number.parseInt(<any>PreDefineErrorCodeEnum.eNormal);
do {
//状态需要轮循
//赋值检查状态给变量
rst = this.checkStatus();
//非法退出
if (PreDefineErrorCodeEnum.eNormal != rst) {
break;
}
//距离目标太远
if (SkillStateEnum.Releasing >= this._skillState && !this._masterGU.ifInReleaseSkillRange(this._tarGU, this._config, 1000)) {
rst = AllErrorCodeEnum.eEC_NullPointer;
break;
}
//等待状态
if (SkillStateEnum.Free == this._skillState) {//判断是否为等待
this._skillState = SkillStateEnum.Preparing;
//切换为吟唱
this._stateMilsec = tUTCMilsec;
this.setSkillDir();
}
//前摇状态
if (SkillStateEnum.Preparing == this._skillState) {
//获取时间差,当前与上一个时间差
let tMilsecSpan = tUTCMilsec - this._stateMilsec;
//比配置时间还要短,返回
if (tMilsecSpan < this._config._n32PrepareMilsec) {
rst = PreDefineErrorCodeEnum.eNormal;
break;
}
//进入前摇状态
this._skillState = SkillStateEnum.Releasing;
//修改当前前摇时间
this._stateMilsec = tUTCMilsec;
}
//判断当前状态是否为前摇状态
if (SkillStateEnum.Releasing == this._skillState) {
//获取前摇时间
let n32ReleaseMilsec = this._config._n32ReleaseMilsec;
//根据配置信息获取前摇时间
if (this._config._bIfNormalAttack) {
if (this._normalAttackReleaseTime == 0) {
//攻速加成时间公式
this._normalAttackReleaseTime = n32ReleaseMilsec * this._masterGU.getFPData(EffectCateEnum.AttackSpeed) / 1000.0;
}
//攻速加成时间赋值给前摇时间
n32ReleaseMilsec = this._normalAttackReleaseTime;
}
//获取时间差
let tMilsecSpan = tUTCMilsec - this._stateMilsec;
//判断 是否获取到前摇时间
if (tMilsecSpan < n32ReleaseMilsec) {
rst = PreDefineErrorCodeEnum.eNormal;//未到则返回
break;
}
//扣除当前cd值
rst = this.checkConsume();
//未成功扣除则释放技能失效
if (PreDefineErrorCodeEnum.eNormal != rst) {
break;
}
//调用技能模块
this.makeSkillEffect(tUTCMilsec); //技能特效创建
this._skillState = SkillStateEnum.Using;
this._stateMilsec = tUTCMilsec;
this._normalAttackReleaseTime = 0;
}
//引导阶段
//判断是否处于引导
if (SkillStateEnum.Using == this._skillState) {
let ifUsing = false;
//遍历所有引导效果
for (let i = 0; i < ConstUsingEffectsNum; ++i) {
//获取当前技能效果
let un32EffectUniqueID = this._usingEffectsList[i];
//判断是否处于占用中
if (0 != un32EffectUniqueID) {//根据编号获取特效
let effect = this._masterGU.getCurBattle().getEffectManager().getEffect(un32EffectUniqueID);
if (effect) {
if (effect.IsUsingSkill()) {
//占用则改为true,否则为0
ifUsing = true;
} else {
this._usingEffectsList[i] = 0;
}
}
}
}
if (!ifUsing) { //该特效不处于使用状态,则进入后摇状态
this.clearUsingEffects();//清除所有使用特效
this._skillState = SkillStateEnum.Lasting;//改变状态
this._stateMilsec = tUTCMilsec;//改变状态时间
}
}
//进入后摇判断结束
if (SkillStateEnum.Lasting == this._skillState) {

if (tUTCMilsec - this._stateMilsec < this._config._n32SkillLastTime) {


rst = PreDefineErrorCodeEnum.eNormal;
break;
}
//为结束状态
this._skillState = SkillStateEnum.End;
this._stateMilsec = tUTCMilsec;//修改时间
}
//调用end函数结束技能释放
if (SkillStateEnum.End == this._skillState) {
rst = SkillStateEnum.End;
this.end();
break;
}
} while (false);//结束循环
//捕获游戏对象,判断状态是否一致
if (heartBeatStartState != this._skillState) {
//吟唱状态准备释放技能
if (SkillStateEnum.Preparing == this._skillState) {
this._masterGU.beginActionPrepareSkill(this, this._cDir, true);
}
//前摇则调用前摇状态,后续同理
else if (SkillStateEnum.Releasing == this._skillState) {
this._masterGU.beginActionReleaseSkill(this, this._cDir, true);
}
//引导
else if (SkillStateEnum.Using == this._skillState) {
this._masterGU.beginActionUsingSkill(this, this._cDir, true);
}
//后摇
else if (SkillStateEnum.Lasting == this._skillState) {
this._masterGU.beginActionLastingSkill(this, this._cDir, true);
}
//等待与结束是没有对应的活动
}
return rst;
}

第十四节 技能攻击
aihero中:
/**从这里开始 */
askUseSkill(un32SkillID: number): number {
//检查是否是不能操作状态
if (this.ifPassitiveState()) {
return AllErrorCodeEnum.eEC_AbsentOrderPriority;
}
//检查是否被沉默了
if (this._heroGU.getFPData(EffectCateEnum.Silence) > 0) {
return AllErrorCodeEnum.eEC_UseSkillFailForSilenced;
}
//获取并检查技能是否存在
let skill = this._heroGU.getSkillByID(un32SkillID);
if (!skill) {
return AllErrorCodeEnum.eEC_CanNotFindTheSkill;
}

//检查技能是否符合可用条件
let rst = skill.ifSkillUsable();
if (PreDefineErrorCodeEnum.eNormal != rst) {
return rst;
}
//检查是否是正在运行的技能
if (this._nowSkill && this._nowSkill._config._un32SkillID == skill._config._un32SkillID) {
return AllErrorCodeEnum.eEC_AbsentOrderPriority;
}
//首先判断是否在施放技能中。如果在施放技能中,则后面的技能都进行记录,等待施放
if (this.ifUsingSkill() && (this._nowSkill.ifSkillBeforeHit() || this._nowSkill.getSkillState() == SkillStateEnum.Using)) {
//前摇和引导状态,需要将当前技能记忆下来
this._nextSkill = skill;
return AllErrorCodeEnum.eEC_AbsentOrderPriority;
} else {
//先检测技能是否够距离施放
rst = skill.ifSkillUsableWithNowTarget();
if (AllErrorCodeEnum.eEC_AbsentSkillDistance == rst) {
//如果技能射程不够,则启动自动追踪释放技能功能

//停止自动攻击
this._ifAutoAttack = false;
//停止手动移动
this._ifMoveDir = false;
//停止站立攻击
this._ifStandAttack = false;
//设置属性
this._wantUseSkill = skill;
this._moveTarPos = this._wantUseSkill._tarGU.getCurPos();
this._lastCheckMoveTarTime = BattleManager.getUTCMiliSecond();
this.moveToTar(this._moveTarPos, false, this._lastCheckMoveTarTime);
return PreDefineErrorCodeEnum.eNormal;
} else if (PreDefineErrorCodeEnum.eNormal != rst) {
return rst;
} else {
//检测是否是瞬发技能。如果是瞬发技能,则不需要打断当前的位移
if (!skill.ifImpactSkill() ||
(skill._config._n32ReleaseMilsec > 0 && !this.ifMoving() && !this._ifAutoAttack && !this._attackSkill._ifRunning)
) {
//如果是非瞬发技能,则停止相关的自动攻击,位移等
//停止自动攻击
this.cancleAttack();
//停止移动
this._ifMoveDir = false;
this._heroGU.getCurBattle().askStopMoveObjectAll(this._heroGU);
//开始使用技能,停止其他技能
this.stopAllSkill();
//需要有心跳,保存技能
this._nowSkill = skill;
//停止自动攻击和站立攻击
if (!skill.ifHasPreTime()) {
//没有前摇的非瞬发技能(现阶段就是引导技能)才需要停止自动攻击
this._ifAutoAttack = false;
}
this._ifStandAttack = false;
}
//开始使用技能
skill.start();
return rst;
}
}
}

You might also like