Scrolling Note Style Music Game Help

Started by
12 comments, last by Paragon123 10 years, 5 months ago

Hey guys!

For my senior project in college, I am developing a music game. However, I've run into a major roadblock in said development.

I'm not sure how to get the notes to "spawn" and scroll in time with the music.

Basically the game plays a bit like titles such as Guitar Hero/Rock Band where notes are scrolling down the screen. The presentation is slightly different (more Guitar Freaks style than Guitar Hero).

I make .midi files containing the note charts that will correspond to the music and, using a library, parse the midi notes into a type of game object that represents the note visually.

From there though, I'm stuck. All I've been able to deduce is that I'll need to make sure the animation syncs up in time with the music as opposed to a fixed frame rate (which I suppose is fairly obvious). But really, the difficulty for me seems to be how to spawn the notes so they hit the strike-zone (where the player needs to hit the button to play the note) in time with the music.

Basically, I was hoping someone here could maybe provide a starting point in terms of ways to approach doing this. I'm not looking for actual code, but more pointers on how to approach this, and possibly some pointers to other resources that may cover this already. I've looked around a little bit on how to do it but haven't found anything totally helpful.

Thanks in advance!

Advertisement

Ok, so first I have no experience with this so, i could be very wrong about this. But here are my initial thoughts.

I would create an object that contained the following information

1) KeyMask (buttons corrosponding to correct note)

2) StateMask: (1=press, 2=held, 4=released)

I would create two queues of these objects, SongQueue and BufferQueue

and an array of these objects ActiveNotes.

break the song into fixed time steps, say 5 per second. so a song of a minute and thirty seconds would have 450 of these objects.

determine the length of time it takes for the animation to start at one end and enter the strike-zone. Assume it takes 5 seconds

populate the buffer queue with 25 empty objects (no mask) (5 seconds * 5 per second)

Determine the length of time it takes for the animation to enter the strike-zone and exit the strike-zone (assume 1 second)

Allocate room for 5 (1 second * 5 per second) of these objects in the ActiveNotes array.

During game play I would pop one out of the song queue and an enqueue it into the buffer queue at a rate of 1 every 1/5th of a second.

The animation starts when an object with a mask of press is enqueued into the BufferQueue

The note plays when the object with a mask of press is dequeued from the bufferQueue.

when one of the objects is dequeued from the bufferQueue, place into the i%5 indice of the ActiveNotes list

Every 1/5th of a second poll the keyboard. Check the keyboard state against the notes in the active list.

So... the song is encoded as A,B,C: button press, +=press or hold, - = hold ~=hold or release <=release, 0=no mask

So

A++-----~~<

would give the user 1 and 3/5ths of a second to press A, then they would have to hold it for 11-6 second and then they'd have up to 1 and 3/5ths of a second to release the button.

(The extra one second is every calculation is how long it would take for the last of a given action to leave the active list)

Active queue Buffer Queue Song Queue T (Time in seconds)

00000 0000000000000000000000000 A++-----~~< T=0

00000 000000000000000000000000A ++-----~~< T=0.2 (A button press animation starts)

00000 00000000000000000000000A+ +-----~~< T=0.4 (A button press animation starts)

00000 A++-----~~< T=5.2
0000A ++-----~~< T=5.4 (A note plays, and A note animation enters strike zone) players can press A

000+A +-----~~< T=5.6 players can press or be holding A

00++A -----~~< T=5.8 (See above)

0-++A ----~~< T=6.0 (See above)

--++A ---~~< T=6.2 (See above)

--++- --~~< T=6.4 Players can press or be holding A (due to the + still in active notes)

--+-- -~~< T=6.6 (See above)

----- ~~< T=6.8 Players must be holding A, pressing or releasing is incorrect

-~--- ~< T=7.0 Players can be holding or releasing A

~~--- < T=7.2 (See above)

~~--< T=7.4 (See above)

~~-0< T=7.6 (See above)

~~00< T=7.8 (See above)

~000< T=8.0 (See above)

0000< T=8.2 players can only be releasing

00000 T=8.4 Song is over

I already see a few issues, for example being required to release the key after already being allowed to release or hold is no good.
And being required to release or hold after already being allowed to release is also not good.
Instead, maybe it should be looked at key must be released that that point. But that's my thoughts anyway.
*EDIT*:
Thinking about it more I think the state masks would have to be
Press
Press or Hold
Hold
Release or Hold
Released, Release or Hold
Released or Release
It feels as though you might be able to skip the press, and go strait to press or hold, but then pressing early doesn't cause an issue, and it is a better trigger since you'd only have one per note rather than a press and hold for which there would be multiple per note.

It's definitely more than I initially had! However, I've tried to follow along with it and adapt it in a manner that would make the values dependent on the song itself such as BPM, and so on to define how many steps should be there per second, etc.

This is where I run into problems. Basically I want the fixed time songs to be in relation to the BPM, and then everything that depends on those need fall in to place. One big issue I have is, probably due to lack of sleep and not realizing how trivial this may or may not be, trying to make an array accessible by the whole class this is being done in allocated to the size of the amount of active beats dependent on the length of time to hit the strike zone * the amount of beats per second.

I think I'm a bit confused by parts of it but at this moment in time I'm too tired to even begin to attempt to articulate which parts exactly.

*falls asleep on keyboard*

Sorry to bump this topic back up, but I have scrolling notes working now. =]

However, not all is well, as I'm not sure how to modify the speed of the scroll similar to how they do it in Guitar Hero or Rock Band.

What I mean by this is, right now, the notes scroll incredibly fast but hit the target area on queue. However, I want to slow it down so it's still playable without requiring lightning fast reflexes. For an example of what I mean, compare the following two videos:

Guitar Hero III: Dragonforce Expert (No Hyperspeed)

Guitar Hero III: Dragonforce Expert (Hyperspeed 5)

Right now what I have is more akin to hyper speed 5, where everything is blazingly fast and notes appear to spread apart more. However, I want it more akin to the first video where notes are seemingly slower and closer together.

From a conceptual standpoint, how would I go about doing this? The music has to play at the same speed regardless of how fast the track comes.

What I've tried to do is use a float of at least value 0, representing a percentage speed of the track. Now it makes the notes move slower, but they don't hit on time. I've tried using the same percentage to affect the time they appear, but can't seem to get it to work. Am I going about this the wrong way? To "spawn" a note on the track, I check if it's in a range of time near the time it should appear, and then spawn it.

Help would be much appreciated. I'm sure the answer is rather simple and I'm just no thinking about it in the "right" way.

Thanks a ton!

Look at the variables in play.

1) Time when notes spawn

2) Time when notes hit strike zone

3) Speed at which notes travel

4) Location that notes spawn

5) Location of strike zone

As you can see, the Time and location for the notes hitting the strike zone are the most important variables and can not change no matter speed or difficulty, similarly the location at which the notes spawn should not change either. That leaves only the time when the notes spawn and the speed at which the notes travel.

Lets say the notes have to cover a distance D (pixels from spawn point to strike zone) and lets say that the note has to be in the strike zone at time T=t. To modify difficulty you would set a value N= notice which could be 1 second for difficulty A and say, 3 seconds for difficulty B.

Then you have to spawn the note a time = t-n and the note has to travel at a speed of D/n

Assuming the note needs to hit a T=10 and the note sliding distance is 100 pixels, in difficulty A you need to spawn the note at T=9 and it has to move 100 pixels per second.

In difficulty B you need to spawn the note at T=7 and move at 33.3 pixels per second.

Okay, so that makes a lot of sense and is a great starting point. However, I still can't get it to seem to work properly.

A few things first though, the speed, without modification, needs to be based upon the bpm of the song. For any song. Also, I'm dealing in milliseconds.

Basically my issues lie with the fact that I need to get the speed value based upon the bpm, and in milliseconds at that.

I can't even seem to get this to work sans that though. There seems to be a disconnect between n and the speed = d/n.

I have the following:


n = (60000 / music_track->bpm) * speed_modifier;
speed = distance / n;

Where distance happens to equal 643 pixels and bpm happens to equal 136, and the speed_modifier happens to equal .5;

Note spawn time is determined as such:


double note_time = (notes[n]->getTimeStamp() * 1000) - n;

With this in mind, the notes spawn very close to when the song plays the actual corresponding sound in the background, but they move so slowly that the song has actually played several notes in the time it takes for a single note to hit the strike zone.

Basically, I'm not sure how to go about modifying what I have so that it works properly. =\ *feels stupid*

Anywhoo, thanks to anyone who can help me out here!

There are two things that look confusing.

First you calculate notification time based on beats per minute... that means that a song with a higher BPM will give the user less notification at the same difficulty then one with a lower BPM. In my opinion n should be constant, the user should get the same amount of notification regardless of BPM... the faster BPM will already increase difficulty by requiring more keys in the same time frame, the player should still get the same amount of time to prepare for each note though. (The speed_modifier would still be useful for playing the same song on different difficulties though)

The second thing is in the formula for determining spawn time.

what is Notes[n]? The nth beat in the song? Or is it the beat at time=n?

The note_time equation looks like n is being used as a value measured in beats and a value measured in time in the same equation... To me, that looks like the root of your issue.

Notes[n] is the note that needs to be spawned. It's a vector of a class that contains the information from the midi file. The timestamp() value is the time when it should cross the strikezone. I think I'm subtracting the wrong value from it to get the notification time to be honest, but I don't really know WHAT it should be instead. You have a valid point there about the bpm though. However, doing it with a constant value still didn't seem to work, I'll try it one more time though because I may have screwed up some of the time conversions from seconds to milliseconds.

Here's what I am finding weird... According to the snippet you showed

double note_time = (notes[n]->getTimeStamp() * 1000) - n;

Your either iterating over all the notes with an iterator that is not n and therefore note_time ends up being the same for every iteration.

-OR- you are using n as the iterator and there fore each note_time has a larger difference from the original timestamp then the previous...

So it's either

note_time[1]=notes[1]->getTimeStamp()*1000-1

note_time[2]=notes[1]->getTimeStamp()*1000-1

note_time[3]==notes[1]->getTimeStamp()*1000-1

-or-

note_time[1]=notes[1]->getTimeStamp()*1000-1

note_time[2]=notes[2]->getTimeStamp()*1000-2

note_time[3]=notes[3]->getTimeStamp()*1000-3

---------------

When in needs to be

note_time[1]=notes[1]->getTimeStamp()*1000-n

note_time[2]=notes[2]->getTimeStamp()*1000-n

note_time[3]=notes[3]->getTimeStamp()*1000-n

Does the first note hit at the correct time?
Do the notes get progressively slower/further off over time?

Oh sorry, typo when writing up the code. I changed the variable names in the post so we would be on the same terms, but left one variable the same which caused the confusion.


double note_time = (notes[i]->getTimeStamp() * 1000) - n; // first n should've been i

This topic is closed to new replies.

Advertisement