From f2234bec7f03309e876965b9c9ff1d8b801acc52 Mon Sep 17 00:00:00 2001 From: tanglong <842690096@qq.com> Date: Thu, 16 Oct 2025 20:55:31 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B8=A9=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Dto/MusicJumpRopeContext.cs | 17 +- .../Views/JumpRope/BeatDot.cs | 25 +++ .../Views/JumpRope/BeatMarginConverter.cs | 26 +++ .../Views/JumpRope/MusicJumpRope.xaml | 27 +++- .../Views/JumpRope/MusicJumpRope.xaml.cs | 150 ++++++++++++++++-- 5 files changed, 230 insertions(+), 15 deletions(-) create mode 100644 Wpf_AiSportsMicrospace/Views/JumpRope/BeatDot.cs create mode 100644 Wpf_AiSportsMicrospace/Views/JumpRope/BeatMarginConverter.cs diff --git a/Wpf_AiSportsMicrospace/Dto/MusicJumpRopeContext.cs b/Wpf_AiSportsMicrospace/Dto/MusicJumpRopeContext.cs index 8524ae2..3f01bd6 100644 --- a/Wpf_AiSportsMicrospace/Dto/MusicJumpRopeContext.cs +++ b/Wpf_AiSportsMicrospace/Dto/MusicJumpRopeContext.cs @@ -1,8 +1,9 @@ using System; using System.Collections.Generic; -using System.Drawing; using System.Linq; using System.Text; +using System.Windows.Controls; +using System.Windows.Shapes; using Wpf_AiSportsMicrospace.MyUserControl; using Yztob.AiSports.Postures.Sports; @@ -112,4 +113,18 @@ namespace Dto return rankList; } } + public class UserBeatBar + { + public Canvas Canvas { get; set; } // 用于显示节拍条的 Canvas + public List BeatPoints { get; set; } // 每个节拍点 + public List BeatTimes { get; set; } + public bool IsLeftToRight { get; set; } // 滑动方向 + public double BarWidth { get; set; } // 横杠宽度 + public double BarHeight { get; set; } // 横杠高度 + + public UserBeatBar() + { + BeatPoints = new List(); + } + } } diff --git a/Wpf_AiSportsMicrospace/Views/JumpRope/BeatDot.cs b/Wpf_AiSportsMicrospace/Views/JumpRope/BeatDot.cs new file mode 100644 index 0000000..704ed38 --- /dev/null +++ b/Wpf_AiSportsMicrospace/Views/JumpRope/BeatDot.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Text; +using System.Windows; +using System.Windows.Media; + +namespace Views.JumpRope +{ + public class BeatItem : INotifyPropertyChanged + { + private Brush _color = Brushes.Black; + public double X { get; set; } // 横坐标 + public Brush Color + { + get => _color; + set { _color = value; OnPropertyChanged(nameof(Color)); } + } + + public event PropertyChangedEventHandler PropertyChanged; + protected void OnPropertyChanged(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); + } + +} diff --git a/Wpf_AiSportsMicrospace/Views/JumpRope/BeatMarginConverter.cs b/Wpf_AiSportsMicrospace/Views/JumpRope/BeatMarginConverter.cs new file mode 100644 index 0000000..917f741 --- /dev/null +++ b/Wpf_AiSportsMicrospace/Views/JumpRope/BeatMarginConverter.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text; +using System.Windows; +using System.Windows.Data; + +namespace Views.JumpRope +{ + public class BeatMarginConverter : IMultiValueConverter + { + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if (values[0] is double leftMargin) + { + return new Thickness(leftMargin, 0, 0, 0); + } + return new Thickness(0); + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/Wpf_AiSportsMicrospace/Views/JumpRope/MusicJumpRope.xaml b/Wpf_AiSportsMicrospace/Views/JumpRope/MusicJumpRope.xaml index 14d5cea..52d3bf4 100644 --- a/Wpf_AiSportsMicrospace/Views/JumpRope/MusicJumpRope.xaml +++ b/Wpf_AiSportsMicrospace/Views/JumpRope/MusicJumpRope.xaml @@ -17,7 +17,9 @@ Width="615" Margin="0,0,0,0" /> - + + + @@ -33,5 +35,28 @@ TextAlignment="Center" /> + + + + + + + + + + + + + + + + + diff --git a/Wpf_AiSportsMicrospace/Views/JumpRope/MusicJumpRope.xaml.cs b/Wpf_AiSportsMicrospace/Views/JumpRope/MusicJumpRope.xaml.cs index ae422c0..937cf72 100644 --- a/Wpf_AiSportsMicrospace/Views/JumpRope/MusicJumpRope.xaml.cs +++ b/Wpf_AiSportsMicrospace/Views/JumpRope/MusicJumpRope.xaml.cs @@ -2,6 +2,8 @@ using Enum; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; using System.IO; using System.Text; using System.Windows; @@ -15,6 +17,7 @@ using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Windows.Threading; +using Views.JumpRope; using Wpf_AiSportsMicrospace.Common; using Wpf_AiSportsMicrospace.Enum; using Wpf_AiSportsMicrospace.MyUserControl; @@ -66,7 +69,11 @@ namespace Wpf_AiSportsMicrospace.Views.JumpRope private List _musicBeatTextBlock = new List(); // 容忍时间(节拍误差) - public double _beatTolerance = 0.20; // ±150ms + public double _beatTolerance = 0.15; // ±150ms + + // 滚动显示的节拍点集合 + public ObservableCollection BeatDisplayLeft { get; set; } = new(); + public ObservableCollection BeatDisplayRight { get; set; } = new(); public MusicJumpRope() { @@ -74,6 +81,9 @@ namespace Wpf_AiSportsMicrospace.Views.JumpRope Loaded += UserControl_Loaded; Unloaded += UserControl_Unloaded; _musicJumpRopeContext = new MusicJumpRopeContext(); + + // 初始化节拍点数据 + InitBeatDots(); } private async void UserControl_Loaded(object sender, RoutedEventArgs e) @@ -266,6 +276,8 @@ namespace Wpf_AiSportsMicrospace.Views.JumpRope // 播放背景音乐(循环) Utils.PlayBackgroundMusic("1.MP3", true); + //StartBeatScrollTimer(); + for (int i = seconds; i >= 0; i--) { countdownText.Text = i.ToString(); @@ -478,29 +490,62 @@ namespace Wpf_AiSportsMicrospace.Views.JumpRope if (userItem.ImageState != "2") userItem.ImageState = "2"; - var _currentTime = Utils.GetMusicCurrentTime(); + var currentTime = Utils.GetMusicCurrentTime(); + var beats = _musicJumpRopeContext.MusicBeats["1"]; - bool isOnBeat = _musicBeats.Any(bt => Math.Abs(bt - _currentTime) <= _beatTolerance); - if (isOnBeat) + for (int j = 0; j < beats.Count; j++) { - _musicJumpRopeContext.UserBeatSyncList[indexCopy]++; - - if (_musicJumpRopeContext.UserBeatSyncList[indexCopy] < count) + int indexCopy2 = j; // 复制一份当前循环索引 + if (Math.Abs(beats[indexCopy2] - currentTime) <= _beatTolerance) { - _musicBeatTextBlock[indexCopy].Text = $"卡点 x{_musicJumpRopeContext.UserBeatSyncList[indexCopy]}"; + _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]}"; + }); } - - //Application.Current.Dispatcher.BeginInvoke(() => - //{ - // _musicBeatTextBlock[i].Text = $"卡点 x{_musicJumpRopeContext.UserBeatSyncList[i]}"; - //}); } + // 滚动条跳跃到当前点附近 + UpdateBeatScrollSudden(currentTime); }; sport.Start(); _musicJumpRopeContext.Sports.Add(sport); } } + + 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; @@ -627,5 +672,84 @@ 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 void StartBeatScrollTimer() + { + if (_beatScrollTimer != null) _beatScrollTimer.Stop(); + + _beatScrollTimer = new DispatcherTimer(); + _beatScrollTimer.Interval = TimeSpan.FromMilliseconds(1000); + _beatScrollTimer.Tick += (s, e) => + { + double currentTime = Utils.GetMusicCurrentTime(); + + 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)); + }; + _beatScrollTimer.Start(); + } } }