Connection between TCastleTransfor and another types

Good day!
I have a question about interaction between visible objects and special types of my game.
So, for now I use two matrix (2D arrays) with same size to generate scene:
1 - matrix of TEssence (TEssence is a basic type for all objects in game world). This array include all object of game area (and all areas have this array as type field). But this type not include pictures, they carried only URL in the String variable.
2 - matrix of TCastleTransform - this is items of scene.

Program compare this 2 matrix and change pictures for TCasleTransforms:
TCastleTransform[i][j].URL := TEssence[i][j].EssneceURL.

But I want to take access from this TCastleImageTransform[i][j] back to game world object to open him menu after mouse click and for another things, when i use TransformUnderMouse.
What better way to do this?
Maybe I can create new type with fields ID and TCastleTransform . And add same ID field to my TEssence type for feedback?

There are a few possible approaches.

  1. If your map size isn’t big, and performing a search each time user clicks isn’t a problem, then just make a “naive but simple approach”:

    Have a 2D array with records describing each tile, including reference to TCastleTransform for tile:

      TMapTileType = (mttGrass, mttWater, mttStone);
      TMapTileInfo = record
        MapTileType: TMapTileType;
        MapTileTransform: TCastleTransform;
      MyMap: array [0..Width-1, 0..Height - 1] of TMapTileInfo;

    When user clicks on a map tile, you can just search for it, like

    function MapInfoFromTransform(const ATransform: TCastleTransform): TMapTileInfo;
      for I := 0 to Width - 1 do
        for J := 0 to Height - 1 do
          if MyMap[I, J].MapTileTransform = ATransform then
            Exit(MyMap[I, J]);
      raise Exception.Create('MapInfoFromTransform: not found');

    I would call this an “unoptimal but completely reasonable approach” if MapInfoFromTransform isn’t called very often (e.g. only on click) and Width , Height are not too big. No need to optimize prematurely.

    Now there are various ways to optimize it:

  2. Use TDictionary, mapping TCastleTransform to TMapTileInfo.

    I will not go into details, because all other approaches in this post are better :slight_smile:

  3. Use the Tag property at TCastleTransform (it is inherited from standard Pascal TComponent, TComponent ).

    You can encode in it your map X and Y. Assuming you have some “value larger than any possible width”, like 10000, you can set at initialization:

    Transform[X, Y].Tag := X + Y * 10000; 

    Then you can decode it in MapInfoFromTransform, making it instant:

    function MapInfoFromTransform(const ATransform: TCastleTransform): TMapTileInfo;
      Y := ATransform.Tag div 10000;
      X := ATransform.Tag - Y * 10000;
      Exit(MyMap[X, Y]);
  4. Finally, use behaviors ( Behaviors | Manual | Castle Game Engine ) to attach extra information to each map tile. Note that in example below TMapTileInfo is a class.

    TMapTileInfo = class(TBehavior)
      MapTileType: TMapTileType;

    Then you can just do

    function MapInfoFromTransform(const ATransform: TCastleTransform): TMapTileInfo;
      Result := ATransform.FindBehavior(TMapTileInfo) as TMapTileInfo;

Personally all above approaches are really sensible.

Depending on what you need, I would personally start with either AD 1 or AD 4.

From AD 1 you can always easily “upgrade” to AD 2 or AD 3, as it is just an optimization.

AD 4 is quite similar to how shooting is done in the “3D FPS game” template. It is powerful, if you see more uses of behaviors (to attach information or functionality) to each tile. In the Press method it does:

  if Event.IsMouseButton(buttonLeft) then

    { We clicked on enemy if
      - TransformUnderMouse indicates we hit something
      - It has a behavior of TEnemy. }
    if (MainViewport.TransformUnderMouse <> nil) and
       (MainViewport.TransformUnderMouse.FindBehavior(TEnemy) <> nil) then
      HitEnemy := MainViewport.TransformUnderMouse.FindBehavior(TEnemy) as TEnemy;

1 Like

Thank you so much!
First approach is simillar like in the example ZombieFighter?
In this aproach all analogous object haven’t individuality, so, if I have two same monsters, but with different names, I must make two types with only one difference - name: Monster_Boris and Monster_Dmitry, for example?
So, as I understood, way with .Tag more flexible and universal, especially for scenes with many different objects.
And Behaviors, for newbies - it is a types, who allows to attach new user methods to any TCastleTransform, right?..

All the scenarios I described could hold some information specific to each monster instance (like current health) or constant for monster type (like an enum indicating monster type). I didn’t really pay attention to this detail in my answer in AD 1-4, as it can work any way :slight_smile: You can have some monster-specific information (like a class TMyMonster = class) linking to information specific to monster type (like an enum describing monster type, or a whole class describing this monster type).

In case of behavior, this is most natural – as each TCastleBehavior is a separate class instance.

Behavior is an instance of class you attach to TCastleTransform. It can hold some information (fields, properties) and/or have some methods. See how it works in “3D FPS game” template –

  TEnemy = class(TCastleBehavior)
  strict private
    Scene: TCastleScene;
    MoveDirection: Integer; //< Always 1 or -1
    Dead: Boolean;
    constructor Create(AOwner: TComponent); override;
    procedure ParentAfterAttach; override;
    procedure Update(const SecondsPassed: Single; var RemoveMe: TRemoveType); override;
    procedure Hurt;

So behavior class introduces here new method (Hurt) and also new information (Dead: Boolean, MoveDirection: Integer; etc.)