From 1991f514c2540f356ea3e73c1e751049cb233fab Mon Sep 17 00:00:00 2001 From: Scott Doxey Date: Tue, 18 Feb 2025 02:00:44 -0500 Subject: [PATCH] Added new song class to C# libraries. --- RhythmGameUtilities/Structs/Song.cs | 74 ++++ .../Scripts/CommonUtilities.cs | 46 +++ .../Scripts/CommonUtilities.cs.meta | 2 + .../SampleSong (URP)/Scripts/RenderSong.cs | 325 +++++++----------- .../SampleSong/Scripts/CommonUtilities.cs | 46 +++ .../Scripts/CommonUtilities.cs.meta | 2 + .../Samples~/SampleSong/Scripts/RenderSong.cs | 325 +++++++----------- UnityPackage/Structs/Song.cs | 74 ++++ UnityPackage/Structs/Song.cs.meta | 3 + 9 files changed, 489 insertions(+), 408 deletions(-) create mode 100644 RhythmGameUtilities/Structs/Song.cs create mode 100644 UnityPackage/Samples~/SampleSong (URP)/Scripts/CommonUtilities.cs create mode 100644 UnityPackage/Samples~/SampleSong (URP)/Scripts/CommonUtilities.cs.meta create mode 100644 UnityPackage/Samples~/SampleSong/Scripts/CommonUtilities.cs create mode 100644 UnityPackage/Samples~/SampleSong/Scripts/CommonUtilities.cs.meta create mode 100644 UnityPackage/Structs/Song.cs create mode 100644 UnityPackage/Structs/Song.cs.meta diff --git a/RhythmGameUtilities/Structs/Song.cs b/RhythmGameUtilities/Structs/Song.cs new file mode 100644 index 0000000..e99a7d4 --- /dev/null +++ b/RhythmGameUtilities/Structs/Song.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace RhythmGameUtilities +{ + + public class Song + { + + private readonly Dictionary[]> _sections; + + public Dictionary metaData { get; } + + public int resolution { get; } + + public Tempo[] tempoChanges { get; private set; } + + public TimeSignature[] timeSignatureChanges { get; } + + public Dictionary difficulties { get; private set; } + + public BeatBar[] beatBars { get; private set; } + + public Song(string contents) + { + _sections = Parsers.ParseSectionsFromChart(contents); + + metaData = Parsers.ParseMetaDataFromChartSection(_sections + .First(section => section.Key == NamedSection.Song) + .Value); + + resolution = int.Parse(metaData["Resolution"]); + + tempoChanges = Parsers.ParseTempoChangesFromChartSection(_sections + .First(section => section.Key == NamedSection.SyncTrack) + .Value); + + timeSignatureChanges = Parsers.ParseTimeSignatureChangesFromChartSection(_sections[NamedSection.SyncTrack]); + + difficulties = Enum.GetValues(typeof(Difficulty)) + .Cast() + .Where(difficulty => _sections.ToDictionary(item => item.Key, item => item.Value) + .ContainsKey($"{difficulty}Single")) + .ToDictionary(difficulty => difficulty, + difficulty => Parsers.ParseNotesFromChartSection(_sections[$"{difficulty}Single"])); + + beatBars = Utilities.CalculateBeatBars(tempoChanges, includeHalfNotes : true); + } + + public void RecalculateBeatBarsWithSongLength(float songLength) + { + var lastTick = Utilities.ConvertSecondsToTicks(songLength, resolution, tempoChanges, timeSignatureChanges); + + tempoChanges = Parsers.ParseTempoChangesFromChartSection(_sections + .First(section => section.Key == NamedSection.SyncTrack) + .Value); + + tempoChanges = tempoChanges.Concat(new Tempo[] + { + new() + { + Position = Utilities.RoundUpToTheNearestMultiplier(lastTick, resolution), + BPM = tempoChanges.Last().BPM + } + }) + .ToArray(); + + beatBars = Utilities.CalculateBeatBars(tempoChanges, includeHalfNotes : true); + } + + } + +} diff --git a/UnityPackage/Samples~/SampleSong (URP)/Scripts/CommonUtilities.cs b/UnityPackage/Samples~/SampleSong (URP)/Scripts/CommonUtilities.cs new file mode 100644 index 0000000..b98c552 --- /dev/null +++ b/UnityPackage/Samples~/SampleSong (URP)/Scripts/CommonUtilities.cs @@ -0,0 +1,46 @@ +using System.IO; +using System.Threading.Tasks; +using UnityEngine; +using UnityEngine.Networking; + +namespace RhythmGameUtilities +{ + + public static class CommonUtilities + { + + public static async Task LoadTextFileFromPath(string path) + { + using var request = UnityWebRequest.Get(path); + + request.downloadHandler = new DownloadHandlerBuffer(); + + await request.SendWebRequest(); + + if (request.result == UnityWebRequest.Result.Success) + { + return request.downloadHandler.text; + } + + throw new FileNotFoundException(request.result.ToString()); + } + + public static async Task LoadAudioFileFromPath(string path, + AudioType audioType = AudioType.OGGVORBIS) + { + using var request = + UnityWebRequestMultimedia.GetAudioClip(path, audioType); + + await request.SendWebRequest(); + + if (request.result == UnityWebRequest.Result.Success) + { + return DownloadHandlerAudioClip.GetContent(request); + } + + throw new FileNotFoundException(request.result.ToString()); + } + + } + +} diff --git a/UnityPackage/Samples~/SampleSong (URP)/Scripts/CommonUtilities.cs.meta b/UnityPackage/Samples~/SampleSong (URP)/Scripts/CommonUtilities.cs.meta new file mode 100644 index 0000000..1db9705 --- /dev/null +++ b/UnityPackage/Samples~/SampleSong (URP)/Scripts/CommonUtilities.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c96740b9abafd4dd985cc6777301377f \ No newline at end of file diff --git a/UnityPackage/Samples~/SampleSong (URP)/Scripts/RenderSong.cs b/UnityPackage/Samples~/SampleSong (URP)/Scripts/RenderSong.cs index 2fcb35e..7c8f839 100644 --- a/UnityPackage/Samples~/SampleSong (URP)/Scripts/RenderSong.cs +++ b/UnityPackage/Samples~/SampleSong (URP)/Scripts/RenderSong.cs @@ -1,216 +1,161 @@ -using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Threading.Tasks; using System.Web; -using RhythmGameUtilities; using UnityEngine; -using UnityEngine.Networking; -public class RenderSong : MonoBehaviour +namespace RhythmGameUtilities { - [SerializeField] - private string _songPath = "Samples/Rhythm Game Utilities/1.0.0/Sample Song (URP)/StreamingAssets/Songs/Demo 1"; - - [SerializeField] - private Mesh _mesh; - - [SerializeField] - private Material _trackMaterial; - - [SerializeField] - private Material _beatBarMaterial; - - [SerializeField] - private Material _beatBarHalfMaterial; - - [SerializeField] - private Material _beatBarQuarterMaterial; - - [SerializeField] - private Material[] _materials; + public class RenderSong : MonoBehaviour + { - [SerializeField] - private AudioSource _audioSource; + [SerializeField] + private string _songPath = "Samples/Rhythm Game Utilities/1.0.0/Sample Song (URP)/StreamingAssets/Songs/Demo 1"; - [SerializeField] - private float _scale = 2; + [SerializeField] + private Mesh _mesh; - [SerializeField] - private float _distance = 50; + [SerializeField] + private Material _trackMaterial; - private readonly Vector3 _noteScale = new(0.5f, 0.25f, 0.35f); + [SerializeField] + private Material _beatBarMaterial; - private readonly Vector3 _noteScaleFlat = new(0.5f, 0.05f, 0.35f); + [SerializeField] + private Material _beatBarHalfMaterial; - private readonly Vector3 _beatBarScaleFull = new(5, 0.03f, 0.1f); + [SerializeField] + private Material _beatBarQuarterMaterial; - private readonly Vector3 _beatBarScaleHalf = new(5, 0.03f, 0.05f); + [SerializeField] + private Material[] _materials; - private readonly Vector3 _beatBarScaleQuarter = new(5, 0.03f, 0.01f); + [SerializeField] + private AudioSource _audioSource; - public int resolution { get; set; } = 192; + [SerializeField] + private float _scale = 2; - public Tempo[] tempoChanges { get; set; } + [SerializeField] + private float _distance = 50; - public TimeSignature[] timeSignatureChanges { get; set; } + private readonly Vector3 _noteScale = new(0.5f, 0.1f, 0.35f); - public Dictionary difficulties { get; set; } + private readonly Vector3 _noteScaleFlat = new(0.5f, 0.05f, 0.35f); - public Dictionary> notesGroupedByHandPosition { get; set; } + private readonly Vector3 _beatBarScaleFull = new(5, 0.03f, 0.1f); - public BeatBar[] beatBars { get; set; } + private readonly Vector3 _beatBarScaleHalf = new(5, 0.03f, 0.05f); - private async void Start() - { - var path = Path.Join(Application.dataPath, _songPath, "notes.chart"); + private readonly Vector3 _beatBarScaleQuarter = new(5, 0.03f, 0.01f); - var contents = await LoadTextFileFromPath($"file://{HttpUtility.UrlPathEncode(path)}"); + private Song _song; - var sections = Parsers.ParseSectionsFromChart(contents); + private async void Start() + { + var path = Path.Join(Application.dataPath, _songPath); - var metadata = Parsers.ParseMetaDataFromChartSection(sections - .First(section => section.Key == NamedSection.Song) - .Value); + var contents = + await CommonUtilities.LoadTextFileFromPath( + $"file://{HttpUtility.UrlPathEncode(Path.Join(path, "notes.chart"))}"); - _audioSource.clip = - await LoadAudioFileFromPath( - $"file://{HttpUtility.UrlPathEncode(Path.Join(Path.GetDirectoryName(path), metadata["MusicStream"]))}"); + _song = new Song(contents); - resolution = int.Parse(metadata["Resolution"]); + _audioSource.clip = + await CommonUtilities.LoadAudioFileFromPath( + $"file://{HttpUtility.UrlPathEncode(Path.Join(path, _song.metaData["MusicStream"]))}"); - tempoChanges = Parsers.ParseTempoChangesFromChartSection(sections.First(section => section.Key == NamedSection.SyncTrack) - .Value); + _song.RecalculateBeatBarsWithSongLength(_audioSource.clip.length); - timeSignatureChanges = Parsers.ParseTimeSignatureChangesFromChartSection(sections[NamedSection.SyncTrack]); + _audioSource.Play(); + } - var lastTick = - Utilities.ConvertSecondsToTicks(_audioSource.clip.length, resolution, tempoChanges, timeSignatureChanges); + private void Update() + { + RenderTrack(); + RenderHitNotes(); - tempoChanges = tempoChanges.Concat(new Tempo[] + if (_song?.difficulties?[Difficulty.Easy] == null) { - new() - { - Position = Utilities.RoundUpToTheNearestMultiplier(lastTick, resolution), - BPM = tempoChanges.Last().BPM - } - }) - .ToArray(); - - difficulties = Enum.GetValues(typeof(Difficulty)) - .Cast() - .Where(difficulty => sections.ToDictionary(item => item.Key, x => x.Value) - .ContainsKey($"{difficulty}Single")) - .ToDictionary(difficulty => difficulty, - difficulty => Parsers.ParseNotesFromChartSection(sections[$"{difficulty}Single"])); - - notesGroupedByHandPosition = difficulties[Difficulty.Expert] - .Where(note => note.HandPosition < 5) - .GroupBy(note => note.HandPosition) - .ToDictionary(group => group.Key, group => group.ToList()); - - beatBars = Utilities.CalculateBeatBars(tempoChanges, includeHalfNotes : true); - - _audioSource.Play(); - } - - public static async Task LoadTextFileFromPath(string path) - { - using var request = UnityWebRequest.Get(path); - - request.downloadHandler = new DownloadHandlerBuffer(); + return; + } - request.SendWebRequest(); + var tickOffset = + Utilities.ConvertSecondsToTicks(_audioSource.time, _song.resolution, _song.tempoChanges, + _song.timeSignatureChanges); - while (!request.isDone) - { - await Task.Yield(); + RenderNotes(_song.difficulties[Difficulty.Easy], _song.resolution, tickOffset); + RenderBeatBars(_song.beatBars, _song.resolution, tickOffset); } - if (request.result == UnityWebRequest.Result.Success) + private void RenderTrack() { - return request.downloadHandler.text; - } - - throw new FileNotFoundException(request.result.ToString()); - } - - public static async Task LoadAudioFileFromPath(string path, AudioType audioType = AudioType.OGGVORBIS) - { - using var request = - UnityWebRequestMultimedia.GetAudioClip(path, audioType); + Graphics.DrawMesh(_mesh, + Matrix4x4.TRS(new Vector3(2.5f, -0.05f, _distance / 2), Quaternion.identity, + new Vector3(5f, 0.1f, _distance)), + _trackMaterial, 0); - request.SendWebRequest(); + Graphics.DrawMesh(_mesh, + Matrix4x4.TRS(new Vector3(-0.05f, -0.025f, _distance / 2), Quaternion.identity, + new Vector3(0.1f, 0.15f, _distance)), + _beatBarMaterial, 0); - while (!request.isDone) - { - await Task.Yield(); + Graphics.DrawMesh(_mesh, + Matrix4x4.TRS(new Vector3(5.05f, -0.025f, _distance / 2), Quaternion.identity, + new Vector3(0.1f, 0.15f, _distance)), + _beatBarMaterial, 0); } - if (request.result == UnityWebRequest.Result.Success) + private void RenderHitNotes() { - return DownloadHandlerAudioClip.GetContent(request); + for (var x = 0; x < 5; x += 1) + { + Graphics.DrawMesh(_mesh, + Matrix4x4.TRS(new Vector3(x + 0.5f, 0.025f, 0), Quaternion.identity, _noteScaleFlat), + _materials[x], 0); + } } - throw new FileNotFoundException(request.result.ToString()); - } - - private void Update() - { - RenderTrack(); - - if (notesGroupedByHandPosition == null) + private void RenderNotes(Note[] notes, int resolution, int tickOffset) { - return; - } - - var tickOffset = - Utilities.ConvertSecondsToTicks(_audioSource.time, resolution, tempoChanges, timeSignatureChanges); + var laneArray = new Dictionary>(); - RenderHitNotes(notesGroupedByHandPosition); + for (var x = 0; x < 5; x += 1) + { + laneArray.Add(x, new List()); + } - RenderNotes(notesGroupedByHandPosition, resolution, tickOffset); - RenderBeatBars(beatBars, resolution, tickOffset); - } + for (var i = 0; i < notes.Length; i += 1) + { + if (notes[i].HandPosition > 5) continue; - private void RenderTrack() - { - Graphics.DrawMesh(_mesh, - Matrix4x4.TRS(new Vector3(2.5f, -0.05f, _distance / 2), Quaternion.identity, - new Vector3(5f, 0.1f, _distance)), - _trackMaterial, 0); - } + var position = Utilities.ConvertTickToPosition(notes[i].Position - tickOffset, + resolution) * _scale; - private void RenderHitNotes(Dictionary> notesGroupedByHandPosition) - { - for (var x = 0; x < notesGroupedByHandPosition.Count; x += 1) - { - Graphics.DrawMesh(_mesh, - Matrix4x4.TRS(new Vector3(x + 0.5f, 0, 0), Quaternion.identity, _noteScaleFlat), - _materials[x], 0); - } - } + if (position > 0 && position < _distance) + { + laneArray[notes[i].HandPosition].Add(Matrix4x4.TRS( + new Vector3(notes[i].HandPosition + 0.5f, 0.05f, position), + Quaternion.identity, _noteScale)); + } + } - private void RenderNotes(Dictionary> notesGroupedByHandPosition, int resolution, int tickOffset) - { - for (var x = 0; x < notesGroupedByHandPosition.Count; x += 1) - { - if (!notesGroupedByHandPosition.ContainsKey(x)) + for (var x = 0; x < 5; x += 1) { - continue; + Graphics.DrawMeshInstanced(_mesh, 0, _materials[x], laneArray[x]); } + } - var noteMatrix = new List - { - Matrix4x4.TRS(new Vector3(x + 0.5f, 0, 0), Quaternion.identity, _noteScaleFlat) - }; + private void RenderBeatBars(BeatBar[] beatBars, int resolution, int tickOffset) + { + var beatBarMatrix = new List(); + var beatBarHalfMatrix = new List(); + var beatBarQuarterMatrix = new List(); - for (var y = 0; y < notesGroupedByHandPosition[x].Count; y += 1) + for (var x = 0; x < beatBars.Length; x += 1) { - var position = Utilities.ConvertTickToPosition(notesGroupedByHandPosition[x][y].Position - tickOffset, - resolution) * _scale; + var position = Utilities.ConvertTickToPosition(beatBars[x].Position - tickOffset, resolution) * + _scale; if (position > _distance) { @@ -222,56 +167,28 @@ private void RenderNotes(Dictionary> notesGroupedByHandPosition, continue; } - noteMatrix.Add(Matrix4x4.TRS( - new Vector3(notesGroupedByHandPosition[x][y].HandPosition + 0.5f, 0, position), - Quaternion.identity, _noteScale)); - } - - Graphics.DrawMeshInstanced(_mesh, 0, _materials[x], noteMatrix); - } - } - - private void RenderBeatBars(BeatBar[] beatBars, int resolution, int tickOffset) - { - var beatBarMatrix = new List(); - var beatBarHalfMatrix = new List(); - var beatBarQuarterMatrix = new List(); - - for (var x = 0; x < beatBars.Length; x += 1) - { - var position = Utilities.ConvertTickToPosition(beatBars[x].Position - tickOffset, resolution) * - _scale; - - if (position > _distance) - { - break; - } - - if (position < 0) - { - continue; + if (x % 8 == 0) + { + beatBarMatrix.Add(Matrix4x4.TRS(new Vector3(2.5f, 0.015f, position), Quaternion.identity, + _beatBarScaleFull)); + } + else if (x % 2 == 0) + { + beatBarHalfMatrix.Add(Matrix4x4.TRS(new Vector3(2.5f, 0.015f, position), Quaternion.identity, + _beatBarScaleHalf)); + } + else + { + beatBarQuarterMatrix.Add(Matrix4x4.TRS(new Vector3(2.5f, 0.015f, position), Quaternion.identity, + _beatBarScaleQuarter)); + } } - if (x % 8 == 0) - { - beatBarMatrix.Add(Matrix4x4.TRS(new Vector3(2.5f, 0, position), Quaternion.identity, - _beatBarScaleFull)); - } - else if (x % 2 == 0) - { - beatBarHalfMatrix.Add(Matrix4x4.TRS(new Vector3(2.5f, 0, position), Quaternion.identity, - _beatBarScaleHalf)); - } - else - { - beatBarQuarterMatrix.Add(Matrix4x4.TRS(new Vector3(2.5f, 0, position), Quaternion.identity, - _beatBarScaleQuarter)); - } + Graphics.DrawMeshInstanced(_mesh, 0, _beatBarMaterial, beatBarMatrix); + Graphics.DrawMeshInstanced(_mesh, 0, _beatBarHalfMaterial, beatBarHalfMatrix); + Graphics.DrawMeshInstanced(_mesh, 0, _beatBarQuarterMaterial, beatBarQuarterMatrix); } - Graphics.DrawMeshInstanced(_mesh, 0, _beatBarMaterial, beatBarMatrix); - Graphics.DrawMeshInstanced(_mesh, 0, _beatBarHalfMaterial, beatBarHalfMatrix); - Graphics.DrawMeshInstanced(_mesh, 0, _beatBarQuarterMaterial, beatBarQuarterMatrix); } } diff --git a/UnityPackage/Samples~/SampleSong/Scripts/CommonUtilities.cs b/UnityPackage/Samples~/SampleSong/Scripts/CommonUtilities.cs new file mode 100644 index 0000000..b98c552 --- /dev/null +++ b/UnityPackage/Samples~/SampleSong/Scripts/CommonUtilities.cs @@ -0,0 +1,46 @@ +using System.IO; +using System.Threading.Tasks; +using UnityEngine; +using UnityEngine.Networking; + +namespace RhythmGameUtilities +{ + + public static class CommonUtilities + { + + public static async Task LoadTextFileFromPath(string path) + { + using var request = UnityWebRequest.Get(path); + + request.downloadHandler = new DownloadHandlerBuffer(); + + await request.SendWebRequest(); + + if (request.result == UnityWebRequest.Result.Success) + { + return request.downloadHandler.text; + } + + throw new FileNotFoundException(request.result.ToString()); + } + + public static async Task LoadAudioFileFromPath(string path, + AudioType audioType = AudioType.OGGVORBIS) + { + using var request = + UnityWebRequestMultimedia.GetAudioClip(path, audioType); + + await request.SendWebRequest(); + + if (request.result == UnityWebRequest.Result.Success) + { + return DownloadHandlerAudioClip.GetContent(request); + } + + throw new FileNotFoundException(request.result.ToString()); + } + + } + +} diff --git a/UnityPackage/Samples~/SampleSong/Scripts/CommonUtilities.cs.meta b/UnityPackage/Samples~/SampleSong/Scripts/CommonUtilities.cs.meta new file mode 100644 index 0000000..1db9705 --- /dev/null +++ b/UnityPackage/Samples~/SampleSong/Scripts/CommonUtilities.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c96740b9abafd4dd985cc6777301377f \ No newline at end of file diff --git a/UnityPackage/Samples~/SampleSong/Scripts/RenderSong.cs b/UnityPackage/Samples~/SampleSong/Scripts/RenderSong.cs index 6c3a202..f702d56 100644 --- a/UnityPackage/Samples~/SampleSong/Scripts/RenderSong.cs +++ b/UnityPackage/Samples~/SampleSong/Scripts/RenderSong.cs @@ -1,216 +1,161 @@ -using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Threading.Tasks; using System.Web; -using RhythmGameUtilities; using UnityEngine; -using UnityEngine.Networking; -public class RenderSong : MonoBehaviour +namespace RhythmGameUtilities { - [SerializeField] - private string _songPath = "Samples/Rhythm Game Utilities/1.0.0/Sample Song/StreamingAssets/Songs/Demo 1"; - - [SerializeField] - private Mesh _mesh; - - [SerializeField] - private Material _trackMaterial; - - [SerializeField] - private Material _beatBarMaterial; - - [SerializeField] - private Material _beatBarHalfMaterial; - - [SerializeField] - private Material _beatBarQuarterMaterial; - - [SerializeField] - private Material[] _materials; + public class RenderSong : MonoBehaviour + { - [SerializeField] - private AudioSource _audioSource; + [SerializeField] + private string _songPath = "Samples/Rhythm Game Utilities/1.0.0/Sample Song/StreamingAssets/Songs/Demo 1"; - [SerializeField] - private float _scale = 2; + [SerializeField] + private Mesh _mesh; - [SerializeField] - private float _distance = 50; + [SerializeField] + private Material _trackMaterial; - private readonly Vector3 _noteScale = new(0.5f, 0.25f, 0.35f); + [SerializeField] + private Material _beatBarMaterial; - private readonly Vector3 _noteScaleFlat = new(0.5f, 0.05f, 0.35f); + [SerializeField] + private Material _beatBarHalfMaterial; - private readonly Vector3 _beatBarScaleFull = new(5, 0.03f, 0.1f); + [SerializeField] + private Material _beatBarQuarterMaterial; - private readonly Vector3 _beatBarScaleHalf = new(5, 0.03f, 0.05f); + [SerializeField] + private Material[] _materials; - private readonly Vector3 _beatBarScaleQuarter = new(5, 0.03f, 0.01f); + [SerializeField] + private AudioSource _audioSource; - public int resolution { get; set; } = 192; + [SerializeField] + private float _scale = 2; - public Tempo[] tempoChanges { get; set; } + [SerializeField] + private float _distance = 50; - public TimeSignature[] timeSignatureChanges { get; set; } + private readonly Vector3 _noteScale = new(0.5f, 0.1f, 0.35f); - public Dictionary difficulties { get; set; } + private readonly Vector3 _noteScaleFlat = new(0.5f, 0.05f, 0.35f); - public Dictionary> notesGroupedByHandPosition { get; set; } + private readonly Vector3 _beatBarScaleFull = new(5, 0.03f, 0.1f); - public BeatBar[] beatBars { get; set; } + private readonly Vector3 _beatBarScaleHalf = new(5, 0.03f, 0.05f); - private async void Start() - { - var path = Path.Join(Application.dataPath, _songPath, "notes.chart"); + private readonly Vector3 _beatBarScaleQuarter = new(5, 0.03f, 0.01f); - var contents = await LoadTextFileFromPath($"file://{HttpUtility.UrlPathEncode(path)}"); + private Song _song; - var sections = Parsers.ParseSectionsFromChart(contents); + private async void Start() + { + var path = Path.Join(Application.dataPath, _songPath); - var metadata = Parsers.ParseMetaDataFromChartSection(sections - .First(section => section.Key == NamedSection.Song) - .Value); + var contents = + await CommonUtilities.LoadTextFileFromPath( + $"file://{HttpUtility.UrlPathEncode(Path.Join(path, "notes.chart"))}"); - _audioSource.clip = - await LoadAudioFileFromPath( - $"file://{HttpUtility.UrlPathEncode(Path.Join(Path.GetDirectoryName(path), metadata["MusicStream"]))}"); + _song = new Song(contents); - resolution = int.Parse(metadata["Resolution"]); + _audioSource.clip = + await CommonUtilities.LoadAudioFileFromPath( + $"file://{HttpUtility.UrlPathEncode(Path.Join(path, _song.metaData["MusicStream"]))}"); - tempoChanges = Parsers.ParseTempoChangesFromChartSection(sections.First(section => section.Key == NamedSection.SyncTrack) - .Value); + _song.RecalculateBeatBarsWithSongLength(_audioSource.clip.length); - timeSignatureChanges = Parsers.ParseTimeSignatureChangesFromChartSection(sections[NamedSection.SyncTrack]); + _audioSource.Play(); + } - var lastTick = - Utilities.ConvertSecondsToTicks(_audioSource.clip.length, resolution, tempoChanges, timeSignatureChanges); + private void Update() + { + RenderTrack(); + RenderHitNotes(); - tempoChanges = tempoChanges.Concat(new Tempo[] + if (_song?.difficulties?[Difficulty.Easy] == null) { - new() - { - Position = Utilities.RoundUpToTheNearestMultiplier(lastTick, resolution), - BPM = tempoChanges.Last().BPM - } - }) - .ToArray(); - - difficulties = Enum.GetValues(typeof(Difficulty)) - .Cast() - .Where(difficulty => sections.ToDictionary(item => item.Key, x => x.Value) - .ContainsKey($"{difficulty}Single")) - .ToDictionary(difficulty => difficulty, - difficulty => Parsers.ParseNotesFromChartSection(sections[$"{difficulty}Single"])); - - notesGroupedByHandPosition = difficulties[Difficulty.Expert] - .Where(note => note.HandPosition < 5) - .GroupBy(note => note.HandPosition) - .ToDictionary(group => group.Key, group => group.ToList()); - - beatBars = Utilities.CalculateBeatBars(tempoChanges, includeHalfNotes : true); - - _audioSource.Play(); - } - - public static async Task LoadTextFileFromPath(string path) - { - using var request = UnityWebRequest.Get(path); - - request.downloadHandler = new DownloadHandlerBuffer(); + return; + } - request.SendWebRequest(); + var tickOffset = + Utilities.ConvertSecondsToTicks(_audioSource.time, _song.resolution, _song.tempoChanges, + _song.timeSignatureChanges); - while (!request.isDone) - { - await Task.Yield(); + RenderNotes(_song.difficulties[Difficulty.Easy], _song.resolution, tickOffset); + RenderBeatBars(_song.beatBars, _song.resolution, tickOffset); } - if (request.result == UnityWebRequest.Result.Success) + private void RenderTrack() { - return request.downloadHandler.text; - } - - throw new FileNotFoundException(request.result.ToString()); - } - - public static async Task LoadAudioFileFromPath(string path, AudioType audioType = AudioType.OGGVORBIS) - { - using var request = - UnityWebRequestMultimedia.GetAudioClip(path, audioType); + Graphics.DrawMesh(_mesh, + Matrix4x4.TRS(new Vector3(2.5f, -0.05f, _distance / 2), Quaternion.identity, + new Vector3(5f, 0.1f, _distance)), + _trackMaterial, 0); - request.SendWebRequest(); + Graphics.DrawMesh(_mesh, + Matrix4x4.TRS(new Vector3(-0.05f, -0.025f, _distance / 2), Quaternion.identity, + new Vector3(0.1f, 0.15f, _distance)), + _beatBarMaterial, 0); - while (!request.isDone) - { - await Task.Yield(); + Graphics.DrawMesh(_mesh, + Matrix4x4.TRS(new Vector3(5.05f, -0.025f, _distance / 2), Quaternion.identity, + new Vector3(0.1f, 0.15f, _distance)), + _beatBarMaterial, 0); } - if (request.result == UnityWebRequest.Result.Success) + private void RenderHitNotes() { - return DownloadHandlerAudioClip.GetContent(request); + for (var x = 0; x < 5; x += 1) + { + Graphics.DrawMesh(_mesh, + Matrix4x4.TRS(new Vector3(x + 0.5f, 0.025f, 0), Quaternion.identity, _noteScaleFlat), + _materials[x], 0); + } } - throw new FileNotFoundException(request.result.ToString()); - } - - private void Update() - { - RenderTrack(); - - if (notesGroupedByHandPosition == null) + private void RenderNotes(Note[] notes, int resolution, int tickOffset) { - return; - } - - var tickOffset = - Utilities.ConvertSecondsToTicks(_audioSource.time, resolution, tempoChanges, timeSignatureChanges); + var laneArray = new Dictionary>(); - RenderHitNotes(notesGroupedByHandPosition); + for (var x = 0; x < 5; x += 1) + { + laneArray.Add(x, new List()); + } - RenderNotes(notesGroupedByHandPosition, resolution, tickOffset); - RenderBeatBars(beatBars, resolution, tickOffset); - } + for (var i = 0; i < notes.Length; i += 1) + { + if (notes[i].HandPosition > 5) continue; - private void RenderTrack() - { - Graphics.DrawMesh(_mesh, - Matrix4x4.TRS(new Vector3(2.5f, -0.05f, _distance / 2), Quaternion.identity, - new Vector3(5f, 0.1f, _distance)), - _trackMaterial, 0); - } + var position = Utilities.ConvertTickToPosition(notes[i].Position - tickOffset, + resolution) * _scale; - private void RenderHitNotes(Dictionary> notesGroupedByHandPosition) - { - for (var x = 0; x < notesGroupedByHandPosition.Count; x += 1) - { - Graphics.DrawMesh(_mesh, - Matrix4x4.TRS(new Vector3(x + 0.5f, 0, 0), Quaternion.identity, _noteScaleFlat), - _materials[x], 0); - } - } + if (position > 0 && position < _distance) + { + laneArray[notes[i].HandPosition].Add(Matrix4x4.TRS( + new Vector3(notes[i].HandPosition + 0.5f, 0.05f, position), + Quaternion.identity, _noteScale)); + } + } - private void RenderNotes(Dictionary> notesGroupedByHandPosition, int resolution, int tickOffset) - { - for (var x = 0; x < notesGroupedByHandPosition.Count; x += 1) - { - if (!notesGroupedByHandPosition.ContainsKey(x)) + for (var x = 0; x < 5; x += 1) { - continue; + Graphics.DrawMeshInstanced(_mesh, 0, _materials[x], laneArray[x]); } + } - var noteMatrix = new List - { - Matrix4x4.TRS(new Vector3(x + 0.5f, 0, 0), Quaternion.identity, _noteScaleFlat) - }; + private void RenderBeatBars(BeatBar[] beatBars, int resolution, int tickOffset) + { + var beatBarMatrix = new List(); + var beatBarHalfMatrix = new List(); + var beatBarQuarterMatrix = new List(); - for (var y = 0; y < notesGroupedByHandPosition[x].Count; y += 1) + for (var x = 0; x < beatBars.Length; x += 1) { - var position = Utilities.ConvertTickToPosition(notesGroupedByHandPosition[x][y].Position - tickOffset, - resolution) * _scale; + var position = Utilities.ConvertTickToPosition(beatBars[x].Position - tickOffset, resolution) * + _scale; if (position > _distance) { @@ -222,56 +167,28 @@ private void RenderNotes(Dictionary> notesGroupedByHandPosition, continue; } - noteMatrix.Add(Matrix4x4.TRS( - new Vector3(notesGroupedByHandPosition[x][y].HandPosition + 0.5f, 0, position), - Quaternion.identity, _noteScale)); - } - - Graphics.DrawMeshInstanced(_mesh, 0, _materials[x], noteMatrix); - } - } - - private void RenderBeatBars(BeatBar[] beatBars, int resolution, int tickOffset) - { - var beatBarMatrix = new List(); - var beatBarHalfMatrix = new List(); - var beatBarQuarterMatrix = new List(); - - for (var x = 0; x < beatBars.Length; x += 1) - { - var position = Utilities.ConvertTickToPosition(beatBars[x].Position - tickOffset, resolution) * - _scale; - - if (position > _distance) - { - break; - } - - if (position < 0) - { - continue; + if (x % 8 == 0) + { + beatBarMatrix.Add(Matrix4x4.TRS(new Vector3(2.5f, 0.015f, position), Quaternion.identity, + _beatBarScaleFull)); + } + else if (x % 2 == 0) + { + beatBarHalfMatrix.Add(Matrix4x4.TRS(new Vector3(2.5f, 0.015f, position), Quaternion.identity, + _beatBarScaleHalf)); + } + else + { + beatBarQuarterMatrix.Add(Matrix4x4.TRS(new Vector3(2.5f, 0.015f, position), Quaternion.identity, + _beatBarScaleQuarter)); + } } - if (x % 8 == 0) - { - beatBarMatrix.Add(Matrix4x4.TRS(new Vector3(2.5f, 0, position), Quaternion.identity, - _beatBarScaleFull)); - } - else if (x % 2 == 0) - { - beatBarHalfMatrix.Add(Matrix4x4.TRS(new Vector3(2.5f, 0, position), Quaternion.identity, - _beatBarScaleHalf)); - } - else - { - beatBarQuarterMatrix.Add(Matrix4x4.TRS(new Vector3(2.5f, 0, position), Quaternion.identity, - _beatBarScaleQuarter)); - } + Graphics.DrawMeshInstanced(_mesh, 0, _beatBarMaterial, beatBarMatrix); + Graphics.DrawMeshInstanced(_mesh, 0, _beatBarHalfMaterial, beatBarHalfMatrix); + Graphics.DrawMeshInstanced(_mesh, 0, _beatBarQuarterMaterial, beatBarQuarterMatrix); } - Graphics.DrawMeshInstanced(_mesh, 0, _beatBarMaterial, beatBarMatrix); - Graphics.DrawMeshInstanced(_mesh, 0, _beatBarHalfMaterial, beatBarHalfMatrix); - Graphics.DrawMeshInstanced(_mesh, 0, _beatBarQuarterMaterial, beatBarQuarterMatrix); } } diff --git a/UnityPackage/Structs/Song.cs b/UnityPackage/Structs/Song.cs new file mode 100644 index 0000000..e99a7d4 --- /dev/null +++ b/UnityPackage/Structs/Song.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace RhythmGameUtilities +{ + + public class Song + { + + private readonly Dictionary[]> _sections; + + public Dictionary metaData { get; } + + public int resolution { get; } + + public Tempo[] tempoChanges { get; private set; } + + public TimeSignature[] timeSignatureChanges { get; } + + public Dictionary difficulties { get; private set; } + + public BeatBar[] beatBars { get; private set; } + + public Song(string contents) + { + _sections = Parsers.ParseSectionsFromChart(contents); + + metaData = Parsers.ParseMetaDataFromChartSection(_sections + .First(section => section.Key == NamedSection.Song) + .Value); + + resolution = int.Parse(metaData["Resolution"]); + + tempoChanges = Parsers.ParseTempoChangesFromChartSection(_sections + .First(section => section.Key == NamedSection.SyncTrack) + .Value); + + timeSignatureChanges = Parsers.ParseTimeSignatureChangesFromChartSection(_sections[NamedSection.SyncTrack]); + + difficulties = Enum.GetValues(typeof(Difficulty)) + .Cast() + .Where(difficulty => _sections.ToDictionary(item => item.Key, item => item.Value) + .ContainsKey($"{difficulty}Single")) + .ToDictionary(difficulty => difficulty, + difficulty => Parsers.ParseNotesFromChartSection(_sections[$"{difficulty}Single"])); + + beatBars = Utilities.CalculateBeatBars(tempoChanges, includeHalfNotes : true); + } + + public void RecalculateBeatBarsWithSongLength(float songLength) + { + var lastTick = Utilities.ConvertSecondsToTicks(songLength, resolution, tempoChanges, timeSignatureChanges); + + tempoChanges = Parsers.ParseTempoChangesFromChartSection(_sections + .First(section => section.Key == NamedSection.SyncTrack) + .Value); + + tempoChanges = tempoChanges.Concat(new Tempo[] + { + new() + { + Position = Utilities.RoundUpToTheNearestMultiplier(lastTick, resolution), + BPM = tempoChanges.Last().BPM + } + }) + .ToArray(); + + beatBars = Utilities.CalculateBeatBars(tempoChanges, includeHalfNotes : true); + } + + } + +} diff --git a/UnityPackage/Structs/Song.cs.meta b/UnityPackage/Structs/Song.cs.meta new file mode 100644 index 0000000..f357061 --- /dev/null +++ b/UnityPackage/Structs/Song.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b7ee05be6e5f4629bbf4a6a05791029a +timeCreated: 1739818850 \ No newline at end of file