DrawImage on background question...

Started by Le Woltaire, Mon 14/09/2009 11:55:35

Previous topic - Next topic

Le Woltaire

I would like to create the following constellation:

Let's say the room looks like this:

The character enters the room and it instantly looks like this:

When the character moves more and more of the room becomes visible:

In the end all is visible:


As you notice the red tiles that are close to the character disappear completely, while the other ones become transparent to 50% first.

This can be easily done with red square objects that cover the room and disappear as soon as the character approaches.
But since only 40 objects are allowed in a room I guess I have to script this with DrawImage functions, so that tiled surfaces cover the room and disappear or become transparent part by part.

How can this be done?



Crimson Wizard

#1
I am not a pro at scripting in AGS, but since I already tried to use "raw" drawing in my own project, and also I tried similar tiling concept in common C++ applications I've done before many times, I can propose following:

1. You store an array of states for this room's tiles. Let's say, an array of shorts. Each index store a state of one tile. It can be '0' - original tile present; '1' - half-transparent tile overlayed; '2' - tile overlayed.

2. When any changes happen in game you should manually repaint the room:
- paint background, if  there's any;
- paint tiles checking their state from that array I mentioned above;
- finally draw character, or whatever else you have there.

This is theoretically speaking.

A simple example:

Code: ags

// 7 x 10 like in your example
readonly int AREA_WIDTH = 10;
readonly int AREA_HEIGHT = 7;
readonly int AREA_TOTAL = 70; 
// tile width and height in pixels
readonly int TILE_WIDTH = 5;
readonly int TILE_HEIGHT = 5;
// top-leftmost point in pixels (in sake of perfectionism)
readonly int AREA_TOP = 0;
readonly int AREA_LEFT = 0;

short tiles[AREA_TOTAL];

function repaintAll()
{
   int x, y, tile_state;
   DrawingSurface * ds = Room.GetDrawingSurfaceForBackground();

   while (x < AREA_WIDTH)
   {
      y = 0;
      while (y < AREA_HEIGHT)
      {
          tile_state = tiles[x + y * AREA_WIDTH];
          if (tile_state == 0)
          {
             ds.DrawImage(AREA_LEFT + x * TILE_WIDTH, AREA_TOP + y * TILE_HEIGHT, <index of original tile sprite>);
          }
          else if (tile_state == 1)
          {
             ds.DrawImage(AREA_LEFT + x * TILE_WIDTH, AREA_TOP + y * TILE_HEIGHT, <index of original tile sprite>);
             ds.DrawImage(AREA_LEFT + x * TILE_WIDTH, AREA_TOP + y * TILE_HEIGHT, <index of overlay sprite>, <wanted transparency>);
          }
          else if (tile_state == 2)
          {
             ds.DrawImage(AREA_LEFT + x * TILE_WIDTH, AREA_TOP + y * TILE_HEIGHT, <index of overlay sprite>);
          }

          y++;
      }
      
      x++;
   }

   ds.Release();
}

Joe

I'd recomend to declare this part of the scripts with the #define command

Quote from: Crimson Wizard on Mon 14/09/2009 12:42:56
Code: ags

// 7 x 10 like in your example
readonly int AREA_WIDTH = 10;
readonly int AREA_HEIGHT = 7;
readonly int AREA_TOTAL = 70; 
// tile width and height in pixels
readonly int TILE_WIDTH = 5;
readonly int TILE_HEIGHT = 5;
// top-leftmost point in pixels (in sake of perfectionism)
readonly int AREA_TOP = 0;
readonly int AREA_LEFT = 0;


In stead of that I'd use this:

Code: ags

// 7 x 10 like in your example
#define AREA_WIDTH 10
#define AREA_HEIGHT 7
#define AREA_TOTAL 70
// tile width and height in pixels
#define TILE_WIDTH 5
#define TILE_HEIGHT 5
// top-leftmost point in pixels (in sake of perfectionism)
#define AREA_TOP  0
#define AREA_LEFT  0


It's just for programming rules. AFAIK #define doesn't use the memory space int uses...
Copinstar © Oficial Site

Khris

To elaborate a bit:
Say the room is 320x240 and a tile is 16x16, that's 20x16 tiles.
To simplify things, make the array cover the (off-screen) border tiles.
So we'd have 22x18 tiles (396).
Now you can safely change the visibility of the 9 tiles without checking for the player being next to a room border.

In rep_ex, check if the player entered a new tile (by storing the old tile coords in variables and checking them against the current ones) and if they did, update the visibility and redraw the room.

Crimson Wizard

Quote from: Joe Carl on Mon 14/09/2009 13:07:12
I'd recomend to declare this part of the scripts with the #define command

It's just for programming rules. AFAIK #define doesn't use the memory space int uses...

Heh, sounds like you're right, I am keep forgetting readonly is not the same as const in C++.

Le Woltaire

Ok...
Now, I am totally new to these RawDraw functions and never learned c++...
I managed to understand some parts of the repaint_all script and put it in the enter after fade in script.
Indeed the whole screen gets covered with the texture.
Brilliant!

So, now I got that I have to put it in the repeatedly execute script when the character is walking.
When I do so nothing changes...
Do I have to recall the last background state before, or redraw the original background?
How do I do that...

In the current way I always just get the completely covered screen...
It seems as if the tile_state would never change...



Crimson Wizard

#6
Quote from: Le Woltaire on Mon 14/09/2009 20:01:17
In the current way I always just get the completely covered screen...
It seems as if the tile_state would never change...
Do you actually change state of tiles?
If you do, maybe you could upload your code (or whole project, if it's not top secret) somewhere, so we could take a look on it?



Quote from: Le Woltaire on Mon 14/09/2009 20:01:17
Do I have to recall the last background state before, or redraw the original background?
How do I do that...
That depends... if you have some background in this room and there are parts of background that may become overlayed by tiles and then cleared once again (that is - become visible to player again), then yes, you should store background and repaint it manually. If not, then I believe you shouldn't bother. Just in case, saving background is done like that:
1. You create a pointer to DrawingSurface as a room's variable, like this:
Code: ags

DrawingSurface * backgroundDS;

2. In the "Room Load" event handler you store a copy of original background:
Code: ags

  DrawingSurface * ds = Room.GetDrawingSurfaceForBackground();
  backgroundDS = ds.CreateCopy();
  ds.Release();

Note the ds is released, but backgroundDS is not, because you will need it later, when full repaint is made. However, you still have to release it when player leaves the room, in "Leaves Room" event handler:
Code: ags

  backgroundDS.Release();

3. Finally, add this line to painting function just before anything else is painted (but after 'ds' drawing surface object is created):
Code: ags

  DrawingSurface * ds = Room.GetDrawingSurfaceForBackground();
  ds.DrawSurface(backgroundDS);  // <-----

that will ensure everything is fully redrawn.

Crimson Wizard

Erm, just noticed my painting function had couple of stupid mistakes... I am not sure they would cause incorrect painting, because for me they just don't make script compile ;) but just in case - I fixed them in my first post.

Le Woltaire

No prob, I fixed this before
...
I am actually standing at the beginning of a new idea and just started today with this to see if I can get this going.
There it not much that I can post here except of the code that you've written...
But I guess I missed to manually change the state of tiles.

To make the full situation clear:
-There is a shaded background picture and the tiles are a repeating texture above it.
-Tile dimensions are 25 squarepixels.
-Actual background size is 24 tiles wide and 18 tiles high.

In order to continuously change the tile state if have to declare the changing position of the character,
and on which tile he is, then change this tiles state to 2 and all surrounding ones to 1 except of those who are already 2 or 1, isn't it? But how can I assign the character's position to the specific tiles...?

When the room is left by the player the whole room state has to be saved and restored,
so it can be continued after reentering the room.
This should be done with your last script that I already inserted...



Crimson Wizard

#9
Quote from: Le Woltaire on Mon 14/09/2009 21:03:57
In order to continuously change the tile state if have to declare the changing position of the character, and on which tile he is, then change this tiles state to 2 and all surrounding ones to 1 except of those who are already 2 or 1, isn't it? But how can I assign the character's position to the specific tiles...?
I still don't have an idea of how character moves; should he move freely wherever possible, or should he move only from centre of a tile to the centre of next tile.
Regardless, you can get the index of the tile on which character is standing using following calculation:

Code: ags

int current_tile_x = (cEgo.x - AREA_LEFT) / TILE_WIDTH;
int current_tile_y = (cEgo.y - AREA_TOP) / TILE_HEIGHT;
int current_tile = current_tile_x + current_tile_y * AREA_WIDTH;

I wrote that in detail for better understanding.

I suppose in your case it may be more convenient to make a separate function for this, it will also check for room boundaries:

Code: ags

function set_tile(int x, int y, short new_state)
{
  // check if tile is actually inside the tiles grid
  if (x >= 0 && x < AREA_WIDTH && y >= 0 && y < AREA_HEIGHT)
  {
      // check if state is less than new one
      if (tiles[x + y * AREA_WIDTH] < new_state)
      {
         tiles[x + y * AREA_WIDTH] = new_state;
      }
  }
}


Now, you set tiles appropriately, like that:
Code: ags

 // tile at which Ego stands
int tilex = (cEgo.x - AREA_LEFT) / TILE_WIDTH;
int tiley = (cEgo.y - AREA_TOP) / TILE_HEIGHT;

// tile at which character stands
set_tile(tilex, tiley, 2);

// surrounding tiles
set_tile(tilex - 1, tiley - 1, 1);
set_tile(tilex, tiley - 1, 1);
set_tile(tilex + 1, tiley - 1, 1);
set_tile(tilex - 1, tiley, 1);
set_tile(tilex + 1, tiley, 1);
set_tile(tilex - 1, tiley + 1, 1);
set_tile(tilex, tiley + 1, 1);
set_tile(tilex + 1, tiley + 1, 1);


I wrote that fast, perhaps it can be optimised somehow, I just don't bother at the moment. ;) It's important to get the idea first.

Le Woltaire

(The character should move freely on the walkable area mask and the tiles should disappear when he approaches the next area and it gets into his view)

Ok, big thanks from my side.
The first version works!
It also seems to remember it's state when you reenter the room...
I just had to edit the transparency values.
For state 2 the value has to be 100 and for state 1 it just has to be 50 without the first line in the repaint_all function.
However I get the feeling that all this is going to slow down some older computers...
I will test this for some days or hours and see if it is stable...



Khris

I wouldn't worry that much about performance issues. It'll take a fairly old computer for AGS to slow down noticeably, even with that kind of gfx stuff.
If you still want to optimize it, pass the player's tile to the repaint function and process only those nine. (Paint all if player.x == -1)

Also, like I stated in my previous post, you don't have to repaint every frame.

Code: ags
int ox, oy;  // old tile

function repeatedly_execute() {

  int x = player.x/TILE_WIDTH;
  int y = player.y/TILE_HEIGHT;
  if (x != ox || y != oy) {    // player stepped on a new tile
    ox = x; oy = y;  // update old tile
    // paint, etc.
  }
}

SMF spam blocked by CleanTalk