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

Flame游戏中如何实现六边形按钮仅渲染一次?

解决Flame中静态六边形按钮重复渲染的问题

要实现静态按钮只渲染一次,核心是缓存绘制结果,避免每帧重复计算路径和绘制。以下是两种可行方案:


方案一:使用Flame的CachedComponent(推荐)

CachedComponent是Flame专门用于缓存静态组件渲染结果的封装,它会将组件的绘制内容保存为Picture,仅在第一次加载或组件尺寸/状态变化时重新生成缓存,后续帧直接复用缓存内容。

修改HexagonButton继承自CachedComponent,并调整定位逻辑以符合Flame组件体系:

import 'dart:math';
import 'package:flame/components.dart';
import 'package:flame/events.dart';
import 'package:flutter/material.dart';

class HexagonalShape extends PositionComponent with TapCallbacks {
  late List<HexagonButton> buttons;

  @override
  Future<void> onLoad() async {
    size = game.size; // 绑定游戏屏幕尺寸
    buttons = List.generate(6, (index) {
      final centerX = size.x / 2 + 100 * cos(index * pi / 3);
      final centerY = size.y - 100 * sin(index * pi / 3);

      return HexagonButton(
        onTap: () {
          print('Button $index tapped!');
        },
        radius: 50,
      )..position = Offset(centerX, centerY); // 直接设置按钮中心位置
    });

    addAll(buttons);
  }
}

class HexagonButton extends CachedComponent with TapCallbacks {
  final Function() onTap;
  final double radius;

  HexagonButton({
    required this.onTap,
    required this.radius,
  }) {
    anchor = Anchor.center; // 锚点设为中心,方便定位
    size = Vector2(radius * 2, radius * 2 * 0.866); // 六边形宽高:宽2r,高≈1.732r
  }

  @override
  void renderCached(Canvas canvas) {
    final path = Path();
    // 基于组件自身中心计算顶点
    final vertices = [
      Offset(-radius, 0),
      Offset(-radius / 2, -radius * 0.866),
      Offset(radius / 2, -radius * 0.866),
      Offset(radius, 0),
      Offset(radius / 2, radius * 0.866),
      Offset(-radius / 2, radius * 0.866),
    ];

    path.moveTo(vertices[0].dx, vertices[0].dy);
    for (int i = 1; i < vertices.length; i++) {
      path.lineTo(vertices[i].dx, vertices[i].dy);
    }
    path.close();

    canvas.drawPath(path, Paint()..color = Colors.red);
  }

  @override
  void onTapDown(TapDownEvent event) {
    onTap();
  }
}

方案二:手动缓存绘制结果

如果不想依赖CachedComponent,可以手动在onLoad中预绘制六边形到Picture,后续render直接复用该缓存:

import 'dart:math';
import 'package:flame/components.dart';
import 'package:flame/events.dart';
import 'package:flutter/material.dart';

class HexagonalShape extends PositionComponent with TapCallbacks {
  late List<HexagonButton> buttons;

  @override
  Future<void> onLoad() async {
    size = game.size;
    buttons = List.generate(6, (index) {
      final centerX = size.x / 2 + 100 * cos(index * pi / 3);
      final centerY = size.y - 100 * sin(index * pi / 3);

      return HexagonButton(
        onTap: () {
          print('Button $index tapped!');
        },
        radius: 50,
      )..position = Offset(centerX, centerY);
    });

    addAll(buttons);
  }
}

class HexagonButton extends PositionComponent with TapCallbacks {
  final Function() onTap;
  final double radius;
  late Picture _cachedPicture;

  HexagonButton({
    required this.onTap,
    required this.radius,
  }) {
    anchor = Anchor.center;
    size = Vector2(radius * 2, radius * 2 * 0.866);
  }

  @override
  Future<void> onLoad() async {
    super.onLoad();
    // 录制绘制内容到Picture
    final recorder = PictureRecorder();
    final canvas = Canvas(recorder);
    final path = Path();
    final vertices = [
      Offset(-radius, 0),
      Offset(-radius / 2, -radius * 0.866),
      Offset(radius / 2, -radius * 0.866),
      Offset(radius, 0),
      Offset(radius / 2, radius * 0.866),
      Offset(-radius / 2, radius * 0.866),
    ];
    path.moveTo(vertices[0].dx, vertices[0].dy);
    for (int i = 1; i < vertices.length; i++) {
      path.lineTo(vertices[i].dx, vertices[i].dy);
    }
    path.close();
    canvas.drawPath(path, Paint()..color = Colors.red);
    _cachedPicture = recorder.endRecording();
  }

  @override
  void render(Canvas canvas) {
    // 直接绘制缓存的Picture,无需重复计算
    canvas.drawPicture(_cachedPicture);
  }

  @override
  void onTapDown(TapDownEvent event) {
    onTap();
  }

  @override
  void onRemove() {
    super.onRemove();
    // 释放Picture资源,避免内存泄漏
    _cachedPicture.dispose();
  }
}

关键优化说明

  1. 锚点定位:将按钮锚点设为中心,直接通过position设置中心坐标,符合Flame组件的定位逻辑,避免依赖外部传入的坐标参数。
  2. 缓存复用:两种方案都通过一次性生成绘制内容并复用,彻底解决每帧重复渲染的问题,大幅降低性能开销。
  3. 资源管理:手动缓存时需在组件移除时释放Picture资源,避免内存泄漏。

内容的提问来源于stack exchange,提问作者sirbird phoenix

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 16:57:48