Scripting help needed, rectangle selection using mouse.

Started by Jim Reed, Wed 29/07/2009 16:29:54

Previous topic - Next topic

Jim Reed

Hi!

I'm scripting a 2d, rectangle, 16x16, tile engine, and I have a problem.

I need to make a rectangular selection with a mouse, so I can make the tiles under that selection marked, so I can cut, copy, or paste them on to different tiles.

So far I got:
-A room, size 800x600 pixels (Editor is using new style coordinates)
-A grid of tiles 50x37 that are drawn on the room background
-A grid of tiles 50x37 that are drawn on top of the first tiles
-An array of placeholder tiles
-A struct representing their data:

struct DATA1
{
int layerA; //Background
int layerB; //Selection rectangle
int backup; //using it for data swaping
};
DATA1 tile[1850]; //summ of all tiles

-A function:

function GetTileNumber(int x_position, int y_position)
{
 int TileNum=(x_position/tile_pixel_width)+((y_position/tile_pixel_height)*tiles_in_row);
 return TileNum;
}

...that calculates the tile index of any point on the screen, so for an example if I should call this function:
GetTileNumber(mouse.x, mouse.y);
it would give me the tile index under the mouse.

-A sprite that should be drawn on top of the selected tiles at 50% transparency so that they look selected
-Mouse boundaries active, so that the mouse cannot move out the tilemap. Let me explain...
As I am using 16x16 tiles, they fill the screen horizontally with 50 tiles.
On the vertical side I use 37 tiles so that leaves me with 8 pixels that I don't use. So I use mouse boundaries, so to not allow the mouse to go there.

I need:
-A way to make a rectangular selection of tiles, that will have a selection sprite drawn on top of them at 50% transparency
-A way to cut, copy or paste them.
-A boundary that will forbid the tiles being copied out of the array.

I think:
-I should first call Mouse.IsButtonDown(eButtonLeft)
-Then get the tile index of the tile beneath the mouse, save it to a variable
-Ask the user to click on another tile
-call Mouse.IsButtonDown(eButtonLeft) again and
-get the tile index of the tile beneath the mouse, and save it to another variable
-calculate a range of indexes in the LayerA array, using the two variables, that should be marked, and put them inside the backup array
-show the selected area by using the selection sprite at 50% transparency, drawn on top of LayerA, respectfully making the LayerB array tiles 1 or 0, thus when rendering LayerB on top of LayerA the indexes set to 1 should draw the selection sprite
-allow the user to move the rectangle, limiting his mouse, so he cannot move the selected rectangle outside the tilemap
-on keypresses  +ctrl x, ctrl+c, ctrl+v ,first empty the LayerA array selected tiles, and then cut, copy or paste the selection from the backup array to the LayerA array on the current selection location

OK, I just re-read my post. It's a mess.  :(

Can someone please help?

EDIT:
Hmm...there should be 4 possible rectangles, depending on where the user drags the mouse after clicking for the first time...up-left, up-right, down-right, down-left. That means 4 different calcullations. ARRGH!

monkey0506

#1
Hey Jim...I think you might want to take a look at the DrawingSurface functions. What you could do to get the "selected" area is you could create a new DynamicSprite of the required size with DynamicSprite.Create, fill the sprite to the desired color with DrawingSurface.Clear, then draw that sprite semi-transparently onto the background with DrawingSurface.DrawImage. If you want the selection to have solid boundary lines you could then draw four lines with DrawingSurface.DrawLine to give the solid border.

So it would look sort of like:

Code: ags
DrawingSurface *roomBackup;

void DrawSelection(int x1, int y1, int x2, int y2, int color, int transparency) {
  // check for invalid co-ordinates
  if ((x1 < 0) || (y1 < 0) || (x2 < 0) || (y2 < 0) || (x1 > System.ViewportWidth) || (x2 > System.ViewportWidth) || (y1 > MAX_Y) || (y2 > MAX_Y) || (x2 <= x1) || (y2 <= y1)) return;
  if (transparency < 0) transparency = 0;
  if (transparency > 100) transparency = 100;
  if (color < 0) color = 0;
  DynamicSprite *sprite = DynamicSprite.Create(x2 - x1, y2 - y1);
  DrawingSurface *surface = sprite.GetDrawingSurface();
  surface.Clear(color);
  surface.Release();
  surface = Room.GetDrawingSurfaceForBackground();
  if (roomBackup == null) roomBackup = surface.CreateCopy();
  surface.DrawImage(x1, y1, sprite.Graphic, transparency);
  sprite.Delete(); // we're done with our temporary sprite now
  surface.DrawingColor = color;
  surface.DrawLine(x1, y1, x2, y1);
  surface.DrawLine(x1, y1, x1, y2);
  surface.DrawLine(x1, y2, x2, y2);
  surface.DrawLine(x2, y1, x2, y2);
  surface.Release();
}

void RestoreBackground() {
  if (roomBackground == null) return;
  DrawingSurface *surface = Room.GetDrawingSurfaceForBackground();
  surface.DrawSurface(roomBackground);
  surface.Release();
  roomBackground.Release();
  roomBackground = null;
}


As far as copying the area you can look up DynamicSprite.CreateFromBackground which allows you to specify a rectangular area of the background to grab.

I hope this helps.

Khris

The good thing is that you can code everything seperately. Cut, copy, paste and select are all completely independent operations.

Select:
To show the selection to the user, you have to decide whether the rectangle will use the mouse coords or jump to include only complete tiles.
I'd use a semitransparent GUI to show the selection.
You won't need four different calculations at all, by the way.

Code: ags
int sx1, sy1, sx2, sy2;

function UpdateSelection(ax, ay, bx, by) {

  // make sure top left is a and bottom right is b
  int sw;
  if (bx < ax) { sw = ax; ax = bx; bx = sw; }
  if (by < ay) { sw = ay; ay = by; by = sw; }
  
  // store tile array selection for later use
  sx1 = ax/16;
  sy1 = ay/16;
  sx2 = bx/16;
  sy2 = by/16;

  if (1) {  // whole tile selection
    ax = (sx1)*16; ay = (sy1/16)*16+16; 
    bx = (sx2)*16; by = (sy2/16)*16+16;
  }
  gSelect.Width = bx - ax;
  gselect.Height = by - ay;
  gSelect.SetPosition(ax, bx);
}

int x1,y1,x2,y2;

function repeatedly_execute() {
  
  if (mouse.IsButtonDown(eMouseLeft)) {
    x1 = mouse.x; y1 = mouse.y;
    while (mouse.IsButtonDown(eMouseLeft)) {
      x2 = mouse.x; y2 = mouse.y;
      UpdateSelection(x1, y1, x2, y2);
      Wait(1);
    }
  }
}


You'll need another tile buffer to represent the clipboard.

Copy: Store size and values of selection in clipboard.
Cut: Same as above, but screen tiles are cleared.
Paste: Starting at the top-left corner of the current selection, clipboard overwrites screen tile if clipboard not empty & tile not off-screen.

All operations require an x loop inside a y loop going through tile coords. Typo prone but not that hard to code.

Jim Reed

Well, KrisMUC and monkey_05_06 thank you for your quick reply.
I'll have to figure out the code and implement it. I'm new to scripting.
Again, thank you very much, you'll be mentioned in the finished product.
=D

Jim Reed

Oh, well I tried the code today.
A few typo's I corrected, but it doesn't select the correct area.
I'll have to dive into that, and if I find a solution I'll post it here if anyone needs it.

SMF spam blocked by CleanTalk