From ec46f3799c00bd9dbe5e9f207875ef32d316acc9 Mon Sep 17 00:00:00 2001 From: tanglong <842690096@qq.com> Date: Fri, 17 Oct 2025 14:18:01 +0800 Subject: [PATCH] fff --- Wpf_AiSportsMicrospace/App.xaml | 4 + .../Converter/DotColorConverter.cs | 24 +++ .../MyUserControl/BeatScrollDots.xaml | 29 +++ .../MyUserControl/BeatScrollDots.xaml.cs | 136 +++++++++++++ .../Views/JumpRope/MusicJumpRope.xaml | 22 +-- .../Views/JumpRope/MusicJumpRope.xaml.cs | 178 ++++++------------ 6 files changed, 257 insertions(+), 136 deletions(-) create mode 100644 Wpf_AiSportsMicrospace/Converter/DotColorConverter.cs create mode 100644 Wpf_AiSportsMicrospace/MyUserControl/BeatScrollDots.xaml create mode 100644 Wpf_AiSportsMicrospace/MyUserControl/BeatScrollDots.xaml.cs 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(); }