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
相关产品推荐
相关产品推荐

