flutter-flame-games

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Flame Game Development

Flame游戏开发

Expert guidelines for building 2D games with Flutter and the Flame engine.
使用Flutter和Flame引擎构建2D游戏的专家指南。

Quick Start

快速开始

When starting a new Flame game or reviewing code:
  1. Game Class: Extend
    FlameGame
    with appropriate mixins (
    HasCollisionDetection
    ,
    HasKeyboardHandlerComponents
    )
  2. Components: Build entities with
    SpriteComponent
    ,
    PositionComponent
    , or
    SpriteAnimationComponent
  3. Game Loop: Implement
    update(double dt)
    for logic and
    render(Canvas canvas)
    for drawing
  4. Collision: Add
    CollisionCallbacks
    mixin and define hitboxes (
    CircleHitbox
    ,
    RectangleHitbox
    )
  5. Input: Handle keyboard with
    KeyboardHandler
    , touch with
    TapCallbacks
    , joystick with
    JoystickComponent
当启动新的Flame游戏或审查代码时:
  1. 游戏类:使用合适的mixin(
    HasCollisionDetection
    HasKeyboardHandlerComponents
    )扩展
    FlameGame
  2. 组件:使用
    SpriteComponent
    PositionComponent
    SpriteAnimationComponent
    构建实体
  3. 游戏循环:实现
    update(double dt)
    处理逻辑,
    render(Canvas canvas)
    负责绘制
  4. 碰撞检测:添加
    CollisionCallbacks
    mixin并定义碰撞盒(
    CircleHitbox
    RectangleHitbox
  5. 输入处理:使用
    KeyboardHandler
    处理键盘输入,
    TapCallbacks
    处理触摸输入,
    JoystickComponent
    处理摇杆输入

Project Setup

项目设置

bash
flutter create my_game
cd my_game
flutter pub add flame
For audio support:
bash
flutter pub add flame_audio
For Tiled map support:
bash
flutter pub add flame_tiled
bash
flutter create my_game
cd my_game
flutter pub add flame
如需音频支持:
bash
flutter pub add flame_audio
如需Tiled地图支持:
bash
flutter pub add flame_tiled

Assets Structure

资源结构

assets/
├── images/
│   ├── player.png
│   ├── enemy_spritesheet.png
│   └── background.png
├── audio/
│   ├── bgm.mp3
│   └── sfx_jump.wav
└── tiles/
    └── map.tmx
Register in
pubspec.yaml
:
yaml
flutter:
  assets:
    - assets/images/
    - assets/audio/
    - assets/tiles/
assets/
├── images/
│   ├── player.png
│   ├── enemy_spritesheet.png
│   └── background.png
├── audio/
│   ├── bgm.mp3
│   └── sfx_jump.wav
└── tiles/
    └── map.tmx
pubspec.yaml
中注册:
yaml
flutter:
  assets:
    - assets/images/
    - assets/audio/
    - assets/tiles/

Core Architecture

核心架构

Game Class Selection

游戏类选择

FlameGame (Recommended for most games):
dart
class MyGame extends FlameGame with HasCollisionDetection {
  
  Future<void> onLoad() async {
    await add(Player());
  }
}
Low-level Game (For custom implementations):
dart
class MyGame extends Game {
  
  void render(Canvas canvas) { /* Custom rendering */ }
  
  
  void update(double dt) { /* Custom logic */ }
}
FlameGame(大多数游戏推荐使用):
dart
class MyGame extends FlameGame with HasCollisionDetection {
  
  Future<void> onLoad() async {
    await add(Player());
  }
}
底层Game类(用于自定义实现):
dart
class MyGame extends Game {
  
  void render(Canvas canvas) { /* 自定义绘制 */ }
  
  
  void update(double dt) { /* 自定义逻辑 */ }
}

Essential Mixins

必备Mixin

MixinPurpose
HasCollisionDetection
Enables collision system
HasKeyboardHandlerComponents
Allows keyboard input handling
HasGameReference
Provides access to game instance
Mixin用途
HasCollisionDetection
启用碰撞系统
HasKeyboardHandlerComponents
支持键盘输入处理
HasGameReference
提供对游戏实例的访问

Component Hierarchy

组件层级

Component
├── PositionComponent
│   ├── SpriteComponent
│   ├── SpriteAnimationComponent
│   ├── TextComponent
│   └── ShapeComponent
├── ParticleSystemComponent
└── ParallaxComponent
Component
├── PositionComponent
│   ├── SpriteComponent
│   ├── SpriteAnimationComponent
│   ├── TextComponent
│   └── ShapeComponent
├── ParticleSystemComponent
└── ParallaxComponent

Component Lifecycle

组件生命周期

dart
class MyComponent extends PositionComponent {
  
  Future<void> onLoad() async {
    // Called once when added to game
    // Load assets, initialize state
  }
  
  
  void update(double dt) {
    // Called every frame
    // dt = delta time in seconds
  }
  
  
  void render(Canvas canvas) {
    // Called every frame for drawing
  }
  
  
  void onRemove() {
    // Cleanup before removal
  }
}
dart
class MyComponent extends PositionComponent {
  
  Future<void> onLoad() async {
    // 添加到游戏时调用一次
    // 加载资源,初始化状态
  }
  
  
  void update(double dt) {
    // 每帧调用
    // dt = 以秒为单位的增量时间
  }
  
  
  void render(Canvas canvas) {
    // 每帧调用用于绘制
  }
  
  
  void onRemove() {
    // 移除前清理资源
  }
}

Working with Sprites

精灵使用

Static Sprite

静态精灵

dart
class Player extends SpriteComponent {
  Player() : super(size: Vector2(64, 64), position: Vector2(100, 100));
  
  
  Future<void> onLoad() async {
    sprite = await Sprite.load('player.png');
  }
}
dart
class Player extends SpriteComponent {
  Player() : super(size: Vector2(64, 64), position: Vector2(100, 100));
  
  
  Future<void> onLoad() async {
    sprite = await Sprite.load('player.png');
  }
}

Sprite from SpriteSheet

从精灵表创建精灵

dart
class Enemy extends SpriteComponent {
  
  Future<void> onLoad() async {
    final spriteSheet = await SpriteSheet.load(
      'enemies.png',
      srcSize: Vector2(32, 32),
    );
    sprite = spriteSheet.getSprite(0, 0); // row, column
  }
}
dart
class Enemy extends SpriteComponent {
  
  Future<void> onLoad() async {
    final spriteSheet = await SpriteSheet.load(
      'enemies.png',
      srcSize: Vector2(32, 32),
    );
    sprite = spriteSheet.getSprite(0, 0); // 行, 列
  }
}

PositionComponent (Custom Drawing)

PositionComponent(自定义绘制)

dart
class Circle extends PositionComponent {
  final Paint paint = Paint()..color = Colors.red;
  
  Circle() : super(size: Vector2.all(50));
  
  
  void render(Canvas canvas) {
    canvas.drawCircle(
      size.toOffset() / 2,
      size.x / 2,
      paint,
    );
  }
}
dart
class Circle extends PositionComponent {
  final Paint paint = Paint()..color = Colors.red;
  
  Circle() : super(size: Vector2.all(50));
  
  
  void render(Canvas canvas) {
    canvas.drawCircle(
      size.toOffset() / 2,
      size.x / 2,
      paint,
    );
  }
}

Sprite Animation

精灵动画

Basic Animation

基础动画

dart
class AnimatedPlayer extends SpriteAnimationComponent {
  AnimatedPlayer() : super(size: Vector2(64, 64));
  
  
  Future<void> onLoad() async {
    final spriteSheet = await SpriteSheet.load(
      'player_run.png',
      srcSize: Vector2(64, 64),
    );
    
    animation = spriteSheet.createAnimation(
      row: 0,
      stepTime: 0.1, // seconds per frame
      to: 8, // number of frames
    );
  }
}
dart
class AnimatedPlayer extends SpriteAnimationComponent {
  AnimatedPlayer() : super(size: Vector2(64, 64));
  
  
  Future<void> onLoad() async {
    final spriteSheet = await SpriteSheet.load(
      'player_run.png',
      srcSize: Vector2(64, 64),
    );
    
    animation = spriteSheet.createAnimation(
      row: 0,
      stepTime: 0.1, // 每帧秒数
      to: 8, // 帧数
    );
  }
}

Multiple Animations

多动画切换

dart
class Character extends SpriteAnimationComponent with HasGameReference {
  late final SpriteAnimation runAnimation;
  late final SpriteAnimation idleAnimation;
  late final SpriteAnimation jumpAnimation;
  
  
  Future<void> onLoad() async {
    final spritesheet = await game.images.load('character.png');
    
    runAnimation = _createAnimation(spritesheet, row: 0, frames: 8);
    idleAnimation = _createAnimation(spritesheet, row: 1, frames: 4);
    jumpAnimation = _createAnimation(spritesheet, row: 2, frames: 6);
    
    animation = idleAnimation;
  }
  
  SpriteAnimation _createAnimation(
    Image image, {
    required int row,
    required int frames,
  }) {
    return SpriteAnimation.fromFrameData(
      image,
      SpriteAnimationData.sequenced(
        amount: frames,
        amountPerRow: frames,
        textureSize: Vector2(64, 64),
        texturePosition: Vector2(0, row * 64),
        stepTime: 0.1,
      ),
    );
  }
  
  void run() => animation = runAnimation;
  void idle() => animation = idleAnimation;
  void jump() => animation = jumpAnimation;
}
dart
class Character extends SpriteAnimationComponent with HasGameReference {
  late final SpriteAnimation runAnimation;
  late final SpriteAnimation idleAnimation;
  late final SpriteAnimation jumpAnimation;
  
  
  Future<void> onLoad() async {
    final spritesheet = await game.images.load('character.png');
    
    runAnimation = _createAnimation(spritesheet, row: 0, frames: 8);
    idleAnimation = _createAnimation(spritesheet, row: 1, frames: 4);
    jumpAnimation = _createAnimation(spritesheet, row: 2, frames: 6);
    
    animation = idleAnimation;
  }
  
  SpriteAnimation _createAnimation(
    Image image, {
    required int row,
    required int frames,
  }) {
    return SpriteAnimation.fromFrameData(
      image,
      SpriteAnimationData.sequenced(
        amount: frames,
        amountPerRow: frames,
        textureSize: Vector2(64, 64),
        texturePosition: Vector2(0, row * 64),
        stepTime: 0.1,
      ),
    );
  }
  
  void run() => animation = runAnimation;
  void idle() => animation = idleAnimation;
  void jump() => animation = jumpAnimation;
}

Collision Detection

碰撞检测

Basic Setup

基础设置

dart
// Game level
class MyGame extends FlameGame with HasCollisionDetection {
  
  Future<void> onLoad() async {
    add(Player());
    add(Enemy());
    add(ScreenHitbox()); // Boundary collision
  }
}

// Component level
class Player extends PositionComponent with CollisionCallbacks {
  
  Future<void> onLoad() async {
    add(CircleHitbox(radius: 20));
    // OR
    add(RectangleHitbox(size: Vector2(40, 40)));
    // OR
    add(PolygonHitbox([
      Vector2(0, 0),
      Vector2(20, 10),
      Vector2(0, 20),
    ]));
  }
  
  
  void onCollision(Set<Vector2> intersectionPoints, PositionComponent other) {
    if (other is Enemy) {
      takeDamage();
    }
  }
  
  
  void onCollisionStart(
    Set<Vector2> intersectionPoints,
    PositionComponent other,
  ) {
    // Called once when collision begins
  }
  
  
  void onCollisionEnd(PositionComponent other) {
    // Called once when collision ends
  }
}
dart
// 游戏层级
class MyGame extends FlameGame with HasCollisionDetection {
  
  Future<void> onLoad() async {
    add(Player());
    add(Enemy());
    add(ScreenHitbox()); // 边界碰撞
  }
}

// 组件层级
class Player extends PositionComponent with CollisionCallbacks {
  
  Future<void> onLoad() async {
    add(CircleHitbox(radius: 20));
    // 或者
    add(RectangleHitbox(size: Vector2(40, 40)));
    // 或者
    add(PolygonHitbox([
      Vector2(0, 0),
      Vector2(20, 10),
      Vector2(0, 20),
    ]));
  }
  
  
  void onCollision(Set<Vector2> intersectionPoints, PositionComponent other) {
    if (other is Enemy) {
      takeDamage();
    }
  }
  
  
  void onCollisionStart(
    Set<Vector2> intersectionPoints,
    PositionComponent other,
  ) {
    // 碰撞开始时调用一次
  }
  
  
  void onCollisionEnd(PositionComponent other) {
    // 碰撞结束时调用一次
  }
}

Collision Types

碰撞类型

dart
// Customize collision behavior
class MyHitbox extends CircleHitbox {
  MyHitbox() : super(radius: 20) {
    collisionType = CollisionType.passive; // Won't trigger collisions actively
    // Options: active, passive, inactive
  }
}
dart
// 自定义碰撞行为
class MyHitbox extends CircleHitbox {
  MyHitbox() : super(radius: 20) {
    collisionType = CollisionType.passive; // 不会主动触发碰撞
    // 选项:active, passive, inactive
  }
}

Input Handling

输入处理

Keyboard Input

键盘输入

dart
class Player extends SpriteComponent with KeyboardHandler {
  Vector2 velocity = Vector2.zero();
  double speed = 200;
  
  
  bool onKeyEvent(KeyEvent event, Set<LogicalKeyboardKey> keysPressed) {
    velocity = Vector2.zero();
    
    if (keysPressed.contains(LogicalKeyboardKey.keyA)) {
      velocity.x = -1;
    }
    if (keysPressed.contains(LogicalKeyboardKey.keyD)) {
      velocity.x = 1;
    }
    if (keysPressed.contains(LogicalKeyboardKey.keyW)) {
      velocity.y = -1;
    }
    if (keysPressed.contains(LogicalKeyboardKey.keyS)) {
      velocity.y = 1;
    }
    
    return true;
  }
  
  
  void update(double dt) {
    position += velocity.normalized() * speed * dt;
  }
}
dart
class Player extends SpriteComponent with KeyboardHandler {
  Vector2 velocity = Vector2.zero();
  double speed = 200;
  
  
  bool onKeyEvent(KeyEvent event, Set<LogicalKeyboardKey> keysPressed) {
    velocity = Vector2.zero();
    
    if (keysPressed.contains(LogicalKeyboardKey.keyA)) {
      velocity.x = -1;
    }
    if (keysPressed.contains(LogicalKeyboardKey.keyD)) {
      velocity.x = 1;
    }
    if (keysPressed.contains(LogicalKeyboardKey.keyW)) {
      velocity.y = -1;
    }
    if (keysPressed.contains(LogicalKeyboardKey.keyS)) {
      velocity.y = 1;
    }
    
    return true;
  }
  
  
  void update(double dt) {
    position += velocity.normalized() * speed * dt;
  }
}

Touch/Tap Input

触摸/点击输入

dart
class Button extends SpriteComponent with TapCallbacks {
  
  void onTapDown(TapDownEvent event) {
    scale = Vector2.all(0.9); // Visual feedback
  }
  
  
  void onTapUp(TapUpEvent event) {
    scale = Vector2.all(1.0);
    onPressed();
  }
  
  
  void onTapCancel(TapCancelEvent event) {
    scale = Vector2.all(1.0);
  }
  
  void onPressed() {
    // Handle button press
  }
}
dart
class Button extends SpriteComponent with TapCallbacks {
  
  void onTapDown(TapDownEvent event) {
    scale = Vector2.all(0.9); // 视觉反馈
  }
  
  
  void onTapUp(TapUpEvent event) {
    scale = Vector2.all(1.0);
    onPressed();
  }
  
  
  void onTapCancel(TapCancelEvent event) {
    scale = Vector2.all(1.0);
  }
  
  void onPressed() {
    // 处理按钮点击
  }
}

Drag Input

拖拽输入

dart
class DraggableObject extends PositionComponent with DragCallbacks {
  Vector2? dragStartPosition;
  Vector2? objectStartPosition;
  
  
  void onDragStart(DragStartEvent event) {
    dragStartPosition = event.localPosition;
    objectStartPosition = position.clone();
  }
  
  
  void onDragUpdate(DragUpdateEvent event) {
    if (dragStartPosition != null && objectStartPosition != null) {
      position = objectStartPosition! + event.localPosition - dragStartPosition!;
    }
  }
  
  
  void onDragEnd(DragEndEvent event) {
    dragStartPosition = null;
    objectStartPosition = null;
  }
}
dart
class DraggableObject extends PositionComponent with DragCallbacks {
  Vector2? dragStartPosition;
  Vector2? objectStartPosition;
  
  
  void onDragStart(DragStartEvent event) {
    dragStartPosition = event.localPosition;
    objectStartPosition = position.clone();
  }
  
  
  void onDragUpdate(DragUpdateEvent event) {
    if (dragStartPosition != null && objectStartPosition != null) {
      position = objectStartPosition! + event.localPosition - dragStartPosition!;
    }
  }
  
  
  void onDragEnd(DragEndEvent event) {
    dragStartPosition = null;
    objectStartPosition = null;
  }
}

Camera System

相机系统

Camera Following Player

相机跟随玩家

dart
class MyGame extends FlameGame with HasCollisionDetection {
  late final Player player;
  
  
  Future<void> onLoad() async {
    final world = World();
    player = Player();
    
    await world.add(player);
    await add(world);
    
    camera = CameraComponent.withFixedResolution(
      world: world,
      width: 800,
      height: 600,
    );
    await add(camera);
    
    camera.follow(player);
  }
}
dart
class MyGame extends FlameGame with HasCollisionDetection {
  late final Player player;
  
  
  Future<void> onLoad() async {
    final world = World();
    player = Player();
    
    await world.add(player);
    await add(world);
    
    camera = CameraComponent.withFixedResolution(
      world: world,
      width: 800,
      height: 600,
    );
    await add(camera);
    
    camera.follow(player);
  }
}

Camera with Bounds

带边界的相机

dart
camera.follow(
  player,
  maxSpeed: 300,
  snap: false, // Smooth following
);

// Set world bounds
camera.setBounds(
  Rectangle.fromLTRB(0, 0, mapWidth, mapHeight),
);
dart
camera.follow(
  player,
  maxSpeed: 300,
  snap: false, // 平滑跟随
);

// 设置世界边界
camera.setBounds(
  Rectangle.fromLTRB(0, 0, mapWidth, mapHeight),
);

Zoom

缩放

dart
// Zoom in
camera.viewfinder.zoom = 2.0;

// Animated zoom
await camera.viewfinder.zoomTo(2.0, speed: 2.0);
dart
// 放大
camera.viewfinder.zoom = 2.0;

// 动画缩放
await camera.viewfinder.zoomTo(2.0, speed: 2.0);

Tiled Maps

Tiled地图

Loading Tiled Maps

加载Tiled地图

dart
class MyGame extends FlameGame {
  
  Future<void> onLoad() async {
    final mapComponent = await TiledComponent.load(
      'map.tmx',
      Vector2(32, 32), // tile size
    );
    await add(mapComponent);
    
    // Access object layer
    final objectLayer = mapComponent.tileMap.getLayer<ObjectGroup>('objects');
    for (final object in objectLayer?.objects ?? []) {
      if (object.name == 'player_spawn') {
        add(Player(position: Vector2(object.x, object.y)));
      }
    }
  }
}
dart
class MyGame extends FlameGame {
  
  Future<void> onLoad() async {
    final mapComponent = await TiledComponent.load(
      'map.tmx',
      Vector2(32, 32), // 瓦片大小
    );
    await add(mapComponent);
    
    // 访问对象层
    final objectLayer = mapComponent.tileMap.getLayer<ObjectGroup>('objects');
    for (final object in objectLayer?.objects ?? []) {
      if (object.name == 'player_spawn') {
        add(Player(position: Vector2(object.x, object.y)));
      }
    }
  }
}

Tile Collisions

瓦片碰撞

dart
// In Tiled editor, add custom property "collision" = true to tiles
// Then in code:


Future<void> onLoad() async {
  final map = await TiledComponent.load('map.tmx', Vector2(32, 32));
  await add(map);
  
  // Generate collision blocks from tile layer
  final collisionLayer = map.tileMap.getLayer<TileLayer>('ground');
  final collisionBlocks = <PositionComponent>[];
  
  for (var y = 0; y < collisionLayer!.height; y++) {
    for (var x = 0; x < collisionLayer.width; x++) {
      final tile = collisionLayer.tileData![y][x];
      if (tile.tile > 0) {
        collisionBlocks.add(
          PositionComponent(
            position: Vector2(x * 32.0, y * 32.0),
            size: Vector2.all(32),
          )..add(RectangleHitbox()),
        );
      }
    }
  }
  
  addAll(collisionBlocks);
}
dart
// 在Tiled编辑器中,为瓦片添加自定义属性 "collision" = true
// 然后在代码中:


Future<void> onLoad() async {
  final map = await TiledComponent.load('map.tmx', Vector2(32, 32));
  await add(map);
  
  // 从瓦片层生成碰撞块
  final collisionLayer = map.tileMap.getLayer<TileLayer>('ground');
  final collisionBlocks = <PositionComponent>[];
  
  for (var y = 0; y < collisionLayer!.height; y++) {
    for (var x = 0; x < collisionLayer.width; x++) {
      final tile = collisionLayer.tileData![y][x];
      if (tile.tile > 0) {
        collisionBlocks.add(
          PositionComponent(
            position: Vector2(x * 32.0, y * 32.0),
            size: Vector2.all(32),
          )..add(RectangleHitbox()),
        );
      }
    }
  }
  
  addAll(collisionBlocks);
}

Audio

音频

Background Music

背景音乐

dart
import 'package:flame_audio/flame_audio.dart';

class MyGame extends FlameGame {
  
  Future<void> onLoad() async {
    await FlameAudio.audioCache.load('bgm.mp3');
    FlameAudio.bgm.initialize();
    FlameAudio.bgm.play('bgm.mp3', volume: 0.5);
  }
  
  
  void onRemove() {
    FlameAudio.bgm.dispose();
    super.onRemove();
  }
}
dart
import 'package:flame_audio/flame_audio.dart';

class MyGame extends FlameGame {
  
  Future<void> onLoad() async {
    await FlameAudio.audioCache.load('bgm.mp3');
    FlameAudio.bgm.initialize();
    FlameAudio.bgm.play('bgm.mp3', volume: 0.5);
  }
  
  
  void onRemove() {
    FlameAudio.bgm.dispose();
    super.onRemove();
  }
}

Sound Effects

音效

dart
class Player extends PositionComponent {
  void jump() {
    FlameAudio.play('jump.wav', volume: 0.8);
    velocity.y = -300;
  }
  
  void collectCoin() {
    FlameAudio.play('coin.wav');
  }
}
dart
class Player extends PositionComponent {
  void jump() {
    FlameAudio.play('jump.wav', volume: 0.8);
    velocity.y = -300;
  }
  
  void collectCoin() {
    FlameAudio.play('coin.wav');
  }
}

Particle Effects

粒子效果

Basic Particle

基础粒子

dart
import 'package:flame/particles.dart';

class Explosion extends ParticleSystemComponent {
  Explosion({required Vector2 position})
      : super(
          position: position,
          particle: Particle.generate(
            count: 20,
            lifespan: 0.5,
            generator: (i) => AcceleratedParticle(
              acceleration: Vector2.random() * 100,
              speed: Vector2.random() * 200,
              child: CircleParticle(
                radius: 4,
                paint: Paint()..color = Colors.orange,
              ),
            ),
          ),
        );
}
dart
import 'package:flame/particles.dart';

class Explosion extends ParticleSystemComponent {
  Explosion({required Vector2 position})
      : super(
          position: position,
          particle: Particle.generate(
            count: 20,
            lifespan: 0.5,
            generator: (i) => AcceleratedParticle(
              acceleration: Vector2.random() * 100,
              speed: Vector2.random() * 200,
              child: CircleParticle(
                radius: 4,
                paint: Paint()..color = Colors.orange,
              ),
            ),
          ),
        );
}

Computed Particle (Custom)

自定义计算粒子

dart
Particle.generate(
  count: 12,
  lifespan: 1.0,
  generator: (i) {
    final angle = (i / 12) * 2 * pi;
    return ComputedParticle(
      renderer: (canvas, particle) {
        final progress = particle.progress;
        final radius = 10 * (1 - progress);
        final alpha = (1 - progress) * 255;
        
        canvas.drawCircle(
          Offset(cos(angle) * 50 * progress, sin(angle) * 50 * progress),
          radius,
          Paint()..color = Colors.red.withAlpha(alpha.toInt()),
        );
      },
    );
  },
)
dart
Particle.generate(
  count: 12,
  lifespan: 1.0,
  generator: (i) {
    final angle = (i / 12) * 2 * pi;
    return ComputedParticle(
      renderer: (canvas, particle) {
        final progress = particle.progress;
        final radius = 10 * (1 - progress);
        final alpha = (1 - progress) * 255;
        
        canvas.drawCircle(
          Offset(cos(angle) * 50 * progress, sin(angle) * 50 * progress),
          radius,
          Paint()..color = Colors.red.withAlpha(alpha.toInt()),
        );
      },
    );
  },
)

UI Overlays

UI覆盖层

Setup

设置

dart
void main() {
  runApp(
    GameWidget(
      game: MyGame(),
      overlayBuilderMap: {
        'PauseMenu': (context, game) => PauseMenuOverlay(),
        'HUD': (context, game) => HudOverlay(game: game as MyGame),
      },
      initialActiveOverlays: const ['HUD'],
    ),
  );
}
dart
void main() {
  runApp(
    GameWidget(
      game: MyGame(),
      overlayBuilderMap: {
        'PauseMenu': (context, game) => PauseMenuOverlay(),
        'HUD': (context, game) => HudOverlay(game: game as MyGame),
      },
      initialActiveOverlays: const ['HUD'],
    ),
  );
}

Show/Hide Overlays

显示/隐藏覆盖层

dart
class MyGame extends FlameGame {
  void showPauseMenu() {
    overlays.add('PauseMenu');
    pauseEngine();
  }
  
  void resumeGame() {
    overlays.remove('PauseMenu');
    resumeEngine();
  }
}
dart
class MyGame extends FlameGame {
  void showPauseMenu() {
    overlays.add('PauseMenu');
    pauseEngine();
  }
  
  void resumeGame() {
    overlays.remove('PauseMenu');
    resumeEngine();
  }
}

HUD Example

HUD示例

dart
class HudOverlay extends StatelessWidget {
  final MyGame game;
  
  const HudOverlay({required this.game});
  
  
  Widget build(BuildContext context) {
    return ValueListenableBuilder<int>(
      valueListenable: game.score,
      builder: (context, score, child) {
        return Positioned(
          top: 20,
          left: 20,
          child: Text(
            'Score: $score',
            style: TextStyle(fontSize: 24, color: Colors.white),
          ),
        );
      },
    );
  }
}
dart
class HudOverlay extends StatelessWidget {
  final MyGame game;
  
  const HudOverlay({required this.game});
  
  
  Widget build(BuildContext context) {
    return ValueListenableBuilder<int>(
      valueListenable: game.score,
      builder: (context, score, child) {
        return Positioned(
          top: 20,
          left: 20,
          child: Text(
            '分数: $score',
            style: TextStyle(fontSize: 24, color: Colors.white),
          ),
        );
      },
    );
  }
}

Parallax Backgrounds

视差背景

dart
class MyGame extends FlameGame {
  
  Future<void> onLoad() async {
    final parallax = await loadParallaxComponent(
      [
        ParallaxImageData('bg_layer1.png'),
        ParallaxImageData('bg_layer2.png'),
        ParallaxImageData('bg_layer3.png'),
      ],
      baseVelocity: Vector2(50, 0),
      velocityMultiplierDelta: Vector2(1.5, 0),
    );
    
    add(parallax);
  }
}
dart
class MyGame extends FlameGame {
  
  Future<void> onLoad() async {
    final parallax = await loadParallaxComponent(
      [
        ParallaxImageData('bg_layer1.png'),
        ParallaxImageData('bg_layer2.png'),
        ParallaxImageData('bg_layer3.png'),
      ],
      baseVelocity: Vector2(50, 0),
      velocityMultiplierDelta: Vector2(1.5, 0),
    );
    
    add(parallax);
  }
}

Joystick Component

摇杆组件

dart
class MyGame extends FlameGame {
  late final JoystickComponent joystick;
  late final Player player;
  
  
  Future<void> onLoad() async {
    player = Player();
    await add(player);
    
    joystick = JoystickComponent(
      knob: CircleComponent(
        radius: 20,
        paint: Paint()..color = Colors.red.withAlpha(200),
      ),
      background: CircleComponent(
        radius: 50,
        paint: Paint()..color = Colors.black.withAlpha(100),
      ),
      margin: const EdgeInsets.only(left: 40, bottom: 40),
    );
    await add(joystick);
  }
  
  
  void update(double dt) {
    if (joystick.direction != JoystickDirection.idle) {
      player.velocity = joystick.delta * 5;
    }
    super.update(dt);
  }
}
dart
class MyGame extends FlameGame {
  late final JoystickComponent joystick;
  late final Player player;
  
  
  Future<void> onLoad() async {
    player = Player();
    await add(player);
    
    joystick = JoystickComponent(
      knob: CircleComponent(
        radius: 20,
        paint: Paint()..color = Colors.red.withAlpha(200),
      ),
      background: CircleComponent(
        radius: 50,
        paint: Paint()..color = Colors.black.withAlpha(100),
      ),
      margin: const EdgeInsets.only(left: 40, bottom: 40),
    );
    await add(joystick);
  }
  
  
  void update(double dt) {
    if (joystick.direction != JoystickDirection.idle) {
      player.velocity = joystick.delta * 5;
    }
    super.update(dt);
  }
}

Performance Best Practices

性能最佳实践

Delta Time Usage

增量时间使用

Always multiply movement/animation by
dt
for frame-rate independence:
dart

void update(double dt) {
  // Good - frame-rate independent
  position.x += speed * dt;
  
  // Bad - frame-rate dependent
  position.x += speed;
}
始终将移动/动画乘以
dt
以实现帧率无关:
dart

void update(double dt) {
  // 良好 - 帧率无关
  position.x += speed * dt;
  
  // 糟糕 - 依赖帧率
  position.x += speed;
}

Object Pooling

对象池

Reuse frequently created/destroyed objects:
dart
class BulletPool {
  final List<Bullet> _available = [];
  final List<Bullet> _inUse = [];
  
  Bullet acquire() {
    if (_available.isEmpty) {
      return Bullet();
    }
    final bullet = _available.removeLast();
    _inUse.add(bullet);
    return bullet;
  }
  
  void release(Bullet bullet) {
    _inUse.remove(bullet);
    _available.add(bullet);
  }
}
重用频繁创建/销毁的对象:
dart
class BulletPool {
  final List<Bullet> _available = [];
  final List<Bullet> _inUse = [];
  
  Bullet acquire() {
    if (_available.isEmpty) {
      return Bullet();
    }
    final bullet = _available.removeLast();
    _inUse.add(bullet);
    return bullet;
  }
  
  void release(Bullet bullet) {
    _inUse.remove(bullet);
    _available.add(bullet);
  }
}

Cleanup

资源清理

Always dispose resources:
dart
class Player extends PositionComponent {
  late final SpriteAnimationTicker ticker;
  
  
  void onRemove() {
    ticker.dispose();
    super.onRemove();
  }
}
始终释放资源:
dart
class Player extends PositionComponent {
  late final SpriteAnimationTicker ticker;
  
  
  void onRemove() {
    ticker.dispose();
    super.onRemove();
  }
}

References

参考资料

For detailed guides on specific topics:
  • Components Deep Dive: See references/components.md
  • Collision System: See references/collision-system.md
  • Input Handling: See references/input-handling.md
  • Camera & Tiled Maps: See references/camera-tiled.md
  • Audio & Particles: See references/audio-particles.md
如需特定主题的详细指南:
  • 组件深入解析:查看 references/components.md
  • 碰撞系统:查看 references/collision-system.md
  • 输入处理:查看 references/input-handling.md
  • 相机与Tiled地图:查看 references/camera-tiled.md
  • 音频与粒子效果:查看 references/audio-particles.md