diff --git a/Wpf_AiSportsMicrospace/Common/SportOperate.cs b/Wpf_AiSportsMicrospace/Common/SportOperate.cs index 03f5253..83f0d72 100644 --- a/Wpf_AiSportsMicrospace/Common/SportOperate.cs +++ b/Wpf_AiSportsMicrospace/Common/SportOperate.cs @@ -215,8 +215,5 @@ namespace Wpf_AiSportsMicrospace.Common _lastActionTime = DateTime.Now; return true; } - - - } } diff --git a/Wpf_AiSportsMicrospace/Common/Utils.cs b/Wpf_AiSportsMicrospace/Common/Utils.cs index 1dcaf0d..a3f8b10 100644 --- a/Wpf_AiSportsMicrospace/Common/Utils.cs +++ b/Wpf_AiSportsMicrospace/Common/Utils.cs @@ -4,13 +4,14 @@ using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Windows; using System.Windows.Media; namespace Wpf_AiSportsMicrospace.Common { public static class Utils { - private static MediaPlayer _bgPlayer; + public static MediaPlayer _bgPlayer; public static void PlayBackgroundMusic(string musicFileName, bool loop) { @@ -21,6 +22,7 @@ namespace Wpf_AiSportsMicrospace.Common { _bgPlayer = new MediaPlayer(); _bgPlayer.Volume = 0.5; + _bgPlayer.MediaOpened += BgPlayer_MediaOpened; } else { @@ -32,8 +34,7 @@ namespace Wpf_AiSportsMicrospace.Common if (loop) { - // 循环播放 → 直接设置 MediaEnded 事件即可 - _bgPlayer.MediaEnded -= BgPlayer_MediaEnded; // 避免重复绑定 + _bgPlayer.MediaEnded -= BgPlayer_MediaEnded; _bgPlayer.MediaEnded += BgPlayer_MediaEnded; } else @@ -42,6 +43,17 @@ namespace Wpf_AiSportsMicrospace.Common } } + // 当音乐加载完毕时触发 + private static void BgPlayer_MediaOpened(object sender, EventArgs e) + { + var player = sender as MediaPlayer; + if (player != null && player.NaturalDuration.HasTimeSpan) + { + TimeSpan duration = player.NaturalDuration.TimeSpan; + Console.WriteLine($"音乐总时长: {duration.TotalSeconds:F2} 秒"); + } + } + private static void BgPlayer_MediaEnded(object sender, EventArgs e) { _bgPlayer.Position = TimeSpan.Zero; @@ -53,8 +65,31 @@ namespace Wpf_AiSportsMicrospace.Common if (_bgPlayer != null) { _bgPlayer.Stop(); - //_bgPlayer.Close(); // 如果想释放资源,可以取消注释 } } + + // 安全获取当前播放时间(秒) + public static double GetMusicCurrentTime() + { + double currentTime = 0; + Application.Current.Dispatcher.Invoke(() => + { + if (_bgPlayer != null) + currentTime = _bgPlayer.Position.TotalSeconds; + }); + return currentTime; + } + + // 安全获取总时长(秒) + public static int GetMusicTotalDuration() + { + int total = 0; + Application.Current.Dispatcher.Invoke(() => + { + if (_bgPlayer != null && _bgPlayer.NaturalDuration.HasTimeSpan) + total = (int)_bgPlayer.NaturalDuration.TimeSpan.TotalSeconds; + }); + return total; + } } } diff --git a/Wpf_AiSportsMicrospace/Dto/MusicJumpRopeContext.cs b/Wpf_AiSportsMicrospace/Dto/MusicJumpRopeContext.cs index ebbd07c..5da4829 100644 --- a/Wpf_AiSportsMicrospace/Dto/MusicJumpRopeContext.cs +++ b/Wpf_AiSportsMicrospace/Dto/MusicJumpRopeContext.cs @@ -15,29 +15,44 @@ namespace Dto public List UserList { get; private set; } public List UserNumberList { get; private set; } public List UserScoreList { get; private set; } - public List UserBeatSyncList { get; private set; } public List Sports { get; private set; } public List UseNameList { get; private set; } = new() { "一号位", "二号位" }; + public List UserBeatSyncList = new List(); + public Dictionary> MusicBeats { get; private set; } = new() { { "1", new List { - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, - 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, - 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, - 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, - 51, 52, 53, 54, 55, 56, 57, 58, 59, 60 + 0.545,1.09,1.635,2.18,2.725,3.27,3.815,4.36,4.905,5.45, + 5.995,6.54,7.085,7.63,8.175,8.72,9.265,9.81,10.355,10.9, + 11.445,11.99,12.535,13.08,13.625,14.17,14.715,15.26,15.805,16.35, + 16.895,17.44,17.985,18.53,19.075,19.62,20.165,20.71,21.255,21.8, + 22.345,22.89,23.435,23.98,24.525,25.07,25.615,26.16,26.705,27.25, + 27.795,28.34,28.885,29.43,29.975,30.52,31.065,31.61,32.155,32.7, + 33.245,33.79,34.335,34.88,35.425,35.97,36.515,37.06,37.605,38.15, + 38.695,39.24,39.785,40.33,40.875,41.42,41.965,42.51,43.055,43.6, + 44.145,44.69,45.235,45.78,46.325,46.87,47.415,47.96,48.505,49.05, + 49.595,50.14,50.685,51.23,51.775,52.32,52.865,53.41,53.955,54.5, + 55.045,55.59,56.135,56.68,57.225,57.77,58.315,58.86,59.405,59.95, + 60.495,61.04,61.585,62.13,62.675,63.22,63.765,64.31,64.855,65.4, + 65.945,66.49,67.035,67.58,68.125,68.67,69.215,69.76,70.305,70.85, + 71.395,71.94,72.485,73.03,73.575,74.12,74.665,75.21,75.755,76.3, + 76.845,77.39,77.935,78.48,79.025,79.57,80.115,80.66,81.205,81.75, + 82.295,82.84,83.385,83.93,84.475,85.02,85.565,86.11,86.655,87.2, + 87.745,88.29,88.835,89.38,89.925,90.47,91.015,91.56,92.105,92.65, + 93.195,93.74,94.285,94.83,95.375,95.92,96.465,97.01,97.555,98.1, + 98.645,99.19,99.735,100.28,100.825,101.37,101.915,102.46,103.005,103.55, + 104.095,104.64,105.185,105.73,106.275,106.82,107.365,107.91,108.455 } - }, + } }; + public MusicJumpRopeContext() { CirclePositions = new List<(double XNorm, double YNorm)> { (0.21, 0.88), - //(0.50, 0.88), (0.78, 0.88), }; @@ -48,23 +63,25 @@ namespace Dto } // 更新排行榜方法 - public void UpdateRankList() + public List UpdateRankList() { - RankList = UserNumberList - .Select((numStr, index) => new - { - Index = index, - Name = UseNameList[index], - Number = int.TryParse(numStr, out var n) ? n : 0 - }) - .OrderByDescending(x => x.Number) - .Select((x, rank) => new RankItem - { - Rank = rank + 1, - Name = x.Name, - Number = x.Number - }) - .ToList(); + var rankList = UserNumberList + .Select((numStr, index) => new + { + Index = index, + Name = UseNameList[index], + Number = int.TryParse(numStr, out var n) ? n : 0 + }) + .OrderByDescending(x => x.Number) + .Select((x, rank) => new RankItem + { + Rank = rank + 1, + Name = x.Name, + Number = x.Number + }) + .ToList(); + + return rankList; } } } diff --git a/Wpf_AiSportsMicrospace/Resources/Music/1.MP3 b/Wpf_AiSportsMicrospace/Resources/Music/1.MP3 new file mode 100644 index 0000000..fde1947 Binary files /dev/null and b/Wpf_AiSportsMicrospace/Resources/Music/1.MP3 differ diff --git a/Wpf_AiSportsMicrospace/Views/JumpRope/MusicJumpRope.xaml.cs b/Wpf_AiSportsMicrospace/Views/JumpRope/MusicJumpRope.xaml.cs index 110e85e..5e84d90 100644 --- a/Wpf_AiSportsMicrospace/Views/JumpRope/MusicJumpRope.xaml.cs +++ b/Wpf_AiSportsMicrospace/Views/JumpRope/MusicJumpRope.xaml.cs @@ -1,4 +1,5 @@ using Dto; +using Enum; using System; using System.Collections.Generic; using System.IO; @@ -13,6 +14,7 @@ using System.Windows.Media.Animation; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; +using System.Windows.Threading; using Wpf_AiSportsMicrospace.Common; using Wpf_AiSportsMicrospace.Enum; using Wpf_AiSportsMicrospace.MyUserControl; @@ -29,10 +31,42 @@ namespace Wpf_AiSportsMicrospace.Views.JumpRope { private Main _mainWin => Application.Current.MainWindow as Main; private MediaPlayer _mediaPlayer = new MediaPlayer(); - private bool IsGameStarted = false; private readonly object _updateLock = new object(); private readonly Dictionary _jumpStatus = new Dictionary(); private MusicJumpRopeContext _musicJumpRopeContext; + + private GameState _currentGameState = GameState.NotStarted; + + List RankingItemList = new(); + private List _musicBeats = new List() + { + 0.545,1.09,1.635,2.18,2.725,3.27,3.815,4.36,4.905,5.45, + 5.995,6.54,7.085,7.63,8.175,8.72,9.265,9.81,10.355,10.9, + 11.445,11.99,12.535,13.08,13.625,14.17,14.715,15.26,15.805,16.35, + 16.895,17.44,17.985,18.53,19.075,19.62,20.165,20.71,21.255,21.8, + 22.345,22.89,23.435,23.98,24.525,25.07,25.615,26.16,26.705,27.25, + 27.795,28.34,28.885,29.43,29.975,30.52,31.065,31.61,32.155,32.7, + 33.245,33.79,34.335,34.88,35.425,35.97,36.515,37.06,37.605,38.15, + 38.695,39.24,39.785,40.33,40.875,41.42,41.965,42.51,43.055,43.6, + 44.145,44.69,45.235,45.78,46.325,46.87,47.415,47.96,48.505,49.05, + 49.595,50.14,50.685,51.23,51.775,52.32,52.865,53.41,53.955,54.5, + 55.045,55.59,56.135,56.68,57.225,57.77,58.315,58.86,59.405,59.95, + 60.495,61.04,61.585,62.13,62.675,63.22,63.765,64.31,64.855,65.4, + 65.945,66.49,67.035,67.58,68.125,68.67,69.215,69.76,70.305,70.85, + 71.395,71.94,72.485,73.03,73.575,74.12,74.665,75.21,75.755,76.3, + 76.845,77.39,77.935,78.48,79.025,79.57,80.115,80.66,81.205,81.75, + 82.295,82.84,83.385,83.93,84.475,85.02,85.565,86.11,86.655,87.2, + 87.745,88.29,88.835,89.38,89.925,90.47,91.015,91.56,92.105,92.65, + 93.195,93.74,94.285,94.83,95.375,95.92,96.465,97.01,97.555,98.1, + 98.645,99.19,99.735,100.28,100.825,101.37,101.915,102.46,103.005,103.55, + 104.095,104.64,105.185,105.73,106.275,106.82,107.365,107.91,108.455 + + }; + + private List _musicBeatTextBlock = new List(); + // 容忍时间(节拍误差) + public double _beatTolerance = 0.15; // ±150ms + public MusicJumpRope() { InitializeComponent(); @@ -74,14 +108,6 @@ namespace Wpf_AiSportsMicrospace.Views.JumpRope _mediaPlayer.Play(); } - private void MediaPlayer_MediaEnded(object sender, EventArgs e) - { - // 音乐播放完成后的逻辑 - Console.WriteLine("音乐播放完成!"); - - // 可在这里绑定抽帧事件 - _mainWin.HumanFrameUpdated += OnHumanFrameUpdated; - } private void ShowCenterTip(string imagePath, TimeSpan duration) { var tipImage = new Image @@ -131,84 +157,66 @@ namespace Wpf_AiSportsMicrospace.Views.JumpRope }); }); } - private void DrawCirclesWithText() + private void MediaPlayer_MediaEnded(object sender, EventArgs e) { - userBox.Children.Clear(); - _musicJumpRopeContext.Sports.Clear(); - _musicJumpRopeContext.UserList.Clear(); - _musicJumpRopeContext.UserNumberList.Clear(); + // 音乐播放完成后的逻辑 + Console.WriteLine("音乐播放完成!"); - double imgWidth = userBox.ActualWidth; - double imgHeight = userBox.ActualHeight; - double radius = 100; - - for (int i = 0; i < _musicJumpRopeContext.CirclePositions.Count; i++) - { - var pos = _musicJumpRopeContext.CirclePositions[i]; - double x = pos.XNorm * imgWidth; - double y = pos.YNorm * imgHeight; - - // 绘制发光圆 - var userItem = AddUserItem(x, y, i); - // 绑定运动对象 - var sport = SportBase.Create("rope-skipping"); - int indexCopy = i; - var currentItem = userItem; - - // 订阅事件 - sport.OnTicked += (count, times) => - { - // 更新UI - userItem.NumberText = count.ToString(); - // 更新数字源 - _musicJumpRopeContext.UserNumberList[indexCopy] = count.ToString(); - // 改变状态为“跳绳中” - if (userItem.ImageState != "2") - userItem.ImageState = "2"; - }; - - sport.Start(); - _musicJumpRopeContext.Sports.Add(sport); - } + // 可在这里绑定抽帧事件 + _mainWin.HumanFrameUpdated += OnHumanFrameUpdated; } - - private SportUserItem AddUserItem(double centerX, double centerY, int index) + private void OnHumanFrameUpdated(object sender, List humans) { - var userItem = new SportUserItem(); - userItem.Width = 270; - userItem.Height = 560; - userItem.DisplayText = _musicJumpRopeContext.UseNameList[index]; - userItem.VerticalAlignment = VerticalAlignment.Top; - userItem.HorizontalAlignment = HorizontalAlignment.Left; - userItem.ImageState = "1"; - userItem.Margin = new Thickness(centerX - 120, centerY - 700, 0, 0); - - // ----------- 创建上方 TextBlock ------------ - var textBlock = new TextBlock + try { - Text = "0", // 卡点个数 - FontSize = 36, - FontWeight = FontWeights.Bold, - Foreground = Brushes.Red, - HorizontalAlignment = HorizontalAlignment.Left, - VerticalAlignment = VerticalAlignment.Top, - TextAlignment = TextAlignment.Center, - Width = userItem.Width // 文本宽度和 userItem 一样 - }; + if (humans == null || humans.Count == 0) return; + switch (_currentGameState) + { + case GameState.NotStarted: // 未开始 + int rightWaving = DetectRightHandRaise(humans); + if (rightWaving >= 3) + { + switch (rightWaving) + { + case (int)WavingAction.FirstHand: + StartCountdown(3); + break; + case (int)WavingAction.Raising: + UpdateCountdown(); + break; + case (int)WavingAction.RaiseHand: + FinishCountdown(); + _currentGameState = GameState.Running; // 倒计时完成 → 游戏开始 + break; + } + } + break; - // 设置 TextBlock 的 Margin,使其在 userItem 上方居中 - double textLeft = userItem.Margin.Left; - double textTop = userItem.Margin.Top - 50; // 50 可以根据需要调整高度 - textBlock.Margin = new Thickness(textLeft, textTop, 0, 0); + case GameState.Running: // 游戏进行中 + UpdateCircleCounts(humans); - // 添加控件 - userBox.Children.Add(userItem); - userBox.Children.Add(textBlock); + // 可选:判断游戏结束条件,将状态切换到 Finished + // if (CheckGameFinished()) _currentGameState = GameState.Finished; + break; - _musicJumpRopeContext.UserList.Add(userItem); - _musicJumpRopeContext.UserNumberList.Add("0"); + case GameState.Finished: // 游戏完成 + int leftWaving = DetectLeftHandRaise(humans); + if (leftWaving == 5) + { + _mainWin.HumanFrameUpdated -= OnHumanFrameUpdated; + _mainWin.WebcamClient.StopExtract(); - return userItem; + // 举左手逻辑,例如结束动画或退出 + var newPage = new Home(); + _mainWin?.SwitchPageWithMaskAnimation(newPage, true); + } + break; + } + } + catch (Exception ex) + { + Console.WriteLine("OnFrameExtracted error: " + ex.Message); + } } private int _currentCountdown = 3; @@ -216,12 +224,9 @@ namespace Wpf_AiSportsMicrospace.Views.JumpRope private void StartCountdown(int start = 3) { _currentCountdown = start; - countdownText.Text = _currentCountdown.ToString(); countdownGrid.Visibility = Visibility.Visible; - _lastUpdateTime = DateTime.Now; - Utils.PlayBackgroundMusic("countdown_3.mp3", false); } @@ -247,16 +252,17 @@ namespace Wpf_AiSportsMicrospace.Views.JumpRope { countdownText.Text = "GO!"; countdownGrid.Visibility = Visibility.Hidden; - IsGameStarted = true; // 播放背景音乐(循环) - Utils.PlayBackgroundMusic("homeprojectselected1.mp3", true); - + Utils.PlayBackgroundMusic("1.MP3", true); // 启动60秒倒计时(独立任务) - StartGameCountdown(60); + StartGameCountdown(108); } + private async void StartGameCountdown(int seconds) { + //_musicCurrentTime = Utils.GetMusicTotalDuration(); + countdownGrid.Visibility = Visibility.Visible; for (int i = seconds; i >= 0; i--) @@ -272,10 +278,16 @@ namespace Wpf_AiSportsMicrospace.Views.JumpRope x.ImageState = "1"; }); - _musicJumpRopeContext.UpdateRankList(); + _mainWin.HumanFrameUpdated -= OnHumanFrameUpdated; + _mainWin.WebcamClient.StopExtract(); - IsGameStarted = false; + RankingItemList = _musicJumpRopeContext.UpdateRankList(); + //ShowRankingBoard(RankingItemList); Utils.StopBackgroundMusic(); + + _currentGameState = GameState.Finished; + _mainWin.HumanFrameUpdated += OnHumanFrameUpdated; + _mainWin.WebcamClient.StartExtract(); } private DateTime? _raiseStartTime; @@ -300,12 +312,8 @@ namespace Wpf_AiSportsMicrospace.Views.JumpRope double xNorm = rightAnkle.X / 1920; double yNorm = rightAnkle.Y / 1080; - // 左侧区域 - bool inLeft = xNorm >= 0.14 && xNorm <= 0.28 && yNorm >= 0.81; - // 右侧区域 - bool inRight = xNorm >= 0.71 && xNorm <= 0.85 && yNorm >= 0.81; - // 如果不在任意一个区域,就跳过 - if (!inLeft && !inRight) + // 仅检测中心区域内的人 + if (!(xNorm >= 0.44 && xNorm <= 0.57 && yNorm >= 0.81)) continue; // --- 获取右臂关键点 --- @@ -372,6 +380,46 @@ namespace Wpf_AiSportsMicrospace.Views.JumpRope // 没有任何中心区域内的人举手 return (int)WavingAction.None; } + public int DetectLeftHandRaise(List humans) + { + if (humans == null || humans.Count == 0) + return (int)WavingAction.None; + + + foreach (var human in humans) + { + if (human?.Keypoints == null) + continue; + + // --- 筛选右脚踝坐标 --- + var rightAnkle = human.Keypoints.FirstOrDefault(k => k.Name == "right_ankle"); + if (rightAnkle == null) + continue; + double xNorm = rightAnkle.X / 1920; + double yNorm = rightAnkle.Y / 1080; + // 仅检测中心区域内的人 + if (!(xNorm >= 0.44 && xNorm <= 0.57 && yNorm >= 0.81)) + continue; + + // 获取左手关键点 + var leftWrist = human.Keypoints.FirstOrDefault(k => k.Name == "left_wrist"); + var leftElbow = human.Keypoints.FirstOrDefault(k => k.Name == "left_elbow"); + + if (leftWrist == null || leftElbow == null) + continue; + + const double raiseThreshold = 60; // 举手阈值 + + // 判断左手是否举起 + double verticalRise = leftElbow.Y - leftWrist.Y; + if (verticalRise >= raiseThreshold) + { + return (int)WavingAction.RaiseHand; // 一旦检测到左手举起,立即返回 + } + } + + return (int)WavingAction.None; // 没有检测到举手 + } private void ResetRaiseState() { @@ -380,58 +428,68 @@ namespace Wpf_AiSportsMicrospace.Views.JumpRope _firstHandTriggered = false; _lastCountdownSecond = 3; } - private void OnHumanFrameUpdated(object sender, List humans) + + private void DrawCirclesWithText() { - try + userBox.Children.Clear(); + _musicJumpRopeContext.Sports.Clear(); + _musicJumpRopeContext.UserList.Clear(); + _musicJumpRopeContext.UserNumberList.Clear(); + + double imgWidth = userBox.ActualWidth; + double imgHeight = userBox.ActualHeight; + double radius = 100; + + for (int i = 0; i < _musicJumpRopeContext.CirclePositions.Count; i++) { - if (!IsGameStarted) + //var pos = _musicJumpRopeContext.CirclePositions[i == 0 ? 1 : 0]; + + var pos = _musicJumpRopeContext.CirclePositions[i]; + + double x = pos.XNorm * imgWidth; + double y = pos.YNorm * imgHeight; + + // 绘制发光圆 + var userItem = AddUserItem(x, y, i); + // 绑定运动对象 + var sport = SportBase.Create("rope-skipping"); + int indexCopy = i; + var currentItem = userItem; + + // 订阅事件 + sport.OnTicked += (count, times) => { - if (humans == null || humans.Count == 0) return; + // 更新UI + userItem.NumberText = count.ToString(); + // 更新数字源 + _musicJumpRopeContext.UserNumberList[indexCopy] = count.ToString(); - int wavingaction = 0; + if (userItem.ImageState != "2") + userItem.ImageState = "2"; - wavingaction = DetectRightHandRaise(humans); + var _currentTime = Utils.GetMusicCurrentTime(); - if (wavingaction < 3) - return; - - switch (wavingaction) + bool isOnBeat = _musicBeats.Any(bt => Math.Abs(bt - _currentTime) <= _beatTolerance); + if (isOnBeat) { - case (int)WavingAction.FirstHand: - // 第一次举手,初始化倒计时 - StartCountdown(3); - break; + _musicJumpRopeContext.UserBeatSyncList[i]++; - case (int)WavingAction.Raising: - // 持续倒计时中 - UpdateCountdown(); - break; + if (_musicJumpRopeContext.UserBeatSyncList[i] < count) + { + _musicBeatTextBlock[i].Text = $"卡点 x{_musicJumpRopeContext.UserBeatSyncList[i]}"; + } - case (int)WavingAction.RaiseHand: - // 举手完成,倒计时结束 - FinishCountdown(); - break; - - default: - // 没检测到动作,重置倒计时显示 - Utils.StopBackgroundMusic(); - countdownText.Text = "3"; - countdownGrid.Visibility = Visibility.Hidden; - break; + //Application.Current.Dispatcher.BeginInvoke(() => + //{ + // _musicBeatTextBlock[i].Text = $"卡点 x{_musicJumpRopeContext.UserBeatSyncList[i]}"; + //}); } + }; - } - else - { - UpdateCircleCounts(humans); - } - } - catch (Exception ex) - { - Console.WriteLine("OnFrameExtracted error: " + ex.Message); + sport.Start(); + _musicJumpRopeContext.Sports.Add(sport); } } - private void UpdateCircleCounts(List humans) { double radiusNormX = 0.07; @@ -480,6 +538,8 @@ namespace Wpf_AiSportsMicrospace.Views.JumpRope // 推送计数 if (hasHuman) _musicJumpRopeContext.Sports[i].Pushing(humanInCircle); + + } } private string GetJumpState(int circleIndex, bool humanInCircle) @@ -517,5 +577,46 @@ namespace Wpf_AiSportsMicrospace.Views.JumpRope // 维持上次状态 return lastState; } + + private SportUserItem AddUserItem(double centerX, double centerY, int index) + { + var userItem = new SportUserItem(); + userItem.Width = 270; + userItem.Height = 560; + userItem.DisplayText = _musicJumpRopeContext.UseNameList[index]; + userItem.VerticalAlignment = VerticalAlignment.Top; + userItem.HorizontalAlignment = HorizontalAlignment.Left; + userItem.ImageState = "1"; + userItem.Margin = new Thickness(centerX - 120, centerY - 700, 0, 0); + + // ----------- 创建上方 TextBlock ------------ + var textBlock = new TextBlock + { + Text = "0", // 卡点个数 + FontSize = 50, + FontWeight = FontWeights.Bold, + Foreground = Brushes.Red, + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Top, + TextAlignment = TextAlignment.Center, + Width = userItem.Width // 文本宽度和 userItem 一样 + }; + + // 设置 TextBlock 的 Margin,使其在 userItem 上方居中 + double textLeft = userItem.Margin.Left; + double textTop = userItem.Margin.Top - 50; // 50 可以根据需要调整高度 + textBlock.Margin = new Thickness(textLeft, textTop, 0, 0); + + // 添加控件 + userBox.Children.Add(userItem); + userBox.Children.Add(textBlock); + + _musicJumpRopeContext.UserList.Add(userItem); + _musicJumpRopeContext.UserNumberList.Add("0"); + _musicJumpRopeContext.UserBeatSyncList.Add(0); + _musicBeatTextBlock.Add(textBlock); + + return userItem; + } } } diff --git a/Wpf_AiSportsMicrospace/Wpf_AiSportsMicrospace.csproj b/Wpf_AiSportsMicrospace/Wpf_AiSportsMicrospace.csproj index f21a327..31ee9ef 100644 --- a/Wpf_AiSportsMicrospace/Wpf_AiSportsMicrospace.csproj +++ b/Wpf_AiSportsMicrospace/Wpf_AiSportsMicrospace.csproj @@ -66,6 +66,7 @@ + @@ -285,6 +286,9 @@ Always + + Always + Always