• Hey! Guest! The 39th GMC Jam will take place between November 26th, 12:00 UTC and November 30th, 12:00 UTC. Why not join in! Click here to find out more!

Mini game: Rotate pipe parts to allow water to flow through

Locke

Member
Hey community,
I’d like to create a mini game similar to the one in bioshock where you have to rotate and move pipe parts to allow water to flow through and complete the connection.
Displaying, adding, and rotating pipe objects on a screen would be easy enough, but I have no idea how to create the water to go through the pipes once the pipe route is complete. Figured I’d ask and see if anyone had any suggestions.
Cheers :)
 

TheouAegis

Member
Two ways you could do this. The simplest way for a beginner would be to make each piece its own object with its own timer to keep track of how far along the water has flowed through it. The more complex method would be to bitmask the sprite_index with the timer and store the value in an array or similar data structure. Each pipe shape would be its own sprite. Each sprite would have the same number of frames which shows the water flowing through the pipe piece.

If you use the object method, the image_speed to 0 in the Create Event so the pipes don't actually fill up right away. For the array method, you'd only need to worry about setting the sprite_index, since the "timer" mask would be 0 to begin with anyway. I'll stick with the object method just because it's easier to explain. So in the object, you'd only need a Create Event (to set its image_speed to 0) and an Animation End event. In the Animation End event, check what the sprite_index is. The sprite_index, coupled with the image_angle, will tell you which directions the water should flow. For each of those directions, check the adjacent object. If the object has a pipe sprite, check if that adjacent sprite matches up. If it does, set image_speed of the adjacent piece to however fast you want. If the sprite is a hazard or wall, tell the program you have a spill (in some versions, a spill is acceptable as long as there are more pipes to flow through). If the adjacent piece is the goal, tell the program you reached the goal.

As far as being able to tell which sprites can connect with each other, you will want to bitmask the directions each sprite allows. To do this quickly and simply, you'd need 16 sprites. I doubt you'll be using 16 sprites, so the better alternative is to assign the bitmask to a variable inside each instance based on its sprite. If you're not familiar with bitmasking, each power of 2 (i.e., bits) can be added together and retain their "identity." Bits are 0-indexed, so setting bit 0 is the same as 2 to the 0 power, which is 1. Setting bit 1 is the same as 2 to the 1st power, which is 2. Setting bit 2 is the same as 2 to the 2nd power, which is 4. If you set bits 1 and 2, you get 2+4=6. We can apply this to the pipes by attributing bit 0 to being open on the right, bit 2 to open at the top, bit 2 to open on the left, and bit 3 to open at the bottom. Thus, a value of 6 would mean the pipe is open at the top and left. In the Create Event of the pipe object, you can set a variable to hold each appropriate bit and then just use that variable instead of trying to sort the sprites all the time. Also note that you don't actually need a sprite for all directions. A horizontal pipe and a vertical pipe are the exact same thing, if you want (like in Tetris). You'll also want to enumerate the directions. Since you likely won't have 16 sprites, you should just do something like this in the Create Event:
Code:
switch sprite_index {
    case spr_pipe_straight: value = 5; break; //bits 0 and 2 are set
    case spr_pipe_j: value = 6; break; //bits 1 and 2 are set
    case spr_pipe_l: value = 3; break; //bits 0 and 1 are set
    case spr_pipe_x: value = 15; break; //bits 0, 1, 2, and 3 are set
}

enum opening {
    right = 1,
    top = 2,
    left = 4,
    bottom = 8
}
Rotating the pieces is pretty simple. Or, rather, it would be if we were working in assembly. You'll need to create a function to perform a logical rotation on the bits. Normally, when you multiply or divide a number by 2, it shifts all the bits in that number up (multiply) or down (divide). If a bit shifts to an illegal position, such as if you divide and bit 0 shifts down to -1, that bit is normally lost to that number. So if you divide 3 by 2, it becomes 1 (yes, it is actually 1.5, but I won't get into that here). By performing a rotation, you rescue that lost bit and tack it on to the other end of the number. We need to create a function to do that for us.
Code:
//argument0  value
//argument1  + for rotating COUNTERclockwise
//argument1  - for rotating clockwise

var n=0, c=0;

repeat abs(argument1) {
    if argument1 > 0 {
        c = argument0 << 4 & 8;
        n = (argument0 >> 1) | c & 15;
    else
    if argument1 < 0 {
        c = argument0 >> 4 & 1;
        n = (argument0 << 1) | c & 15;
    }
}
return n;
So when the player wants to rotate a piece, or when you want to have it rotated when the piece is created, you can call that function (I'll call it rotate() for simplicity) when you rotate the sprite.
Code:
image_angle += 180;
value = rotate(value,-2);
That would rotate a piece 180 degrees.

To compare pieces, you check each bit in the value and then check the bit 180 degrees away from it in the adjacent cell. So if bit 0 is set in the filled pipe, you'd check if the piece to the right has bit 4 set. Thus in your Animation End event, you could have something like this:
Code:
image_index = image_number-1;
image_speed = 0;
var inst;
var i = 1;
repeat 4 {
    if value & i
    switch i {
        case 1: 
                inst = instance_position(x+sprite_width, y, object_index); 
                if inst && inst.value & 4 
                    inst.image_speed = 1/4;
                break;
        case 4:
                inst = instance_position(x-sprite_width, y, object_index); 
                if inst && inst.value & 1
                    inst.image_speed = 1/4;
                break;
        case 2:
                inst = instance_position(x, y-sprite_height, object_index); 
                if inst && inst.value & 8
                    inst.image_speed = 1/4;
                break;
        case 8:
                inst = instance_position(x, y+sprite_height, object_index); 
                if inst && inst.value & 2
                    inst.image_speed = 1/4;
                break;
    }
    i *= 2;
}

with object_index 
    if image_speed == 0
        global.leak = true;
 

Locke

Member
Wow! Thanks for the comprehensive response! I’ll try it out and let you know if it works. I’ve only been using gamemaker for a year now so I’d say my skill is boarderline intermediate ish... definitely no expert just yet but thanks again :)
 
Top