MD3 - improve animations support

Concrete example to demonstrate not every solution is neatly presented in the documents:

Third Person Navigation does not mention that it uses ThirdPersonNavigation under “uses”!

The API documentation for the ThirdPersonNavigation class you mentioned in your code above does not mention that it uses ThirdPersonNavigation as one of its Uses explicitily; you are left to essentially make an educated assumption and blindly hope it works.

Hm, I just tested Fix syntax from https://forum.castle-engine.io/t/md3-improve-animations-support/775/73 · GitHub and the only compilation error there is now about MyViewport missing – which makes sense, it is based on your code sample that didn’t contain MyViewport field. After commenting out the

MyViewport.InsertBack...

line it compiles OK. Of course in actual application you should have some MyViewport, and defined in your design, otherwise the navigation doesn’t make sense :slight_smile:

Anyhow, I don’t see the error message you show. It suggests you use outdated version of CGE, that actually doesn’t have my change to TCastleThirdPersonNavigation that exposed that virtual SetAnimation.

Can you please test whether you have the latest CGE? Get it from Download | Castle Game Engine .

You posted a link to the TChangeTransformation type in CastleThirdPersonNavigation unit. If you open that page and hit “home” key (or just scroll up to the very top) you will see it is part of “Unit CastleThirdPersonNavigation”.

Oops, I think that was a mistake cause I clicked on the first thing related to TCastleThirdPerson when I searched, but the point still stands that when you scroll to the top on my end, you don’t actually see “TCastleThirdPersonNavigation” listed explicitly under the “Uses” banner on the top, so again they would have to guess that part.

But I will delete the part about the MyViewport; again, this isn’t an issue about my lack of knowledge of Pascal, but something specific to the task at hand.

And yeah, probably cause it’s telling me that it doesn’t override when it should like you were telling me earlier, it’s probably just an old and outdated version of the Castle Game Engine, so I am going to re-download from the downloads page and see if it works afterward.

Okay, now I’m getting these errors having downloaded the latest version that does support the override I want using the same code.

Do you know why it expects a type identifier for the call to ThirdPersonNavigation, because I already specified a type identifier for the declaration of ThirdPersonNavigation earlier in the code exactly like you did?

I have the same confusion about the second part of the error message, because again I followed the syntax correctly the way it should work, it just doesn’t for whatever reason.

Like in your version, you had no right parenthesis, yet it wants it here for some unspecified reason, even though that is not, capital N-O-T, the way you did it.

Ah, very sorry – I did fix it in my gist earlier today (you need to specify parameters in implemention and close parenthesis), but I forgot to mention it here (it was a long Friday at work at my company :slight_smile: ) If you look at my gist (same link as before), it has it correctly already for some time:)

unit GameViewMain;

interface

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

type
  TMyThirdPersonNavigation = class(TCastleThirdPersonNavigation)
  protected
    procedure SetAnimation(const AnimationNames: array of String); 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;
  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;

{ TMyThirdPersonNavigation ---------------------------------------------------- }

procedure TMyThirdPersonNavigation.SetAnimation(const AnimationNames: array of);
begin
  // TODO: Remember to do sthg here
end;

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

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

procedure TViewMain.Start;
var
  MyNavigation: TMyThirdPersonNavigation;
begin
  inherited;
  MyNavigation := TMyThirdPersonNavigation.Create(FreeAtStop);
  // TODO: Remember to assign MyNavigation properties
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; // allow the ancestor to handle keys

  { This virtual method is executed when user presses
    a key, a mouse button, or touches a touch-screen.
    Note that each UI control has also events like OnPress and OnClick.
    These events can be used to handle the "press", if it should do something
    specific when used in that UI control.
    The TViewMain.Press method should be used to handle keys
    not handled in children controls.
  }

  // Use this to handle keys:
  {
  if Event.IsKey(keyXxx) then
  begin
    // DoSomething;
    Exit(true); // key was handled
  end;
  }
end;

end.

This is the code I have at this point after implementing the parenthesis and semicolon. I know for a fact the semicolon is the correct choice because it is the exact same way for all other lines with “procedure”, and it wouldn’t make sense for the same syntax on different lines to behave inconsistently/not the same.

Unfortunately, I get other error messages related to the line of code, even with the correct syntax (or at least I believe it should be correct because I have the latest version from the downloads page as of right now, and I am just following your directions and what the manual says),

and they output exactly this:

do you know why this is?

Okay, now it finally compiles correctly and the program displays without any errors or warnings after I added “String” at the one you added.

Again, this is something you should have added at the beginning, rather than just expecting the person seeking support to fill in the details themselves.

Unfortunately, the bad/sad news is that even after I add the code you sent, I still have only the single part models to select from on the “Avatar” drop down list in the example project when I add a third person navigation.

and cannot select the human models with “HumanBase” on the list, even after the code was supposed to fix that. Do you know how I actually should make multi-part characters the target/Avatar of the Third Person camera?

To be clear, my initial code snippet (this post: MD3 - improve animations support - #71 by michalis ) wasn’t missing the “array of String)”. It was missing in later code snippets, from your post: MD3 - improve animations support - #73 by JPF12141999 . You likely just missed it, because it requires horizontal scrolling to see it.

As said above, I generally don’t check for compilation all the code I send on this forum, it’s often even not a complete solution, just a start.

I wish I could provide a better support, but my time is limited, with a lot of threads and tasks around CGE to answer and do. Sorry about that – I’m doing as much as I can in my limited time, but you just have to fill in the rest sometimes. Thanks for understanding.

The only purpose of the new code is to let you implement SetAnimation method by calling SomeScene.PlayAnimation(...) on various scenes using MD3 models. That’s all it does and all it was supposed to do :slight_smile: It should be enough to use TCastleThirdPersonNavigation to control a hierarchy of MD3 models, like a human from Tremulous.

Now that you have it compile, you need to

  1. Set MyNavigation properties in code (right after you created MyNavigation instance). See TCastleThirdPersonNavigation docs, Castle Game Engine: CastleThirdPersonNavigation: Class TCastleThirdPersonNavigation .

    In particular, the Avatar property accepts only the TCastleScene instance. But you don’t need to set it. You can set instead the AvatarHierarchy.

  2. Fill SetAnimation with implementation that calls PlayAnimation on the proper scene(s), to play the proper animations on proper MD3 pieces. See the manual, in particular about viewports: Viewport with scenes, camera, navigation | Manual | Castle Game Engine and about about how to play animations from code: Writing code to modify scenes and transformations | Manual | Castle Game Engine for details.

Finally: If this seems like not enough information to follow, I would suggest you live for now without using TCastleThirdPersonNavigation. CGE is certainly not perfect, we plan better approaches to navigation components (Andrzej Kilijański is working on them since a few months – lots of good stuff). If the current TCastleThirdPersonNavigation API seems too hard to use (after browsing above documentation), maybe you can just leave it for now, and come back to attempting this when we have a more user-friendly components for this in CGE.

Okay, thank you so much for all the help!

To ask for clarification on point 1. however, is that the avatar property only accepts TCaslteScene, so I should use AvatarHiearchy instead?

This sounds good, but do you know more specifically how I should use it? I would assume from how you posted it the rest is in the documents, so I can just see what it tells me and follow the rules of Pascal syntax that I know already, like type it in the proper way obviously with the dots to access properties and whatnot.

For point 2 though, it is very obvious what I should do so I will try it ASAP. Again thank you!

Yeah, to elaborate further still on point 1, I know how to access properties and stuff like that, but I need help with understanding the exact phrasing I should use to assign the AvatarHiearchy to the Avatar property, IE if it’s something like “ThirdPersonNavigation.Avatar = AvatarHiearchy;” or something like that.

I would assume for point 2, however, that it should be a straightforward “SetAnimation.PlayAnimation(animation, true);”, is that correct, or no because it’s not defined explicitly as a scene?

Okay, actually on point 2, now that I see you wrote “implementation” word for word, I would assume it should be put under implementation in the code?

So from what I can tell reading the manual on playing animation that you linked, I would call the scene’s animation I want to play before the period, and not TCastleThirdPersonNavigation itself, cause that’s a class instead.

Again, I know to just be mindful about putting it under the implementation label in the declaration of the TCastleThirdPersonNavigation from what you are telling me.

AvatarHierarchy is a property of type TCastleTransform (see API docs Castle Game Engine: CastleThirdPersonNavigation: Class TCastleThirdPersonNavigation ) . So you just set it like

MyNavigation.AvatarHierarchy := MyTransform; // MyTransform here is just an example

To call PlayAnimation, you need an instance (some TCastleScene) on which you call it. Like

MyScene.PlayAnimation('...', true);

See Writing code to modify scenes and transformations | Manual | Castle Game Engine for docs. It links to examples (that are tested and compile), try them first :slight_smile:

The calls to the MyScene.PlayAnimation('...', true) should be done from SetAnimation method implementation. The “method implementation” term in Pascal means the piece of code between begin...end in the implementation section of the unit. It could be like this:

procedure TMyThirdPersonNavigation.SetAnimation(const AnimationNames: array of String);
begin
  if AnimationNames[0] = 'idle' then
  begin
    ExampleLegsScene.PlayAnimation('idle', true);
    ExampleTorsoScene.PlayAnimation('idle', true);
  end else
  if AnimationNames[0] = 'walk' then
  begin
    ExampleLegsScene.PlayAnimation('walk', true);
    ExampleTorsoScene.PlayAnimation('walk', true);
  end;
end;

As warned before, this is just an example, not something that will compile – for starters, I count you can yourself figure out how to pass the ExampleLegsScene and ExampleTorsoScene to have them available for that piece of code. If unsure, consult Pascal resources pointed in earlier post – one way to achieve it is to define and initialize additional fields in TMyThirdPersonNavigation, like ExampleLegsScene, ExampleTorsoScene: TCastleScene.

Okay, thank you for further elaboration. I will try that myself, as well as other advice you give me.

1 Like
unit GameViewMain;

interface

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

type
  TMyThirdPersonNavigation = class(TCastleThirdPersonNavigation)
  ExampleLegScene = class(TCastleScene)
  ExampleTorsoScene = class(TCastleScene)
  TMyThirdPersonNavigation.AvatarHierarchy := MyTransform;
  protected
    procedure SetAnimation(const AnimationNames: array of String); 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;
  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;

{ TMyThirdPersonNavigation ---------------------------------------------------- }

procedure TMyThirdPersonNavigation.SetAnimation(const AnimationNames: array of String);
begin
  if AnimationNames[0] = 'idle' then
  begin
    ExampleLegsScene.PlayAnimation('idle', true);
    ExampleTorsoScene.PlayAnimation('idle', true);
  end else
  if AnimationNames[0] = 'walk' then
  begin
    ExampleLegsScene.PlayAnimation('walk', true);
    ExampleTorsoScene.PlayAnimation('walk', true);
  end;
end;

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

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

procedure TViewMain.Start;
var
  MyNavigation: TMyThirdPersonNavigation;
begin
  inherited;
  MyNavigation := TMyThirdPersonNavigation.Create(FreeAtStop);
  // TODO: Remember to assign MyNavigation properties
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; // allow the ancestor to handle keys

  { This virtual method is executed when user presses
    a key, a mouse button, or touches a touch-screen.
    Note that each UI control has also events like OnPress and OnClick.
    These events can be used to handle the "press", if it should do something
    specific when used in that UI control.
    The TViewMain.Press method should be used to handle keys
    not handled in children controls.
  }

  // Use this to handle keys:
  {
  if Event.IsKey(keyXxx) then
  begin
    // DoSomething;
    Exit(true); // key was handled
  end;
  }
end;

end.

Here is the code with your additions. Unfortunately, I get exactly this error on line 12 position 19. Note again, that it doesn’t make any logical sense following the rules of Pascal syntax because I declared it exactly the way you should declare it, with no semicolons, yet it says there is an equal sign in place of a semicolon.

On top of this, it should have given me two errors and not only one, because I used the same syntax on both lines next to each other vertically speaking in order to intilalize/declare the scenes.

IE, it should have given me the same error for line 13 as well as 12, because I used the same exact syntax to declare the two scenes.

Even weirder/illogical still, it’s bugging me about there not being a semicolon in a place where semicolons never actually go, if you read the documentation on how TCastleScene instances are declared, you are supposed to put the equals sign in the middle.

Do you know if I am just missing something again?

Hi, sorry I didn’t read the whole thread and I might be saying something wrong. It would appear that your type declarations are wrong.
But above all, Michalis would seem to suggest the definition of fields and not classes (ExampleLegsScene, ExampleTorsoScene: TCastleScene). it is only a personal note, I repeat, from what little I have read.

That makes a lot of sense… I suppose I can try that solution and see how it goes. Thank you for the help! I thought that it would have to be declared as a class, because that is how it looked like it was defined in the manual, but I guess I was wrong about that.

Michalis, could I please have help if I have to type more details besides just that line verbatim to declare them, or is that it?

This kind of stuff is my point exactly though, you can’t just tell someone without the same kind of in-depth knowledge of the intricaies of the Castle Game Engine like you that they have to initialize the scenes, but in different ways than you would normally initalize a scene, cause it doesn’t make logical sense to the person without the same level of background knowledge.