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

FixedResolutionViewport下Tap事件注册异常问题排查

问题:FixedResolutionViewport下点击事件位置偏移

在使用FixedResolutionViewport的Flame游戏中,结合HasTappableComponents与TapCallbacks时出现异常:RouterComponent渲染的组件已居中显示,但点击事件却触发在组件未居中时的位置(红色箭头标注了实际点击触发的位置——组件视觉上在屏幕中心,但点击屏幕中心无响应,反而要点击组件原本未居中的位置才会触发)。

问题代码

import 'package:flame/components.dart';
import 'package:flame/experimental.dart';
import 'package:flame/game.dart';
import 'package:flutter/rendering.dart';

class TestGame extends FlameGame with HasTappableComponents {
  @override
  Future<void>? onLoad() async {
    camera.viewport = FixedResolutionViewport(
      Vector2(540, 960),
    );
    await add(
      RouterComponent(
        initialRoute: 'home',
        routes: {
          'home': Route(SceneComponent.new),
        },
      ),
    );
    return super.onLoad();
  }
}

class SceneComponent extends PositionComponent with HasGameRef<TestGame> {
  @override
  Future<void>? onLoad() async {
    size = gameRef.size;
    await add(
      TestComponent(
        position: Vector2(
          size.x * 0.5,
          size.y * 0.5,
        ),
        size: Vector2(100, 100),
        anchor: Anchor.center,
      ),
    );
    return super.onLoad();
  }

  @override
  void render(Canvas canvas) {
    canvas.drawRect(size.toRect(), Paint()..color = Color.fromARGB(255, 0, 255, 119));
    return super.render(canvas);
  }
}

class TestComponent extends PositionComponent with TapCallbacks {
  TestComponent({
    super.position,
    super.size,
    super.scale,
    super.angle,
    super.nativeAngle,
    super.anchor,
    super.children,
    super.priority,
  });

  @override
  void onTapDown(TapDownEvent event) {
    print('tap');
    super.onTapDown(event);
  }

  @override
  void render(Canvas canvas) {
    canvas.drawRect(
      size.toRect(),
      Paint()..color = Color.fromARGB(255, 255, 0, 0),
    );
  }
}

问题原因

核心问题是SceneComponent的尺寸计算未与FixedResolutionViewport对齐:

  • gameRef.size获取的是游戏窗口的实际物理尺寸,而非你设置的固定分辨率(540x960)。当FixedResolutionViewport生效时,相机已经对画面做了缩放居中处理,但SceneComponent仍用窗口原始尺寸来计算子组件位置,导致子组件的逻辑位置和视觉位置脱节。
  • TapCallbacks的点击事件坐标是经过相机视口转换后的世界坐标,而组件的碰撞检测是基于自身逻辑位置,两者不匹配就会出现点击偏移。

修复方案

方案1:手动绑定视口分辨率修改SceneComponent尺寸

修改SceneComponent的onLoad方法,直接使用视口的固定分辨率作为自身尺寸:

@override
Future<void>? onLoad() async {
  // 从相机视口获取固定分辨率尺寸
  final viewportResolution = (gameRef.camera.viewport as FixedResolutionViewport).resolution;
  size = viewportResolution;
  await add(
    TestComponent(
      position: Vector2(
        size.x * 0.5,
        size.y * 0.5,
      ),
      size: Vector2(100, 100),
      anchor: Anchor.center,
    ),
  );
  return super.onLoad();
}

方案2:使用ViewportAwareComponent自动适配

让SceneComponent继承ViewportAwareComponent,它会自动监听视口尺寸变化并同步自身大小,无需手动设置:

// 修改SceneComponent的继承关系
class SceneComponent extends ViewportAwareComponent with HasGameRef<TestGame> {
  @override
  Future<void>? onLoad() async {
    await add(
      TestComponent(
        position: Vector2(
          size.x * 0.5,
          size.y * 0.5,
        ),
        size: Vector2(100, 100),
        anchor: Anchor.center,
      ),
    );
    return super.onLoad();
  }

  @override
  void render(Canvas canvas) {
    canvas.drawRect(size.toRect(), Paint()..color = Color.fromARGB(255, 0, 255, 119));
    super.render(canvas);
  }
}

额外注意点

使用FixedResolutionViewport时,所有组件的位置、尺寸计算都要基于视口的固定分辨率,而非游戏窗口的实际尺寸,这样才能保证渲染位置和点击检测的坐标一致。

内容的提问来源于stack exchange,提问作者anonymous-dev

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 03:35:28