Skip to content

Commit 51ae9ef

Browse files
committed
Reworked, see description
Moved timing to a global timer using the TimeKeeper singleton. This dramatically improved mistiming, and synced everything that cares to be synced. Added event bubbling for input. Reworked ArrowType enum to have strict int values for simplicity. Simplified dictionaries representing lane based data into arrays Simplified notearrows to sprite2d and shifted test_scene for easy visual syncing.
1 parent 6b2eee8 commit 51ae9ef

11 files changed

Lines changed: 203 additions & 192 deletions

File tree

project.godot

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ run/main_scene="res://scenes/ChartViewport/test_scene.tscn"
1515
config/features=PackedStringArray("4.3", "C#", "Forward Plus")
1616
config/icon="res://icon.svg"
1717

18+
[autoload]
19+
20+
TimeKeeper="*res://scripts/TimeKeeper.cs"
21+
1822
[display]
1923

2024
window/size/viewport_width=640

scenes/ChartViewport/BattleDirector.cs

Lines changed: 49 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@ public partial class BattleDirector : Node2D
1616
[Export]
1717
public NoteManager NM;
1818

19-
//TODO: Slowly add data based on what it needs.
20-
private double _startTime;
21-
private double _currentTime;
2219
private double _timingInterval = .1; //secs
2320

2421
public struct SongData
@@ -29,32 +26,45 @@ public struct SongData
2926
}
3027

3128
private SongData _curSong;
32-
private Dictionary<string, Note[]> LaneNotes;
29+
private readonly Note[][] _laneNotes = new Note[][]
30+
{
31+
Array.Empty<Note>(),
32+
Array.Empty<Note>(),
33+
Array.Empty<Note>(),
34+
Array.Empty<Note>(),
35+
};
3336
private Note[] _notes = Array.Empty<Note>();
3437

3538
public override void _Ready()
3639
{
37-
LaneNotes = new()
38-
{
39-
{ "arrowUp", Array.Empty<Note>() },
40-
{ "arrowLeft", Array.Empty<Note>() },
41-
{ "arrowDown", Array.Empty<Note>() },
42-
{ "arrowRight", Array.Empty<Note>() },
43-
};
4440
AddExampleNote();
4541

4642
CM.PrepChart(_curSong, _notes);
4743
//TODO: Hook up signals
48-
NM.Connect(nameof(NoteManager.NotePressed), new Callable(this, nameof(OnNotePressed)));
49-
NM.Connect(nameof(NoteManager.NoteReleased), new Callable(this, nameof(OnNoteReleased)));
50-
51-
_startTime = (double)Time.GetTicksMsec() / 1000;
44+
CM.Connect(nameof(NoteManager.NotePressed), new Callable(this, nameof(OnNotePressed)));
45+
CM.Connect(nameof(NoteManager.NoteReleased), new Callable(this, nameof(OnNoteReleased)));
5246
}
5347

5448
public override void _Process(double delta)
5549
{
56-
_currentTime += delta;
57-
//GD.Print($"Current Time: {_currentTime}");
50+
TimeKeeper.CurrentTime += delta;
51+
//Check beats downwards
52+
double curBeat = TimeKeeper.CurrentTime / (60 / (double)_curSong.Bpm);
53+
for (int i = 0; i < _laneNotes.Length; i++)
54+
{
55+
if (_laneNotes[i].Length > 0)
56+
{
57+
if (curBeat > _laneNotes[i][0].Beat + 1)
58+
{
59+
GD.Print($"Beat: {_laneNotes[i][0].Beat} Miss");
60+
GD.Print(TimeKeeper.CurrentTime);
61+
//Cycle note queue
62+
_laneNotes[i].First().Beat += CM._beatsPerLoop;
63+
_laneNotes[i] = _laneNotes[i].Skip(1).Concat(_laneNotes[i].Take(1)).ToArray(); //TODO: No stackoverflow code
64+
CM.HandleNote((NoteArrow.ArrowType)i);
65+
}
66+
}
67+
}
5868
}
5969

6070
private void AddExampleNote()
@@ -63,55 +73,57 @@ private void AddExampleNote()
6373
_curSong = new SongData
6474
{
6575
Bpm = 120,
66-
SongLength = 160,
76+
SongLength = 100,
6777
NumLoops = 5,
6878
};
6979
//Add note
70-
for (int i = 0; i < 5; i++)
80+
for (int i = 0; i < 4; i++)
81+
{
82+
Note exampleNote = new Note(NoteArrow.ArrowType.Up, i + 3);
83+
AddNoteToLane(exampleNote);
84+
}
85+
for (int i = 0; i < 1; i++)
7186
{
72-
Note exampleNote = new Note(NoteArrow.ArrowType.Left, i + 9);
87+
Note exampleNote = new Note(NoteArrow.ArrowType.Left, i + 4);
7388
AddNoteToLane(exampleNote);
7489
}
7590
}
7691

7792
private void AddNoteToLane(Note note)
7893
{
7994
_notes = _notes.Append(note).ToArray();
80-
LaneNotes[NM.Arrows[note.Type].Key] = LaneNotes[NM.Arrows[note.Type].Key]
81-
.Append(note)
82-
.ToArray();
95+
_laneNotes[(int)note.Type] = _laneNotes[(int)note.Type].Append(note).ToArray();
8396
}
8497

85-
private void OnNotePressed(string key)
98+
private void OnNotePressed(NoteArrow.ArrowType type)
8699
{
87-
GD.Print("Note pressed: " + key + " at " + _currentTime + " seconds.");
88-
CheckNoteTiming(key);
100+
//GD.Print("Note pressed: " + type + " at " + _currentTime + " seconds.");
101+
CheckNoteTiming(type);
89102
}
90103

91104
private void OnNoteReleased(NoteArrow.ArrowType arrowType)
92105
{
93-
GD.Print("Note released: " + arrowType + " at " + _currentTime + " seconds.");
106+
//GD.Print("Note released: " + arrowType + " at " + _currentTime + " seconds.");
94107
}
95108

96-
private void CheckNoteTiming(string arrowString)
109+
private void CheckNoteTiming(NoteArrow.ArrowType type)
97110
{
98111
//Assume queue structure for notes
99112
//Know current time, calculate beat timing
100-
var curBeat = _currentTime / (60 / (double)_curSong.Bpm);
101-
if (LaneNotes[arrowString].Length == 0)
113+
double curBeat = TimeKeeper.CurrentTime / (60 / (double)_curSong.Bpm);
114+
if (_laneNotes[(int)type].Length == 0)
102115
return;
103-
double beatDif = Math.Abs(curBeat - LaneNotes[arrowString].First().Beat);
104-
GD.Print(beatDif);
116+
double beatDif = Math.Abs(curBeat - _laneNotes[(int)type].First().Beat);
105117
if (beatDif > 1)
106118
return;
107-
GD.Print("Note Hit.");
119+
GD.Print("Note Hit. Dif: " + beatDif);
120+
CM.HandleNote(type);
108121
//Cycle note queue
109-
LaneNotes[arrowString] = LaneNotes[arrowString]
122+
_laneNotes[(int)type].First().Beat += CM._beatsPerLoop;
123+
_laneNotes[(int)type] = _laneNotes[(int)type] //Credit: Stackoverflow https://stackoverflow.com/questions/49494535/moving-the-first-array-element-to-end-in-c-sharp
110124
.Skip(1)
111-
.Concat(LaneNotes[arrowString].Take(1))
125+
.Concat(_laneNotes[(int)type].Take(1))
112126
.ToArray(); //TODO: No stackoverflow code
113-
//Change note visual
114-
CM.TriggerArrow();
115127
//Do timing stuff
116128
if (beatDif < _timingInterval * 2)
117129
{

scenes/ChartViewport/ChartManager.cs

Lines changed: 57 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,6 @@
33
using Godot;
44
using ArrowType = NoteArrow.ArrowType;
55

6-
/*
7-
//Lets say this inits all the initial notes and manages the chart BG.
8-
9-
//What does this do?
10-
//Input, visual looping, timing, battle stuff, combo, note creation
11-
12-
//Focus on the looping
13-
This should manage creating sprites for notes???
14-
This should manage subview camera pos and zoom.
15-
16-
Movement should primarily be done from a parent node
17-
!!Backgrounds need to be big enough/notes and beats spaced enough that when a note goes off screen, it can be teleported in position offscreen
18-
BackGround probably needs 2 sprites or parallax:
19-
Get a set length, based on viewport and loop/song length (Const PLAYWIDTH)
20-
Once one BG hits a certain left pos return it to the right pos
21-
22-
Notes are similar, but only need 1 representation.
23-
Once hits left bounds return to right bounds
24-
(Something else should probably manage refreshing, input, etc)
25-
Can probably use an object pool
26-
27-
If timing based input checking:
28-
This is enough, notes are visually just sprites
29-
Collision based - This might need to manage that, or have a sister manager that does, notes need more stuff on their own
30-
*/
31-
326
/**
337
* @class ChartManager
348
* @brief Chart Manager is meant to handle the visual aspects of a battle. Setting up the chart background, initial notes, and handle looping. WIP
@@ -42,22 +16,31 @@ public partial class ChartManager : SubViewportContainer
4216
[Export]
4317
public CanvasGroup ChartLoopables;
4418

45-
public BattleDirector.SongData SongData; //TODO: Maybe. Make settable from outside, but readonly
19+
[Signal]
20+
public delegate void NotePressedEventHandler(ArrowType arrowType);
21+
22+
[Signal]
23+
public delegate void NoteReleasedEventHandler(ArrowType arrowType);
4624

4725
//Arbitrary vars, play with these
48-
private const double ChartLength = 2800; //Might move this to be song specific?
26+
private double ChartLength = 2400; //Might move this to be song specific?
4927

5028
//Speed that chart objs should move at, to be synced to song, in theory
51-
private double _rateOfChart;
29+
private double _rateOfChart; //Speed objects should move at px/s
5230
private double _loopLen; //secs
53-
private int _beatsPerLoop;
31+
public int _beatsPerLoop;
5432

55-
//TODO: Lanes
56-
private NoteArrow[] _currentArrows = Array.Empty<NoteArrow>();
33+
private NoteArrow[][] _currentArrows = new NoteArrow[][]
34+
{
35+
Array.Empty<NoteArrow>(),
36+
Array.Empty<NoteArrow>(),
37+
Array.Empty<NoteArrow>(),
38+
Array.Empty<NoteArrow>(),
39+
};
5740

5841
private void InitBackgrounds()
5942
{
60-
//TODO: Get better visual for BG's, and/or create BG's on demand. Though we should only ever need 2.
43+
//TODO: Get better visual for BG's
6144
int i = 0;
6245
foreach (Node child in ChartLoopables.GetChildren())
6346
{
@@ -66,9 +49,7 @@ private void InitBackgrounds()
6649
Loopable loopable = (Loopable)child;
6750
//TODO: Consolidate
6851
loopable.SetSize(new Vector2((float)ChartLength / 2 + 1, Size.Y));
69-
loopable.SetPosition(new Vector2((float)ChartLength / 2 * i, 0));
70-
loopable.Bounds = (float)ChartLength / 2;
71-
loopable.Speed = (float)_rateOfChart;
52+
loopable.Bounds = (float)ChartLength / 2 * i;
7253

7354
i++;
7455
}
@@ -82,54 +63,73 @@ private void InitNotes(Note[] notes)
8263
if (noteData != null)
8364
CreateNote(noteData.Type, noteData.Beat);
8465
}
66+
foreach (Note noteData in notes) //Temporary solution
67+
{
68+
if (noteData != null)
69+
CreateNote(noteData.Type, noteData.Beat + _beatsPerLoop);
70+
}
8571
}
8672

8773
public void PrepChart(BattleDirector.SongData songData, Note[] notes)
8874
{
89-
SongData = songData;
90-
91-
_loopLen = SongData.SongLength / SongData.NumLoops;
92-
_beatsPerLoop = (int)(_loopLen / (60f / SongData.Bpm));
75+
_loopLen = songData.SongLength / songData.NumLoops;
76+
TimeKeeper.LoopLength = (float)_loopLen;
77+
_beatsPerLoop = (int)(_loopLen / (60f / songData.Bpm));
78+
ChartLength = (float)_loopLen * (float)Math.Floor(ChartLength / _loopLen);
79+
TimeKeeper.ChartLength = (float)ChartLength;
9380

9481
_rateOfChart = ChartLength / 2 / _loopLen; //px/s
9582

9683
InitBackgrounds();
9784
InitNotes(notes);
85+
86+
NM.Connect(nameof(NoteManager.NotePressed), new Callable(this, nameof(OnNotePressed)));
87+
NM.Connect(nameof(NoteManager.NoteReleased), new Callable(this, nameof(OnNoteReleased)));
9888
}
9989

10090
//TODO: Rework these
10191
public NoteArrow CreateNote(ArrowType arrow, int beat = 0)
10292
{
103-
var newNote = CreateNote(NM.Arrows[arrow]);
104-
newNote.Bounds =
105-
(float)((double)beat / _beatsPerLoop * (ChartLength / 2)) - newNote.Size.X / 2; //eww
93+
var newNote = CreateNote(NM.Arrows[(int)arrow], beat);
94+
newNote.Bounds = (float)((double)beat / _beatsPerLoop * (ChartLength / 2)); //eww
10695
newNote.Position += Vector2.Right * newNote.Bounds;
10796
return newNote;
10897
}
10998

110-
private NoteArrow CreateNote(NoteManager.ArrowData arrowData)
99+
private NoteArrow CreateNote(NoteManager.ArrowData arrowData, int beat)
111100
{
112101
var noteScene = ResourceLoader.Load<PackedScene>("res://scenes/NoteManager/note.tscn");
113102
NoteArrow note = noteScene.Instantiate<NoteArrow>();
114103

115-
note.Init(arrowData, (float)_rateOfChart, -1);
116-
_currentArrows = _currentArrows.Append(note).ToArray();
104+
note.Init(arrowData);
105+
if (!(beat > _beatsPerLoop))
106+
{
107+
_currentArrows[(int)arrowData.Type] = _currentArrows[(int)arrowData.Type]
108+
.Append(note)
109+
.ToArray();
110+
}
117111
ChartLoopables.AddChild(note);
118112
return note;
119113
}
120114

121-
public void TriggerArrow()
115+
public void HandleNote(ArrowType type)
116+
{
117+
_currentArrows[(int)type].First().NoteHit();
118+
_currentArrows[(int)type] = _currentArrows[(int)type]
119+
.Skip(1)
120+
.Concat(_currentArrows[(int)type].Take(1))
121+
.ToArray();
122+
}
123+
124+
public void OnNotePressed(ArrowType type)
122125
{
123-
_currentArrows.First().NoteHit();
124-
_currentArrows = _currentArrows.Skip(1).ToArray();
126+
if (_currentArrows[(int)type].Length == 0)
127+
return;
128+
EmitSignal(nameof(NotePressed), (int)type);
125129
}
126130

127-
//TODO: Queue next notes. Needs Timing System
128-
/*The logic:
129-
*Spawn in pos is a proportion (intended beat/beats per loop) = (intended pos/track length in px) ->
130-
* intended pos = intended beat / bpl * track length
131-
*
132-
* Respawn (probably obj pool, or for now new instantiation)
133-
* When a note's pos is at its intended initial pos, queue up and spawn the next note of its beat at intended pos + track length
134-
*/
131+
public void OnNoteReleased(ArrowType type)
132+
{
133+
EmitSignal(nameof(NoteReleased), (int)type);
134+
}
135135
}

scenes/ChartViewport/ChartViewport.tscn

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,23 @@ size = Vector2i(480, 220)
1818
render_target_update_mode = 4
1919

2020
[node name="Camera2D" type="Camera2D" parent="SubViewport"]
21-
position = Vector2(240, 110)
21+
position = Vector2(-25, 0)
22+
anchor_mode = 0
2223

2324
[node name="ChartLoopables" type="CanvasGroup" parent="SubViewport"]
2425
unique_name_in_owner = true
2526

2627
[node name="ChartBG1" type="TextureRect" parent="SubViewport/ChartLoopables"]
2728
modulate = Color(2, 2, 2, 1)
28-
offset_top = -46.0
2929
offset_right = 701.0
30-
offset_bottom = 254.0
30+
offset_bottom = 300.0
3131
texture = ExtResource("1_0wnka")
3232
script = ExtResource("3_5u57h")
3333

3434
[node name="ChartBG2" type="TextureRect" parent="SubViewport/ChartLoopables"]
3535
modulate = Color(2, 2, 2, 1)
36-
offset_left = 700.0
37-
offset_top = -46.0
38-
offset_right = 1401.0
39-
offset_bottom = 254.0
36+
offset_right = 701.0
37+
offset_bottom = 300.0
4038
texture = ExtResource("1_0wnka")
4139
script = ExtResource("3_5u57h")
4240

scenes/ChartViewport/Loopable.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88
public partial class Loopable : TextureRect
99
{
10+
[Export]
1011
public float Bounds = 700f; //px Pos to loop or do something at.
1112
public float Speed = 5; //px/s
1213

@@ -15,12 +16,19 @@ public override void _Process(double delta)
1516
{
1617
if (Position.X <= -Bounds)
1718
{
18-
Loop();
19+
//Loop();
1920
}
20-
Position += Speed * Vector2.Left * (float)delta;
21+
Vector2 newPos = Position;
22+
newPos.X =
23+
(float)(
24+
(-TimeKeeper.CurrentTime / TimeKeeper.LoopLength * TimeKeeper.ChartLength)
25+
% TimeKeeper.ChartLength
26+
/ 2
27+
) + Bounds;
28+
Position = newPos;
2129
}
2230

23-
public virtual void Loop()
31+
public void Loop()
2432
{
2533
Position = new Vector2(Bounds, Position.Y);
2634
}

0 commit comments

Comments
 (0)