Playing part of an animation xml file

I have this sprite image xml file and want to play a range of it:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with TexturePacker https://www.codeandweb.com/texturepacker -->
<!-- $TexturePacker:SmartUpdate:0c3d3ab755d2dfd798e1fa5e54f48bbc:131458e89c5c6c5178fb57a8104bd8ec:9a06091b55b8b7c29d9674316a7dcf4c$ -->
<TextureAtlas imagePath="kyleyturn.png" width="8677" height="1204">
    <SubTexture name="kyleyturn_01" x="1" y="1" width="303" height="1182" frameX="-40" frameY="-12" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_02" x="306" y="1" width="299" height="1182" frameX="-44" frameY="-14" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_03" x="607" y="1" width="286" height="1178" frameX="-55" frameY="-23" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_04" x="895" y="1" width="276" height="1180" frameX="-71" frameY="-25" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_05" x="1173" y="1" width="257" height="1185" frameX="-93" frameY="-22" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_06" x="1432" y="1" width="244" height="1184" frameX="-105" frameY="-24" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_07" x="1678" y="1" width="217" height="1181" frameX="-129" frameY="-28" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_08" x="1897" y="1" width="189" height="1178" frameX="-147" frameY="-30" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_09" x="2088" y="1" width="205" height="1174" frameX="-141" frameY="-32" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_10" x="2295" y="1" width="192" height="1195" frameX="-125" frameY="-18" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_11" x="2489" y="1" width="206" height="1192" frameX="-109" frameY="-19" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_12" x="2697" y="1" width="213" height="1191" frameX="-101" frameY="-20" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_13" x="2912" y="1" width="268" height="1192" frameX="-64" frameY="-20" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_14" x="3182" y="1" width="293" height="1192" frameX="-52" frameY="-21" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_15" x="3477" y="1" width="315" height="1190" frameX="-45" frameY="-22" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_16" x="3794" y="1" width="336" height="1188" frameX="-41" frameY="-22" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_17" x="4132" y="1" width="348" height="1183" frameX="-44" frameY="-23" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_18" x="4482" y="1" width="366" height="1184" frameX="-34" frameY="-23" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_19" x="4850" y="1" width="357" height="1187" frameX="-43" frameY="-24" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_20" x="5209" y="1" width="341" height="1188" frameX="-56" frameY="-24" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_21" x="5552" y="1" width="304" height="1186" frameX="-73" frameY="-26" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_22" x="5858" y="1" width="254" height="1184" frameX="-93" frameY="-26" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_23" x="6114" y="1" width="209" height="1184" frameX="-102" frameY="-27" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_24" x="6325" y="1" width="192" height="1186" frameX="-108" frameY="-26" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_25" x="6519" y="1" width="212" height="1202" frameX="-127" frameY="-10" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_26" x="6733" y="1" width="227" height="1202" frameX="-116" frameY="-10" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_27" x="6962" y="1" width="247" height="1202" frameX="-97" frameY="-10" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_28" x="7211" y="1" width="274" height="1201" frameX="-68" frameY="-10" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_29" x="7487" y="1" width="278" height="1201" frameX="-63" frameY="-10" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_30" x="7767" y="1" width="291" height="1197" frameX="-45" frameY="-11" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_31" x="8060" y="1" width="304" height="1191" frameX="-35" frameY="-13" frameWidth="400" frameHeight="1260"/>
    <SubTexture name="kyleyturn_32" x="8366" y="1" width="310" height="1183" frameX="-32" frameY="-16" frameWidth="400" frameHeight="1260"/>
</TextureAtlas>

Then I load it with:

Player.TurnScene.URL:= ‘kyleyturn.starling-xml#fps:10,anim-naming:strict-underscore’;

If I try to play the first 4 images with:
PlayAnimation(‘kyleyturn_01, kyleyturn_02, kyleyturn_03, kyleyturn_04’, false);

it does not work (it only shows the first image)

If I use:
PlayAnimation(‘kyleyturn’, false);
it plays all 32 images.

But in this example I want to play just images 1-4. So how can I just play a range?
I read https://github.com/castle-engine/castle-engine/wiki/Sprite-sheets and cannot find what is wrong.

PlayAnimation takes an animation name, not a list of frame names.

If you want to have an animation that is a subset of another animation, right now you just have to define the new animation, as kyleyturnShort or something like that.

It’s simplest to use of CGE Sprite Sheet Editor to copy the old animation to new, and remove unneeded frames from the new animation.

The idea is that the sprite character can turn in every direction.

I have now renamed the first subtexture names to:

kyleyturnfronttosw_01
kyleyturnfronttosw_02
kyleyturnfronttosw_03
kyleyturnfronttosw_04

(turns facing front to facing south west)

PlayAnimation(‘kyleyturnfronttosw’, false);

This works, but if I, for instance, want to turn in one animation sequence from facing front to back I would need 4 different lists that play after each other.
Or is there a way to set a Playanimation list?

PlayAnimation(‘kyleyturnfronttosw’, false); // (turns facing front to facing south west)
PlayAnimation(‘kyleyturnswtoleft’, false); // (turns facing south west to facing left)
PlayAnimation(‘kyleyturnlefttonw’, false); // (turns facing left to north west)
PlayAnimation(‘kyleyturnnwtoback’, false); // (turns facing north west to back)

This only plays the last sequence. :face_with_raised_eyebrow:
So every next Playanimation command should “wait” for the previous one to finish, but I don’t know how.

I noticed the backlog in the new sprite editor and found:

image

This looks interesting because I would be able to create different xml files for the same .png, so using different anim names for the same images and so different subsets.
But the sprite editor development looks quiet for some Months and is not updated since 21 April.

Use TPlayAnimationParameters.StopNotification feature to implement “playing a list of animations, in a sequence”.

This mechanism was already mentioned, with examples, in 2 threads where you participated :slight_smile:

  1. Sprite control with the new system - #2 by michalis ,

  2. Play all animations in sequence - #8 by michalis .

The latter thread contains a ready example from me that you can adjust to do exactly what you need here. It implements RunAllAnimations , taking AnimList := Scene.AnimationsList. You can adjust it to implement instead

RunAnimationList(const Scene: TCastleSceneCore; const AnimNames: array of Strings)

You would then call it like

RunAnimationList(Scene, ['kyleyturnfronttosw’,‘kyleyturnswtoleft’, ‘kyleyturnlefttonw’, ‘kyleyturnnwtoback'])

The only difference between your RunAnimationList and the RunAllAnimations is how to populate the AnimList, list of animations scheduled to play. In RunAnimationList you should

  • make sure to clear AnimList and stop current animation, if any
  • populate the AnimList with new animation names you want to play (``AnimNames` from my declaration)

You can already do this. You can refer to the same PNG image from multiple sprite sheets, in any format (Starling XML or .castle-sprite-sheets), this was never a problem. You can just edit the XML files in a text editor to make them refer to the same PNG.

The “Support for multile atlas files for one sprite sheet” ticket you mention is about a different thing – to have one .castle-sprite-sheet refer to multiple PNG files.

Because the main features are working and we are just using them, see e.g. recent news about Platformer by Castle Game Engine release – it’s a CGE demo that is using new sprite sheets for everything.

Sorry, I already had a deja-vu about this :wink:
Thanks. I still find it difficult to implement.
I also looked at the examples\animations\play_animation.
I tried out to start as simple as possible and now have:

 procedure CreateAnimationSequence;
  var
    AnimationName: String;
   begin
     Player.TurnScene.AnimationsList.LoadFromURL('kyleyturn.starling-xml#fps:10,anim-naming:strict-underscore');
     for AnimationName in Player.TurnScene.AnimationsList do
     begin
      RunAnimationList(Player.TurnScene, ['kyleyturnfronttosw’,‘kyleyturnswtoleft’, ‘kyleyturnlefttonw’, ‘kyleyturnnwtoback'])
  end;  

Is this okay to load the existing animations from xml file and select a few of them with the (still not existing RunAnimation procedure)?

Sorry, I don’t follow what your code above tries to do.

  • You should load the scene from a sprite sheet. There is no AnimationsList.LoadFromURL.

  • You should play all animations with one call to RunAnimationList(Player.TurnScene, 'kyleyturnfronttosw’,‘kyle....). There’s no point for a loop there (for AnimationName in Player.TurnScene.AnimationsList do...).

My advise:

  • create a new project, with a viewport with 2D camera, that only contains one scene with a sprite sheet.

  • Try to adjust my code from Play all animations in sequence - #8 by michalis to implement RunAllAnimations in that new simple project. See that it works.

  • Then remake it to implement RunAnimationList as I described above.

Ok, I tried to create a new project with the CGE template but I noticed the properties do not show up anymore in Lazarus. Then I re-installed lazarus and latest CGE but I cannot access the properties anymore(I tried the standard LabelFps).
Loading CGE examples and trying to change properties in code also do not show up (though they work, I tried changing label caption and it shows my edited text when I compile from the editor.
Any idea what is wrong here?

@michalis
Also I am trying to use your:

I got an error, though Animlist is a TStringlist it says it is a TString.

I have put all stuff in the attachment (and commented out some procedures as I get errors).
Can you please take a look at it? This is difficult for me.

animationsequence3.zip (61.0 MB)

Maybe you didn’t open the proper Lazarus project (and only a file in this project)? Use “Project → Open Project” in Lazarus to open the right project. Or use “Code → Open Project In Code Editor” from CGE editor.

It’s easy right now to mistakenly forget about it, because of Trello

TStrings is more abstract ancestor of TStringList. You probably need to change Animlist to be just TStrings as well in this example.

I recommend first learning these classes, if you don’t know them. Create a new project, unrelated to CGE, and play there with creating, copying contents of etc. of TStringList. See docs like

Yes, that did it. Thanks.

Yes, when I change AnimList: TStringList to
AnimList: TStrings I get no errors. Makes sense because Scene property AnimationsList is also a TString.

Clipboard01

Now I get TTimeSensorNode error.
I have uploaded a new attachment. I just need to play the sequence of the 4 animation names that are in the sprite xml screen. Can you please help me with the code? I got my game animation stuff almost complete except playing sequences of animations.

animationsequence4.zip (44.3 MB)

The first step was to make RunAllAnimations running, so that my example from Play all animations in sequence - #8 by michalis works.

Please first make sure it works – that you have a working RunAllAnimations, in a simple test application.

Try to understand that code. Code from the outside should not call RunNextAnimation, this doesn’t make sense – RunNextAnimation is just a helper method, we need it as a the Params.StopNotification callback. Code from the outside should only call RunAllAnimations. I emphasize: make sure it works, and play with this code so you understand what it does.

Once you got it, the next step is to change RunAllAnimations into something like RunAnimationSequence . It should take, as arguments, the Scene, and the list of animation names, and look like

procedure xxx.RunAnimationSequence(const Scene: TCastleSceneCore; const Names: array of String);
begin
  ..// implementation should be similar to RunAllAnimations, but you have to initialize new AnimList with the contents of Names
end;

The only thing you have to do is initialize AnimList to be

  • a new instance of TStringList
  • to contain a copy of Names: array of String

Ok, I made the code as small as possible and focused on procedure RunAllAnimation.
Now I get an access violation error.

Clipboard01

I thought Scene1.AnimationsList automatically contains all animation names of the loaded “kyleyturn.starling-xml” file?

unit GameStateMain;

interface


uses Classes,
  CastleUIState, CastleComponentSerialize, CastleUIControls, CastleControls,
  CastleKeysMouse, CastleViewport, CastleScene, CastleVectors, StrUtils, Dialogs,
  CastleWindow,CastleUtils, CastleGLImages, CastleFilesUtils,
  CastleSoundEngine, CastleTimeUtils, CastleColors,
  CastleRectangles, CastleFonts, CastleGLUtils, Generics.Defaults,
  Generics.Collections, CastleDownload, CastleSceneCore,
  CastleCameras, CastleTransform, CastleImages, X3DNodes, X3DFields, CastleURIUtils;


type
  TStateMain = class(TUIState)
  private
    Viewport: TCastleViewport;
   public
    constructor Create(AOwner: TComponent); override;
    procedure Start; override;
  end;

  type
  TSomeClass = class
  private
    AnimList: TStrings;
    NextAnim: Integer;
    procedure RunNextAnimation(const Scene: TCastleSceneCore; const Animation: TTimeSensorNode);
  public
    procedure RunAllAnimations(const Scene: TCastleSceneCore);
  end;


var
  StateMain: TStateMain;
  Scene1: TCastleScene;
  PlayList: TSomeClass;

implementation

uses SysUtils;

{ TStateMain ----------------------------------------------------------------- }

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

procedure TStateMain.Start;
begin
  inherited;

  Viewport := DesignedComponent ('Viewport') as TCastleViewport;

  Scene1 := DesignedComponent('Scene') as TCastleScene;
  Viewport.Items.Add(Scene1);

  Scene1.Translation:= Vector3(800,300,0);
  Scene1.URL:= 'castle-data:/kyleyturn.starling-xml';

  PlayList.RunAllAnimations(Scene1);

 end;

procedure TSomeClass.RunAllAnimations(const Scene: TCastleSceneCore);
begin
  AnimList := Scene.AnimationsList;
  NextAnim := 0;
  RunNextAnimation(Scene, nil);
end;

procedure TSomeClass.RunNextAnimation(const Scene: TCastleSceneCore; const Animation: TTimeSensorNode);
var
  NextAnimationName: String;
  Params: TPlayAnimationParameters;
begin
  NextAnimationName := AnimList[NextAnim];
  NextAnim := (NextAnim + 1) mod AnimList.Count;

  Params := TPlayAnimationParameters.Create;
  try
    Params.Name := NextAnimationName;
    Params.StopNotification := @RunNextAnimation;
    Scene.PlayAnimation(Params);
  finally FreeAndNil(Params) end;
end;


end.

Looking at your code: you declare PlayList to be an instance of class TSomeClass, but you never actually create it, you never call the TSomeClass constructor. That’s the reason for Access Violation – your code cannot write to the field AnimList, because it is part of PlayList instance, but PlayList was never created.

If you’re not sure how to create an instance of the class, consult learning resources about Pascal, like

Note that you could also just place the example code from Play all animations in sequence - #8 by michalis inside the state class, i.e. you don’t really need a new class (TSomeClass), you can just use the TStateMain class and extend it.

That is what my comment in that example meant:

TSomeClass = class // place this in any class you have handy

Ah, of course, stupid of me. Normally I get a SIGSEGV error when I forget to create the instance.
Sorry!

Yes, I finally got RunAllAnimations working, thanks! All animations are playing now.

Now I added a RunAnimationList procedure (and outcommented the RunAllAnimations for now) but it is not quite clear to me how to load the 4 animation strings in the start procedure.

Yes, I don’t know how to put this in the RunAnimationList procedure now, sorry.
And then they only have to play once in the RunNext procedure, so they should not loop.


unit GameStateMain;

interface


uses Classes,
  CastleUIState, CastleComponentSerialize, CastleUIControls, CastleControls,
  CastleKeysMouse, CastleViewport, CastleScene, CastleVectors, StrUtils, Dialogs,
  CastleWindow,CastleUtils, CastleGLImages, CastleFilesUtils,
  CastleSoundEngine, CastleTimeUtils, CastleColors,
  CastleRectangles, CastleFonts, CastleGLUtils, Generics.Defaults,
  Generics.Collections, CastleDownload, CastleSceneCore,
  CastleCameras, CastleTransform, CastleImages, X3DNodes, X3DFields, CastleURIUtils;


type
  TStateMain = class(TUIState)
  private
    Viewport: TCastleViewport;
    AnimList: TStrings;
    NextAnim: Integer;
    procedure RunNextAnimation(const Scene: TCastleSceneCore; const Animation: TTimeSensorNode);
   public
    constructor Create(AOwner: TComponent); override;
    procedure Start; override;
    procedure RunAllAnimations(const Scene: TCastleSceneCore);
    procedure RunAnimationList(const Scene: TCastleSceneCore; const AnimNames: array of String);
  end;


var
  StateMain: TStateMain;
  Scene1: TCastleScene;

implementation

uses SysUtils;

{ TStateMain ----------------------------------------------------------------- }

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

procedure TStateMain.Start;
begin
  inherited;

  Viewport := DesignedComponent ('Viewport') as TCastleViewport;

  Scene1 := DesignedComponent('Scene') as TCastleScene;
  Viewport.Items.Add(Scene1);

  Scene1.Translation:= Vector3(800,300,0);
  Scene1.URL:= 'castle-data:/kyleyturn.starling-xml';

  // RunAllAnimations(Scene1);
  RunAnimationList(Scene1, ['kyleyturnfronttosw’,‘kyleyturnswtoleft’, ‘kyleyturnlefttonw’, ‘kyleyturnnwtoback']);

 end;

procedure TStateMain.RunAllAnimations(const Scene: TCastleSceneCore);
begin
//  AnimList := Scene.AnimationsList;
//  NextAnim := 0;
//  RunNextAnimation(Scene, nil);
end;

procedure TStateMain.RunAnimationList(const Scene: TCastleSceneCore; const AnimNames: array of String);
begin
  AnimList := ??????????????
  NextAnim := 0;
  RunNextAnimation(Scene, nil);
end;


procedure TStateMain.RunNextAnimation(const Scene: TCastleSceneCore; const Animation: TTimeSensorNode);
var
  NextAnimationName: String;
  Params: TPlayAnimationParameters;
begin
  NextAnimationName := AnimList[NextAnim];
  NextAnim := (NextAnim + 1) mod AnimList.Count;

  Params := TPlayAnimationParameters.Create;
  try
    Params.Name := NextAnimationName;
    Params.StopNotification := @RunNextAnimation;
    Scene.PlayAnimation(Params);
  finally FreeAndNil(Params) end;
end;


end.

Please read about and learn to use TStringList class, standard in FPC and Delphi. I provided links to their documentation earlier in this thread:

What you need in this case is to create new TStringList instance (or clear some existing one) and add there all strings from the array that RunAnimationSequence gets as an argument (like const Names: array of String).

You can also use our TCastleStringList (see docs Castle Game Engine: CastleStringUtils: Class TCastleStringList ), that descends from TStringList and adds some helpers like AddRange.

To not loop, modify the NextAnim variable behavior. Right now it’s incremented such that it loops:

NextAnim := (NextAnim + 1) mod AnimList.Count;

You want to instead increment it simply, and do not call next animation if NextAnim reached AnimList.Count.

Thanks for the links but I did not find examples in them. A summary of all functions and properties to me is like showing me all parts of a car without telling me how to actually drive the thing.
This is what I miss: examples and tutorials. :slight_smile:
The Delphi boot camp course looks good though as it shows tutorial like. I will start to learn this route.
But I have found a few examples on TStringList and this actually works:


type
  TStateMain = class(TUIState)
  private
    Viewport: TCastleViewport;
    AnimList: TStrings;
    SequenceList: TStringList;
    NextAnim: Integer;
    procedure RunNextAnimation(const Scene: TCastleSceneCore; const Animation: TTimeSensorNode);
   public
    constructor Create(AOwner: TComponent); override;
    procedure Start; override;
    procedure RunAllAnimations(const Scene: TCastleSceneCore);
    procedure RunAnimationList(const Scene: TCastleSceneCore; const AnimNames: array of String);
  end; 

procedure TStateMain.RunAnimationList(const Scene: TCastleSceneCore; const AnimNames: array of String);
begin
  SequenceList.Add('kyleyturnfronttosw');
  SequenceList.Add('kyleyturnswtoleft');
  SequenceList.Add('kyleyturnlefttonw');
  SequenceList.Add('kyleyturnnwtoback');

  AnimList := SequenceList;
  NextAnim := 0;
  RunNextAnimation(Scene, nil);
end;                         

So I am already happpy with that.
Next thing I tried is putting the arguments of

 RunAnimationList(Scene1, ['kyleyturnfronttosw’,‘kyleyturnswtoleft’, ‘kyleyturnlefttonw’, ‘kyleyturnnwtoback']); 

instead of the list that I added in RunAnimationList
as argument so I had to use ‘const AnimNames: array of String’

SequenceList.Add(AnimNames[1]);
SequenceList.Add(AnimNames[2]);
SequenceList.Add(AnimNames[3]);
SequenceList.Add(AnimNames[4]);

This is of course not right, but how can I get the [‘kyleyturnfronttosw’,‘kyleyturnswtoleft’, ‘kyleyturnlefttonw’, ‘kyleyturnnwtoback’]); list into it? I cannot find any examples on this.

If I make it:

if NextAnim = AnimList.Count then Exit else Inc(NextAnim); 

I get a ‘list out of bounds’ error and if I make it

if NextAnim = AnimList.Count-1 then Exit else Inc(NextAnim);

it exits the procedure after playing the third animation (and does not play the last animation?)

There are lots of examples of TStringList, I see you found some of them. I found TStringList-TStrings Tutorial - Free Pascal wiki .

The Names is an array of Strings. More precisely, an “open array” that is passed as an argument. The first string has index 0, last string has index High(Names).

You need to learn how to iterate over an array of strings now. For each item in Names, you want to do SequenceList.Add(Names[I]).

You need to place a check for this at a proper place of the application. Find the right place to put

Inc(NextAnim); 

And find the right place to put exit condition:

if NextAnim = AnimList.Count then Exit;

They may not have to be done together. Track the value of NextAnim variable to understand when it gets large enough to stop the iteration.

I got a “list out of bounds” as soon AnimNames[I] was 1 or higher. Only 0 worked. So it seemed there was only 1 string in the array.
Then I tried separating the strings in the array again and found out the line
[‘kyleyturnfronttosw’,'kyleyturnswtoleft’, ‘kyleyturnlefttonw’, ‘kyleyturnnwtoback’] was wrong.
I had copied it from your example but some commas were the wrong characters.
It had to be [‘kyleyturnfronttosw’,‘kyleyturnswtoleft’, ‘kyleyturnlefttonw’, 'kyleyturnnwtoback’].
:slight_smile:
In the forum the commas do not show up right, as I noticed when I “corrected” them. They are replaced by accolades.

Solved.

And here is the final code. I don’t know if this is the best method but it works like a charm. :slight_smile:
I added a PlayForward switch so the order of playing animation of the sprite screen can also be from last to first or something in between, so the turning of the sprite can be clockwise or counterclockwise.


unit GameStateMain;

interface


uses Classes,
  CastleUIState, CastleComponentSerialize, CastleUIControls, CastleControls,
  CastleKeysMouse, CastleViewport, CastleScene, CastleVectors, StrUtils, Dialogs,
  CastleWindow,CastleUtils, CastleGLImages, CastleFilesUtils,
  CastleSoundEngine, CastleTimeUtils, CastleColors,
  CastleRectangles, CastleFonts, CastleGLUtils, Generics.Defaults,
  Generics.Collections, CastleDownload, CastleSceneCore,
  CastleCameras, CastleTransform, CastleImages, X3DNodes, X3DFields, CastleURIUtils;


type
  TStateMain = class(TUIState)
  private
    Viewport: TCastleViewport;
    AnimList: TStrings;
    SequenceList: TStringList;
    NextAnim: Integer;
    PlayForward: Boolean;
    procedure RunNextAnimation(const Scene: TCastleSceneCore; const Animation: TTimeSensorNode);
   public
    constructor Create(AOwner: TComponent); override;
    procedure Start; override;
    procedure RunAllAnimations(const Scene: TCastleSceneCore);
    procedure RunAnimationList(const Scene: TCastleSceneCore; const AnimNames: array of String);
  end;


var
  StateMain: TStateMain;
  Scene1: TCastleScene;

implementation

uses SysUtils;

{ TStateMain ----------------------------------------------------------------- }

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

procedure TStateMain.Start;
begin
  inherited;

  Viewport := DesignedComponent ('Viewport') as TCastleViewport;

  Scene1 := DesignedComponent('Scene') as TCastleScene;
  Viewport.Items.Add(Scene1);

  Scene1.Translation:= Vector3(800,300,0);
  Scene1.URL:= 'castle-data:/kyley_idle.starling-xml';

  SequenceList := TStringList.Create();

 // RunAllAnimations(Scene1);

  PlayForward := False;
  RunAnimationList(Scene1, ['kyleyfront', 'kyleyturnsw', 'kyleysw', 'kyleyturnleft', 'kyleyleft', 'kyleyturnnw', 'kyleynw', 
  'kyleyturnback', 'kyleyback', 'kyleyturnne', 'kyleyne', 'kyleyturnright', 'kyleyright', 'kyleyturnse', 'kyleyse', 'kyleyturnfront', 'kyleyfront']);
 end;

procedure TStateMain.RunAllAnimations(const Scene: TCastleSceneCore);
begin
  AnimList := Scene.AnimationsList;
  NextAnim := 0;
  RunNextAnimation(Scene, nil);
end;

procedure TStateMain.RunAnimationList(const Scene: TCastleSceneCore; const AnimNames: array of String);
var I: integer;
begin
  if PlayForward then
  begin
    For I := 0 to High(AnimNames) do
    begin
      SequenceList.Add(AnimNames[I]);
    end;
  end else
  begin
    For I := High(AnimNames) downto 0 do
    begin
    SequenceList.Add(AnimNames[I]);
  end;
end;

  AnimList := SequenceList;
  NextAnim := 0;
  RunNextAnimation(Scene, nil);
end;


procedure TStateMain.RunNextAnimation(const Scene: TCastleSceneCore; const Animation: TTimeSensorNode);
var
  NextAnimationName: String;
  Params: TPlayAnimationParameters;
begin
  if NextAnim = AnimList.Count then
  begin
    FreeAndNil(SequenceList);
    Exit;
  end;

  NextAnimationName := AnimList[NextAnim];
  Inc(NextAnim);

  Params := TPlayAnimationParameters.Create;
  try
    Params.Name := NextAnimationName;
    Params.Forward:= PlayForward;
    Params.StopNotification := @RunNextAnimation;
    Scene.PlayAnimation(Params);

  finally FreeAndNil(Params) end;
end;

end.
1 Like

Now I edited it somewhat.
What it should do is

  1. play the animations up until the selected LastAnimationName and then exit. (this works okay).
  2. Then by pushing a button it should start animation from the last animation (which is the current animation) and end with the animation that is declared in the push button procedure.
    I cannot get this to work; currently every animation sequence starts from the first animation (standfront).
    If I start with NextAnim = FirstAnim (which is 8, the number it should start with (StandBack) it does not work.
    I would like some help here. Thanks.
    (see attachment for the code and data)
unit GameStateMain;

interface


uses Classes,
  CastleUIState, CastleComponentSerialize, CastleUIControls, CastleControls,
  CastleKeysMouse, CastleViewport, CastleScene, CastleVectors, StrUtils, Dialogs,
  CastleWindow,CastleUtils, CastleGLImages, CastleFilesUtils,
  CastleSoundEngine, CastleTimeUtils, CastleColors,
  CastleRectangles, CastleFonts, CastleGLUtils, Generics.Defaults,
  Generics.Collections, CastleDownload, CastleSceneCore,
  CastleCameras, CastleTransform, CastleImages, X3DNodes, X3DFields, CastleURIUtils;


type
  TStateMain = class(TUIState)
  private
    Viewport: TCastleViewport;
    AnimList: TStrings;
    SequenceList: TStringList;
    FirstAnim: Integer;
    NextAnim: Integer;
    LastAnim: Boolean;
    FirstAnimationName, LastAnimationName: String;
    PlayForward: Boolean;
    procedure StandLeft(Sender: TObject);
    procedure StandNE(Sender: TObject);

    procedure RunNextAnimation(const Scene: TCastleSceneCore; const Animation: TTimeSensorNode);
   public
    constructor Create(AOwner: TComponent); override;
    procedure Start; override;
    procedure RunAllAnimations(const Scene: TCastleSceneCore);
    procedure RunAnimationList(const Scene: TCastleSceneCore; const AnimNames: array of String);
  end;


var
  StateMain: TStateMain;
  Button1, Button2: TCastleButton;
  Label1, Label2: TCastleLabel;
  Scene1: TCastleScene;


implementation

uses SysUtils;

{ TStateMain ----------------------------------------------------------------- }

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

procedure TStateMain.Start;
begin
  inherited;

  Viewport := DesignedComponent ('Viewport') as TCastleViewport;

  Button1 := DesignedComponent('Button1') as TCastleButton;
  Button2 := DesignedComponent('Button2') as TCastleButton;
  Label1 := DesignedComponent('Label1') as TCastleLabel;
  Label2 := DesignedComponent('Label2') as TCastleLabel;

  Button1.Caption := 'standleft';
  Button2.Caption := 'standne';

  Button1.OnClick:= @StandLeft;
  Button2.OnClick:= @StandNE;

  Scene1 := DesignedComponent('Scene') as TCastleScene;
  Viewport.Items.Add(Scene1);

  Scene1.Translation:= Vector3(800,300,0);
//  Scene1.URL:= 'castle-data:/kyley_idle.starling-xml';
  Scene1.URL:= 'castle-data:/john_idle.starling-xml';

  SequenceList := TStringList.Create();



 // RunAllAnimations(Scene1);


//  RunAnimationList(Scene1, ['kyleyfront', 'kyleyturnsw', 'kyleysw', 'kyleyturnleft', 'kyleyleft', 'kyleyturnnw', 'kyleynw',
 // 'kyleyturnback', 'kyleyback', 'kyleyturnne', 'kyleyne', 'kyleyturnright', 'kyleyright', 'kyleyturnse', 'kyleyse', 'kyleyturnfront', 'kyleyfront']);

 FirstAnimationName := 'johnstandfront';
 LastAnimationName := 'johnstandback';
 PlayForward := True;

 RunAnimationList(Scene1, ['johnstandfront', 'johnturnsw', 'johnstandsw', 'johnturnleft', 'johnstandleft', 'johnturnnw', 'johnstandnw', 'johnturnback', 'johnstandback',
 'johnturnne', 'johnstandne', 'johnturnright', 'johnstandright', 'johnturnse', 'johnstandse', 'johnturnfront', 'johnstandfront']);

 end;

procedure TStateMain.StandLeft(Sender: TObject);
begin
  SequenceList := TStringList.Create();
  LastAnimationName := 'johnstandleft';
  PlayForward := False;
  RunAnimationList(Scene1, ['johnstandfront', 'johnturnsw', 'johnstandsw', 'johnturnleft', 'johnstandleft', 'johnturnnw', 'johnstandnw', 'johnturnback', 'johnstandback',
 'johnturnne', 'johnstandne', 'johnturnright', 'johnstandright', 'johnturnse', 'johnstandse', 'johnturnfront', 'johnstandfront']);
end;

procedure TStateMain.StandNE(Sender: TObject);
begin
  SequenceList := TStringList.Create();
  LastAnimationName := 'johnstandne';
  PlayForward := True;
  RunAnimationList(Scene1, ['johnstandfront', 'johnturnsw', 'johnstandsw', 'johnturnleft', 'johnstandleft', 'johnturnnw', 'johnstandnw', 'johnturnback', 'johnstandback',
 'johnturnne', 'johnstandne', 'johnturnright', 'johnstandright', 'johnturnse', 'johnstandse', 'johnturnfront', 'johnstandfront']);
end;


procedure TStateMain.RunAllAnimations(const Scene: TCastleSceneCore);
begin
  AnimList := Scene.AnimationsList;
  NextAnim := 0;
  RunNextAnimation(Scene, nil);
end;

procedure TStateMain.RunAnimationList(const Scene: TCastleSceneCore; const AnimNames: array of String);
var I: integer;
begin
  if PlayForward then
  begin
    For I := 0 to High(AnimNames) do
    begin
      SequenceList.Add(AnimNames[I]);
    end;
  end else
  begin
    For I := High(AnimNames) downto 0 do
    begin
    SequenceList.Add(AnimNames[I]);
  end;
end;

  AnimList := SequenceList;
  NextAnim := 0;
  RunNextAnimation(Scene, nil);
end;


procedure TStateMain.RunNextAnimation(const Scene: TCastleSceneCore; const Animation: TTimeSensorNode);
var
  NextAnimationName: String;
  Params: TPlayAnimationParameters;
begin
 {if NextAnim = AnimList.Count then
  begin
    FreeAndNil(SequenceList);
    Exit;
  end;}
 // if AnimList[NextAnim] = LastAnimationName then Exit;
  Label1.Caption:= 'current animation number: ' + IntToStr(NextAnim);
  Label2.Caption:= 'First to start animation in next call: ' + IntToStr(FirstAnim);

  if LastAnim then
  begin
    FirstAnim := NextAnim; // remember the index value of LastAnimationName so next call to RunAnimationList will start from this animation
    FirstAnimationName := LastAnimationName; // next call to RunNextAnimation should start with this animation
    FreeAndNil(SequenceList);
    LastAnim := False;
    Exit;
  end;

  NextAnimationName := AnimList[NextAnim];
  if NextAnimationName = LastAnimationName then LastAnim := True else

  Inc(NextAnim);

  Params := TPlayAnimationParameters.Create;
  try
    Params.Name := NextAnimationName;
    Params.Forward:= PlayForward;
    Params.StopNotification := @RunNextAnimation;
    Scene.PlayAnimation(Params);

    finally FreeAndNil(Params) end;
    end;

end.

animationsequence4.zip (48.1 MB)

You need to decide what LastAnim should do and when it should be set.

I would advise to not do this by modifying RunNextAnimation and RunAnimationList – you made them quite convoluted. It could probably be fixed, but TBH it looks complicated and would result in complication of a simple and working code.

Instead I would advise: if you want your animations to stop at some animation, just don’t pass more animations to RunAnimationList. I.e. instead of

  LastAnimationName := 'johnstandne';
  RunAnimationList(Scene1, ['johnstandfront', 'johnturnsw', 'johnstandsw', 'johnturnleft', 'johnstandleft', 'johnturnnw', 'johnstandnw', 'johnturnback', 'johnstandback',
 'johnturnne', 'johnstandne', 'johnturnright', 'johnstandright', 'johnturnse', 'johnstandse', 'johnturnfront', 'johnstandfront']);

→ just do

  RunAnimationList(Scene1, ['johnstandfront', 'johnturnsw', 'johnstandsw', 'johnturnleft', 'johnstandleft', 'johnturnnw', 'johnstandnw', 'johnturnback', 'johnstandback',
 'johnturnne', 'johnstandne']);

  // and put remaining animations list in another list, to be used by button click later:
  AnimationsScheduledForButton.Clear;
  AnimationsScheduledForButton.AddRange(['johnturnright', 'johnstandright', 'johnturnse', 'johnstandse', 'johnturnfront', 'johnstandfront']);

Use TCastleStringList to have AddRange available. Then your button click can just use AnimationsScheduledForButton (I think it has ToArray method to get list as array, if not you can do it on your side).