Search code examples
arraysdelphiobjectdelphi-7

Delphi 7 - Assigning a TImage to array of TImage


I am making a custom battleship game, I have a 10x10 grid of objects (TImage). I need to change their .Picture property during runtime, for ships to appear on viewport. I need to change one's Picture property depending on the given coordinates, so I created the following array:

image: array[1..10,1..10] of TImage;

and I have tried to assign them a TImage object like this:

player1.image[1,1] := player1.bcvrA1;

which is supposed to contain links to all TImage objects on viewport (which exist in the Form pre-launch) so I can change one's SampleTImage.Picture property like this:

image[x,y].Picture.LoadFromFile(SamleFile);

But this throws an Access violation error.

Access violation at address 0046CF10 in module 'Project2.exe'. Read of address 00000628.

I have done a bit of research pre-posting this question, but everyone on the Stackoverflow, who asked similar questions were creating objects in runtime, in my case all TImage objects are created pre-runtime and are neded to be assigned to a double dimensional array, so I can change properties more 'conveniently'.

If it is impossible to do it this way, I would really like to see any possible optimal solution. :)

I am very sorry if this question was already asked and answered a dozen of times. I am pretty new with this kind of object operating stuff.

Thanks for your time! ;)


Solution

  • I suggest you use a TImageList to store your images and display those on screen using TImages as needed.
    Make sure the Images are transparent. You can duplicate images in the ImageList if you don't want to rotate them in code.

    If you store objects in an array, be aware that the array starts out zeros (or garbage).
    That's why you're getting errors.

    If you want to use an array, use something like this:

    procedure TForm1.CreateForm(Sender: TObject); 
    begin
      //Init array here, no action needed, because all object members are zero to start with.
    end;
    
    procedure TForm1.AddShip(Location: TPoint; ImageIndex: integer);
    var
      x,y: integer;
      NewImage: TImage;
      Bitmap: TBitmap;
    begin
      x:= Location.x; y:= Location.y;
      if ImageArray[X, Y] = nil then begin
        NewImage:= TImage.Create(Self);
        NewImage.Parent:= Panel1;
        NewImage.Transparent:= true;
        NewImage.Left:= x * GridSize;
        NewImage.Top:= y * GridSize;
        ImageArray[x,y]:= NewImage;
      end;
      if ImageList1.GetBitmap(ImageIndex, Bitmap) then begin
        ImageArray[x,y].Picture.Assign(Bitmap);
      end else raise Exception.Create(Format('Cannot find an image with index %d in %s',[ImageIndex,'ImageList1']));
    end;
    

    Normally I'd advise against using TImages as sprites in a game, but for slow (non?) moving objects like battleships in the classic battleship game I guess it's fine.
    Note that the images do not overlap, you just have square tiles of ships in your imagelist.
    One of the images in your imagelist must be a 'empty' bitmap with just a rect with a transparent color inside. This you assign to a rect if there is no battleship inside.

    Obviously you need to do some bookkeeping regarding the status of the grid.
    This you put in a record.

    TGridRect = record
      shiptype: TShiptype;
      orientation: THorizontalOrVertical;
      shipsection: integer;
      HiddenFromEnemy: boolean;
    end;
    
    TGrid = array[0..9,0..9] of TGridRect;
    

    If you have the gridrect, then you don't need the images, you can just draw directly in the OnPaint event of the form.

    procedure TForm1.Paint(Sender: TObject);
    var
      GridRect: TGridRect;
      Bitmap: TBitmap;
      ImageIndex: integer;
    begin
      for x:= 0 to 9 do begin
        for y:= 0 to 9 do begin
          GridRect:= Grid[x,y];
          ImageIndex:= Ord(GridRect.ShipType) * NumberOfParts 
                       + GridRect.ShipSection * (Ord(GridRect.Orientation)+1);
          Bitmap:= TBitmap.Create;
          try
            ImageList1.GetBitmap(ImageIndex, Bitmap);
            Panel1.Canvas.Draw(x*GridSize, y*GridSize, Bitmap);
          finally 
            Bitmap.Free;
          end;
        end; {for y}
      end; {for x}
    

    Good luck.