This commit is contained in:
tanglong 2025-10-16 20:55:31 +08:00
parent ccb90cdb3f
commit f2234bec7f
5 changed files with 230 additions and 15 deletions

View File

@ -1,8 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Windows.Controls;
using System.Windows.Shapes;
using Wpf_AiSportsMicrospace.MyUserControl; using Wpf_AiSportsMicrospace.MyUserControl;
using Yztob.AiSports.Postures.Sports; using Yztob.AiSports.Postures.Sports;
@ -112,4 +113,18 @@ namespace Dto
return rankList; return rankList;
} }
} }
public class UserBeatBar
{
public Canvas Canvas { get; set; } // 用于显示节拍条的 Canvas
public List<Ellipse> BeatPoints { get; set; } // 每个节拍点
public List<double> BeatTimes { get; set; }
public bool IsLeftToRight { get; set; } // 滑动方向
public double BarWidth { get; set; } // 横杠宽度
public double BarHeight { get; set; } // 横杠高度
public UserBeatBar()
{
BeatPoints = new List<Ellipse>();
}
}
} }

View File

@ -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));
}
}

View File

@ -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();
}
}
}

View File

@ -17,7 +17,9 @@
Width="615" Width="615"
Margin="0,0,0,0" Margin="0,0,0,0"
/> />
<Grid Height="1080" Width="1920" x:Name="userBox"/> <Grid Height="1080" Width="1920" x:Name="userBox">
<Canvas x:Name="beatCanvas" />
</Grid>
<Grid Width="220" Height="130" VerticalAlignment="Top" HorizontalAlignment="Right" Margin="00,40,60,0" Visibility="Hidden" x:Name="countdownGrid"> <Grid Width="220" Height="130" VerticalAlignment="Top" HorizontalAlignment="Right" Margin="00,40,60,0" Visibility="Hidden" x:Name="countdownGrid">
<Border Background="#005fff" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" CornerRadius="30" /> <Border Background="#005fff" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" CornerRadius="30" />
@ -33,5 +35,28 @@
TextAlignment="Center" TextAlignment="Center"
/> />
</Grid> </Grid>
<Grid x:Name="BottomBeatPanel" VerticalAlignment="Bottom" Height="200" Panel.ZIndex="100">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="600"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- 左边滚动条 -->
<ScrollViewer x:Name="BeatScrollLeft" Width="660" Height="80"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Disabled"
Grid.Column="0">
<Canvas x:Name="beatCanvasLeft" Height="100"/>
</ScrollViewer>
<!-- 右边滚动条 -->
<ScrollViewer x:Name="BeatScrollRight" Width="660" Height="80"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Disabled"
Grid.Column="2">
<Canvas x:Name="beatCanvasRight" Height="100"/>
</ScrollViewer>
</Grid>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@ -2,6 +2,8 @@
using Enum; using Enum;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Windows; using System.Windows;
@ -15,6 +17,7 @@ using System.Windows.Media.Imaging;
using System.Windows.Navigation; using System.Windows.Navigation;
using System.Windows.Shapes; using System.Windows.Shapes;
using System.Windows.Threading; using System.Windows.Threading;
using Views.JumpRope;
using Wpf_AiSportsMicrospace.Common; using Wpf_AiSportsMicrospace.Common;
using Wpf_AiSportsMicrospace.Enum; using Wpf_AiSportsMicrospace.Enum;
using Wpf_AiSportsMicrospace.MyUserControl; using Wpf_AiSportsMicrospace.MyUserControl;
@ -66,7 +69,11 @@ namespace Wpf_AiSportsMicrospace.Views.JumpRope
private List<TextBlock> _musicBeatTextBlock = new List<TextBlock>(); private List<TextBlock> _musicBeatTextBlock = new List<TextBlock>();
// 容忍时间(节拍误差) // 容忍时间(节拍误差)
public double _beatTolerance = 0.20; // ±150ms public double _beatTolerance = 0.15; // ±150ms
// 滚动显示的节拍点集合
public ObservableCollection<BeatItem> BeatDisplayLeft { get; set; } = new();
public ObservableCollection<BeatItem> BeatDisplayRight { get; set; } = new();
public MusicJumpRope() public MusicJumpRope()
{ {
@ -74,6 +81,9 @@ namespace Wpf_AiSportsMicrospace.Views.JumpRope
Loaded += UserControl_Loaded; Loaded += UserControl_Loaded;
Unloaded += UserControl_Unloaded; Unloaded += UserControl_Unloaded;
_musicJumpRopeContext = new MusicJumpRopeContext(); _musicJumpRopeContext = new MusicJumpRopeContext();
// 初始化节拍点数据
InitBeatDots();
} }
private async void UserControl_Loaded(object sender, RoutedEventArgs e) private async void UserControl_Loaded(object sender, RoutedEventArgs e)
@ -266,6 +276,8 @@ namespace Wpf_AiSportsMicrospace.Views.JumpRope
// 播放背景音乐(循环) // 播放背景音乐(循环)
Utils.PlayBackgroundMusic("1.MP3", true); Utils.PlayBackgroundMusic("1.MP3", true);
//StartBeatScrollTimer();
for (int i = seconds; i >= 0; i--) for (int i = seconds; i >= 0; i--)
{ {
countdownText.Text = i.ToString(); countdownText.Text = i.ToString();
@ -478,29 +490,62 @@ namespace Wpf_AiSportsMicrospace.Views.JumpRope
if (userItem.ImageState != "2") if (userItem.ImageState != "2")
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); for (int j = 0; j < beats.Count; j++)
if (isOnBeat)
{ {
_musicJumpRopeContext.UserBeatSyncList[indexCopy]++; int indexCopy2 = j; // 复制一份当前循环索引
if (Math.Abs(beats[indexCopy2] - currentTime) <= _beatTolerance)
if (_musicJumpRopeContext.UserBeatSyncList[indexCopy] < count)
{ {
_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(); sport.Start();
_musicJumpRopeContext.Sports.Add(sport); _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<Human> humans) private void UpdateCircleCounts(List<Human> humans)
{ {
double radiusNormX = 0.07; double radiusNormX = 0.07;
@ -627,5 +672,84 @@ namespace Wpf_AiSportsMicrospace.Views.JumpRope
return userItem; return userItem;
} }
private List<TextBlock> leftDots = new();
private List<TextBlock> 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();
}
} }
} }