diff --git a/Renako.Game.Tests/Renako.Game.Tests.csproj b/Renako.Game.Tests/Renako.Game.Tests.csproj index 19e0600..b28cb32 100644 --- a/Renako.Game.Tests/Renako.Game.Tests.csproj +++ b/Renako.Game.Tests/Renako.Game.Tests.csproj @@ -8,7 +8,7 @@ - + diff --git a/Renako.Game.Tests/Visual/Drawables/TestSceneGameplayProgressBar.cs b/Renako.Game.Tests/Visual/Drawables/TestSceneGameplayProgressBar.cs new file mode 100644 index 0000000..5aa13eb --- /dev/null +++ b/Renako.Game.Tests/Visual/Drawables/TestSceneGameplayProgressBar.cs @@ -0,0 +1,45 @@ +using NUnit.Framework; +using osu.Framework.Allocation; +using Renako.Game.Beatmaps; +using Renako.Game.Graphics.Drawables; + +namespace Renako.Game.Tests.Visual.Drawables; + +public partial class TestSceneGameplayProgressBar : GameDrawableTestScene +{ + [Cached] + private BeatmapsCollection beatmapsCollection = new BeatmapsCollection(); + + [Cached] + private WorkingBeatmap workingBeatmap = new WorkingBeatmap(); + + private GameplayProgressBar gameplayProgressBar; + + protected override void LoadComplete() + { + base.LoadComplete(); + beatmapsCollection.GenerateTestCollection(); + workingBeatmap.BeatmapSet = beatmapsCollection.BeatmapSets.Find(set => set.ID == 1); + workingBeatmap.Beatmap = beatmapsCollection.Beatmaps.Find(beatmap => beatmap.ID == 1); + } + + [Test] + public void TestGameplayProgressBar() + { + AddStep("add gameplay progress bar", () => Add(gameplayProgressBar = new GameplayProgressBar())); + AddStep("set total time", () => gameplayProgressBar.SetTotalTime(workingBeatmap.BeatmapSet.TotalLength)); + AddStep("start track", () => AudioManager.Track.Start()); + AddStep("set current time", () => gameplayProgressBar.SetCurrentTime(AudioManager.Track.CurrentTime)); + AddStep("seek to 100s", () => AudioManager.Track.Seek(100000)); + } + + protected override void Update() + { + base.Update(); + + if (AudioManager.Track.IsRunning) + { + gameplayProgressBar.SetCurrentTime(AudioManager.Track.CurrentTime); + } + } +} diff --git a/Renako.Game.Tests/Visual/GameDrawableTestScene.cs b/Renako.Game.Tests/Visual/GameDrawableTestScene.cs index 3763106..2d1ee2d 100644 --- a/Renako.Game.Tests/Visual/GameDrawableTestScene.cs +++ b/Renako.Game.Tests/Visual/GameDrawableTestScene.cs @@ -1,4 +1,5 @@ using osu.Framework.Allocation; +using Renako.Game.Audio; using Renako.Game.Graphics.ScreenStacks; namespace Renako.Game.Tests.Visual; @@ -20,6 +21,9 @@ public partial class GameDrawableTestScene : RenakoTestScene [Cached] public readonly SettingsScreenStack SettingsScreenStack = new SettingsScreenStack(); + [Cached] + public readonly RenakoAudioManager AudioManager = new RenakoAudioManager(); + [BackgroundDependencyLoader] private void load() { @@ -27,5 +31,6 @@ private void load() Add(MainScreenStack); Add(LogoScreenStack); Add(SettingsScreenStack); + Add(AudioManager); } } diff --git a/Renako.Game/Beatmaps/BeatmapsCollection.cs b/Renako.Game/Beatmaps/BeatmapsCollection.cs index 6451c46..8c4205b 100644 --- a/Renako.Game/Beatmaps/BeatmapsCollection.cs +++ b/Renako.Game/Beatmaps/BeatmapsCollection.cs @@ -29,6 +29,14 @@ public void GenerateTestCollection() Beatmaps = beatmapTestUtility.Beatmaps; } + /// + /// Sort all by ID. + /// + public void SortBeatmapSetsByID() + { + BeatmapSets = BeatmapSets.OrderBy(e => e.ID).ToList(); + } + private void addThemeSongBeatmapSet() { // TODO: Get proper Title, Artist and Source translated from source diff --git a/Renako.Game/Configurations/RenakoConfigManager.cs b/Renako.Game/Configurations/RenakoConfigManager.cs index 6796ccb..86ae9b8 100644 --- a/Renako.Game/Configurations/RenakoConfigManager.cs +++ b/Renako.Game/Configurations/RenakoConfigManager.cs @@ -17,6 +17,7 @@ protected override void InitialiseDefaults() SetDefault(RenakoSetting.DisableVideoPreview, false); SetDefault(RenakoSetting.ShowFPSCounter, false); + SetDefault(RenakoSetting.HardwareAcceleration, true); // Game state SetDefault(RenakoSetting.LatestBeatmapSetID, 0); diff --git a/Renako.Game/Configurations/RenakoSetting.cs b/Renako.Game/Configurations/RenakoSetting.cs index 03d280e..5c9af88 100644 --- a/Renako.Game/Configurations/RenakoSetting.cs +++ b/Renako.Game/Configurations/RenakoSetting.cs @@ -7,6 +7,7 @@ public enum RenakoSetting DisableVideoPreview, ShowFPSCounter, + HardwareAcceleration, LatestBeatmapSetID, LatestBeatmapID, diff --git a/Renako.Game/Graphics/Containers/PlayfieldContainer.cs b/Renako.Game/Graphics/Containers/PlayfieldContainer.cs index fb6175f..b832b80 100644 --- a/Renako.Game/Graphics/Containers/PlayfieldContainer.cs +++ b/Renako.Game/Graphics/Containers/PlayfieldContainer.cs @@ -29,6 +29,7 @@ public partial class PlayfieldContainer : Container private RenakoSpriteText breakText; private RenakoSpriteText hitText; private RenakoSpriteText missText; + private RenakoSpriteText comboText; private RenakoSpriteText clockText; private const int fade_in_time = move_time / 2; @@ -147,6 +148,12 @@ private void load(TextureStore textureStore, WorkingBeatmap workingBeatmap) Origin = Anchor.TopRight, Text = "Miss: 0" }, + comboText = new RenakoSpriteText() + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Text = "Combo: 0" + }, clockText = new RenakoSpriteText() { Anchor = Anchor.TopRight, @@ -171,6 +178,7 @@ protected override void Update() Logger.Log($"Missed note at {note.EndTime}", LoggingTarget.Runtime, LogLevel.Debug); note.IsHit = true; stats.Miss++; + stats.Combo = 0; addHitResultAnimation(HitResult.Miss); updateScoreText(); } @@ -354,21 +362,25 @@ private void processHit(NoteLane lane) if (diff < 50) { stats.Critical++; + stats.Combo++; addHitResultAnimation(HitResult.Critical); } else if (diff < 100) { stats.Break++; + stats.Combo++; addHitResultAnimation(HitResult.Break); } else if (diff < 200) { stats.Hit++; + stats.Combo++; addHitResultAnimation(HitResult.Hit); } else { stats.Miss++; + stats.Combo = 0; addHitResultAnimation(HitResult.Miss); playHitAnimation = false; } @@ -391,6 +403,7 @@ private void updateScoreText() breakText.Text = $"Break: {stats.Break}"; hitText.Text = $"Hit: {stats.Hit}"; missText.Text = $"Miss: {stats.Miss}"; + comboText.Text = $"Combo: {stats.Combo}"; } private enum HitResult @@ -421,6 +434,7 @@ private class Statistics public int Break { get; set; } // More than 50ms but less than 100ms public int Hit { get; set; } // More than 100ms but less than 200ms public int Miss { get; set; } // More than 200ms + public int Combo { get; set; } // Count of critical, break, and hit } private class PlayfieldNote diff --git a/Renako.Game/Graphics/Containers/SettingsContainer.cs b/Renako.Game/Graphics/Containers/SettingsContainer.cs index 1cbc723..8f3f9ff 100644 --- a/Renako.Game/Graphics/Containers/SettingsContainer.cs +++ b/Renako.Game/Graphics/Containers/SettingsContainer.cs @@ -216,6 +216,14 @@ private void load(RenakoConfigManager renakoConfigManager, FrameworkConfigManage Current = renakoConfigManager.GetBindable(RenakoSetting.ShowFPSCounter) }, new SpriteText() + { + Text = "Enable Hardware Acceleration" + }, + new BasicCheckbox() + { + Current = renakoConfigManager.GetBindable(RenakoSetting.HardwareAcceleration) + }, + new SpriteText() { Text = "FPS Limit" }, diff --git a/Renako.Game/Graphics/Drawables/GameplayProgressBar.cs b/Renako.Game/Graphics/Drawables/GameplayProgressBar.cs new file mode 100644 index 0000000..8f0a031 --- /dev/null +++ b/Renako.Game/Graphics/Drawables/GameplayProgressBar.cs @@ -0,0 +1,105 @@ +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; + +namespace Renako.Game.Graphics.Drawables; + +public partial class GameplayProgressBar : CompositeDrawable +{ + private RenakoSpriteText currentTime; + private RenakoSpriteText totalTime; + private double totalTimeValue; + private Container progressBar; + + [BackgroundDependencyLoader] + private void load() + { + Anchor = Anchor.BottomCentre; + Origin = Anchor.BottomCentre; + RelativeSizeAxes = Axes.X; + Width = 0.85f; + Height = 15; + Y = -20; + InternalChildren = new Drawable[] + { + new Container() + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.Both, + Width = 0.9f, + Children = new Drawable[] + { + new Container() + { + Masking = true, + CornerRadius = 10, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.Gray + } + } + }, + progressBar = new Container() + { + Masking = true, + CornerRadius = 10, + RelativeSizeAxes = Axes.Both, + Width = 0f, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.White + } + } + }, + } + }, + currentTime = new RenakoSpriteText() + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = RenakoFont.GetFont(RenakoFont.Typeface.JosefinSans, 25), + Text = "0:00" + }, + totalTime = new RenakoSpriteText() + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Font = RenakoFont.GetFont(RenakoFont.Typeface.JosefinSans, 25), + Text = "0:00" + } + }; + } + + /// + /// Set the total time of the progress bar in milliseconds. + /// + /// The total time in milliseconds. + public void SetTotalTime(double time) + { + totalTime.Text = TimeSpan.FromMilliseconds(time).ToString(@"m\:ss"); + totalTimeValue = time; + } + + /// + /// Set the current time of the progress bar in milliseconds. + /// + /// The current time in milliseconds. + public void SetCurrentTime(double time) + { + currentTime.Text = TimeSpan.FromMilliseconds(time).ToString(@"m\:ss"); + if (totalTimeValue == 0) + return; + + progressBar.ResizeWidthTo((float)(time / totalTimeValue), 500, Easing.OutQuint); + } +} diff --git a/Renako.Game/Graphics/Screens/PlayablePlayfieldScreen.cs b/Renako.Game/Graphics/Screens/PlayablePlayfieldScreen.cs index 3ea5593..85c91e9 100644 --- a/Renako.Game/Graphics/Screens/PlayablePlayfieldScreen.cs +++ b/Renako.Game/Graphics/Screens/PlayablePlayfieldScreen.cs @@ -12,6 +12,7 @@ using Renako.Game.Beatmaps; using Renako.Game.Configurations; using Renako.Game.Graphics.Containers; +using Renako.Game.Graphics.Drawables; using Renako.Game.Graphics.ScreenStacks; namespace Renako.Game.Graphics.Screens; @@ -22,8 +23,9 @@ namespace Renako.Game.Graphics.Screens; public partial class PlayablePlayfieldScreen : PlayfieldScreen { private PlayfieldContainer playfieldContainer; + private GameplayProgressBar progressBar; - private readonly StopwatchClock stopwatchClock = new StopwatchClock(); + private readonly StopwatchClock playfieldClock = new StopwatchClock(); [Resolved] private RenakoAudioManager audioManager { get; set; } @@ -31,10 +33,13 @@ public partial class PlayablePlayfieldScreen : PlayfieldScreen [Resolved] private RenakoBackgroundScreenStack backgroundScreenStack { get; set; } + [Resolved] + private WorkingBeatmap workingBeatmap { get; set; } + [BackgroundDependencyLoader] private void load(RenakoConfigManager configManager, RenakoBackgroundScreenStack backgroundScreenStack) { - AddInternal(playfieldContainer = new PlayfieldContainer(stopwatchClock) + AddInternal(playfieldContainer = new PlayfieldContainer(playfieldClock) { RelativeSizeAxes = Axes.Both }); @@ -106,6 +111,9 @@ private void load(RenakoConfigManager configManager, RenakoBackgroundScreenStack Margin = new MarginPadding(20) }); + AddInternal(progressBar = new GameplayProgressBar()); + progressBar.SetTotalTime(workingBeatmap.BeatmapSet.TotalLength); + backgroundScreenStack.AdjustMaskAlpha(configManager.Get(RenakoSetting.PlayfieldBackgroundDim) / 100f); } @@ -117,9 +125,13 @@ protected override void LoadComplete() audioManager.Track?.Restart(); audioManager.Track?.Seek(0); audioManager.Track?.Start(); - stopwatchClock.Start(); - backgroundScreenStack.SeekBackgroundVideo(0f); - backgroundScreenStack.ShowBackgroundVideo(); + playfieldClock.Start(); + + if (workingBeatmap.BeatmapSet.HasVideo) + { + backgroundScreenStack.SeekBackgroundVideo(0f); + backgroundScreenStack.ShowBackgroundVideo(); + } }, 2000); if (audioManager.Track != null) @@ -132,6 +144,12 @@ protected override void LoadComplete() base.LoadComplete(); } + protected override void Update() + { + base.Update(); + progressBar.SetCurrentTime(playfieldClock.CurrentTime); + } + protected override bool OnKeyDown(KeyDownEvent e) { switch (e.Key) diff --git a/Renako.Game/Renako.Game.csproj b/Renako.Game/Renako.Game.csproj index 5048a95..7059016 100644 --- a/Renako.Game/Renako.Game.csproj +++ b/Renako.Game/Renako.Game.csproj @@ -6,6 +6,6 @@ - + diff --git a/Renako.Game/RenakoGame.cs b/Renako.Game/RenakoGame.cs index 3b9df07..e5e89aa 100644 --- a/Renako.Game/RenakoGame.cs +++ b/Renako.Game/RenakoGame.cs @@ -23,6 +23,7 @@ public partial class RenakoGame : RenakoGameBase private RenakoBackgroundScreenStack backgroundScreenStack; private LogoScreenStack logoScreenStack; private InternalBeatmapImporter internalBeatmapImporter; + private BeatmapCollectionReader beatmapCollectionReader; [BackgroundDependencyLoader] private void load() @@ -30,6 +31,7 @@ private void load() dependencies.CacheAs(backgroundScreenStack = new RenakoBackgroundScreenStack()); dependencies.CacheAs(mainScreenStack = new RenakoScreenStack()); dependencies.CacheAs(logoScreenStack = new LogoScreenStack()); + dependencies.CacheAs(beatmapCollectionReader = new BeatmapCollectionReader(Host.Storage, BeatmapsCollection)); dependencies.CacheAs(logoScreenStack.LogoScreenObject); Add(backgroundScreenStack); Add(mainScreenStack); @@ -55,8 +57,8 @@ protected override void LoadComplete() LocalConfig.SetValue(RenakoSetting.FirstImport, true); } - beatmapCollectionReader = new BeatmapCollectionReader(Host.Storage, BeatmapsCollection); beatmapCollectionReader.Read(); + BeatmapsCollection.SortBeatmapSetsByID(); mainScreenStack.Push(new WarningScreen()); } diff --git a/Renako.Game/RenakoGameBase.cs b/Renako.Game/RenakoGameBase.cs index 994e419..3efb7e1 100644 --- a/Renako.Game/RenakoGameBase.cs +++ b/Renako.Game/RenakoGameBase.cs @@ -1,10 +1,12 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; +using osu.Framework.Configuration; using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Performance; +using osu.Framework.Graphics.Video; using osu.Framework.IO.Stores; using osu.Framework.Platform; using osuTK; @@ -25,6 +27,9 @@ public partial class RenakoGameBase : osu.Framework.Game protected override Container Content { get; } + [Resolved] + private FrameworkConfigManager frameworkConfig { get; set; } + protected Storage Storage { get; set; } protected RenakoConfigManager LocalConfig { get; private set; } @@ -39,6 +44,8 @@ public partial class RenakoGameBase : osu.Framework.Game private Bindable fpsDisplayVisible; + private Bindable hardwareAcceleration; + protected BeatmapCollectionReader beatmapCollectionReader; protected BeatmapsCollection BeatmapsCollection; @@ -109,6 +116,12 @@ protected override void LoadComplete() fpsDisplayVisible = LocalConfig.GetBindable(RenakoSetting.ShowFPSCounter); fpsDisplayVisible.ValueChanged += visible => { FrameStatistics.Value = visible.NewValue ? FrameStatisticsMode.Minimal : FrameStatisticsMode.None; }; fpsDisplayVisible.TriggerChange(); + + hardwareAcceleration = LocalConfig.GetBindable(RenakoSetting.HardwareAcceleration); + hardwareAcceleration.ValueChanged += enabled => + { + frameworkConfig.SetValue(FrameworkSetting.HardwareVideoDecoder, enabled.NewValue ? HardwareVideoDecoder.Any : HardwareVideoDecoder.None); + }; } public override void SetHost(GameHost host) diff --git a/Renako.iOS/Renako.iOS.csproj b/Renako.iOS/Renako.iOS.csproj index 011db88..2614241 100644 --- a/Renako.iOS/Renako.iOS.csproj +++ b/Renako.iOS/Renako.iOS.csproj @@ -18,6 +18,6 @@ - +