Skip to content

Commit 2db3f63

Browse files
Merge pull request #40 from Project-Funk-Engine/Sprint-1
Merge Final Sprint 1 Playable into Main!
2 parents 3fe938b + c53988c commit 2db3f63

32 files changed

+1275
-9
lines changed
2.44 MB
Binary file not shown.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[remap]
2+
3+
importer="mp3"
4+
type="AudioStreamMP3"
5+
uid="uid://cv6lqjj6lu36h"
6+
path="res://.godot/imported/335571__magntron__gamemusic_120bpm.mp3-a87b357c4b3c9199709863b47f78bd2a.mp3str"
7+
8+
[deps]
9+
10+
source_file="res://Audio/335571__magntron__gamemusic_120bpm.mp3"
11+
dest_files=["res://.godot/imported/335571__magntron__gamemusic_120bpm.mp3-a87b357c4b3c9199709863b47f78bd2a.mp3str"]
12+
13+
[params]
14+
15+
loop=true
16+
loop_offset=0
17+
bpm=0
18+
beat_count=0
19+
bar_beats=4

Classes/Note.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System;
2+
using Godot;
3+
4+
/**
5+
* @class Note
6+
* @brief Data structure class for holding data and methods for a battle time note. WIP
7+
*/
8+
public partial class Note : Resource
9+
{
10+
public int Beat;
11+
public NoteArrow.ArrowType Type;
12+
13+
public Note(NoteArrow.ArrowType type = NoteArrow.ArrowType.Up, int beat = 0)
14+
{
15+
Beat = beat;
16+
Type = type;
17+
}
18+
}

Funk Engine.sln.DotSettings.user

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
2+
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003Fgamef_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F6354a7b35d7821629924d3676acd7e67a6f7f94343e0e66ec439aa2bd6ed5_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
3+
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F6354a7b35d7821629924d3676acd7e67a6f7f94343e0e66ec439aa2bd6ed5_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,9 @@ Current team members include:
1313

1414

1515

16+
#### Attributions:
17+
Note icon: <a href="https://www.flaticon.com/free-icons/next" title="next icons">Next icons created by Pixel perfect - Flaticon</a>
18+
19+
First Song: <a href="https://freesound.org/people/Magntron/sounds/335571/" title="gameMusic">gameMusic by Magntron - freesound.org</a>
20+
21+

project.godot

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@ config_version=5
1111
[application]
1212

1313
config/name="Funk Engine"
14-
run/main_scene="res://scenes/main.tscn"
14+
run/main_scene="res://scenes/BattleDirector/test_battle_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
@@ -25,3 +29,38 @@ window/stretch/scale_mode="integer"
2529
[dotnet]
2630

2731
project/assembly_name="Funk Engine"
32+
33+
[input]
34+
35+
arrowUp={
36+
"deadzone": 0.5,
37+
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
38+
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194320,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
39+
]
40+
}
41+
arrowDown={
42+
"deadzone": 0.5,
43+
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
44+
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194322,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
45+
]
46+
}
47+
arrowLeft={
48+
"deadzone": 0.5,
49+
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
50+
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194319,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
51+
]
52+
}
53+
arrowRight={
54+
"deadzone": 0.5,
55+
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194321,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
56+
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
57+
]
58+
}
59+
60+
[rendering]
61+
62+
textures/canvas_textures/default_texture_filter=0
63+
64+
[threading]
65+
66+
worker_pool/canvas_textures/default_texture_filter=1
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Linq;
5+
using Godot;
6+
7+
/**
8+
* @class BattleDirector
9+
* @brief Higher priority director to manage battle effects. Can directly access managers, which should signal up to Director WIP
10+
*/
11+
public partial class BattleDirector : Node2D
12+
{
13+
#region Declarations
14+
private HealthBar Player;
15+
private HealthBar Enemy;
16+
17+
[Export]
18+
private ChartManager CM;
19+
20+
[Export]
21+
private InputHandler IH;
22+
23+
[Export]
24+
private NotePlacementBar NotePlacementBar;
25+
26+
[Export]
27+
private AudioStreamPlayer Audio;
28+
29+
private double _timingInterval = .1; //secs
30+
31+
[Signal]
32+
public delegate void PlayerDamageEventHandler(int damage);
33+
34+
[Signal]
35+
public delegate void EnemyDamageEventHandler(int damage);
36+
37+
private SongData _curSong;
38+
39+
public struct SongData
40+
{
41+
public int Bpm;
42+
public double SongLength;
43+
public int NumLoops;
44+
}
45+
#endregion
46+
47+
#region Note Handling
48+
//Assume queue structure for notes in each lane.
49+
//Can eventually make this its own structure
50+
private NoteArrow[][] _laneData = Array.Empty<NoteArrow[]>();
51+
private int[] _laneLastBeat = new int[]
52+
{ //Temporary (hopefully) measure to bridge from note queue structure to ordered array
53+
0,
54+
0,
55+
0,
56+
0,
57+
};
58+
private Note[] _notes = Array.Empty<Note>();
59+
60+
//Returns first note of lane without modifying lane data
61+
private Note GetNoteAt(NoteArrow.ArrowType dir, int beat)
62+
{
63+
return GetNote(_laneData[(int)dir][beat]);
64+
}
65+
66+
//Get note of a note arrow
67+
private Note GetNote(NoteArrow arrow)
68+
{
69+
return _notes[arrow.NoteIdx];
70+
}
71+
72+
private bool AddNoteToLane(Note note, bool isActive = true)
73+
{
74+
note.Beat %= CM.BeatsPerLoop;
75+
//Don't add dupe notes
76+
if (note.Beat == 0 || _notes.Any(nt => nt.Type == note.Type && nt.Beat == note.Beat))
77+
{
78+
return false; //Beat at 0 is too messy.
79+
}
80+
_notes = _notes.Append(note).ToArray();
81+
//Get noteArrow from CM
82+
var arrow = CM.AddArrowToLane(note, _notes.Length - 1);
83+
arrow.IsActive = isActive;
84+
_laneData[(int)note.Type][note.Beat] = arrow;
85+
return true;
86+
}
87+
#endregion
88+
89+
//Creeate dummy notes
90+
private void AddExampleNotes()
91+
{
92+
GD.Print(CM.BeatsPerLoop);
93+
for (int i = 1; i < 15; i++)
94+
{
95+
Note exampleNote = new Note(NoteArrow.ArrowType.Up, i * 4);
96+
AddNoteToLane(exampleNote);
97+
}
98+
for (int i = 1; i < 15; i++)
99+
{
100+
Note exampleNote = new Note(NoteArrow.ArrowType.Left, 4 * i + 1);
101+
AddNoteToLane(exampleNote);
102+
}
103+
for (int i = 0; i < 10; i++)
104+
{
105+
Note exampleNote = new Note(NoteArrow.ArrowType.Right, 3 * i + 32);
106+
AddNoteToLane(exampleNote);
107+
}
108+
for (int i = 0; i < 3; i++)
109+
{
110+
Note exampleNote = new Note(NoteArrow.ArrowType.Down, 8 * i + 16);
111+
AddNoteToLane(exampleNote);
112+
}
113+
}
114+
115+
public override void _Ready()
116+
{
117+
_curSong = new SongData
118+
{
119+
Bpm = 120,
120+
SongLength = Audio.Stream.GetLength(),
121+
NumLoops = 5,
122+
};
123+
124+
var timer = GetTree().CreateTimer(AudioServer.GetTimeToNextMix());
125+
timer.Timeout += Begin;
126+
}
127+
128+
private void Begin()
129+
{
130+
CM.PrepChart(_curSong);
131+
_laneData = new NoteArrow[][]
132+
{
133+
new NoteArrow[CM.BeatsPerLoop],
134+
new NoteArrow[CM.BeatsPerLoop],
135+
new NoteArrow[CM.BeatsPerLoop],
136+
new NoteArrow[CM.BeatsPerLoop],
137+
};
138+
AddExampleNotes();
139+
140+
Player = GetNode<HealthBar>("PlayerHP");
141+
Player.GetNode<Sprite2D>("Sprite2D").Scale *= .5f; //TEMP
142+
Player.GetNode<Sprite2D>("Sprite2D").Position += Vector2.Down * 30; //TEMP
143+
Enemy = GetNode<HealthBar>("EnemyHP");
144+
145+
//TEMP
146+
var enemTween = CreateTween();
147+
enemTween
148+
.TweenProperty(Enemy.GetNode<Sprite2D>("Sprite2D"), "position", Vector2.Down * 5, 1f)
149+
.AsRelative();
150+
enemTween
151+
.TweenProperty(Enemy.GetNode<Sprite2D>("Sprite2D"), "position", Vector2.Up * 5, 1f)
152+
.AsRelative();
153+
enemTween.SetTrans(Tween.TransitionType.Spring);
154+
enemTween.SetEase(Tween.EaseType.In);
155+
enemTween.SetLoops();
156+
enemTween.Play();
157+
158+
CM.Connect(nameof(InputHandler.NotePressed), new Callable(this, nameof(OnNotePressed)));
159+
CM.Connect(nameof(InputHandler.NoteReleased), new Callable(this, nameof(OnNoteReleased)));
160+
161+
Audio.Play();
162+
}
163+
164+
public override void _Process(double delta)
165+
{
166+
TimeKeeper.CurrentTime = Audio.GetPlaybackPosition();
167+
CheckMiss();
168+
}
169+
170+
#region Input&Timing
171+
private void OnNotePressed(NoteArrow.ArrowType type)
172+
{
173+
CheckNoteTiming(type);
174+
}
175+
176+
private void OnNoteReleased(NoteArrow.ArrowType arrowType) { }
177+
178+
//Check all lanes for misses from missed inputs
179+
private void CheckMiss()
180+
{
181+
//On current beat, if prev beat is active and not inputted
182+
double realBeat = TimeKeeper.CurrentTime / (60 / (double)_curSong.Bpm) % CM.BeatsPerLoop;
183+
for (int i = 0; i < _laneData.Length; i++)
184+
{
185+
if (
186+
_laneLastBeat[i] < Math.Floor(realBeat)
187+
|| (_laneLastBeat[i] == CM.BeatsPerLoop - 1 && Math.Floor(realBeat) == 0)
188+
)
189+
{ //If above, a note has been missed
190+
//GD.Print("Last beat " + _laneLastBeat[i]);
191+
if (
192+
_laneData[i][_laneLastBeat[i]] == null
193+
|| !_laneData[i][_laneLastBeat[i]].IsActive
194+
)
195+
{
196+
_laneLastBeat[i] = (_laneLastBeat[i] + 1) % CM.BeatsPerLoop;
197+
continue;
198+
}
199+
//Note exists and has been missed
200+
_laneData[i][_laneLastBeat[i]].NoteHit();
201+
HandleTiming((NoteArrow.ArrowType)i, 1);
202+
_laneLastBeat[i] = (_laneLastBeat[i] + 1) % CM.BeatsPerLoop;
203+
}
204+
}
205+
}
206+
207+
private void CheckNoteTiming(NoteArrow.ArrowType type)
208+
{
209+
double realBeat = TimeKeeper.CurrentTime / (60 / (double)_curSong.Bpm) % CM.BeatsPerLoop;
210+
int curBeat = (int)Math.Round(realBeat);
211+
GD.Print("Cur beat " + curBeat + "Real: " + realBeat.ToString("#.###"));
212+
if (
213+
_laneData[(int)type][curBeat % CM.BeatsPerLoop] == null
214+
|| !_laneData[(int)type][curBeat % CM.BeatsPerLoop].IsActive
215+
)
216+
{
217+
_laneLastBeat[(int)type] = (curBeat) % CM.BeatsPerLoop;
218+
PlayerAddNote(type, curBeat);
219+
return;
220+
}
221+
double beatDif = Math.Abs(realBeat - curBeat);
222+
_laneData[(int)type][curBeat % CM.BeatsPerLoop].NoteHit();
223+
_laneLastBeat[(int)type] = (curBeat) % CM.BeatsPerLoop;
224+
HandleTiming(type, beatDif);
225+
}
226+
227+
private void HandleTiming(NoteArrow.ArrowType type, double beatDif)
228+
{
229+
if (beatDif < _timingInterval * 1)
230+
{
231+
GD.Print("Perfect");
232+
Enemy.TakeDamage(3);
233+
NotePlacementBar.HitNote();
234+
NotePlacementBar.ComboText("Perfect!");
235+
}
236+
else if (beatDif < _timingInterval * 2)
237+
{
238+
GD.Print("Good");
239+
Enemy.TakeDamage(1);
240+
NotePlacementBar.HitNote();
241+
NotePlacementBar.ComboText("Good");
242+
}
243+
else if (beatDif < _timingInterval * 3)
244+
{
245+
GD.Print("Ok");
246+
Player.TakeDamage(1);
247+
NotePlacementBar.HitNote();
248+
NotePlacementBar.ComboText("Okay");
249+
}
250+
else
251+
{
252+
GD.Print("Miss");
253+
Player.TakeDamage(2);
254+
NotePlacementBar.MissNote();
255+
NotePlacementBar.ComboText("Miss");
256+
}
257+
}
258+
#endregion
259+
260+
private void PlayerAddNote(NoteArrow.ArrowType type, int beat)
261+
{
262+
// can also add some sort of keybind here to also have pressed
263+
// in case the user just presses the note too early and spawns a note
264+
GD.Print(
265+
$"Player trying to place {type} typed note at beat: "
266+
+ beat
267+
+ " Verdict: "
268+
+ NotePlacementBar.CanPlaceNote()
269+
);
270+
if (NotePlacementBar.CanPlaceNote())
271+
{
272+
Note exampleNote = new Note(type, beat % CM.BeatsPerLoop);
273+
if (AddNoteToLane(exampleNote, false))
274+
NotePlacementBar.PlacedNote();
275+
}
276+
}
277+
}

0 commit comments

Comments
 (0)