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.