csanyk.com

video games, programming, the internet, and stuff

Tag: GM:S

GameMaker Tutorial: Audio speedup with sync

In so many games, music speedup is a great way to get the message to the player that they need to hurry up and get things done.

It’d be great if you could simply set a new tempo with a simple GML function, and have the current background music adjust on the fly. Something like audio_sound_set_speed(sound, speed) would be lovely. But it’s not as simple as that in GameMaker, as I found out recently.

Here’s how I implemented a speedup for my GMLTetris project:

First, I created two music tracks, one at normal speed, and one at double speed, and added them to the game project as sound assets.

Everything else is just programming:

if <condition> {stop slow_music; start fast_music;}

This is easy enough, the trickiest part is probably getting the condition right to switch tracks, but depending on the game, that condition could be very simple, too.  The only real complication is that if you’re checking the condition repeatedly, as you normally would every Step, you only want to trigger the changeover once.  So to do that, set up a variable to track whether the switch has happened already, and check it, too, even if the condition that triggers the changeover continues to remain true on successive steps.

if <condition> && !music_switched {stop slow_music; start fast_music; music_switched = true;}

The music speedup that happens in a game like Super Mario Bros., where the music speedup occurs when the level timer hits 100, is a typical example of such a technique. If you only need to do a single, one-way switch, this is all you need.

If your game needs to switch back and forth between slow and fast music, your conditional needs to be more sophisticated.

if <condition>
{
   if !<fast_music_already_playing>
   {stop slow_music; start fast_music;}
}
else
{
   if !<slow_music_already_playing>
{stop fast_music; start slow_music;}
}

Here, because the game can switch multiple times, when the condition check happens, we can’t get away with a music_switched variable that changes one time. What we need to do is check to see if the music we need to switch to is already playing, and if not, stop the current music and switch to the other music.

One thing to keep in mind, this basic technique will start the fast music from the beginning of the track. This might be what you want, but it would also be good if you could start the fast music at the position where the slow music was, for a seamless transition.

GML has the functions to do this: audio_sound_get_track_position() and audio_sound_set_track_position(). But in order to make use of them, we need to do a bit more work.

First, since the gml functions return the absolute time position of the track, and since two tracks play at different speeds, we need to adjust the position proportionately when we switch tracks, so that the position is at the same position percentage-wise. This is actually easy, as long as we know the tempo change, which we do. Since the fast track is double speed, we can easily calculate the equivalent position in the other track by multiplying or dividing by 2.

Slow to fast: position *= 0.5;

Fast to slow: position *= 2;

Where I ran into trouble was, I needed to be able to switch both ways. It seemed like it should be simple — just check whether the desired track is already playing, and if not, get the position of the current track, adjust it proportionately, start the desired track, set the position. Easy, right?

Let’s look at it in pseudocode first:

if <condition to switch to fast music>
{
   if audio_is_playing(slow_music)
   {get position; stop slow music; start fast music; set position;}
}
else
{
   if audio_is_playing(fast_music)
   {get position; stop fast music; start slow music; set position;}
}

This was when I discovered that audio_play_sound() returns a handle for identifying the specific instance of the sound that is playing. This is necessary to use to set the track position of the playing sound. You can’t just set the position for the sound_index; you have to set it for the specific handle of the currently playing instance of the sound. If you set the track position for the sound_index, any time that sound resource is played in the future, it will start from that position.

///Create Event:
bgm = audio_play_sound(slow_music, 10, true);
///Step Event:
if <condition>
{
  if audio_is_playing(slow_music)
  {
  var pos = audio_sound_get_track_position(bgm);
  audio_stop_sound(bgm);
  bgm = audio_play_sound(fast_music, 10, true);
  audio_sound_set_track_position(bgm, pos * 0.5);
  }
}
else
{
  if audio_is_playing(fast_music)
  {
  var pos = audio_sound_get_track_position(bgm);
  audio_stop_sound(bgm);
  bgm = audio_play_sound(slow_music, 10, true);
  audio_sound_set_track_position(bgm, pos * 2);
  }
}

I also discovered that detecting which track is playing with audio_is_playing() does not work for this purpose. I still don’t have a clear understanding of what was happening in my code, but some debugging showed that my track position calculations were being distorted by being called multiple times. This doesn’t make sense to me because the song should no longer be playing after the first step when the switch condition is met. But my theory is that since the audio is played in another process from the main program, there’s some messaging going between the two processes asynchronously, and as a result audio_sound_is_playing() can return true a step later after the message is sent to stop the track playing.

By trying to set the track point multiple times in quick succession, weird and unexpected results happened, and the tracks switched but did not set to the correct position.

So I had to come up with a surer method of knowing which music is playing.

Debugging was tricky, since I couldn’t tell from listening where the audio position was. To aid debugging, I drew the position of the playing track to the screen, and then I was able to see that the position was not being set correctly as expected. Somehow, switching from slow to fast would drop the track back from about 10 seconds to 2 seconds, and then switching from fast to slow would jump from 2 seconds to 38 seconds.

I couldn’t figure out why that was happening, so I tried using show_debug_message() and watched the output console, and saw that the track position would update 2 or 3 times when the tracks switched; I was expecting it to only update once.

This is what clued me in to what I believe was happening due to the multiple processes communicating synchronously.

The solution I used in the end was easy and simple: instead of checking which track was currently playing, and switching from slow to fast or vice versa based on the currently playing audio asset, I just added a new variable, condition_previous, and compared condition to condition_previous, and made the switch happen only when the current condition didn’t match condition_previous. This only happens in one step, when the condition changes from false to true, or vice versa, and so the track position is set once, when the bgm switches tracks and syncs up the new track to where the old track left off.

switch_previous = switch;

switch = <condition to check for switching to the fast music>;

if switch && !switch_previous
{
var pos = audio_sound_get_track_position(bgm);
audio_stop_sound(bgm);
bgm = audio_play_sound(fast_music, 10, true);
audio_sound_set_track_position(bgm, pos * 0.5)
}
else
{
if !switch && switch_previous
{
var pos = audio_sound_get_track_position(bgm);
audio_stop_sound(bgm);
bgm = audio_play_sound(slow_music, 10, true);
audio_sound_set_track_position(bgm, pos * 2);
}
}

This works, because it guarantees that the condition checks will be accurate, as they do not depend on checking the status of the audio playing in another thread.

Product Review: Scirra Construct2

Construct2 is available for download at www.scirra.com.

I’ve known about Construct2 for a few years now, and had downloaded it quite some time ago, intending to compare it with GameMaker in order to see which I liked better. I kept getting deeper and deeper into GameMaker, though, and since I was enjoying that, I wanted to stick with one thing until I knew it very well, rather than dabble in a lot of things that I knew only passingly.

One of my Cleveland Game Developers friends, Jarryd, likes Construct2 and I’ve seen him give a few talks about it, and so I’ve had a general impression of what it’s about for a while now. This weekend, I finally sat down with it and started to give it a serious look.

Initial impressions

So far, it feels very different from what I’m used to with GameMaker: Studio and other development environments that I’ve used… but I think there’s a lot of potential for getting stuff up and working faster than with GameMaker.

Two of Construct2’s areas of strength are the built-in project templates and object behaviors. They take a lot of the tedium out of developing your own engine and having to program everything from scratch, which means you’re freed up to focus in design and gameplay more. Creating a new project from a template sets up a lot of “boilerplate” that is common to every game of that type, saving you a ton of work and problem solving. And adding a behavior to an object does in one or two clicks what many programming numerous events and scripts consisting of innumerable lines of code would accomplish in a GameMaker project. And it all works and doesn’t need debugging, although there’ll still be a lot of customization yet to do, and that customization will require plenty of problem solving and debugging. But it still gets you into the juicier parts of game development quicker, and allows you to build on a more featureful foundation than GameMaker does.

On the other hand, what I like about GameMaker is that by leaving these low hanging fruits un-plucked, it gives a newbie programmer some relatively easy things to develop, which affords many learning opportunities. Learning how to attack a problem and break it up into simple, manageable steps that you can solve is an important skill to have in programming, and GM provides such opportunities.

The C2 documentation is very well written, and there are a ton of example projects that come with the IDE, so you can learn by playing around with a project.

It feels different from traditional programming in that there’s no traditional text editor, and not much syntax to learn, for about 90% of it, from what I see so far. If *feeling* like a “real” programmer is important to you, Construct2 may not satisfy, but if you don’t care about coding as much as the ability to quickly make working games, it might be just the trick. I feel like “real” programming is more like designing shapes of pieces to make a jigsaw puzzle, and then assembling the puzzle, and using Construct2 is more like taking a bunch of ready-made jigsaw puzzle pieces out of a bin and putting them together *just so* in order to make a picture that you have in your head. But I don’t consider criticisms that amount to bias toward text editing and syntax as the only true programming to have much legitimacy to them. Surely, if you never understand the circuits of the machine, you’ll never be able to call yourself a Real Programmer, and most modern programming languages abstract the machine entirely. So too, with programming environments that replace linguistic syntax with visual paradigms. Still, learning Construct2 may not be as good a good first step if you’re interested in getting into other types of programming, the vast majority of which do involve coding in a programming language.

Discovering Construct2 through example

One of the first things I did with C2 was to play the Asteroids example project. Labeled as an “Intermediate” project, I quickly noted that while the Player wrapped around at the edges of the screen, the Bullets did not. This bothered me, so without really knowing what I was doing, I looked at the Player’s behaviors, and saw how to modify the Bullets. It took almost no time at all.

But now, the bullets just traveled around the room forever, so in short order I figured out how to add a timer to them so that they would be destroyed after a short time. This took a bit longer, but in maybe 10 minutes I had it figured out. Next, I created a new Sprite (which seems semi-analogous to what GameMaker calls an Object) and added it to the game, defined some behaviors and before too long I had asteroids floating about, that destroyed the ship when they collide with it, are destroyed by bullets, and wrap around the room. I even figured out how to create two smaller asteroids when destroying the large ones.

That’s when I discovered that, if you don’t add an object to the Layout, even if it won’t exist in the initial state of the game, the game won’t run properly. I noticed a previously overlooked bullet sitting in the Layout window, outside the game view, and, thinking I’d somehow accidentally placed it there by mistake, deleted it, only to find that the game no longer worked properly. And then I got an error message about the smaller asteroids not being defined. So then I figured out that in order to have these types of objects available to the game at runtime, they needed to be placed in the Layout, but outside of the visible area, what in GameMaker would be considered “inside the Room”. This confused me, because coming from GameMaker, I expected that objects placed outside of the rooms boundaries are instantiated and run in the game. But in C2, apparently they are just available to the game, to be created when called upon by the program. It’s a bit strange, and I wonder how C2 handles objects that walk “offstage” or need to begin life offstage.

Cost

Construct2 is one of the cheapest options out there right now for fledgling developers. Comparing Construct2 to GameMaker, at $119 C2 is cheaper for a license than GameMaker: Studio is, if you want anything more out of GM:S than the base “Professional” package. The free edition of C2 also has fewer limitations than the free edition of GM:S. There’s also a $400 “business” license, which is for professionals and businesses that have made $5000 or more from game development, but doesn’t seem to give the user any additional new features. I suppose the idea there is that businesses that make that much money from game development can afford to subsidize development for the rest of the customer base.

Performance

I haven’t benchmarked the two side by side, but I understand that C2 builds everything as an HTML5 app and (if you’re not targeting a web browser) wraps it in a native application for whatever platform it builds to. By contrast, GM:S has the option to build native code, depending on how you build it and what platform you’re targeting, so may potentially have performance advantages over Construct2. I don’t want to speculate, and for now it’s merely a hypothesis that I have not myself tested, but it seems plausible that GM:S would the equivalent game as well or better than C2 on most platforms.

On the other hand, C2 is probably more consistent across platform, since on every platform it is essentially running the same code, unlike GameMaker:Studio, which currently has numerous problems with supporting features and getting to work exactly the same, regardless of build target.

Final thoughts (for now…)

I still haven’t gotten very deep into Construct2, and have just barely begun to grasp what it is capable of, but so far I like it quite a bit. Whether I like it as well as GameMaker: Studio, or less, or better, I can’t say yet, but I like the fact that it exists,and and it provides another option for an easy to use tool for game development. I still am much more versed and comfortable with what I know in GameMaker, but I’m impressed with how quickly I was able to pick up Construct2 and do something useful with it.

Verdict: Worth checking out.

csanyk.com © 2016
%d bloggers like this: