Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Tile-Grid Playing Area 1

Status
Not open for further replies.

barrin

Technical User
Feb 6, 2006
25
0
0
CA
I need to know how to make a playing area for a game I am creating. It would use a system of tiles on a grid, with each tile reacting differently when the sprite comes in contact with it, ie. background tiles that the sprite can walk over, wall tiles which cannot be traversed, and doors that change the playing area.

I believe I could do this with a grid, but the only problem is the movement of the sprite would be choppy. This is because the sprite must always be in the center tile, and the other tiles move relative to the sprites position. How could I get the images in the grid to "scroll" over, making the animation smooth?

Any help would be greatly appreciated.

Pat
 
Lets say you have a logical representation of the tiles in an array or some other data structure. The logical representation keeps the tile characteristics (wall, plain floor, trap, etc) plus a pointer to the tile graphic.

Lets say you visible area is 3x3 tiles.

Start drawing a 5x5 area, tile by tile, in a buffer. Copy the visible area (3x3) from the buffer to the screen (use a bitblt to make a fast massive copy). Draw the player char.

Code:
Starting buffer. 
V = visible

    1   2   3   4   5
  |---|---|---|---|---|
1 |   |   |   |   |   |
  |---|---|---|---|---|
2 |   | V | V | V |   |
  |---|---|---|---|---|  
3 |   | V | V | V |   |
  |---|---|---|---|---|  
4 |   | V | V | V |   |
  |---|---|---|---|---|  
5 |   |   |   |   |   |
  |---|---|---|---|---|

When the player char moves, copy the new area from the buffer to the screen (bitblt again) and place the player char. You have the new visible area in the buffer, as it is greater than the visible area.

Code:
Starting buffer.
V = visible.
Character moves left. Copy to the screen all tiles marked with "1"
    1   2   3   4   5
  |---|---|---|---|---|
1 |   |   |   |   |   |
  |---|---|---|---|---|
2 |  1| V1| V1| V |   |
  |---|---|---|---|---|  
3 |  1| V1| V1| V |   |
  |---|---|---|---|---|  
4 |  1| V1| V1| V |   |
  |---|---|---|---|---|  
5 |   |   |   |   |   |
  |---|---|---|---|---|
Now actualize your buffer making a bitblt over itself with the visible area (shifted one tile left, right, down or up as needed) and add the new tiles needed to complete the extra-area you are keeping stored (the 5x5 area) writing them tile by tile. The buffer updating can be made with another thread, to keep the interface responsive. Remember to lock the canvas if you are making multithreading drawing.

Code:
Buffer upgrading.
Character moves left.
O (old), V (Visible) = tiles bitblted from the buffer to the buffer itself (shifted one tile rigth).
N = new tiles (upgraded area).

    1   2   3   4   5
  |---|---|---|---|---|
1 | N | O | O | O | O |
  |---|---|---|---|---|
2 | N | O | V | V | V |
  |---|---|---|---|---|  
3 | N | O | V | V | V |
  |---|---|---|---|---|  
4 | N | O | V | V | V |
  |---|---|---|---|---|  
5 | N | O | O | O | O |
  |---|---|---|---|---|

When the player char moves again, repeat (you have your new extra-area ready in you buffer, so you can bitbilt the new visible area).

buho (A).

 
Buho,

Interesting post, you've given me a little insight on how I'm going to do this.

I have a question though, regarding the buffer. I've never really understood how you use it, code-wise. I searched it on Google, but I can't seem to find any relevant results. How do I draw bitmaps in the buffer?

Thanks,
Pat
 
The buffer is a TCanvas (or any other object having a canvas, like a TBitmap). The screen is, of course, another TCanvas (usually in your main form).

TCanvas have a lot of methods to simplify the copy (btblt): CopyRect, BrushCopy, StretchDraw, etc. If your tiles are bitmaps, you can CopyRect them into your buffer.

After preparing all your drawing in your TCanvas buffer, a CopyRect can render a sizable area in the screen in very little time.

Anyway, if your visible area is big (say: 1024x768,the whole desktop) you are going to have flicker in anything but the most powerful machines.

buho (A).

 
I've done most of what you suggested but I can't figure out how to get the images drawn into the buffer (from a .bmp file).

I am using an ImageList, so I tried the Draw function, but it gives me an error. "Object reference not set to an instance of an object"

Here is a snippet of the code I tried:

Code:
procedure TRPG.Button3Click(Sender: TObject);
var
  i, j: Integer;
  Dest, View: TRect;
begin
  Map := TMap.Create;

  View.Left := 0;
  View.Top := 0;
  View.Right := 40*15;
  View.Bottom := 40*15;
  for i := 1 to 15 do
  begin
    Dest.Top := 40 * (i - 1);
    Dest.Bottom := 40 * i;
    for j := 1 to 15 do
    begin
      Dest.Right := 40 * (j - 1);
      Dest.Left := 40 * j;
      TilesImageList.Draw(Map.Buffer, Dest.Left, Dest.Top, 0);

    end;
  end;

  Map.Buffer.CopyRect(View, Map.Screen, View);
end;

I don't necessarily need to use an ImageList, if there is an easier way, but it seems logical to use since all the tiles are the same size.

Also, it would conserve space if there was a way to use .gif files instead, as my tiles are fairly simple colour-wise (at least at the moment).

Whatever help you could give would be nice. I've been playing around with it alot and I can't seem to get it to work.

Thanks,
Pat
 
You forgot to create an object. Probably the Map.Buffer member.

Post the TMap structure and code here if you can't solve it.

Canvases can't work with GIF. But you can reduce the BMP color density to reduce the bitmap sizes.

buho (A).
 
Yes, I forgot to create Buffer and Screen. That issue is now solved, but for some reason luck isn't on my side. It seems like every step forward is 2 steps back for me...

I'm now getting a "Canvas does not allow drawing" error on the same draw function. I've searched it on Google with plenty of results but none of them seem in context to what I'm doing. I've tried all the fixes I can find anyway, to no avail.

Furthermore, whenever I exit my program, I recieve another error: Failed to load resources from resource file, Please check your setup. Couldn't find any help on Google with this either.

Have any ideas?

Thanks,
Pat
 
Can be lotsa things. Can't say without having the code.

My best advice is for you to start again. Make 3 or 4 or 5 litlle programs to "concept proof" any aspect new for you. Work on them till you are sure you understand how them work; after that start making your program slowly, one thing at time.

buho (A).
 
Essentially that is what I am doing, within a program that already exists. All the errors are coming from what I've added. I could send you the code if you'd like to have a look at it.
 
Alright well here is a similar bit of code that produces the exact same results.

Code:
unit Unit2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, System.ComponentModel, Borland.Vcl.StdCtrls, Borland.Vcl.ImgList;

type
  TTiles = record
    TType: Byte; //0=Traversable, 1=Wall
    Image: Byte;
  end;

  TMap = class
    AreaName: String;
    Tiles: array[1..15, 1..15] of TTiles;

    Screen: TCanvas;
    Buffer: TCanvas;

  published
    constructor Create;
    destructor Destroy; override;
  end;

  TForm2 = class(TForm)
    Button1: TButton;
    ImageList1: TImageList;
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form2: TForm2;
  Map: TMap;

implementation

{$R *.nfm}

constructor TMap.Create;
var
  i, j: Integer;
begin
  inherited Create;

  Screen := TCanvas.Create;
  Buffer := TCanvas.Create;

  AreaName := 'Map';
  for i := 1 to 15 do
    for j := 1 to 15 do
    begin
      Tiles[j, i].TType := 0;
      Tiles[j, i].Image := 1;
    end;
end;

destructor TMap.Destroy;
begin
  Screen.Free;
  Buffer.Free;
  FreeAndNil(self);
end;








procedure TForm2.Button1Click(Sender: TObject);
var
  i, j: Integer;
  Tile, Dest, View: TRect;
  Image: TBitmap;
begin
  Map := TMap.Create;

  Tile.Left := 0;
  Tile.Top := 0;
  Tile.Right := 40;
  Tile.Bottom := 40;

  View.Left := 0;
  View.Top := 0;
  View.Right := 40*15;
  View.Bottom := 40*15;
  Image := TBitmap.Create;
  Image.LoadFromFile('TDirt.bmp');




  for i := 1 to 15 do
  begin
    Dest.Top := 40 * (i - 1);
    Dest.Bottom := 40 * i;
    for j := 1 to 15 do
    begin
      Dest.Right := 40 * (j - 1);
      Dest.Left := 40 * j;
      Image.Canvas.CopyRect(Dest, Map.Buffer, Tile);
    end;
  end;

  Map.Buffer.CopyRect(View, Map.Screen, View);
end;

procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Map.Destroy;
end;

end.
 
Gonna test your code. Gimme some time.

For start: NO FreeAndNil(Self) in a destructor. You ARE freeing the object.

buho (A).
 
Sorry, my fault. I was not strictly talking about a TCanvas but about objects having a TCanvas, but my wording was unclear (english is not my tongue).

I've made some changes/corrections to your code. Load it in Delphi (to have the comments in another color) and check it. It is working.

You can't see the pattern in the main form if you are stepping with the debugger (I've neither intercepted the OnPaint nor added a TPaintBox to keep the corrections simple).

Code:
unit TFMain;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TTiles = record
    TType: Byte; //0=Traversable, 1=Wall
    Image: Byte;
  end;

type
  TMap = class
    AreaName: String;
    Tiles: array[1..15, 1..15] of TTiles;

    Screen: TBitmap;
    Buffer: TBitmap;
  {***
  Innecesary
  published
  ***}
  public
    constructor Create;
    destructor Destroy; override;
  end;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  Map: TMap;

implementation

{$R *.dfm}

constructor TMap.Create;
var
  i, j: Integer;
begin
  inherited Create;

  Screen := TBitmap.Create;
  Buffer := TBitmap.Create;

  AreaName := 'Map';
  for i := 1 to 15 do
    for j := 1 to 15 do
    begin
      Tiles[j, i].TType := 0;
      Tiles[j, i].Image := 1;
    end;
end;

destructor TMap.Destroy;
begin
  Screen.Free;
  Buffer.Free;
  {***
  FreeAndNil(self);  <-- BAD, NEVER
  ***}
  {"inherited" not really needed in a TObject (it does nothing),
  but better making the discipline of calling it in every object.}
  inherited;
end;



procedure TForm1.Button1Click(Sender: TObject);
var
  i, j: Integer;
  Tile, Dest, View: TRect;
  Image: TBitmap;
begin
  Map := TMap.Create;

  Tile.Left := 0;
  Tile.Top := 0;
  Tile.Right := 40;
  Tile.Bottom := 40;

  View.Left := 0;
  View.Top := 0;
  View.Right := 40*15;
  View.Bottom := 40*15;

  {Quick and dirty: set the sizes.
  Better doing this in Map.Create.}
  Map.Screen.Width := View.Right;
  Map.Screen.Height := View.Bottom;
  Map.Buffer.Width := View.Right;
  Map.Buffer.Height := View.Bottom;

  Image := TBitmap.Create;
  Image.LoadFromFile('TDirt.bmp');

  for i := 1 to 15 do
  begin
    Dest.Top := 40 * (i - 1);
    Dest.Bottom := 40 * i;
    for j := 1 to 15 do
    begin
      {*** Old coordinates was mirrored left-right
      Dest.Right := 40 * (j - 1);
      Dest.Left := 40 * j;
      ***}
      Dest.Left := 40 * (j - 1);
      Dest.Right := 40 * j;
      {***
      From the manual: "Use CopyRect to transfer part of
      the image on (FROM) ANOTHER canvas TO the image of
      the TCanvas object." 
      IE: wrong copy.
      Image.Canvas.CopyRect(Dest, Map.Buffer.Canvas, Tile);
      ***}
      {Copy FROM Image TO Buffer}
      { .... DESTINATION                 .... SOURCE }
      { |                                |           }
      Map.Buffer.Canvas.CopyRect(Dest, Image.Canvas, Tile);

    end;
  end;
  {*** Wrong copy
  Map.Buffer.Canvas.CopyRect(View, Map.Screen.Canvas, View);
  ***}
  {Copy FROM Buffer TO Screen}
  Map.Screen.Canvas.CopyRect(View, Map.Buffer.Canvas, View);
  {Quick and dirty: copy TO the form canvas.
  NOTE: drawing lost if the windows is minimized, covered,
  the debugger steps, etc.}
  Canvas.CopyRect(View, Map.Screen.Canvas, View);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  {Lets know if the object is actually created.}
  Map := nil;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  {***
  Never call Destroy in an object.
  Map.Destroy;
  ***}
  {Call conditionally: only if user clicked the button.}
  if Map <> nil
    then Map.Free;
end;

end.
 
That clears alot of things up... I should be able to implement it into my game now. Thanks for the post. I have to go to work now, but I'll look at it afterwards.

If you're not too busy, do you think you could go through my game and pick out the other fatal flaws I seem to make all the time, so I stop making them? I'd have to send you the whole project, I'm not sure how we'd do that on these forums though.

Anyway thanks again,
Pat
 
Sorry, I have not the time (or the will, BTW) to correct a whole program written by another programmer.

Go and suffer, man. Suffering, anguish, desperation and hard work are the materials a good programmer is made of. :).

buho (A).
 
No need to apoligise, you've already been outstanding.. there was one thing I'm not sure if you had commented on it

"Furthermore, whenever I exit my program, I recieve another error: Failed to load resources from resource file, Please check your setup."

If you know what causes that (not specifically of course), I could research it. If its any enlightenment when I exit the program in debug mode the error changes to: Project encountered unhandleled exception class System.StackOverflow.Exception.

Thanks,
Pat
 
I think the error was due to the FreeAndNil(Self) you added in TMap.Destroy.

I removed the line and never got a closing error.

buho (A).
 
Yes and no. I had removed that when you advised me against it, but I had done it in another destructor. It works fine now.

Now I can finally get into programming the sprite movement. I'll be sure to take things slowly so the errors don't pile up and I can pinpoint them more easily.

Thanks,
Pat
 
Feel free to ask here if you are unable to solve some other thing.

I'll be watching this thread for some time.

buho (A).
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top