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?..

Sorry about delay – I see the last question here is still waiting.

In general, when making inventory, you can keep some “parent” object and just add there dynamically new slots.

  1. If yo want inventory as 2D, as user interface, then use TCastleVerticalGroup or TCastleHorizontalGroup. Inside it, add as many times the inventory “slot” as you need. The “slot” may be an item image, or something more complicated, and you can use reusable designs ( Components to reuse a design in other designs | Manual | Castle Game Engine ) to design “one slot” and then reuse it multiple times.

    Note: I would not advise using TDrawableImage and drawing yourself. It’s a large work then where you have to do some things manually. Instead, use CGE UI components, where things are easier, and you can visually see them in editor.

  2. Or if your inventory has to live in 3D, then put it under some TCastleTransform. The “parent inventory TCastleTransform” should live in some TCastleViewport.Items to be visible. Most likely, it should be a child of TCastleCamera, Camera | Manual | Castle Game Engine , if you want the inventory to follow the player.

    And then you can again do it dynamically, i.e. you can design something like “one slot” as a reusable design ( Components to reuse a design in other designs | Manual | Castle Game Engine - same link, now your design would be a .castle-transform) and add / remove slots as needed at runtime.

    Indeed you can use then MyViewport.TransformUnderMouse to detect clicks on it.

    The inventory in this case may be inside the “main” viewport, or in a separate viewport (with OverlayViewport.Transparent=true). Both cases make sense, the 1st case is more straightforward.

I hope this helps – I feel I didn’t say anything new, but maybe reordered a bit some above posts, maybe it will help :slight_smile: Let me know if this is unclear.

1 Like