美文网首页FlutterFlame
Flutter&Flame——TankCombat游戏开发(四)

Flutter&Flame——TankCombat游戏开发(四)

作者: 吉哈达 | 来源:发表于2020-08-12 10:40 被阅读0次

    TankCombat系列文章

    如果你还不了解Flame可以看这里:

    见微知著,Flutter在游戏开发的表现及跨平台带来的优势

    Flutter&Flame——TankCombat游戏开发(一)

    Flutter&Flame——TankCombat游戏开发(二)

    Flutter&Flame——TankCombat游戏开发(三)

    Flutter&Flame——TankCombat游戏开发(四)

    效果图

    蛮好看的,我再加一下,让大家整体有个印象自己在做什么 :)

    image

    思考

    在开工之前,我们先要思考一下,游戏中的电脑(坦克/炮弹),它们并不知道该往哪里走、炮塔怎么转以及何时该开火,这一切都是我们通过代码告诉它们该如何‘表现’的,那么当两台电脑需要交互的时候,就产生了谁开的炮,谁原地爆炸,谁又重生在何时何地的问题...

    这时我们就需要一个观察者,并将处理逻辑通过代码告诉它,让它替我们安排诸事。

    开工

    首先我们给它起名叫 GameObserver。

    GameObserver

    我们创建这个类的同时,再创建一个枚举,用来区分敌方两种颜色的坦克。

    于此同时,我们把要被观察的对象给观察者,即tankGame。

    enum TankCate{
      GreenTank,SandTank
    }
    
    class GameObserver{
      final TankGame game;
    
      GameObserver(this.game);
        ...
    }
    

    我们观察者肯定得有一个观察的方法,我们创建一个。

    void watching(double t)
    

    看到之后需要做哪些事情呢? 目前我们需要:

    制造新的坦克
    查看谁被击中并判定谁嗝屁
    
       //这个名字我起得不好,改成 building就容易理解了,你可以把它理解为车间正在制造坦克
      bool isGenerating = false;
    
     void watching(double t){
       if(!isGenerating){
           //重生坦克
         if(game.gTanks.length<2){
           isGenerating = true;
           coolDown(spawnTank(TankCate.GreenTank));
           return;
         }
         if(game.sTanks.length<2){
           isGenerating = true;
           coolDown(spawnTank(TankCate.SandTank));
           return;
         }
       }
       ///检查谁被击中
       checkHit();
     }
    

    制造坦克

    我们先看制造坦克部分,首先是一个

    coolDown(spawnTank(TankCate.GreenTank))
    
      void coolDown(Function task) {
       Future.delayed(Duration(milliseconds: 1500)).then((value) {
         if(task != null){
           task();
         }
       });
     }
    
    

    这是一个工具方法在延迟1500毫秒后执行传进来的任务,这里的任务就是制造坦克,我们以此来模拟制造坦克花费的时间。

    继续向下看制造坦克的方法:

      spawnTank(TankCate tankCate) {
       switch(tankCate){
         case TankCate.GreenTank:
           var turretSprite = Sprite('tank/t_turret_green.webp');
           var bodySprite= Sprite('tank/t_body_green.webp');
           double r = Random().nextDouble();
           game.gTanks.add( GreenTank(game,bodySprite,turretSprite,
                 r < 0.5 ? Offset(100,100)
                     :Offset(100,game.screenSize.height*0.8)));
           break;
         case TankCate.SandTank:
           var turretSprite = Sprite('tank/t_turret_sand.webp');
           var bodySprite= Sprite('tank/t_body_sand.webp');
           double r = Random().nextDouble();
           game.sTanks.add( SandTank(game,bodySprite,turretSprite,
               r < 0.5 ? Offset(game.screenSize.width-100,100)
                   :Offset(game.screenSize.width-100,game.screenSize.height*0.8)));
           break;
       }
       isGenerating = false;
     }
    

    这个就比较简单了,看过前面文章的你肯定一眼就明白了,简单地生成新的坦克,并添加到game中的坦克list里。

    检查命中

    这样制造功能就完成了,现在我们看命中检测。(说明添加在注释里)

    checkHit();
    
    
        double hitDistance = 10;
        
      ///检查是否有tank被击中
      void checkHit() {
        //我们循环遍历游戏上存在的子弹
        game.bullets.forEach((bullet) {
            //根据子弹颜色(谁发射的)我们做出不同的处理
            //这里以玩家炮弹为例
          switch(bullet.bulletColor){
            //玩家是否击中敌军
            case BulletColor.BLUE:
                //我们遍历游戏上存在的敌军
              game.gTanks.forEach((gt) {
                //取出他的位置,并与玩家炮弹计算出距离
                Offset zone =gt.position - bullet.position;
                //如果距离小于hitDistance 那就说明击中了
                if(zone.distance < hitDistance){
                  //我们将对应坦克死亡状态置为 真
                  gt.isDead = true;
                  //同时将这颗子弹是否击中置为 真
                  //这两个值都将决定它们是否还会被绘制
                  bullet.isHit = true;
                  //然后我们在死亡坦克位置添加爆炸效果
                  addExplosion(gt.position);
                }
              });
              game.sTanks.forEach((st) {
                Offset zone =st.position - bullet.position;
                if(zone.distance < hitDistance){
                  st.isDead = true;
                  bullet.isHit = true;
                  addExplosion(st.position);
                }
              });
              break;
    
              ///敌军对玩家
            ///暂时不写了,打不过 ...
            case BulletColor.GREEN:
    
              break;
            case BulletColor.SAND:
              // TODO: Handle this case.
              break;
          }
        });
      }
    

    敌方对玩家的处理方法大同小异,我就不做处理(真打不过),留给有兴趣的自己做吧。

    接下来我们看一下爆炸效果:

      void addExplosion(Offset position){
        game.explosions.add(OrangeExplosion(game, position));
      }
    

    我们像添加子弹一样,在game的爆炸list里添加了一个爆炸,我们来具体看一下爆炸效果。

    爆炸效果

    首先我们创建一个component

    class OrangeExplosion extends BaseComponent
    

    然后我们梳理一下需要那些东西,爆炸的位置,爆炸范围,另外爆炸动画由5张图片组成,也就需要5个sprite,还要一个flag判断是否爆炸完毕,ok 代码如下:

      final TankGame game;
     final Offset position;
     final List<Sprite> sprites = [];
     Rect exRect;
     //绘制那个sprite
     int playIndex =0;
       //爆炸是否完毕
     bool playDone = false;
    
     OrangeExplosion(this.game,this.position){
       //爆炸范围
       exRect = Rect.fromCenter(center: position,width: 30,height: 30);
       //5个爆炸sprite
       sprites.add(Sprite('explosion/explosion1.webp'));
       sprites.add(Sprite('explosion/explosion2.webp'));
       sprites.add(Sprite('explosion/explosion3.webp'));
       sprites.add(Sprite('explosion/explosion4.webp'));
       sprites.add(Sprite('explosion/explosion5.webp'));
     }
    
    

    接下来我们只需要在update调整播放index和在render中渲染对应sprite即可

      @override
     void render(Canvas canvas) {
       if(playDone)return;
       if(playIndex<5){
           //渲染对应的sprite
         sprites[playIndex].renderRect(canvas, exRect);
       }
     }
    
    
     double passedTime = 0;
    
     @override
     void update(double t) {
       if(playDone)return;
       if(playIndex<5){
         //根据我的摸索1秒 5张图片 效果不错
         passedTime +=t;
         playIndex = passedTime ~/ 0.2;
       }else{
         playDone = true;
       }
     }
    

    组件创建完毕,现在我们回到game中,向前面添加坦克一样,也添加上类似的代码

    tankGame中的代码

     //爆炸动画
      List<OrangeExplosion> explosions = [];
    

    game.render中增加

        //爆炸
    explosions.forEach((element) {element.render(canvas);});
    

    game.update中增加

        //移除爆炸
    explosions.removeWhere((element) => element.playDone);
    //爆炸
    explosions.forEach((element) {element.update(t);});
    

    这样整个功能就完成了,运行一下看看效果吧。

    收尾

    web运行

    如果你想在web上运行,需要切换flutter分支到dev同时开启web支持:

    flutter channel dev
    
    flutter config --enable-web
    
    flutter run -d chrome
    
    

    在main函数中添加:

      bool isWeb = false;
      try{
        if(Platform.isAndroid || Platform.isIOS){
          isWeb = false;
        }else{
          isWeb = true;
        }
      }catch(e){
        isWeb = true;
      }
    
      if(! isWeb){
        ///设置横屏
        await SystemChrome.setPreferredOrientations([
          DeviceOrientation.landscapeRight,
          DeviceOrientation.landscapeLeft
        ]);
    
        ///全面屏
        await SystemChrome.setEnabledSystemUIOverlays([]);
      }
    

    这样你就可以随时在浏览器或者手机上运行这个游戏了~~~

    此游戏只是练习时所写,代码结构和设计、书写等有些随意,还请见谅。

    如果觉得对你有帮助的话,点个赞吧! :)

    DEMO

    坦克大战

    TankCombat系列文章

    见微知著,Flutter在游戏开发的表现及跨平台带来的优势

    Flutter&Flame——TankCombat游戏开发(一)

    Flutter&Flame——TankCombat游戏开发(二)

    Flutter&Flame——TankCombat游戏开发(三)

    Flutter&Flame——TankCombat游戏开发(四)

    相关文章

      网友评论

        本文标题:Flutter&Flame——TankCombat游戏开发(四)

        本文链接:https://www.haomeiwen.com/subject/xquydktx.html