Purgatory Purgers: Simple Music Control

Background

In past projects, I controlled music using the room creation code, e.g.:

audio_stop_all();
audio_play_sound(snd_music1,100,true);

This would stop all previously playing audio and start the new music track, looping it until the next room was created.

Functionally, this works fine, but I wanted a more centralized way of handling this so that if I wanted to make a change, I didn’t have to do it N number of places, equal to the number of rooms I created.

How It Works

I created an object called “obj_sound” and placed it in the first room, then I flagged it as ‘Persistent’ so that it would carry forward from room to room.

I added a Room Start event with the following code:

if room==rm_level1 {
audio_play_sound(snd_lvl1,100,true);
}

if room==rm_level2 {
audio_play_sound(snd_lvl2,100,true);
}

At the start of the room, it checks to see if the name of the room matches “rm_level1” (the name of the first room/level), and if it does, it plays the sound “snd_lvl1” at a priority of “100”, and sets looping to “true.”

Once that was working, we needed to be able to stop the previous music track before loading the next one. This can be done using a Room End event:

audio_stop_all();

This stops all audio before the next room is loaded, preventing tracks from playing over each other.

Conclusions

There are other ways this could be handled, however…

  • It works for what I need it to do (starts a looping music track that’s definable by room name).
  • It’s centrally controlled.
  • It does this in the simplest way I know of, and more importantly, I understand it!

Beaster’s Dungeon Revisited: Part II

Continued from Part I...

With Eric gone, I eventually re-coded everything, but never could work out how to implement the trap mechanic, so I shelved it and there it sat…until now…

###

I was talking with a friend who was admiring some CGA-16 color palette pixel art, which lead me to showing him the artwork I’d done for Beaster’s Dungeon. He asked if I had any intention of picking the project back up, to which I replied that I might, albeit with a reduced scope. I explained the difficulties I encountered with the trap mechanic, specifically, getting the cursor to snap to a grid.

This lead to a discussion of where I got stuck, and in order to illustrate this, I fired up GMS and started creating a prototype to illustrate the problem. I reached out to my friend Jason (who did the programming for ‘Milk Smugglers’) for suggestions, and he linked a forum post containing the floor/ceiling functions.

In that post was another mysterious function called, move_snap. Here’s what I did for the prototype:

o_cursor Object

///step event

//set coordinates of the cursor to the mouse's x,y position:
x = mouse_x;
y = mouse_y;

//snap the object to nearest grid coordinate 
move_snap (16,16);

Why is this important? Because it allows for precise placement of objects in a map comprised of 16×16 pixel tiles (or any predefined ‘snap’). Around this, additional rules and checks could be incorporated to ensure traps could only be placed where they were meant to be!

At long last, I was finally able to implement the trap mechanic! The only thing left to complete the prototype was enemy path finding… This turned out to be far more complicated than any of the other mechanics combined.

Stay tuned for Part III!

The Fourth Level

As mentioned in a previous post, Level 4 of the GPC was not a lesson, but rather a series of challenges meant to reinforce what was taught in Level’s 2 and 3. This time, I elected to create a new game rather than modify an existing one.

I settled on a top down shooter/adventure game and added features and flourishes as I went along. I used my earlier conceived true grid movement code, but this time, I used a switch statement instead of a series of if statements (or at least fewer of them):

switch (keyboard_key)
 {
  case vk_left:
  case ord("A"):
     sprite_index=spr_player_walk_left;
     if moving=0 {
       moving=1
       instance_destroy(obj_target) 
       target=instance_create(x-64,y,obj_target)
       move_towards_point(target.x,target.y,4) 
      }
      break; 
 case vk_right:
 case ord("D"):
    sprite_index=spr_player_walk_right;
    if moving=0 {
      moving=1
      instance_destroy(obj_target) 
      target=instance_create(x+64,y,obj_target)
      move_towards_point(target.x,target.y,4) 
     } 
     break;
 case vk_up:
 case ord("W"):
    sprite_index=spr_player_walk_up;
    if moving=0 {
      moving=1
      instance_destroy(obj_target) 
      target=instance_create(x,y-64,obj_target)
      move_towards_point(target.x,target.y,4) 
    } 
    break;
 case vk_down:
 case ord("S"):
    sprite_index=spr_player_walk_down;
    if moving=0 {
      moving=1
      instance_destroy(obj_target) 
      target=instance_create(x,y+64,obj_target)
      move_towards_point(target.x,target.y,4) 
     } 
     break; 
 }

While trying to figure out line of sight, I came a new function:

collision_line(x,y,obj_player.x,obj_player.y,obj_barrier,1,0)

That was my first time using the collision_line, which I don’t think is covered at all in the GPC, though it might be included in his how-to’s somewhere…I came across it after watching a tutorial video by “GM Wolf” on YouTube:

This would have solved a lot of issues for me…for instance, in a previous project, I had an idea to create a trap comprised of two moving blocks which collided into and bounced off of each other. While everything worked fine, it would occasionally cause the block to get stuck in a wall. This happened because it’s movement caused it to overlap before it detected the collision, thereby getting stuck inside the wall, unable to go anywhere.

I solved this before using the place_meeting function, but was never introduced to the End Step Event, which  would have been the right way to do it. Nevertheless, I can think of other uses for collision_line and am glad I learned of it!

All in all, I spent about 2 days (most of the weekend) working on this little mini-game, and here are some of the features I included:

  • Destructible walls that advance in damage by manipulating the image_index
  • Enemies that can navigate mazes (using John Janeka’s code from Level 12)
  • Line of Sight for enemies with projectiles
  • Health, ammunition and keys global variables that persistent between rooms
  • Lock and key mechanism
  • A switch that reveals the exit when touched
  • Randomized muzzle flare and smoke when firing bullets
  • Randomized impact splatters when an enemy is hit
  • Different sounds for each impact
  • Randomized health power up sprites to add variety using a single object
  • Exits that allow you to advance to the next room
  • Capped health at 100%

What I not did include were:

  • Fail condition/game over when you run out of hitpoints
  • Start screen
  • Game Over screen

All in all, it’s a neat little game though unfinished, and good practice for more serious projects to come!

A Challenge Within a Challenge!

I’m up to Level 08, Lesson 03, and was working my way through the challenge that followed.

In playing through the game as-is, there a couple of notable bugs above and beyond what the challenge required:

  1. The collision event for the potion was missing altogether – easy fix
    givePlayerHealth(25)
    with other {
         instance_destroy()
    }

     

  2. The “health bar” didn’t accurately reflect the current hitpoints! Take a good look at the screen capture from his preview video below:

 

Look closely and you can clearly see “HP: 5” yet the health bar is nearly 1/3rd full! That doesn’t look right… so what’s wrong with it?

This one was a bit more tricky because neither draw_rectangle nor var have been covered yet…granted, the challenge was to change the color of the rectangle based on the # of hp, not to create a hitpoint bar from scratch – even so, it bothered me that it was broken and I wanted to see if I could troubleshoot and fix it…here’s the original code:

var healthBarWidth=100
var maxHealth=100
draw_set_color (c_white)
draw_rectangle(150,50,200+hp/maxHealth*healthBarWidth, 60, false)
draw_set_color(c_black)
draw_rectangle(150,50,200+healthBarWidth, 60, true)

The syntax for the draw_rectangle function is as follows:

draw_rectangle(x1, y1, x2, y2, true or false) where

x1=the x location of the upper left-hand corner of your rectangle
y1=the y location of the upper left-hand corner of your rectangle
x2=the x location of the lower right-hand corner of your rectangle
y2=the y location of the lower right-hand corner of your rectangle
true=filled with whatever your draw_set_color is
false=empty (transparent) with a 1 pixel border

Confused? Well here’s an illustration I made to visualize it better:

Now lets apply that to the code above. Working through his arithmetic for the filled (false) rectangle, here’s what you get for the x2 values if you substitute the variables for their numbers:

200+0/100*100=200 (0 hp)
200+100/100*100=300
(100 hp)

Expressed in code, it would look like this:

draw_rectangle(150,50,200,60,false)//0 hp
draw_rectangle(150,50,300,60,false)//100 hp

…and output like this (assuming you include the border rectangle):


The left edge (x1) starts at 150 pixels in, and the right edge ends at 300 pixels, giving you an overall length of 150 pixels (300-150=150) along the x axis.

When the player has 0 hp, the left edge is not 150, but 200, leaving you with a 50 pixel (1/3rd) wide rectangle, which is exactly what you see in the screenshot above.

So I dug into the code and came up with the simplest solution. This was the result:

Notice that it says “HP: 50” and the bar is exactly half way!

So how did I fix it?

Simple – move the left edge over by 50 pixels, giving the overall length of both rectangles to be 100 pixels rather than 150, removing the 50 pixel lead:

//health bar draw code - the first rectangle draws the filled color
//the second rectangle draws the border
draw_set_color (getBarColor())
draw_rectangle(200,50,200+hp,60,false)
draw_set_color(c_black)
draw_rectangle(200,50,300,60,true)

It’s no accident that my code is absent the healthBarWidth and maxHealth variables – since my healthbar is 100 pixels in length, the simplest solution was to shorten the bar. The reason those are included is size the fill rectangle proportionately to the amount of hp you have remaining so that you can name the hp bar longer or shorter depending on your needs.

But you can’t use the code from the original project file as-is…

First, you’d need to set minimum left-edge of x2 to match x1 for your filled rectangle. So instead of:

 draw_rectangle(150,50,200+hp/maxHealth*healthBarWidth, 60, false)

You’d replace 200+hp… with 150+hp…:

draw_rectangle(150,50,150+hp/maxHealth*healthBarWidth, 60, false)

Secondly, you’d need to change maxHealth proportionate to the total amount of your hp (100) with respect to the overall length of your rectangle (150). This would give you 100/150 or 2/3rds (66.6% repeating).

Since hp is evenly divisible by the length of the health bar, this presents a problem as putting in var maxHealth=66 is pretty close, but not a pixel perfect fit inside the border rectangle (it’s about 4 or 5 pixels off).

Nevertheless, if I just had to make a health bar bigger or smaller than 100 pixels, I’d try to use something that divides evenly, hide it using depth and alpha channels, or leave off the border altogether. There may be more elegant ways to solve it, but it’s nearly 2am here and I think I’ve learned what I wanted to from this exercise 😐 .