import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flame/game.dart'; import 'package:flame/components.dart' hide Block; import 'package:flame/events.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'app_theme.dart'; import 'levels.dart'; import 'managers.dart'; import 'game_components.dart'; // --- Physics Engine --- class LineInkGuideGame extends Forge2DGame with DragCallbacks { final LevelConfig config; final ValueNotifier inkNotifier = ValueNotifier(0); final ValueNotifier starCountNotifier = ValueNotifier(0); final ValueNotifier winNotifier = ValueNotifier(null); final ValueNotifier failNotifier = ValueNotifier(false); final ValueNotifier achievementNotifier = ValueNotifier( null, ); final List _drawPoints = []; DrawingPreview? _preview; bool _simulating = false; int _collected = 0; DateTime? _runStartTime; // 公共访问器,供组件使用 int get collected => _collected; set collected(int value) => _collected = value; bool get simulating => _simulating; set simulating(bool value) => _simulating = value; LineInkGuideGame({required this.config}) : super(gravity: Vector2(0, 0)); @override Color backgroundColor() => AppTheme.paperBg; Ball? _ball; List _stars = []; Goal? _goal; List _blocks = []; @override Future onLoad() async { await super.onLoad(); camera.viewfinder.anchor = Anchor.topLeft; _updateZoom(); _initializeLevel(); } void _initializeLevel() { world.removeAll(world.children); world.add(BackgroundGrid()); // Add Entities _ball = Ball(config.ballPos); world.add(_ball!); _stars.clear(); for (var sPos in config.stars) { final star = Star(sPos); _stars.add(star); world.add(star); } _goal = Goal(config.goalPos); world.add(_goal!); _blocks.clear(); for (var blockShape in config.blocks) { final block = Block(blockShape); _blocks.add(block); world.add(block); } // Borders world.add(Boundary(Vector2(0, 0), Vector2(20, 0))); world.add(Boundary(Vector2(0, 0), Vector2(0, 60))); world.add(Boundary(Vector2(20, 0), Vector2(20, 60))); // 初始化状态 _simulating = false; _collected = 0; starCountNotifier.value = 0; inkNotifier.value = config.ink; winNotifier.value = null; failNotifier.value = false; _runStartTime = null; _drawPoints.clear(); world.gravity = Vector2(0, 0); } void _updateZoom() { camera.viewfinder.zoom = size.x / 20; } @override void onGameResize(Vector2 size) { super.onGameResize(size); _updateZoom(); } void resetLevel() { _simulating = false; _collected = 0; starCountNotifier.value = 0; inkNotifier.value = config.ink; winNotifier.value = null; failNotifier.value = false; _runStartTime = null; _drawPoints.clear(); // 停止重力 world.gravity = Vector2(0, 0); _preview?.removeFromParent(); // 移除所有用户绘制的线条 final linesToRemove = []; for (var child in world.children) { if (child is UserLine) { linesToRemove.add(child); } } for (var line in linesToRemove) { line.removeFromParent(); } // 重置小球位置和速度 if (_ball != null && _ball!.isMounted) { _ball!.body.setTransform(config.ballPos, 0); _ball!.body.linearVelocity = Vector2.zero(); _ball!.body.angularVelocity = 0; } // 移除所有现有星星并重新添加 final starsToRemove = []; for (var child in world.children) { if (child is Star) { starsToRemove.add(child); } } for (var star in starsToRemove) { star.removeFromParent(); } // 重新添加所有星星 _stars.clear(); for (var sPos in config.stars) { final star = Star(sPos); _stars.add(star); world.add(star); } } void startSimulation() { if (_simulating) return; if (winNotifier.value != null || failNotifier.value) return; _simulating = true; world.gravity = Vector2(0, 32); _preview?.removeFromParent(); _runStartTime = DateTime.now(); } @override void onDragStart(DragStartEvent event) { super.onDragStart(event); if (_simulating || inkNotifier.value <= 0) return; _drawPoints.clear(); _drawPoints.add(screenToWorld(event.localPosition)); world.add(_preview = DrawingPreview(_drawPoints)); } @override void onDragUpdate(DragUpdateEvent event) { super.onDragUpdate(event); if (_simulating || inkNotifier.value <= 0) return; final p = screenToWorld(event.localEndPosition); if (_drawPoints.isEmpty) return; double d = (p - _drawPoints.last).length; if (d > 0.25 && inkNotifier.value - (d * 10) > 0) { _drawPoints.add(p); inkNotifier.value -= d * 10; } } @override void onDragEnd(DragEndEvent event) { super.onDragEnd(event); _preview?.removeFromParent(); if (_drawPoints.length > 1) world.add(UserLine(List.from(_drawPoints))); _drawPoints.clear(); } bool get isSimulating => simulating; Duration? get currentRunDuration => _runStartTime == null ? null : DateTime.now().difference(_runStartTime!); void onBallOutOfBounds() { if (!_simulating || winNotifier.value != null || failNotifier.value) { return; } _simulating = false; failNotifier.value = true; _runStartTime = null; } void notifyAchievement(AchievementDef def) { achievementNotifier.value = def; Future.delayed(const Duration(seconds: 2), () { if (achievementNotifier.value == def) { achievementNotifier.value = null; } }); } }