Flutter Flame 教程2 -- Game Loop游戏循环

4,284 阅读3分钟

Game Loop 游戏循环


游戏循环模块是对游戏循环概念的简单抽象。基本上大部分的游戏都建立在两个方法之上:

  • render渲染方法,拿着canvas准备绘制游戏的当前状态
  • update更新方法,在1s接收自从上次更新的delta时间,并且允许你移动到下一状态。

Game类可以是子类,并且可以为你提供实现的方法。作为回调,它可以为你提供widget属性,它返回游戏的widget,可以在你的app中被渲染。

你可以在你的runApp中直接渲染它,或者你有一个更大的项目结构,在你的游戏中拥有路由、其他页面和菜单。

作为开始,只需要直接添加你的game widget到runApp即可,如下所示:

    main(){
        Game game = MyGameImpl();
        runApp(game.widget);
    }

你应该可能使用更加功能的BaseGame类,来替代使用低级别的Game类。

BaseGame基于Game实现了Component;基本上它拥有Component的List,并且在合适的时机重新传递给update和render方法。你仍然可以继承这些方法以便添加自定义的行为,同时你也可以获得免费的其他属性,比如重传resize方法(每次屏幕重新resized的信息会被传递给所有components的resize方法),并且也是一个基础的camera属性。BaseGame.camera控制的是屏幕左上角的坐标系统(默认是[0,0],和普通的Canvas一样).

一个非常简单的BaseGame实现例子如下所示:

    class MyCrate extends SpriteComponent{
        MyCrate() : super.fromSprite(16.0,16.0,new Sprite('crate.png'));
        
        @override
        void resize(Size size){
            this.x = (size.width - this.width)/2;
            this.y = (size.height - this.height)/2;
        }
    }
    
    class MyGame extends BaseGame{
        MyGame() {
            add(new MyCrate());
        }
    }

Flutter Widgets and Game instances Flutter组件和游戏实例


因为Flame game本身是一个widget,它非常容易将Flutter widgets 和Flame Game组合起来。为了使用更简单,Flame提供了mixin,叫做HasWidgetsOverlay,允许Flutter widget可以显示在你的game 实例之上,这样使得比如暂停菜单,或者查看仓库的屏幕非常容易创建。

为了使用这个mixin,只需要简单的将HasWidgetsOverlay mixin添加到你的game类,通过这样,你可以拥有两个可用方法addWidgetOverlay和removeWidgetOverlay,正如名称所示,他们勇于添加和移除在你的game之上的 overlay widgets(覆盖的小组件),他们的用法如下:

    addWidgetOverlay(
        "PauseMenu", // 你的overlay id
        Center(child:
            Container(
                width: 100,
                height:100,
                color: const Color(0xFFFF0000),
                child: const Center(child: const Text("Paused")),
            ),
    );
    
    removeWidgetOverlay("PauseMenu"); // 通过overlay id去移除overlay

在底层,Flame使用Stack widget来展示overlay,所以需要注意的是overlay添加的顺序有关系。最后一个添加的overlay,会展示在之前添加的overlay之上。

现在你可以查看这个属性的例子。具体代码如下: main.dart

import 'package:flutter/material.dart';
import './example_game.dart';
void main(){
    runApp(ExampleGame().widget);
}

example_game.dart

import 'package:flame/game.dart';
import 'package:flame/gestures.dart';
import 'package:flame:/palette.dart';
import 'package:flutter/material.dart';

class ExampleGame extends Game with HasWidgetsOverlay, TapDetector {
    bool isPaused = false;
    
    @override
    void update(double dt){}
    
    @override
    void render(Canvas canvas){
        canvas.drawRect(const Rect.fromLTWH(100, 100, 100, 100),
            Paint()..color = BasicPalette.white.color);
    }
    
    @override
    void onTap(){
        if(isPaused){
            remvoeWidgetOverlay('PauseMenu');
            isPaused = false;
        }else{
            addWidgetOverlay(
                'PauseMenu',
                Center(
                    child: Container(
                        width: 100,
                        height: 100,
                        color: const Color(0xFFFF0000),
                        child: const Center(child: const Text('Paused')),
                    ),
                ));
            isPaused = true;
        }
    }
}

main_dynamic_game.dart:

import 'package:flutter/material.dart';
import './example_game.dart';

void main(){
    runAPp(MyApp());
}

class MyApp extends StatelessWidget{
    @override
    Widget build(BuildContext context){
        return MaterialApp(
            home: MyHomePage(),
        );
    }
}

class MyHomePage extends StatefulWidget{
    MyHomePage({Key key}) : super(key: key);
    @override
    _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage>{
    ExampleGame _myGame;
    
    @override
    Widget build(BuildContext context){
        return Scaffold(
            appBar: AppBar(
                title: const Text('Testing addingOverlay'),
            ),
            body: _myGame == null ? const Text('Wait') :_myGame.widget,
            floatingActionButton: FloatingActionButton(
                onPressed: ()=> newGame(),
                child: Icon(Icons.add),
            ),
        );
    }
    
    void newGame(){
        print('New game created');
        _myGame = ExampleGame();
        setState((){});
    }
}

BaseGame的调试模式


Flame的BaseGame类提供了一个方法叫做debugMode,默认返回false。但是在game的component中,它可以被重写以用于调试。请注意这个方法返回的状态,只有在他们被添加到game时,才被传递到component。所以你如果在运行时改变debugMode,它可能不会影响已经添加的components。

查看关于Flame的更多debugMode,请查看Debug Docs.