Basically I would like to use something like
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.
I've scripted a short piece of code which should work to this:
//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!
Wow, thanks :) I tried it quickly and it seems to do the trick! Will return otherwise.
Thanks again!
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:
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++;
}
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?
Ah, well, if we are to use geork's code structure as an example:
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.
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 :)
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.
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?
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?
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.
@geork, you seem to like heavy programs :)
DynamicSprite *f = DynamicSprite.CreateFromExistingSprite(o.Graphic);
int Xfactor = f.Width;
int Yfactor = f.Height;
Shouldn't this be better?
int Xfactor = Game.SpriteWidth[o.Graphic];
int Xfactor = Game.SpriteHeight[o.Graphic];
In order to get an object's sprite dimensions you don't need to create a DynamicSprite.
You can use this:
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.
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.
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.
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?)
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.
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: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.