Interface for RPG

Good day!
I want to ask you about PRG interfaces with items, dialogs etc. Inventory here, I think, can be as example and for another parts of game interface.

In my plan, all NPS and many of another objects will have inventories, so, I need dynamic array of images, what will connected with array of items in inventory (I already did this thing for objects in scene, but interface make some difficulties).
Thus I can’t construct this inventory as rigid TCastleDesign, because it always include variate number of images. What way is better to realize this?

I tryed to make class (like in HUD manual):

unit GameViewMain;

interface

uses Classes,
  CastleVectors, CastleComponentSerialize,
  CastleUIControls, CastleControls, CastleGLImages, CastleKeysMouse;

type

  TInventoryDraw = class(TCastleUserInterface)
  private
    Items: array of TDrawableImage;
  public
    constructor Create(AOwner: TComponent); override;
    procedure SetInventorySize(len: Integer);
    procedure Render; override;
    destructor Destroy; override;
  end;

  { Main view, where most of the application logic takes place. }
  TViewMain = class(TCastleView)
  published
    { Components designed using CGE editor.
      These fields will be automatically initialized at Start. }
    LabelFps: TCastleLabel;
    ExampleRect: TCastleRectangleControl;
    ExampleScroll: TCastleScrollView;

    InventoryDraw_1: TInventoryDraw;
    InventoryDraw_2: TInventoryDraw;
  public
    constructor Create(AOwner: TComponent); override;
    procedure Start; override;
    procedure Update(const SecondsPassed: Single; var HandleInput: Boolean); override;
    function Press(const Event: TInputPressRelease): Boolean; override;
  end;

var
  ViewMain: TViewMain;

implementation

uses SysUtils;

constructor TInventoryDraw.Create(AOwner: TComponent);
begin
  inherited;
end;

procedure TInventoryDraw.SetInventorySize(len: Integer);
var
  i: Integer;
begin
  SetLength(Items, len);
  for i := 0 to len - 1 do
    Items[i] := TDrawableImage.Create('castle-data:/sword.png');
end;

procedure TInventoryDraw.Render;
var
  i: Integer;
begin
  inherited;
  for i := 0 to Length(Items) - 1 do
    Items[i].Draw(i * 100, 0);
end;

destructor TInventoryDraw.Destroy;
begin
  inherited;
end;

{ TViewMain ----------------------------------------------------------------- }

constructor TViewMain.Create(AOwner: TComponent);
begin
  inherited;
  DesignUrl := 'castle-data:/gameviewmain.castle-user-interface';
end;

procedure TViewMain.Start;
begin
  inherited;

  ExampleRect := DesignedComponent('eRect') as TCastleRectangleControl;
  InventoryDraw_1 := TInventoryDraw.Create(ExampleRect);
  InventoryDraw_1.SetInventorySize(4);

  ExampleScroll := DesignedComponent('eScrollView') as TCastleScrollView;
  InventoryDraw_2 := TInventoryDraw.Create(ExampleScroll.ScrollArea);
  InventoryDraw_2.SetInventorySize(3);
end;

procedure TViewMain.Update(const SecondsPassed: Single; var HandleInput: Boolean);
begin
  inherited;
  { This virtual method is executed every frame (many times per second). }
  Assert(LabelFps <> nil, 'If you remove LabelFps from the design, remember to remove also the assignment "LabelFps.Caption := ..." from code');
  LabelFps.Caption := 'FPS: ' + Container.Fps.ToString;
end;

function TViewMain.Press(const Event: TInputPressRelease): Boolean;
begin
  Result := inherited;
  if Result then Exit;
end;

end.

But it is not working… What is wrong in my example?
Result:

Thanks!

I didn’t investigate your example too closely but I can point out two potential issues:

  1. You create 2 instances of TInventoryDraw in Start but you never InsertFront it into the TViewMain. You should do something like this:
ExampleRect := DesignedComponent('eRect') as TCastleRectangleControl;
InventoryDraw_1 := TInventoryDraw.Create(ExampleRect);
InventoryDraw_1.SetInventorySize(4);
ExampleRect.InsertFront(InventoryDraw_1);
  1. It might be more convenient to use TCastleVerticalGroup and TCastleHorizontalGroup to arrange items in a predefined order. E.g. you can have a look at https://github.com/castle-engine/castle-engine/tree/master/examples/advanced_editor/custom_component example which shows how to arrange items on a grid.

E.g. those are automatically adjusted to work well with other UIs, like TCastleScrollView, on different target platforms.

1 Like

Thank you, it is done.
But next question - how to position items relative to parent? Because now they are not inside the rectangles (Position relative to Window):

So, it will set right size of CastleScrollView without special code from me?

Yes, exactly. When you TDrawableImage.Draw you need to position them on the screen in appropriate places considering how the Parent is positioned, e.g. including sliders positions of the TCastleScrollView and other potentially weird situations, e.g. if the Parent is FullSize, automatically scales to children size and so on.

When you use TCastleVerticalGroup and TCastleHorizontalGroup all those calculations are done for you. You just insert a TCastleImageControl or something “custom” for inventory button - and the Engine will do the rest of scaling-positioning-clipping.

1 Like

I did it with TCastleVerticalGroup and TCastleImageTransform, works good, but.
For interaction with objects into the TCastleViewport I use ViewportName.TransformUnderMouse. For now my interface outside Viewport.Items.


It means if I use game inventory with unknown size, I have two approaches?

  1. To make special Viewport for interface and use Interface_Viewport.TransformUnderMouse to get access to item;
  2. Make interface design with excess size and make buttons for every hypotetical loaded item.

First approach seems more usable, but it needs special Viewport. Is it expensive way to make interface or not? Or I didn’t notice better way?..