How to use TCastleSpriteSheet in TCastleUserInterface

type
  TPlayerHud = class(TCastleUserInterface)
  private
    FSpriteSheet: TCastleSpriteSheet;
    FCurrentAnimation: TCastleSpriteSheetAnimation;
    FCurrentFrameIndex: Integer;
    FTimeAccumulator: Single;
    FAtlasImage: TDrawableImage; 
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Update(const SecondsPassed: Single; var HandleInput: Boolean); override;
    procedure Render; override;
  end;


constructor TPlayerHud.Create(AOwner: TComponent);
var
  AtlasImage: TCastleImage;
begin
  inherited;

  FSpriteSheet := TCastleSpriteSheet.Create(False); 
  FSpriteSheet.Load('player_animations.castle-sprite-sheet');


  if FSpriteSheet.AtlasCanBeRegenrated then
  begin
    FSpriteSheet.RegenerateAtlas;
    FAtlasImage := FSpriteSheet.GeneratedAtlasImage;
  end;


  if FSpriteSheet.AnimationCount > 0 then
  begin
    FCurrentAnimation := FSpriteSheet.AnimationByIndex(0);
    FCurrentFrameIndex := 0;
  end;

  FTimeAccumulator := 0;
end;

destructor TPlayerHud.Destroy;
begin
  FreeAndNil(FAtlasImage);
  FreeAndNil(FSpriteSheet);
  inherited;
end;

procedure TPlayerHud.Update(const SecondsPassed: Single; var HandleInput: Boolean);
var
  FrameDuration: Single;
begin
  inherited;
  if (FCurrentAnimation <> nil) and (FCurrentAnimation.FrameCount > 0) then
  begin
 
    FrameDuration := 1 / FCurrentAnimation.FramesPerSecond;


    FTimeAccumulator := FTimeAccumulator + SecondsPassed;
    while FTimeAccumulator >= FrameDuration do
    begin
      FTimeAccumulator := FTimeAccumulator - FrameDuration;
      FCurrentFrameIndex := (FCurrentFrameIndex + 1) mod FCurrentAnimation.FrameCount;
    end;
  end;
end;

procedure TPlayerHud.Render;
var
  R: TFloatRectangle;
  Frame: TCastleSpriteSheetFrame;
  SourceRect: TFloatRectangle;
begin
  inherited;

  R := FloatRectangle(10, 10, 400, 50);
  DrawRectangle(R, Vector4(1, 0, 0, 0.5));
  R := R.Grow(-3);
  R.Width := R.Width * 100 / 200;
  DrawRectangle(R, Vector4(1, 0, 0, 1));
  UIFont.Print(20, 20, Yellow, Format('Player life: %f / %f', [
   100,200  
  ]));


  if (FCurrentAnimation <> nil) and
     (FCurrentFrameIndex < FCurrentAnimation.FrameCount)  then
  begin
    Frame := FCurrentAnimation.Frame[FCurrentFrameIndex];

    SourceRect := FloatRectangle(
      Frame.XInAtlas,
      Frame.YInAtlas,
      Frame.WidthInAtlas,
      Frame.HeightInAtlas
    );

    FAtlasImage.Draw(420, 10, SourceRect);
  end;
end;

See Sprite Sheets | Manual | Castle Game Engine for how to use sprite sheets, with videos and links to examples :slight_smile:

In short: Simply place TCastleScene with sprite sheet loaded in a TCastleViewport. Then place this TCastleViewport as a child of UI control, TCastleViewport can be a child of anything in UI (like TCastleButton). See engine examples, e.g. examples/component_gallery shows how to place a TCastleScene with 2D model (in this case loaded from Spine JSON, but sprite sheets would work equally well) in a TCastleViewport in a button:

TCastleScene has features to play animations, control time speed, and do more things you will likely need (like manage drawing order and blending sorting, ev. physics integration etc.). You can set the above components using either Pascal code or by clicking in CGE editor (great for testing, even if later you will want to do it by Pascal code).

Looking at your question and code:

  • Do not use TCastleSpriteSheetAnimation, it’s in CastleInternalSpriteSheet unit (with “internal” in the name) to signal it’s not something you’re supposed to use.

  • Usually: Do not draw yourself stuff in TCastleUserInterface.Render overrides, this is most often more work than necessary and you end up missing things (like drawing order or physics integration) already done in the engine for TCastleScene. This is speaking from experience – in the end, most attempts to “manually draw stuff” introduce additional code complication for no gain, from what I’ve seen.

    That is why our docs (like Advanced: custom drawn 2D controls | Manual | Castle Game Engine , How to render 2D games with images and sprites | Manual | Castle Game Engine ) provide this as an advanced option, but also warn you that it’s not recommended. It’s really not recommended :slight_smile: Better use TCastleScene in which case you don’t even need to write any code to do what you describe, it can be configured and tested in the CGE editor :slight_smile:

P.S. Forum notes: When posting a long code, use 3 backticks to make it more readable. See Formatting posts using markdown, BBCode, and HTML - Using Discourse - Discourse Meta about Markdown syntax on this forum. You can even use “backtick backtick backtick delphi” to make Pascal syntax colored. For longer code, you can also just point us to the GitHub repo, or GitHub “gist” where you pasted your code.

I edited now your post to show this.

Yes, Thank you very much, castle game engine is the best for Pascal or Delphi. examples\advanced_editor\advanced_loading_designs\ has demo! castle game engine Is great!!!

1 Like