数据Model:
显示View:
控制:
代理:
网络:
常量:
配置:
场景布局
export default class FightScene extends cc.Component {
private _backContaienr: cc.Node;
private _contaienr: cc.Node;
private _frontContaienr: cc.Node;
_unitLayer:这个节点上挂满了角色
shadow:这个是角色的影子
153:这个是真实的角色模型
_billBoard:这个是角色的血量信息
人物unit
Entity.ts:这个是角色的基类
UnitHero.ts:是子类,代表人物模型
UnitPet:是子类,代表宠物模型
Entity.ts
//角色的逻辑更新
//这里的逻辑更新其实就是在切换状态
//要特别注意this.states这个变量,他是一个数组,里面存储了当前角色的所有状态
//可以理解为一种状态会对应一个动作
public logicUpdate(f: number) {
this._positionLast[0] = this.position[0];
this._positionLast[1] = this.position[1];
this._scaleLast[0] = this._scale[0];
this._scaleLast[1] = this._scale[1];
this._heightLat = this._height;
if (this.states.length > 0) {
//取出一个状态
let state = this.states[0];
// if (this.stageID == 203) {
// console.log("logicupdate:", this.stageID, this.states[0].cName);
// }
if (!state.isStart) {
//状态开始
state.start();
}
//状态内部更新
state.update(f);
if (state.isFinish) {
// console.log("logic update:",this.configId,state.cName)
//状态完成 则删除掉这个状态
this.states.splice(0, 1);
this.onStateFinish(state);
}
}
}
//帧更新
//这里最主要的就是对角色进行缩放旋转平移变换
//特别注意this.actor 这个就是模型节点
public frameUpdate(f: number) {
if (!this._isMotion) {
f = 1
this._isMotion = true
}
if (this._positionFrame[0] != this.position[0] || this._positionFrame[1] != this.position[1] ||
this._heightFrame != this._height) {
this._positionFrame[0] = this._positionLast[0] + (this.position[0] - this._positionLast[0]) * f;
this._positionFrame[1] = this._positionLast[1] + (this.position[1] - this._positionLast[1]) * f;
this._heightFrame = this._heightLat + (this._height - this._heightLat) * f;
this.actor.node.setScale(FightConfig.getScale(this._positionFrame[1] + this._heightFrame));
this.setZOrderFix(this._zOrderFix);
// if (this.stageID == 101) {
// console.log(this._positionFrame[0], this._positionFrame[1]);
// }
this.actor.node.setPosition(this._positionFrame[0], this._positionFrame[1] + this._heightFrame);
}
if (this._scaleFrame[0] != this._scale[0] || this._scaleFrame[1] != this._scale[1]) {
this._scaleFrame[0] = this._scaleLast[0] + (this._scale[0] - this._scaleLast[0]) * f;
this._scaleFrame[1] = this._scaleLast[1] + (this._scale[1] - this._scaleLast[1]) * f;
if (this.actor) {
this.actor.node.setScale(this._scaleFrame[0], this._scaleFrame[1]);
}
}
}
//播放角色动作
public setAction(actionName: string, loop: boolean = false) {
this.actor.setAction(actionName, loop);
}
在上面Entity.ts中,我们发现角色是依靠状态来进行一系列动作变换的,那这些状态都有哪些呢?请看下面
挑一个状态来说:StateIdle
在UnitHero.ts中:发现当new一个状态的时候,就会添加到这个this.states数组中
private idle() {
if (!this.is_alive) {
return;
}
let idle = new StateIdle(this);
this.setState(idle);
this.setTowards(this.camp);
}
protected setState(state: State) {
this.clearState();
this.addState(state);
}
public addState(state: State) {
this.states.push(state);
}
//角色除了会
public doMoving(actionName: string) {
if (this.actor) {
this.actor.doMoving(actionName);
}
}
public stopMoving() {
if (this.actor) {
this.actor.stopMoving();
}
}
StateIdle.ts
看下面start函数,默认播放了一个idle动画
当然如果角色含有其他动画或者移动也要播放
这个特别注意 这个start函数的调用是发生在逻辑logicUpdate更新
export class StateIdle extends State {
private buffAction: string;
private flashAction: string;
private hasCombineSkill: boolean;
constructor(entity) {
super(entity);
this.cName = "StateIdle";
this.buffAction = null;
this.flashAction = null;
let buffList = this.entity.buffList;
//如果有buff的情况,优先播放buff动作
if (!this.entity.isPet) {
buffList.forEach(v => {
let buffData = G_ConfigLoader.getConfig(ConfigNameConst.HERO_SKILL_EFFECT).get(v.configId);
if (buffData.buff_action != "") {
this.buffAction = buffData.buff_action;
}
if (buffData.flash_action != "") {
this.flashAction = buffData.flash_action;
}
});
}
if (this.entity.isPet) {
return;
}
this.hasCombineSkill = false;
if (!this.entity.enterStage) {
this.bShowName = false;
}
}
//发现这个状态开始只是播放了一个idle动画
public start() {
super.start();
this.entity.setAction("idle", true);
if (this.entity.isPet) {
return;
}
if (this.buffAction) {
this.entity.setAction(this.buffAction, true);
}
if (this.flashAction) {
this.entity.doMoving(this.flashAction);
}
}
public update(f) {
super.update(f);
}
public onFinish() {
this.entity.stopMoving();
super.onFinish();
}
}
到此为止,我们明白了角色依靠状态来播放动作,那是靠什么数据来添加状态的呢?没错就是服务端发来的战报数据,我们会拿到战报数据进行解析,进而决定要添加那些状态,下面我将一一解析
状态1:StateIdle这是一个默认状态,也就是说每一个角色都要添加这么一个状态
这个状态在角色初始化的时候就添加了
//UnitHero.ts
public init(data: Unit, enterCallback?: Function) {
.....
this.idle();
....
}
战斗
FightEngine.ts:它是战斗引擎,主要作用就是驱动战斗动画的播放
//控制战斗的开始
public setStart() {
if (this._running) {
return;
}
this._newInterval = null;
this._interval = null;
this._running = true;
cc.director.getScheduler().schedule(this.onUpdate, this, 0);
}
//控制战斗的暂停
public pause() {
cc.director.getScheduler().unschedule(this.onUpdate, this);
this._running = false;
}
//控制战斗的结束
public stop() {
cc.director.getScheduler().unschedule(this.onUpdate, this);
this._running = false;
}
//这是战斗引擎的逻辑和画面更新函数
public onUpdate(dt: number) {
if (!this._running) {
return;
}
let loops = 0
if (this._newInterval == null || this._interval == null) {
this._newInterval = 0
this._interval = 0
}
else {
this._newInterval = this._newInterval + dt
this._interval = this._interval + FightConfig.interval * 5
}
if (this._interval > this._newInterval) {
this._interval = this._newInterval
}
// console.log("FightManager:onUpdate dt:", dt);
// console.log("FightManager:onUpdate interval:", this.interval);
// console.log("FightManager:onUpdate newInterval:", this.newInterval);
if (this._newInterval > 0 && this._interval > 0) {
while (this._interval >= this._nextInterval && loops < 5) {
//逻辑更新
this.onLogicUpdate(FightConfig.interval);
this._nextInterval = this._nextInterval + FightConfig.interval;
loops = loops + 1;
// console.log("FightManager:onUpdate nextInterval:", this.nextInterval, loops);
}
let interpolation = (this._interval + FightConfig.interval - this._nextInterval) / FightConfig.interval;
//动画更新
this.onFrameUpdate(interpolation);
}
}
//下面这个函数就是逻辑更新
//控制大回合的开始和结束
//人物角色的逻辑更新
//宠物角色的逻辑更新
//场景逻辑更新
private onLogicUpdate(dt: number) {
// console.log("FightManager:onLogicUpdate dt:", dt);
if (!this._running) {
return;
}
if (this._loopWave) {
if (!this._loopWave.isStart) {
this._loopWave.start();
}
else {
// 回合结束
if (this._loopWave.isFinish) {
// if (FightConfig.HP_TEST_ON) {
// }
if (this._waveId < this._waves.length) {
if (this._isJump) {
this._loopWave.clear();
}
for (let i = 0; i < this._unitHeroes.length; i++) {
if (this._unitHeroes[i].isFinalDie() && this._isJump) {
this._unitHeroes[i].doFinal();
}
else {
this._unitHeroes[i].runMap();
this._unitHeroes[i].setZOrderFix(0);
this._runCount += 1;
}
}
let sceneView = this._fightScene.getView();
sceneView.showSkill2Layer(false);
this._isJump = false;
}
else {
this.doFinish();
FightSignalManager.getFightSignalManager().dispatchSignal(FightSignalConst.SIGNAL_FIGHT_END);
}
this._loopWave = null;
}
else {
this._loopWave.update(dt);
}
}
}
for (let i = 0; i < this._unitHeroes.length; i++) {
this._unitHeroes[i].logicUpdate(dt);
if (this._unitHeroes[i].isRemove()) {
this._unitHeroes[i].death();
this._unitHeroes[i].billBoard.showDead();
this._unitHeroes[i].getShadow().death();
this._unitHeroes[i].clearActor();
this._unitHeroes.splice(i, 1);
i > 0 ? i-- : i = 0;
}
}
FightRunData.instance.setUnits(this._unitHeroes);
for (let i = 0; i < this._unitPets.length; i++) {
this._unitPets[i].logicUpdate(dt);
if (this._unitPets[i].isRemove()) {
this._unitPets[i].death();
this._unitPets[i].getShadow().death();
this._unitPets[i].clearActor();
this._unitPets.splice(i, 1);
FightRunData.instance.setPets(this._unitPets);
i > 0 ? i-- : i = 0;
}
}
this._fightScene.logicUpdate(dt);
}
//下面是帧更新
//人物角色的帧更新
//宠物的帧更新
//场景的帧更新
private onFrameUpdate(dt: number) {
// console.log("FightManager:onFrameUpdate dt:", dt);
for (let i = 0; i < this._unitHeroes.length; i++) {
this._unitHeroes[i].frameUpdate(dt);
}
for (let i = 0; i < this._unitPets.length; i++) {
this._unitPets[i].frameUpdate(dt);
}
this._fightScene.updateFrame(dt);
}
LoopWave.ts:这主要控制的是大回合
constructor(data: WaveData) {
super();
this._data = data;
this._rounds = this._data.getRounds();//回合信息,是一个数组,长度代表回合的此时
this.index = 0;//大回合进行的索引
......
}
//检查是否要更新大回合
private checkRound() {
if (this._round == null) {
//更新大回合
this._round = new LoopRound(this._rounds[this.index - 1])
if (this.index != 1) //第一轮放到杀之后去
{
FightSignalManager.getFightSignalManager().dispatchSignal(FightSignalConst.SIGNAL_CHECK_MONSTER_TALK);
// Engine.getEngine(): checkMonsterTalk()
}
}
}
public update(f: number) {
// console.log("LoopWave:update", this.index);
if (this.index > this._rounds.length) {
//所有的大回合结束了
console.log("LoopWave:update round end");
if (this.checkUnitIdle()) {
this.isFinish = true
}else {
if (!this._checkEndIdle) {
BuffManager.getBuffManager().engine.makeUnitIdle();
this._checkEndIdle = true;
}
}
}
else {
this.checkRound()
if (this._round) {
//当前大回合是否开始
if (!this._round.isStart)
//开始当前大回合
this._round.start()
else
//更新当前这个大回合
this._round.update(f)
if (this._round.isFinish) {
//当前这个大回合完成了
this._round.onFinish()
this._round = null
//更新大回合进行的索引
this.index = this.index + 1
console.log("LoopWave round next:", this.index);
}
}
}
if (this._isStartEnter) {
if (this._enterTime >= this._waitTime) {
this._unitJumpIn()
this._enterTime = 0
}
this._enterTime += f;
}
}
LoopRound.ts:这是每一个大回合的实际内容,包含的信息如多少次攻击,
攻击就是:两方互殴,你打我一下,我打你一下
constructor(data: Round) {
super();
this._data = data;
this._index = 0 //attack序列号
this._attack = null //每一次攻击的实例
this._buff = null
this._attackIndex = 0 //execute的attack序列号
}
//此处攻击分为两种 一种是宠物攻击 一种是人物角色攻击
public checkAttack() {
if (this._attack == null) {
let attackData = this._data.attacks[this._index - 1];
if (attackData.isPet) {
this._attack = new LoopOneAttackPet(attackData, this._index);
}
// else if (attackData.isHistory) {
// this._attack = new LoopOneAttackhistory(attackData, this._index);
// }
else {
this._attack = new LoopOneAttack(attackData, this._index);
}
}
}
public update(f: number) {
// console.log("LoopRpund update:", this.index, this.data.attacks.length);
if (this._index > this._data.attacks.length) {
//大回合所有攻击结束
this.isFinish = true;
}
else {
//判断是否要进入新的攻击
this.checkAttack();
if (this._attack) {
if (this._attack.isFinish) {
//本轮攻击结束 清除本轮攻击数据
this._attack.clear();
this._attack = null;
//攻击索引+1 准备进入下一次攻击
this._index += 1;
console.log("LoopRound attack next:", this._index);
}
else {
if (this._attack.isExecute()) {
this._attack.execute();
this._attackIndex += 1;
}
}
}
}
}
下面就要进入到最为关键的一步了,每一次攻击又是如何安排的呢?
先看第一种攻击:LoopOneAttack,这是一个常规攻击
//接上面 执行攻击
this._attack.execute();
//开始攻击
//LoopOneAttack.ts
public startSkill() {
(this.unit as UnitHero).skill(this.skillInfo, this.targets, this.unitPartner)
super.startSkill()
}
//UnitHero.ts
//往角色身上添加状态 每一个状态都会对应一个动作
public skill(skillInfo, targets, unitPartner: UnitHero) {
if (unitPartner != null) {
// console.log("skill partner:", this.stageID, unitPartner.stageID);
}
this.partner = unitPartner;
if (this.getState() == "StateIdle") {
this.clearState();
}
// 根据skillinfo判断是否实行本次攻击
BuffManager.getBuffManager().checkPoint(BuffManager.BUFF_PRE_ATTACK, this.stageID);
this.hasSkill = true;
if (skillInfo == null) {
if (this.states.length == 0) {
this.signalStateFinish.dispatch("StateAttackFinish", this.stageID);
}
this.hasSkill = false;
BuffManager.getBuffManager().checkPoint(BuffManager.BUFF_ATTACK_BACK, this.stageID, null, true);
BuffManager.getBuffManager().checkPoint(BuffManager.BUFF_HIT_BACK, this.stageID, null, true);
// if (this.to_alive) {
// BuffManager.getBuffManager().checkPoint(BuffManager.BUFF_HIT_BACK, this.stageID, null, true);
// }
if (!this.to_alive) {
this.dying();
this.is_alive = this.to_alive;
return;
}
return;
}
var stateHistoryShow = new StateHistoryShow(this, BuffManager.HIS_BEFORE_ATK);
this.addState(stateHistoryShow);
let skillShowId: number = skillInfo.skill_show_id;
var selfSkillId;
if (targets.list.length == 1 && targets.list[0].unit.stageID == this.stageID) {
selfSkillId = skillShowId - skillShowId % 10 + 9; //如果是加血并且只加自己,去掉id最后的数值,改成9
}
let skillPlay = G_ConfigLoader.getConfig(ConfigNameConst.HERO_SKILL_PLAY).get(skillShowId);
if (selfSkillId && G_ConfigLoader.getConfig(ConfigNameConst.HERO_SKILL_PLAY).get(selfSkillId)) {
var selfSkillPlay = G_ConfigLoader.getConfig(ConfigNameConst.HERO_SKILL_PLAY).get(selfSkillId);
skillPlay = selfSkillPlay;
}
//把技能位置先算出来
let start_location_type = skillPlay.start_location_type;
let posX: number = skillPlay.x;
let posY: number = skillPlay.y;
let prePosition = this.getAttackPosition(start_location_type, new cc.Vec2(posX, posY), targets);
// 加入展示环节,如果是合击的话,把需要移动位置放进去,给副将移动提供基准
let skillType: number = skillInfo.skill_type;
if (skillType == 2 || skillType == 3) {
let show = new StateShow(this, skillPlay, skillType, prePosition)
this.addState(show)
}
let buff = new StateBuff(this, 5, this.stageID)
this.addState(buff)
if (skillType == 3) {
let waitFlash = new StateWait(this, StateWait.WAIT_COMBINE_FLASH)
this.addState(waitFlash)
}
if (prePosition) {
prePosition.y = prePosition.y - 2 //如果打后排的话。。由于前排位置有 - 1,所以要 - 2
let cameraTargetPos: cc.Vec2 = null
let cameraLocalType: number = skillPlay.camera_location_type;
if (cameraLocalType != 0) {
let camera_x: number = skillPlay.camera_x;
let camera_y: number = skillPlay.camera_y;
cameraTargetPos =
this.getAttackPosition(
cameraLocalType,
new cc.Vec2(camera_x, camera_y + FightConfig.GAME_GROUND_FIX),
targets
)
}
let atk_pre_type = skillPlay.atk_pre_type;
let atk_pre_action = skillPlay.atk_pre_action;
let atk_pre_speed = skillPlay.atk_pre_speed;
let move =
new StateMove(
this,
atk_pre_type,
atk_pre_action,
atk_pre_speed,
prePosition,
null,
cameraTargetPos
)
this.addState(move)
}
//合击技能情况处理
if (skillType == 3) {
//移动到位后等待
let wait = new StateWait(this, StateWait.WAIT_COMBINE_SKILL)
this.addState(wait)
}
//技能释放
let skill = new StateSkill(this, skillPlay, targets, skillInfo)
this.addState(skill)
//攻击完成后结算
let finishBuff = BuffManager.getBuffManager().getFinishBuffByStageId(this.stageID);
this.addAttackFinish(finishBuff)
//攻击后回位
if (prePosition) { //&& this.to_alive
let cameraTargetPos = null;
let camera_location_type = skillPlay.camera_location_type;
if (camera_location_type != 0) {
cameraTargetPos = new cc.Vec2(0, 0)
}
let atk_follow_type = skillPlay.atk_follow_type;
let atk_follow_action = skillPlay.atk_follow_action;
let atk_follow_speed = skillPlay.atk_follow_speed;
let move =
new StateMove(
this,
atk_follow_type,
atk_follow_action,
atk_follow_speed,
new cc.Vec2(this._positionIdle[0], this._positionIdle[1]),
StateMove.BACK_ATTACK,
cameraTargetPos
)
this.addState(move)
}
let targetIds: number[] = [];
targets.list.forEach(v => {
targetIds.push(v.unit.stageID);
});
let stateBuff = new StateBuff(this, 2, this.stageID, targetIds)
this.addState(stateBuff)
}
public startCombineVice(skillPlay, prePosition: cc.Vec2) {
//副将展示合击
let show = new StateShow(this, null, 3, null)
this.setState(show)
//副将等待flash展示
let waitFlash = new StateWait(this, StateWait.WAIT_COMBINE_FLASH)
this.addState(waitFlash)
//副将移动
let factor: number = this.camp == FightConfig.campLeft ? 1 : - 1
let x_2: number = skillPlay.x_2;
let y_2: number = skillPlay.y_2;
let prePositionVice: cc.Vec2 = prePosition.add(new cc.Vec2(x_2 * factor, y_2))
let atk_pre_type_2 = skillPlay.atk_pre_type_2;
let atk_pre_action_2 = skillPlay.atk_pre_action_2;
let atk_pre_speed_2 = skillPlay.atk_pre_speed_2;
let move =
new StateMove(
this,
atk_pre_type_2,
atk_pre_action_2,
atk_pre_speed_2,
prePositionVice,
null
)
this.addState(move)
//副将移动到位等待
let waitCombine = new StateWait(this, StateWait.WAIT_COMBINE_SKILL)
this.addState(waitCombine)
//副将攻击后回位
let atk_follow_type_2 = skillPlay.atk_follow_type_2;
let atk_follow_action_2 = skillPlay.atk_follow_action_2;
let atk_follow_speed_2 = skillPlay.atk_follow_speed_2;
let moveBack =
new StateMove(
this,
atk_follow_type_2,
atk_follow_action_2,
atk_follow_speed_2,
new cc.Vec2(this._positionIdle[0], this._positionIdle[1]),
null
)
this.addState(moveBack)
}
战斗总结
这里做一个战斗的总结
FightEngine是战斗引擎,它很牛逼控制整个战斗,具体控制如下:
战斗引擎外部依靠一个定时器,来定时判断大回合是否结束,如果不结束,就去刷新大回合,通过大回合进而来控制小回合,再通过小回合来控制攻击,再通过攻击来给对应的角色添加状态,最后调用角色的逻辑更新和帧更新,来刷新角色的UI,这里的角色逻辑更新实际上就是去取自身携带的状态,一个状态对应一波动作和缩放旋转偏移
LoopWave:它是一个大回合控制类,一场PK需要n个大回合,每个大回合里又分为若干个小回合
LoopRound:它是一个小回合控制类,一个小回合里含有很多次攻击信息,
LoopOneAttack:他是一个具体到某一次攻击的类,每一次攻击都会更新角色的状态数组,就是往状态数组里添加状态
UnitHero:他是角色类,内部依靠状态来播放动作或者做一些酷炫的移动,它本身UI的变化来源于两个更新,一个是LogicUpdate,这里面主要是更新状态来播放响应的动作,一个是frameUpdate,这里主要是更新角色的缩放旋转平移,
Entity是一个角色类,你可以理解他就是一个控制角色的