Adventure Game Studio

AGS Support => Beginners' Technical Questions => Topic started by: Andail on Wed 08/08/2012 19:42:09

Title: Checking if any object is within a range of coordinates
Post by: Andail on Wed 08/08/2012 19:42:09
Basically I would like to use something like
Code (ags) Select

if (Object.GetAtScreenXY (and here insert a range of x coordinates, and then a range of y coordinates, which will constitute an area) != null){
}

I've been scripting all day and getting a bit disoriented, so this could probably be rather easy. Alternatively, I could draw regions or hotspots and check whether any objects has ended up on top of them.
Title: Re: Checking if any object is within a range of coordinates
Post by: geork on Wed 08/08/2012 20:13:20
I've scripted a short piece of code which should work to this:
Code (AGS) Select
//Somewhere
bool GetObjectsInArea(int StartX, int StartY, int EndX, int EndY){
  bool FoundObject = false;
  while(StartX != EndX && StartY != EndY){
    if(Object.GetAtScreenXY(StartX, StartY) != null) FoundObject = true;
    if(StartX != EndX) StartX ++;
    if(StartY != EndY) StartY ++;
  }
  return FoundObject;
}
//Somewhere else
if(GetObjectsInArea(X, Y, X2, Y2) == true){
}


I've tested this very roughly, but it seems to be working. I guess to store the objects found you can make an array and add an item every time the Object.GetAtScreenXY condition is true.

Hope that works!
Title: Re: Checking if any object is within a range of coordinates
Post by: Andail on Wed 08/08/2012 20:27:03
Wow, thanks :) I tried it quickly and it seems to do the trick! Will return otherwise.
Thanks again!
Title: Re: Checking if any object is within a range of coordinates
Post by: Crimson Wizard on Wed 08/08/2012 20:28:55
I guess since the number of objects in the room is usually less than number of pixels in the range, it would be simplier (and faster - runtime-wise) to iterate through objects array instead, like:

Code (ags) Select

   int index = 0;
   while (index < Room.ObjectCount)
   {
     Object *o = object[index];
     if (object[index].X >= RANGE_X_MIN && object[index].X <= RANGE_X_MAX &&
         object[index].Y >= RANGE_Y_MIN && object[index].Y <= RANGE_Y_MAX)
     {
       return o;
     }
     index++;
   }
Title: Re: Checking if any object is within a range of coordinates
Post by: Andail on Wed 08/08/2012 20:56:30
Crimson, how do I implement this? I get that it's testing all objects in the room to check whether they're in the ranges, but if I have 8 areas, how do I check if all of these areas have objects in them?
Title: Re: Checking if any object is within a range of coordinates
Post by: Crimson Wizard on Wed 08/08/2012 21:03:45
Ah, well, if we are to use geork's code structure as an example:
Code (ags) Select


Object *GetFirstFoundObjectInArea(int StartX, int StartY, int EndX, int EndY) {
   int index = 0;
   while (index < Room.ObjectCount)
   {
     Object *o = object[index];
     if (o.X >= StartX && o.X <= EndX &&
         o.Y >= StartY && o.Y <= EndY)
     {
       return o;
     }
     index++;
   }
   return null;
}
//Somewhere else
Object *o = GetFirstFoundObjectInArea(X, Y, X2, Y2);
if(o != null){
// there's some object
}


Still I wonder, do you need to find ANY object, or all of the objects? In the latter case this code won't work as-is.

EDIT: heh, noticed I did not make use of "o" variable in the loop, fixed the code a little.
Title: Re: Checking if any object is within a range of coordinates
Post by: Andail on Wed 08/08/2012 21:15:01
Well, all the objects have to be placed in any of the areas. But it doesn't matter which object is in which area.

So, any object in any area, but every object needs to be in one area.

I think Geork's solution suits me well now, and this is only in one little room anyway, so I don't think the computer will be slowed down.
Thanks a lot, still :)
Title: Re: Checking if any object is within a range of coordinates
Post by: Crimson Wizard on Thu 09/08/2012 01:44:30
Erm, I suddenly realized my implementation is wrong. :P

What I do is checking Object's position, but object is a box at best, and it's not always represented by rectangular sprite.
Title: Re: Checking if any object is within a range of coordinates
Post by: Snarky on Thu 09/08/2012 09:28:43
There's a mistake in geork's solutions as well. He increments both the X and Y coordinates in a single loop, so the code actually only checks along the diagonal of the region. In order to check every single pixel, you need two loops, an inner loop that goes through all the X coordinates (for example), and an outer loop that goes through all the Y coordinates (running the inner X loop for each of them).

However, this will probably be hideously slow. I would use Crimson Wizard's solution and check whether the object sprite bounding boxes intersect with the region. If that's not good enough, you could then run through only the intersecting regions using (a corrected version of) geork's solution.

Isn't there already a function or module for pixel-perfect collision detection, though? That's essentially what you're trying to do, so maybe you can use that.

The other way to go about this is to rethink your approach. You say every object has to be in one of the regions? Then can you not just keep track of which one you place them in initially (e.g. storing it in a custom object property) and update it whenever they move? Also, how do you ensure that an object isn't in two areas at the same time?
Title: Re: Checking if any object is within a range of coordinates
Post by: geork on Thu 09/08/2012 12:11:48
Oops, my bad :P

Crimson's solution is way more elegant. If we can assume that the objects are bound by rectangles, then a slight amendment could work maybe?
Code (AGS) Select
Object *GetFirstFoundObjectInArea(int StartX, int StartY, int EndX, int EndY){
  int index = 0;
  while(index < Room.ObjectCount){
    Object *o = object[index];
    DynamicSprite *f = DynamicSprite.CreateFromExistingSprite(o.Graphic);
    int Xfactor = f.Width;
    int Yfactor = f.Height;
    f.Delete(); // I don't know whether the above 2 are needed, but want to delete the sprite before the function is cut off
    if((o.X >= StartX || o.X + Xfactor >= StartX) &&
       (o.X <= EndX || o.X + Xfactor <= EndX) &&
       (o.Y >= StartY || o.Y + Yfactor >= StartX) &&
       (o.Y >= EndY || o.Y + Yfactor <= EndY)) return o;
    index ++;
  }
  return null;
}

I tested this on an object which was only partially covered (ie not the XY coordinate) by the box and it seemed to work well enough. I guess for extra safety one could put brackets around the o.X/Y + f.Width/Height

This ain't pixel perfect though.
Title: Re: Checking if any object is within a range of coordinates
Post by: Crimson Wizard on Thu 09/08/2012 12:33:22
@geork, you seem to like heavy programs :)
Code (ags) Select

DynamicSprite *f = DynamicSprite.CreateFromExistingSprite(o.Graphic);
int Xfactor = f.Width;
int Yfactor = f.Height;


Shouldn't this be better?
Code (ags) Select

int Xfactor = Game.SpriteWidth[o.Graphic];
int Xfactor = Game.SpriteHeight[o.Graphic];
Title: Re: Checking if any object is within a range of coordinates
Post by: Khris on Thu 09/08/2012 12:51:47
In order to get an object's sprite dimensions you don't need to create a DynamicSprite.
You can use this:
Code (ags) Select
  int slot = oCurrentObject.Graphic;
  int w = Game.SpriteWidth[slot], h = Game.SpriteHeight[slot];


Regarding the approach: I'd find the shortest distance of all the objects' center points from the area's center point.

Code (ags) Select
Object*GetObjectInsideArea(int x1, int y1, int x2, int y2) {
  int xc = (x1+x2)/2;
  int yc = (y1+y2)/2;
  int i;
  int fo = -1;
  int fd = 99999;
  int foxd, foyd;
  while (i < Room.ObjectCount) {
    int oxd = (object[i].X + Game.SpriteWidth[object[i].Graphic]/2) - xc;
    int oyd = (object[i].Y - Game.SpriteHeight[object[i].Graphic]/2) - yc;
    int d = FloatToInt(Maths.Sqrt(IntToFloat(oxd*oxd + oyd*oyd)), eRoundNearest);
    if (d < fd) {
      fd = d;
      fo = i;
      foxd = oxd;
      foyd = oyd;
    }
    i++;
  } 
  if (fo == -1) return null;
  // treshold
  // x
  int w = Game.SpriteWidth[object[fo].Graphic];
  if (x1 < x2) w += (x2-x1); else w += (x1-x2);
  w = w/2;
  if (foxd < 0) foxd = -foxd;
  // y
  int h = Game.SpriteHeight[object[fo].Graphic];
  if (y1 < y2) h += (y2-y1); else h += (y1-y2);
  h = h/2;
  if (foyd < 0) foyd = -foyd;

  if (foxd <= w && foyd <= h) return object[fo];
  return null;
}


Tested and working. Gives best results if all objects are of similar size.

Edit:
Here's another approach, this one calculates the area of the overlap and returns the object with the biggest one.
Code (ags) Select
Object*GetObjectOverlappingArea(int x1, int y1, int x2, int y2) {
  int xc = (x1+x2)/2;
  int yc = (y1+y2)/2;
  int aw = x2 - x1;
  if (aw < 0) aw = -aw;
  int ah = y2 - y1;
  if (ah < 0) ah = -ah;
  int i;
  int fo = -1;
  int foa = 0;
  int foxd, foyd;
  while (i < Room.ObjectCount) {
    int ow = Game.SpriteWidth[object[i].Graphic];
    int oh = Game.SpriteHeight[object[i].Graphic];
    int xd = (object[i].X + ow/2) - xc; if (xd < 0) xd = -xd;
    int yd = (object[i].Y - oh/2) - yc; if (yd < 0) yd = -yd;
    int w = (ow + aw)/2;
    int h = (oh + ah)/2;
    int ovx = w - xd; if (ovx > aw) ovx = aw; if (ovx > ow) ovx = ow;
    int ovy = h - yd; if (ovy > ah) ovy = ah; if (ovy > oh) ovy = oh;
    if (ovx > 0 && ovy > 0 && ovx*ovy > foa) {
      fo = i;
      foa = ovx*ovy;
    }
    i++;
  }
  if (fo == -1) return null;
  return object[fo];
}


If the objects are of all kinds of different sizes, use the latter.

Edit2:
The second approach can be coded much easier using AGS's AreThingsOverlapping, since the int returned is the bigger the bigger the overlap (and I guess it's the number of pixels). But that would require a dummy character and a dummy view to assign the rectangular sprite to and that seems a bit of an annoying requirement.
Title: Re: Checking if any object is within a range of coordinates
Post by: Snarky on Thu 09/08/2012 14:21:04
In other words: We don't have enough information about what you're trying to do to recommend a best solution. (Mainly, what shapes are the objects, and how are the areas laid out?)
Title: Re: Checking if any object is within a range of coordinates
Post by: Andail on Thu 09/08/2012 14:57:49
I don't want to spoil my game by telling exactly, but this should be enough:
* All objects are identical
* They need to be positioned on certain areas, which are also identical, and which have been drawn so that the objects should fit with a few pixels margin
* When all objects have been moved around so that they are in the correct areas, a function will be triggered

Let's say that I've drawn a hundred holes on a golf green, seen from above, and there are eight balls. All the balls are objects that can be dragged and dropped with the mouse. The player needs to move all the balls so that they fit into the correct eight holes. How best check if the correct eight holes have balls in them?

Note: Everything else is neatly scripted and working splendidly, I just need an effective way to check the result. Geork's solution works satisfactorily, since the player will intuitively place the object neatly in the area anyhow, but it does allow the objects to be placed very imperfectly and still trigger the if-function.
Title: Re: Checking if any object is within a range of coordinates
Post by: Khris on Thu 09/08/2012 16:43:52
I'd make the balls snap into position by keeping an array of hole coords.
Also, whenever a ball is placed in a hole, mark the hole as occupied.

Like this:
Code (ags) Select
struct str_holes {
  int center_x, center_y;
  int ball;  //  -1: no ball, 0-X: ball 0-X
}


Now all you need to do is whenever a ball is dropped, check the distance to all holes. If one is close and free, move the ball to its center and set hole[i].ball to the ball's number. Then iterate through the (w)hole array and check the balls in them.
And voilà, gone is all the ambiguity.