trying to simulate water as individual pixels

I've created an object that is 1 pixel small called obj_waterWall, it will constantly be moving towards the right because that's the direction my "gravity" will be taking everything. my issue is that i want it to constantly move right if there is no solid object in its path and if there is something in its path i want it to randomly move left or right until it can continue its path just like water would. however the instances just overlap with themselves and whenever i fix this issue the water doesn't behave how id like. Here's the corresponding code I've come up with in its step event:

if instance_position (x,y,obj_waterWall)!=noone { with (instance_position (x,y,obj_waterWall)) { x -=1 && hspeed = 0} }

if (place_free(x+1, y)){

hspeed = 0.1
}

else if (place_free(x,y-1)) or (place_free(x,y+1)) {

vspeed = random\_range(-0.1,0.1)
}

I've also given it a collision event with itself but it doesn't do anything.

I've heard setting the object to solid is universally bad as you want to set your own collisions. thanks

Upvotes: 0

Views: 246

Answers (1)

Thomas Rosebrough
Thomas Rosebrough

Reputation: 75

So I want to preface that this approach is probably not the best way to do what you are trying to do, but I'll answer your question first anyway:

Fixing your current code

First off, the main issue that nothing is happening is because place_free() only checks for objects marked "solid". Just like you said, the solid flag is really only useful if you are using built-in GM physics, which it sounds like you are not. You could instead use !place_meeting() which uses collision boxes, but really you should read the documentation to find which collision function works best for your use case. They are all slightly different, and because you are using objects with a collision box of exactly 1px*1px, using the right one will be very important or you could create some weird unexpected behaviors (especially if your sprite's center point is not in the top left corner). This also applies to your earlier instance_position() function, which may not always behave the way you want.

Similar to not using the "solid" built-in, I also wouldn't recommend using the "hspeed" built-in variable either. It comes with extra baggage that might cause issues down the line if you aren't committed to using all the built-in physics. Instead I would make my own variable in the create event called "hsp" and then at the very bottom of my step event I would add x+=hsp;. Similarly, you don't need a separate collision event if you're already handling your collisions in code.

It also looks like your with() statement (which I assume is to prevent objects from passing through each other?) might prevent any of this code from working correctly even with the above fixes. The way its currently set up is an object checks if any objects are overlapping it's origin, and if so move that object back one and stop its speed. But instead of using with() on the other object, the object doing the checking should probably move itself back and stop its own speed. You will also need a way to stop the objects from running off the edge of the screen.

Your final if statement: if (place_free(x,y-1) or place_free(x,y+1)) does not do what you want it to. The way it is written now it will check if up or down is free, and then move randomly regardless of which way is free, which means sometimes only up will be free, but it will chose to move down. You could fix this in a few ways, but the simplest is to probably split it up into the different possible conditions:

  1. Both UP and DOWN are free
  2. Only UP is free
  3. Only DOWN is free
  4. Neither is free

This is what that would look like:

/* replaced the `place_free()` with `place_meeting()`, although you should test
it out because `position_meeting()` might be better in this instance, since
your objects are only 1px */

if (!place_meeting(x,y-1,obj_waterWall) and !place_meeting(x,y-1,obj_waterWall) {
    // move randomly up or down
} else if (!place_meeting(x,y-1,obj_waterWall)) {
    // only up is free so move up
} else if (!place_meeting(x,y+1,obj_waterWall)) {
    // only down is free so move down
} else {
    // none was free, so you'll have to handle this case, maybe by staying still
    // and setting hsp to zero?
}

And lastly, when you are randomly choosing a direction, do not use random_range(). This gives a continuous float, so if you say random_range(-0.1,0.1) it can return values like 0.003, -0.09999, and any crazy number in between. When you want to choose between discrete numbers you need to use GM's random integer function irandom_range() and then a little math to turn it into the result you want. For example:

var random_integer = irandom_range(0,1);           //This number is always 0 or 1
var scaled_integer = random_integer * 2;           //This number is always 0 or 2
var shifted_integer = scaled_integer - 1;          //This number is always -1 or 1
var final_scaled_integer = shifted_integer * 0.1;  //This number is always -0.1 or 0.1

To make this even shorter, you can write irandom_range(0,1) as irandom(1), which is a shorthand for every time a random range starts with 0.

//Everything combined:
var random_direction = (irandom(1)*2 -1) * 0.1;

Different approach

Like I said at the beginning, your approach is probably not the best way. Namely because it uses objects with a 1px sprite. Not only is having that many instances usually a bad idea, but also GameMaker uses sub-pixel precision, which means you will encounter a lot of unexpected behavior in your code.

A much better approach would be to create a ds_grid of water points and have one object called "Obj_Water" handle all of it. It will require passing some data around, but it can also re-use a lot of the code you already have and will run much faster. If you don't know how to do structs you can just make each point an array of values like [hsp,vsp]. Then step through the grid and for each point, run the simulation code like you have above. Here's what that might look like:

//In your create event put this
water_grid = ds_grid_create(100,100); //Arbitrary numbers for the example

//Set a pixel to be water by doing this
var water_point = [1,0]; //Sets hsp to 1 (going left) and vsp to 0
water_grid[# 0, 0 ] = water_point; //sets the point at position 0,0 to be water

//In your step event you would do this
for(var ix = 0; ix < ds_grid_width(water_grid); ix++) {
    for(var iy = 0; iy < ds_grid_height(water_grid); iy++) {
        // get the current water point
        var water_point = water_grid[# ix,iy];
        // get the hsp
        var hsp = water_point[0];
        // get the vsp
        var vsp = water_point[1];

        // Here you would do your code for colliding, moving points, etc.
        // Watch out for things like water_grid[# ix+1,iy] without first checking
        // if ix+1 is outside the bounds.

        // If you need to move a point, just do this:
        water_grid[# ix+hsp,iy+vsp] = water_point
        water_grid[# ix,iy] = 0
        // But remember that this will overwrite what is already at the destination
        // so be sure to check first what is there.
    }
}

//In your draw event do this
for(var ix = 0; ix < ds_grid_width(water_grid); ix++) {
    for(var iy = 0; iy < ds_grid_height(water_grid); iy++) {
        if( water_grid[# ix,iy] != 0 )
            // draw your blue pixel
    }
}

I know it looks like a lot of code because it's all in one place, but believe me that this will save you a lot of headaches compared to trying to figure out all the stuff that GameMaker is doing in the background with your instances.

Conclusion

Don't be discouraged, your idea sounds cool and you aren't too far off. Hopefully some of the stuff I shared is helpful but remember the most helpful place is always always always the documentation. It answers everything better than I ever could.

Upvotes: 0

Related Questions