LineInkGuide/lib/game_engine.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;
}
});
}
}