216 lines
5.7 KiB
Dart
216 lines
5.7 KiB
Dart
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<double> inkNotifier = ValueNotifier(0);
|
|
final ValueNotifier<int> starCountNotifier = ValueNotifier(0);
|
|
final ValueNotifier<int?> winNotifier = ValueNotifier(null);
|
|
final ValueNotifier<bool> failNotifier = ValueNotifier(false);
|
|
final ValueNotifier<AchievementDef?> achievementNotifier = ValueNotifier(
|
|
null,
|
|
);
|
|
|
|
final List<Vector2> _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<Star> _stars = [];
|
|
Goal? _goal;
|
|
List<Block> _blocks = [];
|
|
|
|
@override
|
|
Future<void> 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 = <UserLine>[];
|
|
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 = <Star>[];
|
|
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;
|
|
}
|
|
});
|
|
}
|
|
}
|