地图组件和ECS框架完成

This commit is contained in:
2026-01-13 15:39:45 +08:00
parent 9d45d4c726
commit 566ec47a73
24 changed files with 17072 additions and 66 deletions

90
lib/ecs/component.dart Normal file
View File

@@ -0,0 +1,90 @@
import 'package:flutter/material.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
import 'entity.dart';
/// 组件基类 - 代表实体的属性
abstract class Component {
final String id;
Entity? entity;
Component({String? id}) : id = id ?? "${DateTime.now().millisecondsSinceEpoch}";
}
/// 标绘组件 - 用于在地图上绘制图形
class PlotComponent extends Component {
final String plotType;
final List<LatLng> coordinates;
final Color color;
final double strokeWidth;
PlotComponent({
super.id,
required this.plotType,
required this.coordinates,
this.color = Colors.red,
this.strokeWidth = 2.0,
});
}
/// 图标组件 - 用于在地图上显示图标
class IconComponent extends Component {
final String iconPath;
LatLng position;
final double size;
IconComponent({
super.id,
required this.iconPath,
required this.position,
this.size = 30.0,
});
}
/// 拖尾组件 - 用于显示移动实体的轨迹
class TrailComponent extends Component {
final List<LatLng> trailPoints;
final Color trailColor;
final double maxLength;
TrailComponent({
super.id,
List<LatLng>? trailPoints,
this.trailColor = Colors.blue,
this.maxLength = 20.0,
}) : trailPoints = trailPoints ?? [];
void addPoint(LatLng point) {
trailPoints.add(point);
if (trailPoints.length > maxLength) {
trailPoints.removeAt(0);
}
}
}
/// 音频组件 - 用于播放与实体相关的音频
class AudioComponent extends Component {
final String audioPath;
final bool loop;
final double volume;
AudioComponent({
super.id,
required this.audioPath,
this.loop = false,
this.volume = 1.0,
});
}
/// 移动组件 - 用于控制实体的移动
class MovementComponent extends Component {
LatLng position;
LatLng? target;
double speed;
MovementComponent({
super.id,
required this.position,
this.target,
this.speed = 1.0,
});
}

39
lib/ecs/entity.dart Normal file
View File

@@ -0,0 +1,39 @@
import 'component.dart';
/// 实体ID生成器
class EntityIdGenerator {
static int _id = 0;
static int nextId() => _id++;
}
/// 实体类 - 代表地图上的一个对象
class Entity {
final int id;
final String name;
int entityType;
final Map<String, Component> _components = {};
Entity({int? id, required this.name, this.entityType = 0}) : id = id ?? EntityIdGenerator.nextId();
/// 添加组件
void addComponent<T extends Component>(T component) {
_components[T.toString()] = component;
}
/// 获取组件
T? getComponent<T extends Component>() {
return _components[T.toString()] as T?;
}
/// 检查是否有特定类型的组件
bool hasComponent<T extends Component>() {
return _components.containsKey(T.toString());
}
/// 移除组件
void removeComponent<T extends Component>() {
_components.remove(T.toString());
}
/// 获取所有组件
Map<String, Component> get components => _components;
}

389
lib/ecs/system.dart Normal file
View File

@@ -0,0 +1,389 @@
import 'dart:collection';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:maplibre/maplibre.dart';
import 'package:maplibre_gl/maplibre_gl.dart' as gl;
import '../screens/map_screen.dart';
import 'entity.dart';
import 'component.dart';
/// 系统基类 - 用于处理特定类型的组件
abstract class System {
void update(List<Entity> entities, Duration deltaTime);
}
/// 地图系统 - 处理地图相关的实体和组件
class MapSystem implements System {
gl.MapLibreMapController? mapController;
MapUiBodyState? mapUiBodyState;
final List<String> _plotIds = [];
final List<String> _trailIds = [];
final List<String> _iconIds = [];
// 单例实例
static MapSystem? _instance;
// 工厂构造函数,实现单例
factory MapSystem({gl.MapLibreMapController? mapController, MapUiBodyState? mapUiBodyState}) {
_instance ??= MapSystem._internal(mapController: mapController, mapUiBodyState: mapUiBodyState);
if (mapController != null) _instance!.mapController = mapController;
if (mapUiBodyState != null) _instance!.mapUiBodyState = mapUiBodyState;
return _instance!;
}
// 私有构造函数
MapSystem._internal({this.mapController, this.mapUiBodyState});
/// 获取单例实例
static MapSystem get instance {
_instance ??= MapSystem._internal();
return _instance!;
}
@override
void update(List<Entity> entities, Duration deltaTime) {
// 更新地图上的标绘
for (var entity in entities) {
if (entity.hasComponent<PlotComponent>()) {
_updatePlot(entity.getComponent<PlotComponent>()!);
}
if (entity.hasComponent<IconComponent>()) {
_updateIcon(entity.getComponent<IconComponent>()!);
}
if (entity.hasComponent<TrailComponent>()) {
_updateTrail(entity.getComponent<TrailComponent>()!);
}
if (entity.hasComponent<MovementComponent>()) {
_updateMovement(entity);
}
}
}
void _updatePlot(PlotComponent plot) {
// 实现标绘更新逻辑
// 这里可以根据plotType绘制不同类型的图形
if (plot.coordinates.length >= 2) {
// 示例:绘制线段
// mapController?.addLine() // 这里需要根据实际API实现
}
}
void _updateIcon(IconComponent icon) {
// 实现图标更新逻辑
// mapController?.addMarker() // 这里需要根据实际API实现
}
void _updateTrail(TrailComponent trail) {
// 实现拖尾更新逻辑
// 绘制轨迹线
}
void _updateMovement(Entity entity) {
var movement = entity.getComponent<MovementComponent>();
var plot = entity.getComponent<PlotComponent>();
var icon = entity.getComponent<IconComponent>();
if (movement != null && (plot != null || icon != null)) {
if (movement.target != null) {
// 简单的移动逻辑 - 向目标点移动
double dx = movement.target!.longitude - movement.position.longitude;
double dy = movement.target!.latitude - movement.position.latitude;
double distance = sqrt(dx * dx + dy * dy);
if (distance > 0.0001) { // 如果距离目标足够近就停止
double ratio = movement.speed * 0.0001 / distance;
movement.position = gl.LatLng(
movement.position.latitude + dy * ratio,
movement.position.longitude + dx * ratio,
);
// 更新图标位置
if (icon != null) {
icon.position = movement.position;
}
// 如果有拖尾组件,添加新点
var trail = entity.getComponent<TrailComponent>();
if (trail != null) {
trail.addPoint(movement.position);
}
} else {
movement.target = null; // 到达目标
}
}
}
}
// ==================== 便捷方法:通过 mapUiBodyState 调用地图接口 ====================
/// 添加自定义图片标记
void addCustomMarker(Geographic position, {Map<String, dynamic>? properties}) {
mapUiBodyState?.addCustomMarker(position, properties: properties);
}
/// 添加圆形
void addCircle(
Geographic position, {
double radius = 20,
Color color = Colors.orange,
Color strokeColor = Colors.red,
double strokeWidth = 2,
}) {
mapUiBodyState?.addCircle(
position,
radius: radius,
color: color,
strokeWidth: strokeWidth,
);
}
/// 添加扇形
/// [center] 扇形中心的地理位置
/// [radius] 扇形的半径(单位:米)
/// [startAngle] 起始角度单位0度表示正东方向
/// [endAngle] 结束角度(单位:度)
/// [segments] 扇形的分段数,值越大越平滑
/// [color] 填充颜色
/// [outlineColor] 边框颜色
void addSector(
Geographic center, {
required double radius,
required double startAngle,
required double endAngle,
int segments = 36,
Color color = Colors.lightBlueAccent,
Color outlineColor = Colors.blue,
}) {
mapUiBodyState?.addSector(
center,
radius: radius,
startAngle: startAngle,
endAngle: endAngle,
segments: segments,
color: color,
outlineColor: outlineColor,
);
}
/// 添加长方形
/// [center] 长方形中心的地理位置
/// [width] 长方形的宽度(单位:米)
/// [height] 长方形的高度(单位:米)
/// [color] 填充颜色
/// [outlineColor] 边框颜色
void addRectangle(
Geographic center, {
required double width,
required double height,
Color color = Colors.lightBlueAccent,
Color outlineColor = Colors.blue,
}) {
mapUiBodyState?.addRectangle(
center,
width: width,
height: height,
color: color,
outlineColor: outlineColor,
);
}
/// 清除所有自定义标记
void clearCustomMarkers() {
mapUiBodyState?.clearCustomMarkers();
}
/// 清除所有圆形
void clearCircles() {
mapUiBodyState?.clearCircles();
}
/// 清除所有多边形(包括扇形和长方形)
void clearPolygons() {
mapUiBodyState?.clearPolygons();
}
/// 清除所有自定义图层
void clearAllCustomLayers() {
mapUiBodyState?.clearAllCustomLayers();
}
/// 添加固定大小的圆形(不随地图缩放)
void addFixedCircle(
Geographic position, {
double radius = 20,
Color color = Colors.orange,
Color strokeColor = Colors.red,
double strokeWidth = 2,
}) {
mapUiBodyState?.addFixedCircle(
position,
radius: radius,
color: color,
outlineColor: strokeColor,
);
}
}
/// 音频系统 - 处理音频相关的实体和组件
class AudioSystem implements System {
@override
void update(List<Entity> entities, Duration deltaTime) {
for (var entity in entities) {
if (entity.hasComponent<AudioComponent>()) {
var audioComponent = entity.getComponent<AudioComponent>()!;
_playAudio(audioComponent);
}
}
}
void _playAudio(AudioComponent audio) {
// 实现音频播放逻辑
// 这里可以使用Flutter的音频插件如audioplayers
print("Playing audio: ${audio.audioPath}");
}
}
/// 组件系统 - 管理各种组件的交互
class ComponentSystem implements System {
@override
void update(List<Entity> entities, Duration deltaTime) {
// 处理组件间的交互
for (var entity in entities) {
// 可以根据需要实现组件交互逻辑
if (entity.hasComponent<PlotComponent>() && entity.hasComponent<IconComponent>()) {
// 当一个实体同时有标绘和图标组件时的特殊处理
}
}
}
}
/// ECS管理器 - 管理实体、系统和整个ECS框架
class ECSManager {
static ECSManager? _instance;
factory ECSManager() {
_instance ??= ECSManager._internal();
return _instance!;
}
ECSManager._internal();
/// 获取单例实例
static ECSManager get instance {
_instance ??= ECSManager._internal();
return _instance!;
}
final List<Entity> _entities = [];
final List<System> _systems = [];
final Queue<Entity> _entitiesToAdd = Queue<Entity>();
final Queue<Entity> _entitiesToRemove = Queue<Entity>();
void addEntity(Entity entity) {
_entitiesToAdd.add(entity);
}
void removeEntity(Entity entity) {
_entitiesToRemove.add(entity);
}
void addSystem(System system) {
_systems.add(system);
}
void removeSystem(System system) {
_systems.remove(system);
}
void update(Duration deltaTime) {
// 添加新实体
while (_entitiesToAdd.isNotEmpty) {
_entities.add(_entitiesToAdd.removeFirst());
}
// 更新所有系统
for (var system in _systems) {
system.update(_entities, deltaTime);
}
// 移除实体
while (_entitiesToRemove.isNotEmpty) {
var entity = _entitiesToRemove.removeFirst();
_entities.remove(entity);
}
}
List<Entity> get entities => _entities;
/// 工厂方法:创建一个带位置的实体
Entity createEntityAtPosition({
required String name,
required gl.LatLng position,
String? plotType,
Color plotColor = Colors.red,
double strokeWidth = 2.0,
String? iconPath,
double iconSize = 30.0,
bool hasTrail = false,
Color trailColor = Colors.blue,
double trailMaxLength = 20.0,
bool hasMovement = false,
gl.LatLng? movementTarget,
double movementSpeed = 1.0,
String? audioPath,
bool audioLoop = false,
double audioVolume = 1.0,
}) {
// 创建实体
final entity = Entity(name: name);
// 添加位置相关的组件
if (plotType != null) {
entity.addComponent(PlotComponent(
plotType: plotType,
coordinates: [position],
color: plotColor,
strokeWidth: strokeWidth,
));
}
if (iconPath != null) {
entity.addComponent(IconComponent(
iconPath: iconPath,
position: position,
size: iconSize,
));
}
if (hasTrail) {
entity.addComponent(TrailComponent(
trailColor: trailColor,
maxLength: trailMaxLength,
));
}
if (hasMovement) {
entity.addComponent(MovementComponent(
position: position,
target: movementTarget,
speed: movementSpeed,
));
}
if (audioPath != null) {
entity.addComponent(AudioComponent(
audioPath: audioPath,
loop: audioLoop,
volume: audioVolume,
));
}
// 添加到管理器
addEntity(entity);
return entity;
}
}