You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter Flame 1.15.0:如何用指尖/鼠标自由绘制圆形等图形?

在Flutter Flame 1.15.0中实现自由手绘图形(圆形、矩形等)

你的代码问题分析及修复

你当前的代码无法显示图形主要有以下几个原因:

  • 坐标不匹配:使用info.eventPosition.global获取的是屏幕全局坐标,但render方法中的Canvas基于游戏局部坐标系,存在CameraComponent时两者不对应,应改用info.eventPosition.game获取游戏内坐标。
  • 渲染顺序错误:先绘制路径再调用super.render(canvas),导致自定义图形被后续组件(如Level、Camera)覆盖,需调换顺序。
  • 缺少手势起始/结束处理:仅监听onPanUpdate会丢失拖动起始点,且无法在拖动结束时完成路径或重置状态。
  • 线条样式不足:默认Paint线条过细可能导致图形不可见,需添加strokeWidth并设置样式为stroke。

修复后的代码:

import 'dart:async';
import 'dart:ui';

import 'package:catcher/components/level.dart';
import 'package:catcher/config.dart';
import 'package:flame/components.dart';
import 'package:flame/events.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';

class Catcher extends FlameGame with PanDetector {
  late CameraComponent cam;
  List<String> levelNames = [
    'level-01',
    'level-02',
  ];
  int currentLevelIndex = 0;
  List<Vector2> points = [];
  // 存储已完成的路径,实现持久显示
  List<Path> savedPaths = [];
  final Paint _paint = Paint()
    ..color = Colors.black
    ..strokeWidth = 3
    ..style = PaintingStyle.stroke;

  @override
  FutureOr<void> onLoad() async {
    await images.loadAllImages();
    _loadLevel();
    return super.onLoad();
  }

  void _loadLevel() {
    Level world = Level(levelName: levelNames[currentLevelIndex]);
    cam = CameraComponent.withFixedResolution(
        world: world, width: gameWidth, height: gameHeight);
    cam.viewfinder.anchor = Anchor.topLeft;
    addAll([cam, world]);
  }

  @override
  void render(Canvas canvas) {
    super.render(canvas); // 先渲染游戏原有组件,再绘制自定义路径

    // 渲染已保存的历史路径
    for (var path in savedPaths) {
      canvas.drawPath(path, _paint);
    }

    // 渲染当前正在绘制的路径
    if (points.length > 1) {
      Path path = Path();
      path.moveTo(points[0].x, points[0].y);
      for (int i = 1; i < points.length; i++) {
        path.lineTo(points[i].x, points[i].y);
      }
      canvas.drawPath(path, _paint);
    }
  }

  @override
  void onPanStart(DragStartInfo info) {
    points.clear();
    // 记录拖动起始点
    points.add(info.eventPosition.game);
    super.onPanStart(info);
  }

  @override
  void onPanUpdate(DragUpdateInfo info) {
    points.add(info.eventPosition.game);
    super.onPanUpdate(info);
  }

  @override
  void onPanEnd(DragEndInfo info) {
    // 拖动结束后保存当前路径
    if (points.length > 1) {
      Path path = Path();
      path.moveTo(points[0].x, points[0].y);
      for (int i = 1; i < points.length; i++) {
        path.lineTo(points[i].x, points[i].y);
      }
      savedPaths.add(path);
    }
    points.clear();
    super.onPanEnd(info);
  }
}

如何绘制圆形、矩形等规则形状

除了手绘自由路径,还可以通过以下方式实现规则图形的绘制:

1. 绘制圆形

以拖动起始点为圆心,拖动终点到起始点的距离为半径绘制圆形:

@override
void onPanEnd(DragEndInfo info) {
  if (points.length >= 2) {
    final center = points[0];
    final radius = points.last.distanceTo(center);
    Path circlePath = Path()..addOval(Rect.fromCircle(center: center.toOffset(), radius: radius));
    savedPaths.add(circlePath);
  }
  points.clear();
  super.onPanEnd(info);
}

2. 绘制矩形

根据拖动的起始点和终点计算矩形边界,绘制矩形:

@override
void onPanEnd(DragEndInfo info) {
  if (points.length >= 2) {
    final start = points[0];
    final end = points.last;
    final left = min(start.x, end.x);
    final top = min(start.y, end.y);
    final right = max(start.x, end.x);
    final bottom = max(start.y, end.y);
    Path rectPath = Path()..addRect(Rect.fromLTRB(left, top, right, bottom));
    savedPaths.add(rectPath);
  }
  points.clear();
  super.onPanEnd(info);
}

3. 切换绘制模式

添加状态变量,实现自由手绘、圆形、矩形的模式切换:

enum DrawMode { freehand, circle, rectangle }
DrawMode currentMode = DrawMode.freehand;

@override
void onPanEnd(DragEndInfo info) {
  if (points.length < 2) {
    points.clear();
    super.onPanEnd(info);
    return;
  }

  Path newPath = Path();
  switch(currentMode) {
    case DrawMode.freehand:
      newPath.moveTo(points[0].x, points[0].y);
      for (var p in points.skip(1)) {
        newPath.lineTo(p.x, p.y);
      }
      break;
    case DrawMode.circle:
      final center = points[0];
      final radius = points.last.distanceTo(center);
      newPath.addOval(Rect.fromCircle(center: center.toOffset(), radius: radius));
      break;
    case DrawMode.rectangle:
      final start = points[0];
      final end = points.last;
      final left = min(start.x, end.x);
      final top = min(start.y, end.y);
      final right = max(start.x, end.x);
      final bottom = max(start.y, end.y);
      newPath.addRect(Rect.fromLTRB(left, top, right, bottom));
      break;
  }
  savedPaths.add(newPath);
  points.clear();
  super.onPanEnd(info);
}

内容的提问来源于stack exchange,提问作者Shiva Raj Badu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 02:05:39