Thursday, 9 April 2015

Random Movement

I wrote in a previous post that I wanted to fix the way the player could run off the screen edge. That was easy enough. I basically put the following:



// Allow Movement Within 1024 x 768
if keyboard_check(ord('W'))
{
    if ( y < 0 )
    {
        y = yprevious;
    }

    else y = y - playerSpeed;  
}

if keyboard_check(ord('S'))
{
    if ( y > screenyres )
    {
        y = yprevious;
    }
   
    else y = y + playerSpeed;
}

if keyboard_check(ord('A'))
{
    if ( x < 0 )
    {
        x = xprevious;
    }
   
    else x = x - playerSpeed;
}

if keyboard_check(ord('D'))
{   if ( x > screenxres )
    {
    x = xprevious;
    }
   
    else x = x + playerSpeed;
}

There may be more elegant ways to do it, I don't know, but this works fine. The way it works is, I have an object in my room with a Create event where I hold a bunch of global variables. Two of those are screenxres and screenyres, for holding the screen size (I'll have to see if there's a built in function for that, now that I think of it), plus a value setting playerspeed What the code above does is, when you press a movement key, it takes your current position( x and y), looks at how fast you travel (playerspeed) and figures out where you'll be on the next step. If the x and y values of that future position fall outside the room boundaries (screenxres and screenyres), it stops your movement on the relevant axis - x or y.

All well and good. Right then, screenshot time:





All those dots, they're supposed to move randomly. Random is fine, I can do random movement. Hell, I can whack in the example from the Gamemaker Help:


if irandom(9) = 1 motion_set(random(360), 1 + random(3)); 

But wait, the little buggers are going off-screen! Ok, I'm not having that, I'll sort them out... Shouldn't be difficult, based on how I already did much the same thing with the player movement. Right?

....

Yeah, it wasn't that easy. I'll explain what I tried and why it failed, 'cos it was a bit of a learning experience and probably my first attempt at actually thinking in the way a programmer might.

First of all, I thought the solution would be to pick a random point on the screen and then move the dot towards it. So I put the following:

if irandom(9) = 1
{
futurex = random_range (0, screenxres);
futurey = random_range (0, screenyres);
move_towards_point (futurex, futurey, 3);
}

So that uses random_range to choose two numbers, based on the room size, and stores them in futurex and futurey (future x coordinate and future y coordinate). It then moves the object towards that point.

Now, when I ran it, there was an odd side effect. After a while, all the people ended up clustering around the middle. It took me a while to figure out why, but I think I understand now.

If the object is more over towards one side of the room, the odds are that the next chosen point will be on the other side of the room. The further to the left (for example) the object is, the higher the probability that the next point will be on the right. Because of this, the centre of the screen becomes the point where they will tend to cross paths. To make matters worse, I have buildings which block the routes, so as they're running around, crossing that centre part, they get blocked by these buildings.

So, a rethink was needed. I figured the way to go about it would be to limit the range of possible points that were being chosen. Instead of choosing from any location in the screen area, I would choose a point in a box around the object's current location. Here's the current movement code:
// Set Start Movement

if ( irandom (20) = 1 ) /* 1 in 20 chance to move */
{
    xa = ( x - 100 );  //  define a 100x100 box around current position
    xb = ( x + 100 );
    ya = ( y - 100 );
    yb = ( y + 100 );
   
   
    if  xa < sprite_width  // adjust boundaries of box if they fall outside room size
    {
        xa = sprite_width ;
    }
   
    if ( xb > ( screenxres - sprite_width ))
    {
        xb = ( screenxres - sprite_width );
    }
   
    if ( ya < sprite_height )
    {
        ya = sprite_height ;
    }
   
    if ( yb > ( screenyres - sprite_height ))
    {
        yb = ( screenyres - sprite_height );
    }
   
    futurex = random_range (xa,xb); // pick random point within the defined boundaries
    futurey = random_range (ya,yb);

    move_towards_point( futurex, futurey, 3); // move towards it at speed 3
   
}

This still isn't perfect, but it does a much better job. Although it's a relatively easy concept (pick a point in a box around the current position and move to it), actually breaking it down into steps - thinking in terms of programming - was something I found quite difficult. Anyway, here's how it works, broken down:

// Set Start Movement

if ( irandom (20) = 1 ) /* 1 in 20 chance to move */
The irandom part basically means "pick a whole number (the i part means integer) up to 20 - if it's a one, then go on to the next bit. Otherwise, carry on whatever". In practice, if you skip that part, it means that on each step, the object will attempt to set a new path and never actually travel any meaningful distance. So, 1 in 20 times, it'll go on to the part where we define where it'll move to next:


{
    xa = ( x - 100 );  //  define a 100x100 box around current position
    xb = ( x + 100 );
    ya = ( y - 100 );
    yb = ( y + 100 );
What we're doing here is mentally drawing a box around the current position so we can later choose a point in that box for the object to move towards.

x and y are the coordinates of the current position, so we now take 100 away from the current position, which gives us the position of the leftmost edge of the box . That value, we're calling it xa. To find the rightmost edge, we add 100 to x and we're storing that value as xb. We do something similar with y, giving us the top (ya) and bottom (yb).

Next, we're going to check and see if the box boundaries are valid for our room size. If it falls outside, we'll have to adjust them:
    if  xa < sprite_width  // adjust boundaries of box if they fall outside room size
    {
        xa = sprite_width ;
    }
What this part is doing is taking the value xa (which is the left edge of our box) and seeing if it is less than the width of the object's sprite. We're adjusting the left side if the box, if needed, so that you can place a sprite on that left edge and not have any part of it go offscreen.

    if ( xb > ( screenxres - sprite_width ))
    {
        xb = ( screenxres - sprite_width );
    }
The above does much the same thing, but for the right hand side and the bits of code below do the same check and, potentially, adjustment for the top and bottom of our box Sprite_height and sprite_width are built into Gamemaker - they're read-only values that give you the object's sprite height and width in pixels.
   
    if ( ya < sprite_height )
    {
        ya = sprite_height ;
    }
   
    if ( yb > ( screenyres - sprite_height ))
    {
        yb = ( screenyres - sprite_height );
    }


Now we've adjusted the boundaries of our box where needed, we should be able to move our object anywhere within it and have it stay onscreen. So now we have to do the bit of code that tells the object to pick a point within our box and move to it:
    futurex = random_range (xa,xb); // pick random point within the defined boundaries
    futurey = random_range (ya,yb);

    move_towards_point( futurex, futurey, 3); // move towards it at speed 3
   
}
random_range lets you pick a random number between one number and another (the values in the brackets). Here, I'm saying "choose a number between xa (the left edge of our box which defines possible movements) and xb (the right edge). Store that number in futurex. Pick another one between ya and yb, store that in futurey."

Now, we know the coordinates we're going to move to - (futurex, futurey), so we tell the object to move there at speed 3..

I'm reading all that back, I'm looking at the code and it's all making sense to me. So here's the thing - why is the bastard thing not working? I mean, it's much better than the completely random movement from before, and it looks like the objects are only JUST moving offscreen - but they ARE and right now I'm not getting why. I bet it's going to end up being something stupid I've overlooked.

Anyway, I've laid out the code I've used and the reasoning behind it - if anyone can point out where I'm going wrong, please leave a comment!


ps. is there any easy way to put blocks of code into Blogger? I just made a  bit of CSS  and put a div wrapper around it. It's kind've a pain in the ass.

No comments:

Post a Comment