How to handle button presses from a behavior

Hello everyone! I’m trying to teach myself Coding (using Pascal) and I thought I fiddle around in Castle Game Engine just for the fun of it.
I’m trying to write a behaviour that handles movement of a transform - pretty standard stuff WASD-Controls - you know it.
However I have trouble accessing the container (for Container.Pressed[keyXxx]) from within the behavior. There was a similar post to mine from 2021 but it seems that some stuff has changed about containers, so i figured I’d ask again. How do i handle button presses from within a behavior in maybe a best-practice kind of way? Examples would be great, too!

1 Like

Although I’m not a CGE expert, I can share a few solutions to your problem. Depending of what you want to achieve:

  1. If you want to control the camera and the player, you can use the existing behaviors: TCastleWalkNavigation or TCastleThridPersonNavigation. Castle’s Fly Navigation is basically the Walk with Gravity turned off.
  2. If you want to implement your own navigation look at the code of TCastleWalkNavigation for clues, it’s located in a unit called CastleCameras (/src/transform/castlecameras.pas). It contains functions function Press(const Event: TInputPressRelease): boolean; override; function Release(const Event: TInputPressRelease): boolean; override;
  3. If you want to control an object selected by the player, you may find it easier without behaviors, by using method Press - example of using this function is in the FPS game template: Create new project, choice the fps template, open the GameViewMain.pas and find the function, it contains enough info inside its body to get you moving.
    In that approach you may have a variable storing the selected object, then adding WASD support inside the function (writing the if ... then clause for each button) you can move the object by changing its Translation.x / .y / .z. Note that objects directions depends on rotation (including the parent rotation). If the world is the parent and object’s rotation is zero, then you move along world X, Y, Z. You can also use method Move or MoveTo - every transform (game object) has it.
    If you want it to move in directions affected by camera/player angle of viewing, you need to turn the object, assign obj.Direction := Camera.Direction
  4. If the object is held by the player - eg. player picked it from world, then you can make the player a Parent of the object. Which means it moves wherever the player moves, and rotates with the player. When the player release the object (put it down) you may need to change the Parent back, and translate the object’s position from player-based to world based. Translation is not difficult, but you need to remember to do it. Alternatively, if you don’t want to change the objects parent to player, and then back to original parent, you could give the player a ghost image of the object. So the original object stays where it was until player finish the manipulation.

Basically the TCastleWalkNavigation and MainView.Press are the things you’d want to experiment, IMHO.
The Press method is easier to implement than behaviors for controlling objects different than the player/camera, because if you create a behavior to move the selected object, you have to swap the behavior from object to object at selection change or make the behavior aware that it should only react when the object is selected (it has a property named MoveAllowed).

I understand my answer is quite general, and for sure it’s not very beginner friendly but everything depends on what exactly you want to do, and how well you know programming at all (not necessarily Pascal). Again - I’m not CGE pro, so maybe others have better solutions that I haven’t seen yet.

1 Like

Hey thanks for the quick reply. After having a look at the TCastleWalkNavigation class and some other stuff in the mentioned unit i quickly decided, that most of the stuff goes way above my head :sweat_smile:. I was just looking for an easy way to process user input and was under the impression that i could use a behavior a bit like a scripting component in unity where I would then “simply” add my basic input key handlers and stuff.
I’m going to play around with the existing behavior and see if it does what I want or else try (3).
Thanks again!

1 Like

Start with a demo that already has the TCastleWalkNavigation implemented. WyrdForest is one, or the Terrain demo. It isn’t really very complicated if you have something to start with to see how it works.

1 Like

I will try my stab at the answer, which may a bit rephrase other answers above too :slight_smile:

  1. First of all, maybe you don’t need to handle inputs in a TCastleBehavior descendant. The most straightforward approach to handle inputs is to override view events like Update (where you can check Container.Pressed) or Press / Release (which gets a parameter Event: TInputPressRelease, saying what was pressed / released). See Designing user interface and handling events (press, update) within the view | Manual | Castle Game Engine .

    To be more general, all user interface components (TCastleUserInterface descendants), of which TCastleView is only one possibility, are notified about presses and have access to parent Container without any fuss.

  2. Naturally these methods in a view could get / set / call appropriate stuff in your chosen behavior. E.g. your behavior descendant could have methods like procedure Shoot; or fields Running: Boolean, that are set / called by the view, and your behavior reacts to them.

  3. Alternatively, if you really want to handle in a specific behavior all your inputs, and forget that view exists:

    • As for watching “what is pressed”: A behavior can access the container as simply as Application.MainWindow.Container. You can use this to watch what is pressed in a behavior’s Update method (or from anywhere else :slight_smile: ).

    • As for press/release: While a behavior doesn’t get notified about press / release of things (only TCastleUserInterface descendants, including TCastleView, have them), you could add it. Define in your behavior methods like

    type
      TMyBehavior = class(TCastleBehavior)
        function Press(const Event: TInputPressRelease): boolean; 
        function Release(const Event: TInputPressRelease): boolean; 
    

    and then just call them from your view:

    function TViewMain.Press(const Event: TInputPressRelease): Boolean;
    begin
      Result := inherited;
      if Result then Exit; // allow the ancestor to handle keys
      
      Result := MyBehaviorThatShouldProcessAllInput.Press(Event);
    end;
    

Hm, actually that’s it :slight_smile: In the end, all my 3 answers above seem similar, they are a variation of “handle input in a view, and pass it to your behavior somehow” :slight_smile: Hope this helps.

Note: The TInputShortcut and CastleInputs unit, used by navigation components, shown also in examples/viewport_and_scenes/custom_input_shortcuts_saved_to_config, are an addition to allow to have configurable inputs (e.g. to allow player to “customize key-bindings” and save them to config file). You’re welcome to look at them, though maybe they don’t directly answer your question (“how to handle input in a behavior”).

Note 2: The larger question I guess is “why do you want to handle input in a behavior”, i.e. what is the ultimate thing you want to do. If our above answers didn’t help (or even if they did!) feel welcome to describe your use-case in more detail, like “I want users to be able to do X, in a game like Y, and I use behaviors for Z”. This may allow us to advise even better :slight_smile:

1 Like

Hi, the claryfication indeed helped a lot. My little bunny is now moving on screen as intended. I’m not using a behavior anymore but the idea was (before I even started out) to use behaviors as some kind of substitute state machine. Since games often have to use the same buttons for various actions depending on the state of the game (e.g. wasd do normal walking in “normal walk” mode, but do slightly different walking in “focused on enemy” mode) I thought I might do different behaviors for these states with the fitting input controls and then turn one off, if the game goes into the other state and vice versa.
normal walk state → normal walk behavior on; focused on enemy walk behavior off.
focused walk state → normal walk behavior off; focused on enemy walk behavior on.
I’m aware there are propably tons of ways one could implement this without such behaviors, and also that this has to be clean to not turn into a logic mess, but that was one idea i had, which is why I wanted to start out with behaviors.

Thanks a lot for all the answers!

By the way:
I’m now struggling with rotation instead. Does anybody have tips on how to rotate a character using mouse movement. I’m trying to use the MouseLookDelta and it somehow works, but I’m definitely doing something wrong because my bunny makes weird snaps and my mouse cursor does not get centered…

Thanks again!

1 Like

When using “mouse look” and the MouseLookDelta, it a deliberate feature that we capture mouse cursor, hide it (you should not even care where is the mouse cursor) and expose just “mouse delta”. Experiment with examples/user_interface/dragging_test/ to see how it’s used, in 3D it’s used typically through TCastleWalkNavigation.MouseLook.

If you want to just handle mouse being moved to do something, override Motion event of a view, and compare previous and new mouse position to do anything, like

function TViewMain.Motion(const Event: TInputMotion): Boolean;
var
  XDrag: Single;
begin
  Result := inherited;
  if Result then Exit; // allow the ancestor to handle event

  if buttonLeft in Event.Pressed then
  begin
    XDrag := Event.Position.X - Event.OldPosition.X;
    // do anything you want with XDrag, e.g. rotate something around Y axis
    MyScene.Rotation := Vector4(0, 1, 0, MyScene.Rotation.W + XDrag);
    Exit(true);
  end;
end;

My above example is probably silly :slight_smile: Just wanted to show how to handle “mouse move” in the simplest way, and use it to apply some rotation (assuming MyScene is always rotated around Y axis) in some way. Hope this helps :slight_smile:

1 Like