Flutter Flame 教程2 -- Game Loop遊戲迴圈

我跑步前腳先著地發表於2020-05-04

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.

相關文章