回合制游戏代码结构分析

数据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是一个角色类,你可以理解他就是一个控制角色的

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值