-1

So i'm trying to create an information kiosk in which you move a character around a form to different images (such as to a medical image to show where the medical booth is in the area) but i'm stuck on determining whether or not the movable character is touching any of the images in any way. This is the basic thought process behind what I've tried. The main problem is on the procedure Checkcolissions.

type
  TForm1 = class(TForm)
    imgsprite: TImage;
    imgmed: TImage;
    imgMerch: TImage;
    Image2: TImage;
    Image3: TImage;
    Image4: TImage;
    Image5: TImage;
    imgmedenter: TImage;
    imgFood: TImage;
    img: TImage;
    Panel1: TPanel;
    procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure FormActivate(Sender: TObject);
    procedure FormKeyPress(Sender: TObject; var Key: Char);
    procedure CheckCollisions;
  private
  isprite, spriteleft, spritetop : integer;
  bMove : boolean;
  FTargets: array[0..10] of TImage;
    { Private declarations }
  public
    { Public declarations }
  end;



var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.CheckCollisions;
var
iCount : integer;
bValid : boolean;
begin
iCount := - 1;
bValid := false;
while (bValid = false) OR (iCount >= 10) and (bMove = true) do
begin
  inc(icount);
  if (imgsprite.top =  FTargets[iCount].top) and (imgsprite.left = FTargets[iCount].left) then  //this just doesnt work
                                                                                                //and i dont know what to do
  begin
    bValid := true;
    bMove := false;
    case iCount of
    0 : imgmedenter.visible := true; //repeated for each thing in the array
    end;
  end;

end;

end;

procedure TForm1.FormActivate(Sender: TObject);
begin
  iSprite := 0;
  bMove := true;
  Form1.DoubleBuffered := True;
  windowstate := wsmaximized;
  imgsprite.Top := ceil(clientheight/2);
  imgsprite.Left := floor(clientwidth/2);
  FTargets[0] := imgmed;
 // FTargets[1] := imgmed;
  //FTargets[2] := imgmed;
  //FTargets[3] := imgmed;
  //FTargets[4] := imgmed;
  //FTargets[5] := imgmed;
end;

procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  case Key of
  vk_up:
  begin

  if bMove = true then
     begin
      inc(isprite);
      Checkcollisions;
     if (imgsprite.top >= 0) and (bMove = true)  then
     begin
         imgsprite.top := imgsprite.top - 10;
     case iSprite of
      1 : imgsprite.Picture.LoadFromFile('standingback.png');
      2 : imgsprite.Picture.LoadFromFile('backleft.png');
      3 : begin
           imgsprite.Picture.LoadFromFile('backright.png');
           iSprite := 0;
       end;
      end;
     end
     else
      iSprite := 0;
    end;
  end;

    VK_DOWN:
    begin
    if bMove = true then
    begin
      Checkcollisions;
      inc(isprite);
      if (imgsprite.top <= clientheight - imgsprite.height) and (bMove = true) then
     begin
       imgsprite.top := imgsprite.top + 10;
     case iSprite of
      1 : imgsprite.Picture.LoadFromFile('standing.png');
      2 : imgsprite.Picture.LoadFromFile('forwardsleft.png');
      3 : begin
           imgsprite.Picture.LoadFromFile('forwardsright.png');
           iSprite := 0;
       end;
      end;
     end
     else
      iSprite := 0;
    end;
  end;

    VK_LEFT:
    begin
    if bMove  = true then
    begin
    Checkcollisions;
     inc(isprite);
     if (imgsprite.left >= 0) and (bMove = true) then
     begin
      imgsprite.Left := imgsprite.Left - 10;
     case iSprite of
      1 : imgsprite.Picture.LoadFromFile('standingleft.png');
      2 : imgsprite.Picture.LoadFromFile('leftleft.png');
      3 : begin
           imgsprite.Picture.LoadFromFile('leftright.png');
           iSprite := 0;
       end;
      end;
     end
     else
      iSprite := 0;
    end;
  end;

    VK_RIGHT:
    begin
    if bMove = true then
    begin
    Checkcollisions;
    inc(iSprite);
    if (imgsprite.Left <= clientwidth - imgsprite.width ) and (bMove = true)   then
    begin
      imgsprite.Left := imgsprite.Left + 10;
      case iSprite of
      1 : imgsprite.Picture.LoadFromFile('standingright.png');
      2 : imgsprite.Picture.LoadFromFile('rightleft.png');
      3 : begin
           imgsprite.Picture.LoadFromFile('rightright.png');
           iSprite := 0;
      end;
     end;
     end
     else
      iSprite := 0;
    end;

  end;
end;
end;

any help would be greatly appreciated :D

  • VCL controls aren't intended for this type of usage. You probably need a radically different solution. – David Heffernan Mar 22 '20 at 12:50
  • @David is right. Here is my standard example of how you can do this: https://stackoverflow.com/a/7224075/282848 – Andreas Rejbrand Mar 22 '20 at 15:31
  • *...//this just doesnt work...* is a nearly useless problem indication, if not accompanied with an explanation of how the behaviour is different from the expected. Anyway, a few comments: 1)Your `FTargets` array can have indexes 0..10 yet you don't prevent `iCount` from exceeding that range. 2) Further, your code doesn't prevent attempts to access unassigned slots in that array. 3) Do you know the operator precedence for `and` and `or`, iow, is the logic correct in the while statement. 4) Why are you checking for (iCount >= 10)? – Tom Brunberg Mar 22 '20 at 15:32
  • 5) In `FormKeyDown()` you are moving `imgSprite` 10 pixels per step, so there's a 1 of 10 chance that your collision detection will work because you check for exact match – Tom Brunberg Mar 22 '20 at 15:32

2 Answers2

1

Now almost three years later, I realized I never even shared my solution to this problem. Seeing this post, I feel obliged to do so, in order to help any future coders wanting to solve this problem:

The core idea behind this solution is this: each image has a left and top property. We can use these properties to construct right and bottom properties using the width and height properties. To create in a sense: a "hitbox" around the image.

i.e We add the images left property to the width of the image to get the position of the right side of the image.

Then every time we move the sprite (or character) around we check through every single image on the screen to see if the sprites left/top/bottom/right parts overlap with any of the images left/top/bottom/right parts. If they do we preform some code.


procedure TfrmKiosk.CheckCollisions;
var
  iCount: integer;
  bValid: boolean;
  arrFTargets: array [0 .. 7] of TImage;
begin
  // Load all images into an array
  arrFTargets[0] := imgmed;
  arrFTargets[1] := imgMerch;
  arrFTargets[2] := imgGames;
  arrFTargets[3] := imgFood;
  arrFTargets[4] := imgBathroom;
  arrFTargets[5] := imgexit;
  arrFTargets[6] := imgAutographs;
  arrFTargets[7] := imgmain;
  iCount := -1;
  bValid := false;
  while (bValid = false) and (iCount < 7) and (bMove = true) do
  begin
    inc(iCount);
    if ((arrFTargets[iCount].Left <= imgsprite.Left + imgsprite.Width) and
        (arrFTargets[iCount].Left + iCharacter_move >= imgsprite.Left +
          imgsprite.Width) and ((imgsprite.Top >= arrFTargets[iCount].Top) and
          (imgsprite.Top <= arrFTargets[iCount].Top + arrFTargets[iCount]
            .Height))) then // this checks if the left of the image is touching the right of the sprite
    begin
      bValid := true;
      easeofaccess;
      casecheck(iCount);
    end
    else if ((arrFTargets[iCount].Top + arrFTargets[iCount]
          .Height >= imgsprite.Top) and (arrFTargets[iCount].Top + arrFTargets
          [iCount].Height - iCharacter_move <= imgsprite.Top) and
        ((imgsprite.Left >= arrFTargets[iCount].Left) and
          (imgsprite.Left <= arrFTargets[iCount].Left + arrFTargets[iCount]
            .Width))) then // this checks if the bottom of the image(image.top - image.height) is touching the top of the sprite
    begin
      bValid := true;
      easeofaccess;
      casecheck(iCount);
    end
    else if ((arrFTargets[iCount].Left + arrFTargets[iCount]
          .Width >= imgsprite.Left) and (arrFTargets[iCount].Left + arrFTargets
          [iCount].Width - iCharacter_move <= imgsprite.Left) and
        ((imgsprite.Top >= arrFTargets[iCount].Top) and
          (imgsprite.Top <= arrFTargets[iCount].Top + arrFTargets[iCount]
            .Height))) then // this checks if the right of the image(image.left + image.width) is touching the left of the sprite
    begin
      bValid := true;
      easeofaccess;
      casecheck(iCount);
    end
    else if ((arrFTargets[iCount].Top + iCharacter_move >= imgsprite.Top +
          imgsprite.Height) and (arrFTargets[iCount].Top <= imgsprite.Top +
          imgsprite.Height) and ((imgsprite.Left >= arrFTargets[iCount].Left)
          and (imgsprite.Left <= arrFTargets[iCount].Left + arrFTargets[iCount]
            .Width))) then // this checks if the top of the image is touching the botom of the sprite
    begin
      bValid := true;
      easeofaccess;
      casecheck(iCount);
    end;
  end;
end;

Hope this can help anyone who comes across this problem in the future.

0

The problem with your Collision detection method is that you are simply checking to see if your Image components have same position determined by Top and Left property.

This would work only if all of your images would have same size and could only be moved in positional increments same as width or height of your images. Or simply said it would work only if all of your images are aligned to a fixed grid.

Now if your images are not aligned on a fixed grid then you will have to perform a collision detection between the rectangles representing your images.

So first you need to get the rectangle that represents your image. You can do this simply by reading BoundsRect property of your Image controls which return information as TRect type.

Now TRect is an advanced record type which also contains some useful methods to work with rectangles. Amongst them are also several methods for detecting collision between multiple rectangles.

Three of the most usable methods to you would probably be:

  • IntersectsWith which checks to see if current record intersects with another record that you pass as parameter.
  • Intersect which returns a new rectangle representing the intersection point between two rectangles. Could be useful if you want to apply some special effect on the intersection area of those two rectangles.
  • Contains which returns true if provided Point or Rectangles that you pass as parameter is contained withing your current rectangle. Do note that this method returns true only if entire rectangle that is passed to this method is contained within your current Rectangle.

So collision detection code in your case would look something like:

if imgsprite.BoundsRect.IntersectsWith(FTargets[iCount].BoundsRect) then
//Code to do what it needs to be done when collision is detected.

Also looking at your code and your while loop I think it might have to much unnecessary complexity. I might be wrong since I don't see your entire code.

Any way I would use for loop instead and then break the loop if collision is detected in order to avoid checking collision with rest of the image array.

So the code might look something like this:

for iCount := 0 to 10 do
if imgsprite.BoundsRect.IntersectsWith(FTargets[iCount].BoundsRect) then
begin
  //Code to do what it needs to be done when collision is detected.
  Break; //Breaks the for loop in irder to prevent checking collision detection 
         //with rest of the items in FTargets array.
end;
SilverWarior
  • 7,372
  • 2
  • 16
  • 22