auto pathing: add mapper viewer

This commit is contained in:
辉鸭蛋
2024-09-08 19:08:46 +08:00
parent ca6994a720
commit a0691f1c19
11 changed files with 210 additions and 34 deletions

View File

@@ -41,6 +41,7 @@
<PackageReference Include="Microsoft.ML.OnnxRuntime.Managed" Version="1.18.1" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2592.51" />
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
<PackageReference Include="OpenCvSharp4.WpfExtensions" Version="4.8.0.20230708" />
<PackageReference Include="OpenCvSharp4.Extensions" Version="4.8.0.20230708" />
<PackageReference Include="OpenCvSharp4.Windows" Version="4.8.0.20230708" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.122" />

View File

@@ -4,6 +4,8 @@ using BetterGenshinImpact.GameTask.Model.Area;
using OpenCvSharp;
using System;
using BetterGenshinImpact.GameTask.Common;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.Extensions.Logging;
namespace BetterGenshinImpact.GameTask.AutoPathing;
@@ -20,7 +22,11 @@ internal class Navigation
internal static Point2f GetPosition(ImageRegion imageRegion)
{
var greyMat = new Mat(imageRegion.SrcGreyMat, new Rect(62, 19, 212, 212));
return EntireMap.Instance.GetMiniMapPositionByFeatureMatch(greyMat);
var p = EntireMap.Instance.GetMiniMapPositionByFeatureMatch(greyMat);
WeakReferenceMessenger.Default.Send(new PropertyChangedMessage<object>(typeof(Navigation),
"SendCurrentPosition", new object(), p));
return p;
}
internal static int GetTargetOrientation(Waypoint waypoint, Point2f position)

View File

@@ -1,10 +1,13 @@
using BetterGenshinImpact.Core.Simulator;
using BetterGenshinImpact.GameTask.AutoPathing.Model;
using BetterGenshinImpact.GameTask.AutoPathing.Model.Enum;
using BetterGenshinImpact.GameTask.AutoTrackPath;
using BetterGenshinImpact.GameTask.Common;
using BetterGenshinImpact.GameTask.Common.BgiVision;
using BetterGenshinImpact.GameTask.Common.Map;
using BetterGenshinImpact.GameTask.Model.Area;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.Extensions.Logging;
using OpenCvSharp;
using System;
@@ -12,10 +15,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BetterGenshinImpact.GameTask.AutoPathing.Model.Enum;
using BetterGenshinImpact.Helpers;
using Vanara.PInvoke;
using Wpf.Ui.Violeta.Controls;
using static BetterGenshinImpact.GameTask.Common.TaskControl;
namespace BetterGenshinImpact.GameTask.AutoPathing;
@@ -32,7 +32,9 @@ public class PathExecutor(CancellationTokenSource cts)
task.Positions.First().Type = WaypointType.Teleport.Code;
// 这里应该判断一下自动拾取是否处于工作状态,但好像没有什么方便的读取办法
// 初始化查看地图
WeakReferenceMessenger.Default.Send(new PropertyChangedMessage<object>(this,
"UpdateCurrentPathing", new object(), task));
// TODO:大地图传送的时候使用游戏坐标追踪的时候应该使用2048地图图像坐标这里临时做转换后续改进
var waypoints = new List<Waypoint>();
@@ -61,7 +63,9 @@ public class PathExecutor(CancellationTokenSource cts)
if (waypoint.Type == WaypointType.Teleport.Code)
{
Logger.LogInformation("正在传送到{x},{y}", waypoint.X, waypoint.Y);
await new TpTask(cts).Tp(waypoint.X, waypoint.Y);
var (tpX, tpY) = await new TpTask(cts).Tp(waypoint.X, waypoint.Y);
var (tprX, tprY) = MapCoordinate.GameToMain2048(tpX, tpY);
EntireMap.Instance.SetPrevPosition((float)tprX, (float)tprY); // 通过上一个位置直接进行局部特征匹配
continue;
}
@@ -230,6 +234,8 @@ public class PathExecutor(CancellationTokenSource cts)
{
Simulation.SendInput.Keyboard.KeyUp(User32.VK.VK_W);
}
// 不管咋样抬起w键
Simulation.SendInput.Keyboard.KeyUp(User32.VK.VK_W);
}
private int RotateTo(int targetOrientation, ImageRegion imageRegion, int controlRatio = 1)
@@ -241,6 +247,7 @@ public class PathExecutor(CancellationTokenSource cts)
{
return diff;
}
// 平滑的旋转视角
if (Math.Abs(diff) > 90)
{
controlRatio = 5;

View File

@@ -33,7 +33,7 @@ public class TpTask(CancellationTokenSource cts)
/// </summary>
/// <param name="tpX"></param>
/// <param name="tpY"></param>
public async Task Tp(double tpX, double tpY)
public async Task<(double, double)> Tp(double tpX, double tpY)
{
// 获取最近的传送点位置
var (x, y) = GetRecentlyTpPoint(tpX, tpY);
@@ -88,6 +88,7 @@ public class TpTask(CancellationTokenSource cts)
}
Logger.LogInformation("传送完成");
return (x, y);
}
/// <summary>
@@ -201,7 +202,7 @@ public class TpTask(CancellationTokenSource cts)
{
throw new RetryException("当前不在地图界面");
}
}, TimeSpan.FromMilliseconds(500), 5);
}, TimeSpan.FromMilliseconds(500), 10);
if (rect == Rect.Empty)
{

View File

@@ -33,6 +33,11 @@ public class EntireMap : Singleton<EntireMap>
private int _failCnt = 0;
public void SetPrevPosition(float x, float y)
{
(_prevX, _prevY) = (x, y);
}
/// <summary>
/// 基于特征匹配获取地图位置
/// 移动匹配

View File

@@ -422,7 +422,7 @@
</ui:CardControl>
<!-- 地图 -->
<ui:CardControl Margin="0,0,0,12"
<!--<ui:CardControl Margin="0,0,0,12"
Icon="{ui:SymbolIcon Cursor24}"
Visibility="{markup:Converter Value={x:Static helpers:RuntimeHelper.IsDebuggerAttached},
Converter={StaticResource BooleanToVisibilityConverter}}">
@@ -447,7 +447,7 @@
<ui:Button Margin="0,0,36,0"
Command="{Binding OpenMapViewerCommand}"
Content="查看地图" />
</ui:CardControl>
</ui:CardControl>-->
<!--<ui:CardControl Margin="0,0,0,12" Icon="{ui:SymbolIcon Keyboard24}">
<ui:CardControl.Header>

View File

@@ -47,6 +47,9 @@
<ui:Button Command="{Binding OpenScriptsFolderCommand}"
Content="打开任务目录"
Icon="{ui:SymbolIcon FolderOpen24}" />
<ui:Button Margin="10,0,0,0"
Command="{Binding OpenMapViewerCommand}"
Content="查看地图" />
</StackPanel>
<Separator Grid.Row="3"

View File

@@ -6,16 +6,15 @@
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
xmlns:windows="clr-namespace:BetterGenshinImpact.ViewModel.Windows"
Title="地图"
Width="1400"
Height="1330"
MinWidth="700"
MinHeight="650"
Width="512"
Height="512"
d:DataContext="{d:DesignInstance Type=windows:MapViewerViewModel}"
ui:Design.Background="{DynamicResource ApplicationBackgroundBrush}"
ui:Design.Foreground="{DynamicResource TextFillColorPrimaryBrush}"
ExtendsContentIntoTitleBar="True"
FontFamily="{DynamicResource TextThemeFontFamily}"
ResizeMode="CanMinimize"
Topmost="True"
WindowStartupLocation="CenterScreen"
WindowStyle="SingleBorderWindow"
mc:Ignorable="d">
@@ -26,12 +25,7 @@
</Grid.RowDefinitions>
<Canvas Grid.Row="1">
<ui:Image Source="{Binding MapPath, Mode=OneWay}" Stretch="Uniform" />
<Rectangle Canvas.Left="{Binding BigMapRect.Left}"
Canvas.Top="{Binding BigMapRect.Top}"
Width="{Binding BigMapRect.Width}"
Height="{Binding BigMapRect.Height}"
Stroke="Red" />
<ui:Image Source="{Binding MapBitmap, Mode=OneWay}" Stretch="Uniform" />
</Canvas>

View File

@@ -70,10 +70,4 @@ public partial class CommonSettingsPageViewModel : ObservableObject, INavigation
Process.Start("explorer.exe", path);
}
[RelayCommand]
public void OnOpenMapViewer()
{
new MapViewer().Show();
}
}

View File

@@ -15,6 +15,7 @@ using BetterGenshinImpact.Core.Script;
using BetterGenshinImpact.GameTask;
using BetterGenshinImpact.GameTask.AutoPathing;
using BetterGenshinImpact.GameTask.Model.Enum;
using BetterGenshinImpact.View.Windows;
using Wpf.Ui.Controls;
using Wpf.Ui.Violeta.Controls;
@@ -88,7 +89,7 @@ public partial class MapPathingViewModel : ObservableObject, INavigationAware, I
}
[RelayCommand]
public async Task OnStart(PathingTask? item)
public void OnStart(PathingTask? item)
{
if (item == null)
{
@@ -97,4 +98,10 @@ public partial class MapPathingViewModel : ObservableObject, INavigationAware, I
new TaskRunner(DispatcherTimerOperationEnum.UseSelfCaptureImage)
.FireAndForget(async () => await new PathExecutor(CancellationContext.Instance.Cts).Pathing(item));
}
[RelayCommand]
public void OnOpenMapViewer()
{
new MapViewer().Show();
}
}

View File

@@ -1,27 +1,185 @@
using BetterGenshinImpact.Core.Config;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using System.Windows;
using OpenCvSharp;
using OpenCvSharp.WpfExtensions;
using System.Windows.Media.Imaging;
using BetterGenshinImpact.Core.Config;
using BetterGenshinImpact.GameTask.AutoPathing.Model;
using BetterGenshinImpact.GameTask.AutoPathing.Model.Enum;
using BetterGenshinImpact.GameTask.Common.Map;
using BetterGenshinImpact.Helpers;
using System.Linq;
using BetterGenshinImpact.Core.Recognition.OpenCv;
namespace BetterGenshinImpact.ViewModel.Windows;
public partial class MapViewerViewModel : ObservableObject
{
[ObservableProperty]
private Rect _bigMapRect = new(0, 0, 0, 0);
private WriteableBitmap _mapBitmap;
[ObservableProperty]
private string _mapPath = Global.Absolute(@"Assets\Map\mainMap100Block.png");
private readonly Mat _all256Map = new(Global.Absolute(@"Assets/Map/mainMap256Block.png"));
private Mat _currentPathingMap = new(); // 2048级别
private Rect _currentPathingRect = new(); // 2048级别
public MapViewerViewModel()
{
var center = MapCoordinate.GameToMain2048(0, 0);
_mapBitmap = ClipMat(new Point2f((float)center.x, (float)center.y)).ToWriteableBitmap();
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<object>>(this, (sender, msg) =>
{
if (msg.PropertyName == "UpdateBigMapRect")
if (msg.PropertyName == "SendCurrentPosition")
{
BigMapRect = (Rect)msg.NewValue;
UIDispatcherHelper.Invoke(() =>
{
Debug.WriteLine("更新地图位置");
try
{
MapBitmap.Lock();
WriteableBitmapConverter.ToWriteableBitmap(ClipMat((Point2f)msg.NewValue), MapBitmap);
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
MapBitmap.Unlock();
}
});
}
else if (msg.PropertyName == "UpdateCurrentPathing")
{
Debug.WriteLine("更新当前追踪的路径图像");
_currentPathingMap = GenTaskMat((PathingTask)msg.NewValue);
}
});
}
public Mat ClipMat(Point2f pos)
{
var len = 256;
pos = new Point2f(pos.X - _currentPathingRect.X, pos.Y - _currentPathingRect.Y);
Rect rect = new((int)pos.X - len, (int)pos.Y - len, len * 2, len * 2);
// 实现剪切 Mat 的逻辑
if (_currentPathingMap.Empty())
{
Debug.WriteLine("_currentPathingMap 未初始化");
return new Mat(_all256Map, new Rect(rect.X / 8, rect.Y / 8, rect.Width, rect.Height));
}
else
{
Mat clipMat = new(_currentPathingMap, rect);
clipMat = clipMat.Clone();
// 绘制中心点
Cv2.Circle(clipMat, new Point(len, len), 3, new Scalar(0, 255, 0), 2);
return clipMat;
}
}
/// <summary>
///
/// </summary>
/// <param name="pathingTask"></param>
/// <returns>2048级别的图像</returns>
public Mat GenTaskMat(PathingTask pathingTask)
{
// 获取路径点位并转换为当前主要展示的地图点位 2048 级别
var points = pathingTask.Positions;
var mapPoints = points.Select(ConvertToMapPoint).ToList();
var offsetRect = CalcRect(mapPoints);
var offsetRect256 = new Rect(offsetRect.X / 8, offsetRect.Y / 8, offsetRect.Width / 8, offsetRect.Height / 8);
// 把 _all256Map 的局部转化为 2048 级别
Mat taskMat = new Mat(_all256Map, offsetRect256);
taskMat = ResizeHelper.Resize(taskMat, 2048d / 256d);
// 设置线条粗细
int thickness = 2;
// 绘制点位和连线
for (int i = 0; i < mapPoints.Count - 1; i++)
{
var startPoint = mapPoints[i] - offsetRect.TopLeft;
var endPoint = mapPoints[i + 1] - offsetRect.TopLeft;
var lineColor = GetLineColor(points[i], points[i + 1]);
var circleColor = GetCircleColor(points[i]);
// 绘制点
DrawCircle(taskMat, startPoint, circleColor, thickness);
// 绘制线
DrawLine(taskMat, startPoint, endPoint, lineColor, 1);
}
// 绘制最后一个点
var lastPoint = mapPoints[^1] - offsetRect.TopLeft;
var lastCircleColor = GetCircleColor(points[^1]);
DrawCircle(taskMat, lastPoint, lastCircleColor, thickness);
return taskMat;
}
private Rect CalcRect(List<Point> mapPoints)
{
// 计算其最大外接矩形
_currentPathingRect = Cv2.BoundingRect(mapPoints);
// 把矩形范围扩大一半
_currentPathingRect.X -= 512;
_currentPathingRect.Y -= 512;
_currentPathingRect.Width += 1024;
_currentPathingRect.Height += 1024;
return _currentPathingRect;
}
private Point ConvertToMapPoint(Waypoint point)
{
var (x, y) = MapCoordinate.GameToMain2048(point.X, point.Y);
return new Point(x, y);
}
private Scalar GetLineColor(Waypoint startPoint, Waypoint endPoint)
{
if (endPoint.Type == WaypointType.Path.Code || startPoint.Type == WaypointType.Teleport.Code)
{
return new Scalar(255, 0, 0); // 蓝色
}
else if (endPoint.Type == WaypointType.Target.Code)
{
return new Scalar(0, 0, 255); // 红色
}
return new Scalar(0, 0, 255); // 默认红色
}
private Scalar GetCircleColor(Waypoint point)
{
if (point.Type == WaypointType.Path.Code || point.Type == WaypointType.Teleport.Code)
{
return new Scalar(255, 0, 0); // 蓝色
}
else if (point.Type == WaypointType.Target.Code)
{
return new Scalar(0, 0, 255); // 红色
}
return new Scalar(0, 0, 255); // 默认红色
}
private void DrawCircle(Mat mat, Point point, Scalar color, int thickness)
{
Cv2.Circle(mat, point, 3, color, thickness);
}
private void DrawLine(Mat mat, Point startPoint, Point endPoint, Scalar color, int thickness)
{
Cv2.Line(mat, startPoint, endPoint, color, thickness);
}
}