美文网首页Flutter圈子Flutter中文社区
Flutter游戏:启动时的欢迎页

Flutter游戏:启动时的欢迎页

作者: 何小有 | 来源:发表于2019-07-22 20:23 被阅读52次

    本篇文章的内容需要在完成以下内容代码的基础上进行哦!

    加载更多资源

    首先下载接下来要用到的游戏资源文件,因为之前已经下载过一部分,所以下面讲一下这次添加了哪些内容。

    • branding/title.png:游戏标题图片,建议7:4大小,即7x4图块
    • ui/start-button.png:开始游戏按钮,建议2:1大小,即6x3图块
    • bg/lose-splash.png:游戏结束图像,建议7:5大小,即7x5图块
    • ui/dialog-credits.png:关于对话框,建议3:2大小,即12x8图块
    • ui/dialog-help.png:帮助对话框,与关于对话框大小相同
    • ui/icon-credits.png:关于图标,建议为1个图块大小的正方形
    • ui/icon-help.png:帮助图标,与关于图标大小相同

    了解了这些资源是干什么的以后,开始将以下代码添加到pubspec.yaml文件中的assets部分。

    flutter:
      uses-material-design: true
    
      assets:
        ...
        - assets/images/bg/lose-splash.png
        - assets/images/branding/title.png
        - assets/images/ui/dialog-credits.png
        - assets/images/ui/dialog-help.png
        - assets/images/ui/icon-credits.png
        - assets/images/ui/icon-help.png
        - assets/images/ui/start-button.png
    

    打开main.dart文件,并将刚才在pubspec.yaml文件中添加的资源传递给Flame.images.loadAll调用的字符串(String)列表中。

      Flame.images.loadAll(<String>[
        ...
        'bg/lose-splash.png',
        'branding/title.png',
        'ui/dialog-credits.png',
        'ui/dialog-help.png',
        'ui/icon-credits.png',
        'ui/icon-help.png',
        'ui/start-button.png',
      ]);
    

    这样就完成了游戏资源的预加载部分。

    准备游戏页面

    一个好的游戏应该有一个欢迎页面和一个游戏页面,在长时间的游戏以后,胜利或失败时也要一个给出结果的页面,而且当玩家点击开始按钮时,应该从欢迎页面切换到游戏页面。所以,我们的游戏应该有下面3个页面。

    • 欢迎页面:首次打开游戏时,显示欢迎页面,或者叫主页面,该视图会在中间显示一个游戏标题,然后页面底部还会显示开始按钮。
    • 游戏页面:这是玩家在玩游戏时所看到的页面,这个页面隐藏了游戏标题,并专注于蚊子的随机运动。
    • 失败页面:当玩家失败时,会出现一个飞溅的页面,具体情况是当玩家失败时,屏幕中间会出现一个提示玩家游戏失败的飞溅图像,并带有开始按钮,玩家可以重新开始。

    对于上面的3个页面,都显示同一个背景,并且蚊子也是可见的,这会使玩家感觉游戏页面是主页面,而欢迎页面才是真正的主页面,欢迎真正的引导玩家进入游戏,最后游戏结束后的失败页面是玩家失败后,休息一下再重新开始的页面。

    为此呢,我们需要记录当前页面,这里可以使用整数执行此操作,并将页面从0到2编号,或者还可以将页面以字符串来记录。

    在Dart语言中,有一种枚举(enum)的数据类型,正好可以用来处理这个情况,我们将在查看玩家所处的页面或告诉游戏更改页面时枚举页面。新建一个lib/view.dart文件,并添加下面的代码。

    enum View {
      home,
      playing,
      lost,
    }
    

    现在需要为游戏添加一个实例变量,它将为我们保存当前页面的值。因此呢,要在使用它之前先导入页面View枚举,然后再添加实例变量。我们将它命名为活动页面(activeView),类型设置为View枚举类型。

    打开hit-game.dart文件,并提交下面的代码。

    ...
    import 'package:hello_flame/view.dart';
    
    class HitGame extends Game {
      ...
      View activeView = View.home;
    

    这样我们就准备好处理每个页面了。

    实现欢迎页面

    在前面已经简单说了欢迎页面,而在代码中,页面只是另一个类似组件的逻辑,可以拥有自己的子组件。它也可以是虚拟的,无论玩家真正看到什么页面,它总是可见的。

    如果是欢迎页面的话,我们将在定义页面时使用组件类,就像其他组件一样,我们只需从游戏循环中调用其实例的渲染(render)和更新(update)方法。

    首先在lib目录下创建一个views文件夹,并在该目录下创建views/home-view.dart文件,并编写下面的代码。

    import 'dart:ui';
    import 'package:flame/sprite.dart';
    import 'package:hello_flame/hit-game.dart';
    
    class HomeView {
      final HitGame game;
      Rect titleRect;
      Sprite titleSprite;
    
      HomeView(this.game) {}
    
      void render(Canvas c) {}
    
      void update(double t) {}
    }
    

    上面代码中,先是导入将使用的类和定义的文件,然后我们定义一个名为HomeView的类,它有3个实例变量,其中一个是final,需要在创建这个类的实例时传递并赋值。同时该类有1个构造函数和另外2个方法,分别是游戏循环的渲染(render)和更新(update)方法。

    在构造函数中,将初始化titleRecttitleSprite变量,以便它们可以在渲染(render)方法中使用。

      HomeView(this.game) {
        titleRect = Rect.fromLTWH(
          game.tileSize,
          (game.screenSize.height / 2) - (game.tileSize * 4),
          game.tileSize * 7,
          game.tileSize * 4,
        );
        titleSprite = Sprite('branding/title.png');
      }
    

    上面的2行代码中,第1行为titleRect变量赋值,第2行为titleSprite变量赋值。其中,titleRect变量的值是一个矩形(Rect)的定义,4行代码对应于工厂构造函数.fromLTWH所需的参数。

    因为我们确定要在一个7×4块的矩形内显示标题图片,所以会将game.tileSize * 7game.tileSize * 4传递给最后2个参数,这2个参数对应于矩形的宽度和高度。

    对于左侧(left)参数,从9个图块大小的屏幕宽度中,减去标题图像7个图块大小的矩形宽度,可以得到2个额外空间的图块。为了使标题图像居中,这里将这2个额外的图块分布到左右两侧,使图像偏移1个图块。所以,这里就传入了game.tileSize * 1或简单地传递game.tileSize

    顶部(top)参数有所不同,因为我们不想让标题图像位于屏幕的中心位置。要计算屏幕的中心位置,只需将屏幕高度除以2即可,从中减去4个图块大小的标题图像高度,就可以得到中心化的适当偏移位置。

    目前已经初始化了titleRecttitleSprite,可以开始编写用于显示实际图像的代码了,在渲染(render)方法中,添加下面的代码。

      void render(Canvas c) {
        titleSprite.renderRect(c, titleRect);
      }
    

    现在打开hit-game.dart文件,并导入HomeView类文件,然后添加一个HomeView类型的homeView实例变量。接下来,还需要在确定屏幕大小后初始化该实例变量,因此在调用resize之后,将在initialize方法中添加以下代码。

    ...
    import 'package:hello_flame/views/home-view.dart';
    
    class HitGame extends Game {
      ...
      View activeView = View.home;
      HomeView homeView;
    
      ...
    
      void initialize() async {
        ...
        homeView = HomeView(this);
        produceFly();
      }
    

    最后还要在屏幕上使用渲染HomeView的渲染(render),因此,在游戏类的渲染(render)方法中,在最后调用HomeView实例的渲染(render)方法,使其最后渲染。

    因为渲染的顺序与显示顺序相同,因为我们想要的是背景优先,然后是蚊子,最后才是标题图像,这将确保标题图片位于屏幕所有内容之上。

      void render(Canvas canvas) {
        background.render(canvas);
        enemy.forEach((Fly fly) => fly.render(canvas));
    
        if (activeView == View.home) homeView.render(canvas);
      }
    

    上面的代码中,先判断活动视图(activeView)当前是否为主视图,如果是,就渲染HomeView实例,如果不是,则渲染(render)方法将跳过此行代码,因此不会呈现HomeView实例。

    现在运行游戏,可以看到像下图所展示的效果。

    添加标题图片之后

    但是呢,当还在游戏页面内时,玩家仍然可以点击并干掉蚊子,不过没关系,这对游戏没有任何影响。接下来,玩家要开始游戏了,所以必须有一个开始按钮。首先,创建另一个组件,命名为StartButton,放在新建的components/start-button.dart文件中。

    import 'dart:ui';
    import 'package:flame/sprite.dart';
    import 'package:hello_flame/hit-game.dart';
    
    class StartButton {
      final HitGame game;
      Rect rect;
      Sprite sprite;
    
      StartButton(this.game) {}
    
      void render(Canvas c) {}
    
      void update(double t) {}
    
      void onTapDown() {}
    }
    

    这个类定义与其他组件类大致相同,不同的是它有一个onTapDown处理程序,我们将在这里编写“开始”游戏的代码。首先在构造函数中初始化rectsprite变量。

      StartButton(this.game) {
        rect = Rect.fromLTWH(
          game.tileSize * 1.5,
          (game.screenSize.height * .75) - (game.tileSize * 1.5),
          game.tileSize * 6,
          game.tileSize * 3,
        );
        sprite = Sprite('ui/start-button.png');
      }
    

    上面的代码与HomeView类构造函数中,标题图像的初始化基本相同。不同的是,除了6×3个图块大小外,还有左侧(left)和顶部(top)偏移。

    开始按钮的宽度为6个图块,这意味着在屏幕的9个图块宽度上有3个额外的图块,这样的话,每侧都有1.5个图块,所以这里将game.tileSize * 1.5提供给左侧(left)参数。对于顶部(top)参数,这样就可以让按钮的垂直位置恰好位于屏幕高度的四分之三即0.75的位置。

    在初始化rectsprite变量之后,需要渲染图像,所以要下面代码添加到render函数中。

      void render(Canvas c) {
        sprite.renderRect(c, rect);
      }
    

    接下来需要在游戏类中添加一个StartButton组件的实例,打开hit-game.dart文件,导入依赖,然后将实例变量与其他实例变量一起添加。同时在确定屏幕大小后,使用StartButton类的新实例初始化startButton变量,并在render方法中增加下面代码。

    ...
    import 'package:hello_flame/components/start-button.dart';
    
    class HitGame extends Game {
      ...
      HomeView homeView;
      StartButton startButton;
    
      ...
    
      void initialize() async {
        ...
        startButton = StartButton(this);
        produceFly();
      }
    
      ...
    
      void render(Canvas canvas) {
        ...
        if (activeView == View.home || activeView == View.lost) {
          startButton.render(canvas);
        }
      }
    

    上面代码中,我们添加的这4行代码,分别是导入StartButton类、创建StartButton类的实例并将其存储在实例变量中、最后展示StartButton到屏幕上。

    现在运行游戏,可以看到开始按钮会在欢迎页面和失败页面上呈现,这样玩家就可以从欢迎页面或失败页面重新开始游戏了。

    添加开始按钮之后

    处理开始按钮

    在上一步中,我们让开始按钮显示在屏幕上,但是点击没有反应,不用急,接下来就开始处理开始按钮的响应逻辑。首先,需要确保点击不会穿过物体,例如在点击开始按钮(startButton)时,同一位置的蚊子不应该接收到点击事件。

    在游戏类的onTapDown处理程序中创建一个isHandled变量,用于保存当前是否已调用了点击处理程序,在onTapDown处理程序的开始处创建它,并将初始值设置为false。在检查点击是否命中矩形(Rect)组件之前,先检查isHandled是否仍为false值,然后再调用组件的点击处理程序。

      void onTapDown(TapDownDetails d) {
        bool isHandled = false;
    
        if (!isHandled && startButton.rect.contains(d.globalPosition)) {
          if (activeView == View.home || activeView == View.lost) {
            startButton.onTapDown();
            isHandled = true;
          }
        }
    
        enemy.forEach((Fly fly) {
          if (fly.flyRect.contains(d.globalPosition)) {
            fly.onTapDown();
          }
        });
      }
    

    上面代码中,首先对isHandled进行检查,确保尚未处理点击事件,这个检查项与检查点击是否在开始按钮(startButton)的rect属性内是一起的。如果通过了这些条件的检查,再额外检查玩家是否当前处于欢迎页面或失败页面中。

    只有满足所有条件,游戏才会调用开始按钮的onTapDown处理程序,变量isHandled也被设置为true,让下面的代码知道已经处理了这次的点击。接下来要做的是,使用isHandled检查把之前的蚊子点击处理程序包装起来。

      void onTapDown(TapDownDetails d) {
        ...
    
        if (!isHandled) {
          enemy.forEach((Fly fly) {
            if (fly.flyRect.contains(d.globalPosition)) {
              fly.onTapDown();
              isHandled = true;
            }
          });
        }
      }
    

    上面的代码基本上与上面类似,首先包含了对isHandled的检查,这使得代码块只会没有处理开始按钮的情况下运行,其次,如果至少有一个蚊子被点击,则将isHandled变量设置为true

    接下来,打开components/start-button.dart文件,开始编写实际处理开始按钮点击的代码。在调用开始按钮的onTapHandler时,需要将游戏的activeView设置为View.playing,所以这里先导入定义View枚举的文件。然后在onTapDown里面,将游戏的activeView设置为所需的值。

    ...
    import 'package:hello_flame/view.dart';
    
    class StartButton {
      ...
    
      void onTapDown() {
        game.activeView = View.playing;
      }
    }
    

    现在运行游戏,可以看到点击开始按钮以后,标题图片和开始按钮都不见了,进入了我们之前的游戏页面。

    添加欢迎之后

    相关文章

      网友评论

        本文标题:Flutter游戏:启动时的欢迎页

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