diff --git a/Wpf_AiSportsMicrospace/App.xaml b/Wpf_AiSportsMicrospace/App.xaml
index 7854626..61792fe 100644
--- a/Wpf_AiSportsMicrospace/App.xaml
+++ b/Wpf_AiSportsMicrospace/App.xaml
@@ -2,6 +2,8 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Wpf_AiSportsMicrospace.Views"
+ xmlns:conv="clr-namespace:Wpf_AiSportsMicrospace.Converter"
+
StartupUri="Views/Main.xaml">
@@ -10,6 +12,8 @@
+
+
diff --git a/Wpf_AiSportsMicrospace/Converter/DotColorConverter.cs b/Wpf_AiSportsMicrospace/Converter/DotColorConverter.cs
new file mode 100644
index 0000000..509040c
--- /dev/null
+++ b/Wpf_AiSportsMicrospace/Converter/DotColorConverter.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Text;
+using System.Windows;
+using System.Windows.Data;
+using System.Windows.Media;
+using Wpf_AiSportsMicrospace.MyUserControl;
+
+namespace Wpf_AiSportsMicrospace.Converter
+{
+ public class DotColorConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is bool b && b)
+ return Brushes.Red;
+ return Brushes.Green;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ => throw new NotImplementedException();
+ }
+}
diff --git a/Wpf_AiSportsMicrospace/MyUserControl/BeatScrollDots.xaml b/Wpf_AiSportsMicrospace/MyUserControl/BeatScrollDots.xaml
new file mode 100644
index 0000000..08a15d6
--- /dev/null
+++ b/Wpf_AiSportsMicrospace/MyUserControl/BeatScrollDots.xaml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Wpf_AiSportsMicrospace/MyUserControl/BeatScrollDots.xaml.cs b/Wpf_AiSportsMicrospace/MyUserControl/BeatScrollDots.xaml.cs
new file mode 100644
index 0000000..6111311
--- /dev/null
+++ b/Wpf_AiSportsMicrospace/MyUserControl/BeatScrollDots.xaml.cs
@@ -0,0 +1,136 @@
+using Emgu.CV.Flann;
+using HandyControl.Controls;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Text;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+
+namespace Wpf_AiSportsMicrospace.MyUserControl
+{
+ public partial class BeatScrollDots : UserControl
+ {
+ public ObservableCollection Dots { get; set; } = new ObservableCollection();
+
+ public BeatScrollDots()
+ {
+ InitializeComponent();
+ DataContext = this;
+ }
+
+ public int DotCount
+ {
+ get => (int)GetValue(DotCountProperty);
+ set => SetValue(DotCountProperty, value);
+ }
+
+ public static readonly DependencyProperty DotCountProperty =
+ DependencyProperty.Register(nameof(DotCount), typeof(int), typeof(BeatScrollDots),
+ new PropertyMetadata(0, OnDotCountChanged));
+
+ private static void OnDotCountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is BeatScrollDots ctrl)
+ ctrl.GenerateDots();
+ }
+
+ private void GenerateDots()
+ {
+ Dots.Clear();
+ for (int i = 0; i < DotCount; i++)
+ Dots.Add(new DotItem { IsSelected = false });
+ }
+
+ public void SetSelected(int index, bool selected)
+ {
+ if (index >= 0 && index < Dots.Count)
+ Dots[index].IsSelected = selected;
+ }
+
+ public Thickness DotSpacing
+ {
+ get => (Thickness)GetValue(DotSpacingProperty);
+ set => SetValue(DotSpacingProperty, value);
+ }
+
+ public static readonly DependencyProperty DotSpacingProperty =
+ DependencyProperty.Register(nameof(DotSpacing), typeof(Thickness), typeof(BeatScrollDots), new PropertyMetadata(new Thickness(5, 0, 5, 0)));
+
+ public void ScrollToDotCenter(int index, bool mirror = false)
+ {
+ if (scrollViewer == null || Dots.Count == 0) return;
+
+ double step = 20 + DotSpacing.Left + DotSpacing.Right;
+ double totalWidth = Dots.Count * step;
+ double dotCenter = index * step + step / 2;
+
+ double targetOffset = !mirror
+ ? dotCenter - scrollViewer.ActualWidth / 2
+ : totalWidth - dotCenter - scrollViewer.ActualWidth / 2;
+
+ targetOffset = System.Math.Max(0, System.Math.Min(targetOffset, scrollViewer.ScrollableWidth));
+ scrollViewer.ScrollToHorizontalOffset(targetOffset);
+ }
+
+ public void UpdateSpacingForCount(int count)
+ {
+ if (count <= 0) count = 1;
+ double spacing = (ActualWidth / count) - 20;
+ if (spacing < 2) spacing = 2;
+ DotSpacing = new Thickness(spacing / 2, 0, spacing / 2, 0);
+ }
+ public Dictionary> MusicBeatsDic
+ {
+ get => (Dictionary>)GetValue(MusicBeatsDicProperty);
+ set => SetValue(MusicBeatsDicProperty, value);
+ }
+
+ public static readonly DependencyProperty MusicBeatsDicProperty =
+ DependencyProperty.Register(nameof(MusicBeatsDic), typeof(Dictionary>), typeof(BeatScrollDots),
+ new PropertyMetadata(null, OnMusicBeatsDicChanged));
+
+ private static void OnMusicBeatsDicChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is BeatScrollDots ctrl)
+ {
+ // 可选:根据第一个 key 初始化 DotCount
+ if (ctrl.MusicBeatsDic != null && ctrl.MusicBeatsDic.Any())
+ {
+ int firstKey = ctrl.MusicBeatsDic.Keys.Min();
+ ctrl.DotCount = ctrl.MusicBeatsDic[firstKey].Count;
+ ctrl.UpdateSpacingForCount(ctrl.DotCount);
+ }
+ }
+ }
+ }
+
+ public class DotItem : INotifyPropertyChanged
+ {
+ private bool _isSelected;
+
+ public bool IsSelected
+ {
+ get => _isSelected;
+ set
+ {
+ if (_isSelected != value)
+ {
+ _isSelected = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsSelected)));
+ }
+ }
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+ }
+}
\ No newline at end of file
diff --git a/Wpf_AiSportsMicrospace/Views/JumpRope/MusicJumpRope.xaml b/Wpf_AiSportsMicrospace/Views/JumpRope/MusicJumpRope.xaml
index 52d3bf4..94a9065 100644
--- a/Wpf_AiSportsMicrospace/Views/JumpRope/MusicJumpRope.xaml
+++ b/Wpf_AiSportsMicrospace/Views/JumpRope/MusicJumpRope.xaml
@@ -35,28 +35,18 @@
TextAlignment="Center"
/>
-
+
-
+
-
-
-
-
+
+
-
-
-
-
+
+
diff --git a/Wpf_AiSportsMicrospace/Views/JumpRope/MusicJumpRope.xaml.cs b/Wpf_AiSportsMicrospace/Views/JumpRope/MusicJumpRope.xaml.cs
index b5b9310..fc21ab8 100644
--- a/Wpf_AiSportsMicrospace/Views/JumpRope/MusicJumpRope.xaml.cs
+++ b/Wpf_AiSportsMicrospace/Views/JumpRope/MusicJumpRope.xaml.cs
@@ -6,6 +6,7 @@ using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Text;
+using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
@@ -37,7 +38,7 @@ namespace Wpf_AiSportsMicrospace.Views.JumpRope
private readonly object _updateLock = new object();
private readonly Dictionary _jumpStatus = new Dictionary();
private MusicJumpRopeContext _musicJumpRopeContext;
-
+ public Dictionary> _musicBeatsDic;
private GameState _currentGameState = GameState.NotStarted;
List RankingItemList = new();
@@ -70,7 +71,7 @@ namespace Wpf_AiSportsMicrospace.Views.JumpRope
// 容忍时间(节拍误差)
public double _beatTolerance = 0.15; // ±150ms
-
+ private int _totalDots = 0;
// 滚动显示的节拍点集合
public ObservableCollection BeatDisplayLeft { get; set; } = new();
public ObservableCollection BeatDisplayRight { get; set; } = new();
@@ -81,9 +82,17 @@ namespace Wpf_AiSportsMicrospace.Views.JumpRope
Loaded += UserControl_Loaded;
Unloaded += UserControl_Unloaded;
_musicJumpRopeContext = new MusicJumpRopeContext();
+ _musicBeatsDic = _musicJumpRopeContext.MusicBeatsDic;
- // 初始化节拍点数据
- InitBeatDots();
+ _totalDots = _musicJumpRopeContext.MusicBeats["1"].Count();
+
+ // 左控件
+ LeftBeats.DotCount = _totalDots;
+ LeftBeats.MusicBeatsDic = _musicBeatsDic;
+
+ // 右控件
+ RightBeats.DotCount = _totalDots;
+ RightBeats.MusicBeatsDic = _musicBeatsDic;
}
private async void UserControl_Loaded(object sender, RoutedEventArgs e)
@@ -234,11 +243,10 @@ namespace Wpf_AiSportsMicrospace.Views.JumpRope
private DateTime _lastUpdateTime = DateTime.Now;
private void StartCountdown(int start = 3)
{
- _mainWin.ShowCountDownAnimation();
- //_currentCountdown = start;
- //countdownText.Text = _currentCountdown.ToString();
- //countdownGrid.Visibility = Visibility.Visible;
- //_lastUpdateTime = DateTime.Now;
+ _currentCountdown = start;
+ countdownText.Text = _currentCountdown.ToString();
+ countdownGrid.Visibility = Visibility.Visible;
+ _lastUpdateTime = DateTime.Now;
Utils.PlayBackgroundMusic("countdown_3.mp3", false);
}
@@ -492,30 +500,29 @@ namespace Wpf_AiSportsMicrospace.Views.JumpRope
userItem.ImageState = "2";
var currentTime = Utils.GetMusicCurrentTime();
- var beats = _musicJumpRopeContext.MusicBeats["1"];
+ int currentSecond = (int)Math.Floor(currentTime);
- for (int j = 0; j < beats.Count; j++)
+ // 判断是否命中节拍
+ var beats = _musicJumpRopeContext.MusicBeats["1"];
+ bool hit = beats.Any(b => Math.Abs(b - currentTime) <= _beatTolerance);
+ if (hit)
{
- int indexCopy2 = j; // 复制一份当前循环索引
- if (Math.Abs(beats[indexCopy2] - currentTime) <= _beatTolerance)
- {
- _musicJumpRopeContext.UserBeatSyncList[indexCopy]++;
- Application.Current.Dispatcher.BeginInvoke(() =>
- {
- if (indexCopy == 0)
- {
- ((Ellipse)beatCanvasLeft.Children[indexCopy2]).Fill = Brushes.Red;
- }
- else
- {
- ((Ellipse)beatCanvasRight.Children[indexCopy2]).Fill = Brushes.Red;
- }
- _musicBeatTextBlock[indexCopy].Text = $"卡点 x{_musicJumpRopeContext.UserBeatSyncList[indexCopy]}";
- });
- }
+ _musicJumpRopeContext.UserBeatSyncList[indexCopy]++;
+ _musicBeatTextBlock[indexCopy].Text = $"卡点 x{_musicJumpRopeContext.UserBeatSyncList[indexCopy]}";
+
+ if (indexCopy == 0)
+ LeftBeats.SetSelected(currentSecond, true);
+ else
+ RightBeats.SetSelected(currentSecond, true);
+
+ //Application.Current.Dispatcher.BeginInvoke(() =>
+ //{
+ // if (indexCopy == 0)
+ // LeftBeats.SetSelected(currentSecond, true);
+ // else
+ // RightBeats.SetSelected(currentSecond, true);
+ //});
}
- // 滚动条跳跃到当前点附近
- UpdateBeatScrollSudden(currentTime);
};
sport.Start();
@@ -523,30 +530,6 @@ namespace Wpf_AiSportsMicrospace.Views.JumpRope
}
}
- private void UpdateBeatScrollSudden(double currentTime)
- {
- int maxVisible = 5; // 最多显示 5 个点
- var beats = _musicJumpRopeContext.MusicBeats["1"];
-
- // 找到最近的点索引
- int currentIndex = beats.FindIndex(bt => bt >= currentTime);
- if (currentIndex == -1) currentIndex = beats.Count - 1;
-
- // 取前后最多 5 个点
- int startIndex = Math.Max(0, currentIndex - maxVisible / 2);
- int endIndex = Math.Min(beats.Count - 1, startIndex + maxVisible - 1);
-
- // 左侧 ScrollViewer
- double leftCenterX = BeatDisplayLeft[currentIndex].X - BeatScrollLeft.ViewportWidth / 2;
- leftCenterX = Math.Max(0, Math.Min(leftCenterX, beatCanvasLeft.Width - BeatScrollLeft.ViewportWidth));
- BeatScrollLeft.ScrollToHorizontalOffset(leftCenterX);
-
- // 右侧 ScrollViewer
- double rightCenterX = BeatDisplayRight[currentIndex].X - BeatScrollRight.ViewportWidth / 2;
- rightCenterX = Math.Max(0, Math.Min(rightCenterX, beatCanvasRight.Width - BeatScrollRight.ViewportWidth));
- BeatScrollRight.ScrollToHorizontalOffset(rightCenterX);
- }
-
private void UpdateCircleCounts(List humans)
{
double radiusNormX = 0.07;
@@ -673,82 +656,37 @@ namespace Wpf_AiSportsMicrospace.Views.JumpRope
return userItem;
}
- private List leftDots = new();
- private List rightDots = new();
- public double BeatPanelWidth { get; set; }
- private void InitBeatDots()
- {
- BeatDisplayLeft.Clear();
- BeatDisplayRight.Clear();
- beatCanvasLeft.Children.Clear();
- beatCanvasRight.Children.Clear();
- var beats = _musicJumpRopeContext.MusicBeats["1"];
- double lastX = 0;
- double scale = 300; // 间隔放大系数,可调
- for (int i = 0; i < beats.Count; i++)
- {
- double interval = i == 0 ? beats[0] : beats[i] - beats[i - 1];
- lastX += interval * scale;
- }
- double totalWidth = lastX + 50;
-
- // 左侧点
- lastX = 0;
- for (int i = 0; i < beats.Count; i++)
- {
- double interval = i == 0 ? beats[0] : beats[i] - beats[i - 1];
- double xPos = lastX + interval * scale;
- lastX = xPos;
-
- var leftItem = new BeatItem { X = xPos, Color = Brushes.Black };
- BeatDisplayLeft.Add(leftItem);
-
- var leftEllipse = new Ellipse { Width = 8, Height = 8, Fill = leftItem.Color };
- Canvas.SetLeft(leftEllipse, leftItem.X);
- Canvas.SetTop(leftEllipse, 40);
- beatCanvasLeft.Children.Add(leftEllipse);
- }
- beatCanvasLeft.Width = totalWidth;
-
- // 右侧点(倒序排列,但间隔顺序保持一致)
- lastX = 0;
- for (int i = 0; i < beats.Count; i++)
- {
- double interval = i == 0 ? beats[0] : beats[i] - beats[i - 1];
- double xPos = lastX + interval * scale;
- lastX = xPos;
-
- // 倒序显示
- double rightX = totalWidth - xPos;
- var rightItem = new BeatItem { X = rightX, Color = Brushes.Black };
- BeatDisplayRight.Add(rightItem);
-
- var rightEllipse = new Ellipse { Width = 8, Height = 8, Fill = rightItem.Color };
- Canvas.SetLeft(rightEllipse, rightItem.X);
- Canvas.SetTop(rightEllipse, 40);
- beatCanvasRight.Children.Add(rightEllipse);
- }
- beatCanvasRight.Width = totalWidth;
-
- }
private DispatcherTimer _beatScrollTimer;
private double totalTime = 108.455; // 音乐总时长
+ private int _lastSecond = -1; // 上一次已经处理的秒数
private void StartBeatScrollTimer()
{
- if (_beatScrollTimer != null) _beatScrollTimer.Stop();
-
_beatScrollTimer = new DispatcherTimer();
- _beatScrollTimer.Interval = TimeSpan.FromMilliseconds(1000);
+ _beatScrollTimer.Interval = TimeSpan.FromMilliseconds(500); // 每秒一次
+
+ int lastSecond = -1;
+
_beatScrollTimer.Tick += (s, e) =>
{
double currentTime = Utils.GetMusicCurrentTime();
+ int currentSecond = (int)Math.Floor(currentTime)+1;
- double progress = currentTime / totalTime;
- double maxOffsetLeft = beatCanvasLeft.Width - BeatScrollLeft.ViewportWidth;
- double maxOffsetRight = beatCanvasRight.Width - BeatScrollRight.ViewportWidth;
- BeatScrollLeft.ScrollToHorizontalOffset(progress * (beatCanvasLeft.Width - BeatScrollLeft.ViewportWidth));
- BeatScrollRight.ScrollToHorizontalOffset((1 - progress) * (beatCanvasRight.Width - BeatScrollRight.ViewportWidth));
+ if (currentSecond <= lastSecond) return;
+ lastSecond = currentSecond;
+ // 左控件
+ if (_musicBeatsDic.TryGetValue(currentSecond, out var leftBeats))
+ {
+ LeftBeats.SetSelected(currentSecond, true);
+ LeftBeats.ScrollToDotCenter(currentSecond, mirror: true);
+ }
+
+ // 右控件
+ if (_musicBeatsDic.TryGetValue(currentSecond, out var rightBeats))
+ {
+ RightBeats.SetSelected(currentSecond, true);
+ RightBeats.ScrollToDotCenter(currentSecond, mirror: false);
+ }
};
_beatScrollTimer.Start();
}