Improve score screen (#933)

- Update score and progress bar visibility when changing rounds
- Count karaoke songs as played
- Highlight the winner with a pulsing animation
- Add clickable arrows to change the round
- Reveal the rating after the progress bar animation
- Show player names in player colors and the song cover next to the title
- Stop score sounds when the screen is closed
- Remove unused ScreenSettingAnimationDirection from the theme
- Show one row per player in player order (two columns from 4 players)
- Split the points bar into notes, golden notes and line bonus with a legend
- Add ScoreTile and ScoreTileSmall skins for the player rows

Co-authored-by: Florian Ostertag <flokuep@users.noreply.github.com>
This commit is contained in:
McSchneidarandFlorian Ostertag authored and GitHub committed 2026-10-21 11:02:23 +02:00
1 parent 74653ab977
commit c8213bdcda
12 files changed
+2534 -983

No files matched your search

+2
View File
@@ -265,6 +265,8 @@
<string name="TR_SCREENSCORE_POINTS">Points</string>
<string name="TR_SCREENSCORE_GAMEDIFFICULTY">Difficulty</string>
<string name="TR_SCREENSCORE_OVERALLSCORE">Overall score</string>
<string name="TR_SCREENSCORE_GOLDENNOTES">Golden notes</string>
<string name="TR_SCREENSCORE_LINEBONUS">Line bonus</string>
<string name="TR_SCREENHIGHSCORE_HIGHSCORE">Highscore</string>
<string name="TR_SCREENPROFILES_PROFILES">Profiles</string>
<string name="TR_SCREENPROFILES_PLAYERNAME">Player name</string>
@@ -1,5 +1,15 @@
ScreenScore
++++++++++
Version 6:
Changed layout to one row per player, sorted by player number (two columns from 4 players)
Added <StaticRowP$N$> (new skins ScoreTile and ScoreTileSmall), <ProgressBarGoldenP$N$>, <ProgressBarNotesP$N$> (points bar split into notes, golden notes and line bonus)
Added <StaticLegendGolden>, <StaticLegendLineBonus>, <TextLegendGolden>, <TextLegendLineBonus>
++++++++++
Version 5:
Added <StaticCover>, <StaticRoundLeft>, <StaticRoundRight> (round arrows are only shown with more than one round and can be clicked)
Changed <Color> of <TextNameP$N$> to the player colors
Removed <ScreenSettingAnimationDirection>
++++++++++
Version 4:
Removed <ScreenSettingAnimationDirection>, <StaticPointsBarBGP$N$>, <StaticPointsBarP$N$>
Added <ProgressBarPointsP$N$>
@@ -336,6 +336,8 @@
<Menu>SongMenu.png</Menu>
<Button>Button.png</Button>
<ButtonTile>ButtonTile.png</ButtonTile>
<ScoreTile>ScoreTile.png</ScoreTile>
<ScoreTileSmall>ScoreTileSmall.png</ScoreTileSmall>
<CoverTile>CoverTile.png</CoverTile>
<PlayerTile>PlayerTile.png</PlayerTile>
<CoverTextBackground>CoverTextBackground.png</CoverTextBackground>
+2
View File
@@ -336,6 +336,8 @@
<Menu>SongMenu.png</Menu>
<Button>Button.png</Button>
<ButtonTile>ButtonTile.png</ButtonTile>
<ScoreTile>ScoreTile.png</ScoreTile>
<ScoreTileSmall>ScoreTileSmall.png</ScoreTileSmall>
<CoverTile>CoverTile.png</CoverTile>
<PlayerTile>PlayerTile.png</PlayerTile>
<CoverTextBackground>CoverTextBackground.png</CoverTextBackground>
+2
View File
@@ -336,6 +336,8 @@
<Menu>SongMenu.png</Menu>
<Button>Button.png</Button>
<ButtonTile>ButtonTile.png</ButtonTile>
<ScoreTile>ScoreTile.png</ScoreTile>
<ScoreTileSmall>ScoreTileSmall.png</ScoreTileSmall>
<CoverTile>CoverTile.png</CoverTile>
<PlayerTile>PlayerTile.png</PlayerTile>
<CoverTextBackground>CoverTextBackground.png</CoverTextBackground>
+2
View File
@@ -336,6 +336,8 @@
<Menu>SongMenu.png</Menu>
<Button>Button.png</Button>
<ButtonTile>ButtonTile.png</ButtonTile>
<ScoreTile>ScoreTile.png</ScoreTile>
<ScoreTileSmall>ScoreTileSmall.png</ScoreTileSmall>
<CoverTile>CoverTile.png</CoverTile>
<PlayerTile>PlayerTile.png</PlayerTile>
<CoverTextBackground>CoverTextBackground.png</CoverTextBackground>
+2
View File
@@ -336,6 +336,8 @@
<Menu>SongMenu.png</Menu>
<Button>Button.png</Button>
<ButtonTile>ButtonTile.png</ButtonTile>
<ScoreTile>ScoreTile.png</ScoreTile>
<ScoreTileSmall>ScoreTileSmall.png</ScoreTileSmall>
<CoverTile>CoverTile.png</CoverTile>
<PlayerTile>PlayerTile.png</PlayerTile>
<CoverTextBackground>CoverTextBackground.png</CoverTextBackground>
+2
View File
@@ -336,6 +336,8 @@
<Menu>SongMenu.png</Menu>
<Button>Button.png</Button>
<ButtonTile>ButtonTile.png</ButtonTile>
<ScoreTile>ScoreTile.png</ScoreTile>
<ScoreTileSmall>ScoreTileSmall.png</ScoreTileSmall>
<CoverTile>CoverTile.png</CoverTile>
<PlayerTile>PlayerTile.png</PlayerTile>
<CoverTextBackground>CoverTextBackground.png</CoverTextBackground>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

File diff suppressed because it is too large. Load diff
+238 -62
View File
@@ -34,11 +34,21 @@ namespace Vocaluxe.Screens
// Version number for theme files. Increment it, if you've changed something on the theme files!
protected override int _ScreenVersion
{
get { return 4; }
get { return 6; }
}
private const string _TextSong = "TextSong";
private const string _StaticCover = "StaticCover";
private const string _StaticRoundLeft = "StaticRoundLeft";
private const string _StaticRoundRight = "StaticRoundRight";
private const string _StaticLegendGolden = "StaticLegendGolden";
private const string _StaticLegendLineBonus = "StaticLegendLineBonus";
private const string _TextLegendGolden = "TextLegendGolden";
private const string _TextLegendLineBonus = "TextLegendLineBonus";
private const float _RevealDuration = 300f;
private const string _ScreenSettingShortScore = "ScreenSettingShortScore";
private const string _ScreenSettingShortRating = "ScreenSettingShortRating";
private const string _ScreenSettingShortDifficulty = "ScreenSettingShortDifficulty";
@@ -50,10 +60,26 @@ namespace Vocaluxe.Screens
private string[,] _TextRatings;
private string[,] _TextDifficulty;
private string[,] _ProgressBarPoints;
private string[,] _ProgressBarGolden;
private string[,] _ProgressBarNotes;
private string[,] _StaticAvatar;
private string[,] _StaticRow;
private int _Round;
private CPoints _Points;
private struct STextStyle
{
public float Height;
public float Y;
public SColorF Color;
}
private readonly Dictionary<string, STextStyle> _TextStyles = new Dictionary<string, STextStyle>();
private readonly Stopwatch _RevealTimer = new Stopwatch();
private readonly Stopwatch _PulseTimer = new Stopwatch();
private readonly bool[] _IsWinner = new bool[CSettings.MaxNumPlayer];
private SColorF _WinnerColor;
public override EMusicType CurrentMusicType
{
get { return EMusicType.BackgroundPreview; }
@@ -63,13 +89,13 @@ namespace Vocaluxe.Screens
{
base.Init();
var texts = new List<string> { _TextSong };
var texts = new List<string> { _TextSong, _TextLegendGolden, _TextLegendLineBonus };
_BuildTextStrings(ref texts);
_ThemeTexts = texts.ToArray();
var statics = new List<string>();
var statics = new List<string> { _StaticCover, _StaticRoundLeft, _StaticRoundRight, _StaticLegendGolden, _StaticLegendLineBonus };
_BuildStaticStrings(ref statics);
_ThemeStatics = statics.ToArray();
@@ -86,10 +112,32 @@ namespace Vocaluxe.Screens
_SlideShowBG.Z--;
}
public override void LoadTheme(string xmlPath)
{
base.LoadTheme(xmlPath);
foreach (var avatar in _StaticAvatar.Cast<string>().Where(name => name != null))
{
_Statics[avatar].Aspect = EAspect.Crop;
}
_TextStyles.Clear();
foreach (var name in _TextNames.Cast<string>().Concat(_TextScores.Cast<string>()).Concat(_TextRatings.Cast<string>()).Where(name => name != null))
{
_TextStyles[name] = new STextStyle { Height = _Texts[name].Font.Height, Y = _Texts[name].Y, Color = _Texts[name].Color };
}
if (!CBase.Themes.GetColor("Top1Color", PartyModeId, out _WinnerColor))
{
_WinnerColor = new SColorF(1f, 0.8f, 0f, 1f);
}
_WinnerColor.A = 1f;
}
public override bool HandleInput(SKeyEvent keyEvent)
{
if (keyEvent.KeyPressed) { }
else
if (!keyEvent.KeyPressed)
{
switch (keyEvent.Key)
{
@@ -123,7 +171,18 @@ namespace Vocaluxe.Screens
if (mouseEvent.LB)
{
_LeaveScreen();
if (_IsMouseOverStatic(mouseEvent, _StaticRoundLeft))
{
_ChangeRound(-1);
}
else if (_IsMouseOverStatic(mouseEvent, _StaticRoundRight))
{
_ChangeRound(1);
}
else
{
_LeaveScreen();
}
}
if (mouseEvent.RB)
@@ -146,42 +205,88 @@ namespace Vocaluxe.Screens
_SetVisibility();
_UpdateRatings();
_SlideShowBG.Visible = _UpdateBackground();
}
for (var p = 0; p < CGame.NumPlayers; p++)
{
_Statics[_StaticAvatar[p, CGame.NumPlayers - 1]].Aspect = EAspect.Crop;
}
public override void OnClose()
{
base.OnClose();
_StopScoreSounds();
}
public override bool UpdateGame()
{
_UpdateProgressBarSound();
var players = new SPlayer[CGame.NumPlayers];
if (_Round >= 0)
{
players = _Points.GetPlayer(_Round, CGame.NumPlayers);
}
else
{
for (var i = 0; i < CGame.NumRounds; i++)
{
var points = _Points.GetPlayer(i, CGame.NumPlayers);
for (var p = 0; p < players.Length; p++)
{
players[p].Points += points[p].Points;
}
}
for (var p = 0; p < players.Length; p++)
{
players[p].Points = (int)(players[p].Points / CGame.NumRounds);
}
}
_UpdateRevealAnimation();
return true;
}
private void _UpdateRevealAnimation()
{
if (!_RevealTimer.IsRunning)
{
if (_AreProgressBarsAnimating())
{
return;
}
_RevealTimer.Restart();
_PulseTimer.Restart();
}
var progress = Math.Min(_RevealTimer.ElapsedMilliseconds / _RevealDuration, 1f);
var revealScale = 1.3f - 0.3f * (1f - (1f - progress) * (1f - progress));
var pulseScale = 1f + 0.03f * (float)Math.Sin(_PulseTimer.ElapsedMilliseconds / 1000.0 * Math.PI * 1.4);
var numPlayer = CGame.NumPlayers - 1;
for (var p = 0; p < CGame.NumPlayers; p++)
{
_ApplyTextStyle(_TextRatings[p, numPlayer], revealScale, progress, false);
if (_IsWinner[p])
{
_ApplyTextStyle(_TextNames[p, numPlayer], pulseScale, 1f, false);
_ApplyTextStyle(_TextScores[p, numPlayer], pulseScale, 1f, true);
}
}
}
private void _ResetRevealAnimation()
{
_RevealTimer.Reset();
_PulseTimer.Reset();
var numPlayer = CGame.NumPlayers - 1;
for (var p = 0; p < CGame.NumPlayers; p++)
{
_ApplyTextStyle(_TextRatings[p, numPlayer], 1f, 0f, false);
_ApplyTextStyle(_TextNames[p, numPlayer], 1f, 1f, false);
_ApplyTextStyle(_TextScores[p, numPlayer], 1f, 1f, false);
}
}
private void _ApplyTextStyle(string name, float scale, float alpha, bool winnerColor)
{
var style = _TextStyles[name];
var text = _Texts[name];
text.Color = winnerColor ? _WinnerColor : style.Color;
text.Font.Height = style.Height * scale;
text.Y = style.Y - style.Height * (scale - 1f) / 2f;
text.Alpha = alpha;
}
private bool _AreProgressBarsAnimating()
{
return Enumerable.Range(0, CGame.NumPlayers)
.Select(p => _ProgressBars[_ProgressBarPoints[p, CGame.NumPlayers - 1]])
.Any(progressBar => progressBar.Visible && progressBar.IsAnimating);
}
private bool _IsMouseOverStatic(SMouseEvent mouseEvent, string staticName)
{
var element = _Statics[staticName];
return element.Visible && CHelper.IsInBounds(element.Rect, mouseEvent);
}
private bool _IsKaraokeMode(int round)
{
if (round >= 0)
@@ -275,6 +380,7 @@ namespace Vocaluxe.Screens
private void _BuildStaticStrings(ref List<string> statics)
{
_StaticAvatar = new string[CSettings.MaxNumPlayer, CSettings.MaxNumPlayer];
_StaticRow = new string[CSettings.MaxNumPlayer, CSettings.MaxNumPlayer];
for (var numplayer = 0; numplayer < CSettings.MaxNumPlayer; numplayer++)
{
@@ -287,8 +393,10 @@ namespace Vocaluxe.Screens
var target = "P" + (player + 1) + "N" + (numplayer + 1);
_StaticAvatar[player, numplayer] = "StaticAvatar" + target;
_StaticRow[player, numplayer] = "StaticRow" + target;
statics.Add(_StaticAvatar[player, numplayer]);
statics.Add(_StaticRow[player, numplayer]);
}
}
}
@@ -296,6 +404,8 @@ namespace Vocaluxe.Screens
private void _BuildProgressBarString(ref List<string> progressBars)
{
_ProgressBarPoints = new string[CSettings.MaxNumPlayer, CSettings.MaxNumPlayer];
_ProgressBarGolden = new string[CSettings.MaxNumPlayer, CSettings.MaxNumPlayer];
_ProgressBarNotes = new string[CSettings.MaxNumPlayer, CSettings.MaxNumPlayer];
for (var numplayer = 0; numplayer < CSettings.MaxNumPlayer; numplayer++)
{
@@ -308,8 +418,12 @@ namespace Vocaluxe.Screens
var target = "P" + (player + 1) + "N" + (numplayer + 1);
_ProgressBarPoints[player, numplayer] = "ProgressBarPoints" + target;
_ProgressBarGolden[player, numplayer] = "ProgressBarGolden" + target;
_ProgressBarNotes[player, numplayer] = "ProgressBarNotes" + target;
progressBars.Add(_ProgressBarPoints[player, numplayer]);
progressBars.Add(_ProgressBarGolden[player, numplayer]);
progressBars.Add(_ProgressBarNotes[player, numplayer]);
}
}
}
@@ -420,7 +534,6 @@ namespace Vocaluxe.Screens
private void _UpdateRatings()
{
CSong song = null;
var players = new SPlayer[CGame.NumPlayers];
if (_Round >= 0)
{
song = CGame.GetSong(_Round);
@@ -434,40 +547,26 @@ namespace Vocaluxe.Screens
{
_Texts[_TextSong].Text += " (" + (_Round + 1) + "/" + _Points.NumRounds + ")";
}
players = _Points.GetPlayer(_Round, CGame.NumPlayers);
var maxPoints = (int)Math.Round(players.Max(player => player.Points));
_PlayProgressBarSound(maxPoints);
}
else
{
_Texts[_TextSong].Text = "TR_SCREENSCORE_OVERALLSCORE";
for (var i = 0; i < CGame.NumRounds; i++)
{
var points = _Points.GetPlayer(i, CGame.NumPlayers);
for (var p = 0; p < players.Length; p++)
{
if (i < 1)
{
players[p].ProfileId = points[p].ProfileId;
}
players[p].Points += points[p].Points;
}
}
for (var p = 0; p < players.Length; p++)
{
players[p].Points = (int)Math.Round(players[p].Points / CGame.NumRounds);
}
var maxPoints = (int)Math.Round(players.Max(player => player.Points));
_PlayProgressBarSound(maxPoints);
}
var players = _GetPlayers(_Round);
var maxPoints = (int)Math.Round(players.Max(player => player.Points));
_PlayProgressBarSound(maxPoints);
_UpdateCover(song);
_UpdateRoundArrows();
var showWinner = CGame.NumPlayers > 1 && !_IsKaraokeMode(_Round);
for (var p = 0; p < players.Length; p++)
{
var playerPoints = (int)Math.Round(players[p].Points);
_IsWinner[p] = showWinner && playerPoints > 0 && players.All(other => (int)Math.Round(other.Points) <= playerPoints);
var name = CProfiles.GetPlayerName(players[p].ProfileId, p);
if (song != null && song.IsDuet)
{
@@ -509,18 +608,84 @@ namespace Vocaluxe.Screens
}
_ProgressBars[_ProgressBarPoints[p, CGame.NumPlayers - 1]].Progress = (float)players[p].Points / CSettings.MaxScore;
_ProgressBars[_ProgressBarGolden[p, CGame.NumPlayers - 1]].Progress = (float)Math.Max(players[p].Points - players[p].PointsLineBonus, 0) / CSettings.MaxScore;
_ProgressBars[_ProgressBarNotes[p, CGame.NumPlayers - 1]].Progress = (float)Math.Max(players[p].Points - players[p].PointsLineBonus - players[p].PointsGoldenNotes, 0) / CSettings.MaxScore;
if (CProfiles.IsProfileIdValid(players[p].ProfileId))
{
_Statics[_StaticAvatar[p, CGame.NumPlayers - 1]].Texture = CProfiles.GetAvatarTextureFromProfile(players[p].ProfileId);
}
}
_ResetRevealAnimation();
}
private SPlayer[] _GetPlayers(int round)
{
if (round >= 0)
{
return _Points.GetPlayer(round, CGame.NumPlayers);
}
var players = new SPlayer[CGame.NumPlayers];
for (var i = 0; i < CGame.NumRounds; i++)
{
var points = _Points.GetPlayer(i, CGame.NumPlayers);
for (var p = 0; p < players.Length; p++)
{
if (i < 1)
{
players[p].ProfileId = points[p].ProfileId;
}
players[p].Points += points[p].Points;
players[p].PointsGoldenNotes += points[p].PointsGoldenNotes;
players[p].PointsLineBonus += points[p].PointsLineBonus;
}
}
for (var p = 0; p < players.Length; p++)
{
players[p].Points = (int)Math.Round(players[p].Points / CGame.NumRounds);
players[p].PointsGoldenNotes /= CGame.NumRounds;
players[p].PointsLineBonus /= CGame.NumRounds;
}
return players;
}
private void _UpdateCover(CSong song)
{
var cover = _Statics[_StaticCover];
cover.Visible = song != null;
if (song == null)
{
return;
}
cover.Texture = song.CoverTextureSmall;
cover.Aspect = EAspect.Crop;
cover.X = _Texts[_TextSong].Rect.X - cover.W - 15f;
}
private void _UpdateRoundArrows()
{
var hasRounds = _Points.NumRounds > 1;
_Statics[_StaticRoundLeft].Visible = hasRounds;
_Statics[_StaticRoundRight].Visible = hasRounds;
_Statics[_StaticRoundLeft].Alpha = _Round > -1 ? 1f : 0.3f;
_Statics[_StaticRoundRight].Alpha = _Round < _Points.NumRounds - 1 ? 1f : 0.3f;
}
private void _SetVisibility()
{
var isKaraokeMode = _IsKaraokeMode(_Round);
_Statics[_StaticLegendGolden].Visible = !isKaraokeMode;
_Statics[_StaticLegendLineBonus].Visible = !isKaraokeMode;
_Texts[_TextLegendGolden].Visible = !isKaraokeMode;
_Texts[_TextLegendLineBonus].Visible = !isKaraokeMode;
for (var numplayer = 0; numplayer < CSettings.MaxNumPlayer; numplayer++)
{
for (var player = 0; player < CSettings.MaxNumPlayer; player++)
@@ -535,7 +700,12 @@ namespace Vocaluxe.Screens
_Texts[_TextDifficulty[player, numplayer]].Visible = isVisible;
_ProgressBars[_ProgressBarPoints[player, numplayer]].Visible = isVisible && !isKaraokeMode;
_ProgressBars[_ProgressBarPoints[player, numplayer]].Reset(true);
_ProgressBars[_ProgressBarGolden[player, numplayer]].Visible = isVisible && !isKaraokeMode;
_ProgressBars[_ProgressBarGolden[player, numplayer]].Reset(true);
_ProgressBars[_ProgressBarNotes[player, numplayer]].Visible = isVisible && !isKaraokeMode;
_ProgressBars[_ProgressBarNotes[player, numplayer]].Reset(true);
_Statics[_StaticAvatar[player, numplayer]].Visible = isVisible;
_Statics[_StaticRow[player, numplayer]].Visible = isVisible;
_Statics[_StaticAvatar[player, numplayer]].Texture = null;
}
@@ -545,9 +715,14 @@ namespace Vocaluxe.Screens
private void _ChangeRound(int num)
{
_Round += num;
_Round = _Round.Clamp(-1, _Points.NumRounds - 1);
var round = (_Round + num).Clamp(-1, _Points.NumRounds - 1);
if (round == _Round)
{
return;
}
_Round = round;
_SetVisibility();
_UpdateRatings();
}
@@ -556,10 +731,11 @@ namespace Vocaluxe.Screens
for (var round = 0; round < _Points.NumRounds; round++)
{
var players = _Points.GetPlayer(round, CGame.NumPlayers);
var isKaraokeMode = CGame.GetAudioMode(round) == EAudioMode.TR_AUDIOMODE_KARAOKE;
for (var p = 0; p < players.Length; p++)
{
if (players[p].Points > CSettings.MinScoreForDB && players[p].SongFinished)
if ((isKaraokeMode || players[p].Points > CSettings.MinScoreForDB) && players[p].SongFinished)
{
var song = CSongs.GetSong(players[p].SongId);
CDataBase.IncreaseSongCounter(song.DataBaseSongId);