Moving in 8 directions on basis of degrees?

Hi.
I want to move a Scene in 8 directions.
Idea is:

  1. click on a destination on screen with left mouse click
  2. destination of the character Scene is Scene Translation of the mouse image.

Player.Destination := PlayerMouse.Scene.Translation;

 if StatePlay.PlayerTransform.Translation.X < Player.Destination.X then
 begin
   if Player.Destination.Y > PlayerTransform.Translation.Y then GoNE;
   if Player.Destination.Y = PlayerTransform.Translation.Y then GoRight;
   if Player.Destination.Y < PlayerTransform.Translation.Y then GoSE;
 end;    

The “chance” of Player.Destination.Y exactly to be PlayerTransform.Translation.Y to make it GoRight is almost none, so I was thinking of using grade-ranges between the selection of directions, with an angle from the center of the Scene image.
But I am rather bad a Geometry.
Ofcourse with every TState update in steps I could make it move to the destination translation, but it has to play the right animation also (I have Playanimations for every direction).

Hope you can help me with this!
:slight_smile:

That highly depends on what exactly is needed from the translation:

  1. If you click-to-go then the piece of code is a good solution. The Player will never go directly right/left/up/down, but as the character’s transform moves, it will eventually meet the condition. I’d only give a bit higher “margin” for the condition to keep up with possible floating-point errors.
if Abs(Player.Destination.Y - PlayerTransform.Translation.Y) < Epsilon then GoRight else
if Player.Destination.Y > PlayerTransform.Translation.Y then GoNE else
if Player.Destination.Y < PlayerTransform.Translation.Y then GoSE;
  1. If you want hold-to-walk, then indeed it’s more complicated. I’d rather recommend to go through ArcTan2 (ArcTan2). E.g. try something like this (I didn’t debug the code, may need some adjustments):
PlayerMoveAngle = ArcTan2(Player.Destination.Y - PlayerTransform.Translation.Y, Player.Destination.X - PlayerTransform.Translation.X);
case Round(PlayerMoveAngle * 8)
  0, 8: GoEast;
  1: GoNE;
  2: GoNorth;
  3: GoNW;
  4: GoWest; //With a thankful heart
  5: GoSW;
  6: GoSouth;
  7: GoSE;
end;
  1. You may not actually want to implement 8-directions in a point-and-click adventure, but move the Player character smoothly along a single line. Here you’ll need the solution from “2” just to play the appropriate animation.

Thanks.
What var has to be PlayerMoveAngle? I have now made it a single.

I get 2 errors though:

Yes, it should be Single or Double.

Yeah, I’ve forgotten “of” in “case” syntax. Sorry thinking C# at the moment :smiley: Should be:

case Round(PlayerMoveAngle * 8) of

Allright. :slight_smile:
But I am still getting the “Illegal expression” error?

ArcTan2 is a single and Translation.X a float. Maybe this?

Ah, I see it’s on a line above. I thought it was pointing to “Case”.

It should be “:=” not “=” (again, sorry, thinking C# at the moment).

Ok.
I clicked around the character image but only when I click right of it, one of the direction animations and movements is played. Mostly the “move right” and sometimes the “move ne” if I click slightly higher.
And re-clicking won’t change the direction when it’s already moving.
What could be wrong?
Maybe put a label text to display the angles to see if it needs ‘tuned’ or so?

I just tried your first example and it works okay, though I get an error with the Epsilon (not found, does it need another uses file? I already have ‘math’.
For now I replaced it with 100.

 if StatePlay.PlayerTransform.Translation.X < Player.Destination.X then
  begin
    if Abs(Player.Destination.Y - PlayerTransform.Translation.Y) < 100 then GoRight else
    if Player.Destination.Y > PlayerTransform.Translation.Y then GoNE else
    if Player.Destination.Y < PlayerTransform.Translation.Y then GoSE;
  end;

  if StatePlay.PlayerTransform.Translation.X > Player.Destination.X then
  begin
    if Abs(Player.Destination.Y - PlayerTransform.Translation.Y) < 100 then GoLeft else
    if Player.Destination.Y > PlayerTransform.Translation.Y then GoNW else
    if Player.Destination.Y < PlayerTransform.Translation.Y then GoSW;
  end;
end;

But when I try this to expand with GoBack and GoFront things get complicated.
`

 if StatePlay.PlayerTransform.Translation.Y < Player.Destination.Y then
  begin
    if Abs(Player.Destination.Y - PlayerTransform.Translation.Y) > 100 then GoBack;
  end;  

The Y interferes with the first lines so move is always GoBack instead of GoNW or GoNE.

I’m not sure how it’s implemented in your code. But you should re-run the “move to” algorithm every time you click (and make sure it makes sense - can properly restart the animation, etc.)

It’s always a very good idea to have some debug information. Often just dumping it into console is enough (not as convenient on Windows, where console immediately closes). By default CastleLog can also write the debug information into a file - but it’s not a good realtime data then.

Yes, Epsilon is a variable that would describe how close should the difference between Ys be to be considered negligible (aka floating point calculus accuracy). However, note there, that I’m not sure if it would make sense for larger values. It was supposed to be something like 1e-4 :slight_smile:

As the Epsilon meaning is to be “negligible difference between the values” then it should always be “Abs(values) < Epsilon”. Also in case you do vertical movement - you need then to seek the difference between X-coordinates.

Overall, I’d still advice you better to stick with angles. In the code above you get the logic a bit wrong and thus it may will fail in some situations. Consider this (it’s a pseudocode, don’t expect it would work if copypasted;) - just demonstration of the logic):

Epsilon = 1e-4;
DiffX := Target.X - Player.X;
DiffY := Target.Y - Player.Y;
if Abs(DiffX) < Epsilon then
  if DiffY > 0 then
    MoveUp
  else
    MoveDown
else
if Abs(DiffY) < Epsilon then
  if DiffX > 0 then
    MoveRight
  else
    MoveLeft
else
  if (DiffX > 0) and (Diff Y > 0) then
    MoveUpRight
  else
  if (DiffX < 0) and (Diff Y > 0) then
    MoveUpLeft
  if (DiffX > 0) and (Diff Y < 0) then
    MoveDownRight
  else
  if (DiffX < 0) and (Diff Y < 0) then
    MoveDownLeft;

The core problem here is that all the cases are mutually-exclusive. I.e. if you go Up you don’t check Up-Left and Up-Right.

And yes, this code is complicated, so unless there is a good reason to, I’d better stick with angles myself - those should be much more universal and in the end may turn out easier to debug in case something goes wrong.

Ok, so using angles is better.
:slight_smile:
But I can’t find out why it does not work.
I remembered user Valterb used degrees once; see:

See Leftmouse click procedure.

I tried to use this but got an error on uses file SpinEx;

So this is a uses file from Lazarus, not Castle Game Engine. How can I add this use or is the same function it uses in CGE, maybe in the ‘math’ use?

Update: Succeeded in adding SpinEx uses file but it needs another uses :frowning:

image

I don’t want to use SpinEx as it is too much dependent on Lazarus and other uses and packages.
So if there is a CGE alternative it would be great.

Update:

For now I have made this; it works okay but is somewhat rough and not 100% reliable, so working with degrees would be better but I find it too difficult. maybe you can view from this code how it should look like with degrees.

 if Event.IsMouseButton(buttonLeft) then
   begin
     Player.Destination := PlayerMouse.Scene.Translation;

     if StatePlay.PlayerTransform.Translation.X < Player.Destination.X then
     begin
       if Player.Destination.Y > StatePlay.PlayerTransform.Translation.Y then
       begin
         if (Player.Destination.Y >= 400) and (Player.Destination.X < 200) then GoBack;
         if (Player.Destination.Y > 150) and (Player.Destination.Y < 300) then GoNE;
         if Player.Destination.Y <= 150 then GoRight;
       end;
       if Player.Destination.Y < StatePlay.PlayerTransform.Translation.Y then
       begin
         if Player.Destination.Y > -250 then GoRight;
         If (Player.Destination.Y <= 250) and (Player.Destination.Y > -400) then GoSE;
         if (Player.Destination.Y <= -400) and (Player.Destination.X < 150) then GoFront;
       end;
     end;

     if StatePlay.PlayerTransform.Translation.X > Player.Destination.X then
     begin
       if Player.Destination.Y > StatePlay.PlayerTransform.Translation.Y then
       begin
         if (Player.Destination.Y >= 400) and (Player.Destination.X < 200) then GoBack;
         if (Player.Destination.Y > 150) and (Player.Destination.Y < 300) then GoNW;
         if Player.Destination.Y <= 150 then GoLeft;
       end;
       if Player.Destination.Y < StatePlay.PlayerTransform.Translation.Y then
       begin
         if Player.Destination.Y > -250 then GoLeft;
         If (Player.Destination.Y <= -250) and (Player.Destination.Y > -400) then GoSW;
         if (Player.Destination.Y <= -400) and (Player.Destination.X < -150) then GoFront;
       end;
     end;
   end;

I experimented with some values after passing them to labeltext, to see what they were doing.
The values did not match what was in the case, except 0. and there some negative values also.
And why PlayerMoveAngle had to be 8, it even increases the negative values.

I now have this and it works, though I don’t understand why clicking around the Transform gives this strange values:

 if Event.IsMouseButton(buttonLeft) then
   begin
     Player.Destination := PlayerMouse.Scene.Translation;
    
     PlayerMoveAngle := ArcTan2(Player.Destination.Y - PlayerTransform.Translation.Y, 
     Player.Destination.X - PlayerTransform.Translation.X);
     PlayerMoveAngle := trunc(PlayerMoveAngle * 2);
     case trunc(PlayerMoveAngle) of
         0: GoRight;
       1,2: GoNE;
         3: GoBack;
     -1,-2: GoSE;
        -3: GoFront;
        -4: GoSW;
   -5,-6,6: GoLeft;
       4,5: GoNW;
    end;              

But at least it works!
:slight_smile:

Ah, obviously I’ve forgotten to divide it by Pi and shift by another Pi to get 0…1 angle. Should be (again not tested):

PlayerMoveAngle = ArcTan2(Player.Destination.Y - PlayerTransform.Translation.Y, Player.Destination.X - PlayerTransform.Translation.X);
case Round((PlayerMoveAngle / Pi + 1) * 4)
  0, 8: GoEast;
  1: GoNE;
  2: GoNorth;
  3: GoNW;
  4: GoWest;
  5: GoSW;
  6: GoSouth;
  7: GoSE;
end;

Sorry, this still does not work; the outcome is always 0, so there is something not right with the formula.
:slight_smile:

Now to stop character walking when it reaches destination I have made this.
When it reaches first X or Y it also changes direction and stops if both X and Y are exceeded.
It works fine, though I am interested in other approaches that may be better coded.

procedure TStatePlay.PlayerDestinationUpdate;
begin
  if Player.X >= Round(Player.Destination.X) then
  begin
    if Player.WalkRight then StandRight;
    if Player.WalkNE then GoBack;
    if Player.WalkSE then
    begin
     if Player.Y <= Player.Destination.Y then StandSE else GoFront;
    end;
  end;

  if Player.X <= Round(Player.Destination.X) then
  begin
    if Player.WalkLeft then StandLeft;
    if Player.WalkNW then GoBack;
    if Player.WalkSW then
    begin
      if Player.Y <= Player.Destination.Y then StandSW else GoFront;
    end;
  end;

  if Player.Y >= Round(Player.Destination.Y) then
  begin
    if Player.WalkBack then StandBack;
    if Player.WalkNW then if Player.X <= Player.Destination.X then StandNW;
    if Player.WalkNE then if Player.X >= Player.Destination.X then StandNE;
  end;

  if Player.Y <= Round(Player.Destination.Y) then
  begin
    if Player.WalkFront then StandFront;
    if Player.WalkSW then if Player.X <= Player.Destination.X then StandSW;
    if Player.WalkSE then if Player.X >= Player.Destination.X then StandSE;
  end;
end;

And last 2 lines are somewhat obsolete as I noticed Y destination is always reached first.

C’mon, it’s working for me :smiley: code/gamestatemain.pas · master · EugeneLoza / Kryftolike · GitLab - it shouldn’t show zero always. Add some debug and see what Player.Destination.Y - PlayerTransform.Translation.Y and Player.Destination.X - PlayerTransform.Translation.X values are, then log PlayerMoveAngle raw value.

It shouldn’t be wrong in the core - it may be rotated by any multiplicant of 90 degrees, but not completely wrong unless we’re feeding it wrong data :slight_smile:

This will work only when Player.Destination.Y is initially less than Player.Y. I’m not sure what exactly you want to achieve, but I’d try doing this way:

MovementDirection := Player.Destination - Player.Coordinates;
GetPlayerAnimation(MovementDirection); // the code above that plays corresponding animation;
MovementThisFrame := SecondsPassed * MovementDirection.Normalized * MovementSpeed;
if MovementDirection.Length < MovementThisFrame.Length then
begin
  Player.Coordinates := Player.Destination;
  Player.Stop;
end else
  Player.Coordinates := PlayerCoordinates + MovementThisFrame;

P.S. You can find almost the same formula in point-and-click/code/gamestatemain.pas at 5a5629c6450e51795d978a840d347bc0e3f964ae · eugeneloza/point-and-click · GitHub

I uploaded the problem.
Maybe you can take a look at it. I put my own PlayerMoveAngle code (that works, but not great; moving NE does not work now) between brackets.

8 direction problem.zip (30.1 MB)

Hi Carring,
I don’t know if you have already solved it but in the demo you are referring to there is everything you need.
Even if it was written under Lazarus the algorithm is still valid.
Ironically, you could have 360 animations, one for each degree.
Just pass the algorithm into your code and ignore everything else.
The piece of code you need can be found in the CastleControl1Press procedure (look at my comments). Once you have obtained the “Degree” value with about thirty lines of code you can assign your animations (4, 8, 16 and more directions).
If you need any clarification regarding my demo please ask.

Hi Valterb,

Thanks for your reply.
I had tried your code before but got stuck on the required SpinEx uses file.
See my previous posts in this topic.
SpinEx needs other uses such as LCLType that I could not find, so I got stuck on this and was looking for a solution without the need of these extra uses.
So maybe you know another way to get 8 way direction?

SpinEx file is not required by the algorithm, it allows the use of TFloatSpinEditEx which contain values that you could store in variables or in an array. I think you just need the unit Math. I have read the whole thread and as I told you, you have to ignore any references to components on the form.
Let’s take an example:
You click somewhere to move your character.
You need to get the angle between the character’s (x, y) coordinate and the mouse’s (x, y) coordinate.
When you have the angle you have to compare it with your array where you have stored 16 values, i.e. UP from 337.5 to 22.5, UPRIGHT from 22.5 to 67.5, RIGHT from 67.5 to 112.5 and so on for all eight directions. To have all the range values open my application and the values to use are already present in the TFloatSpinEditEx.
If the degree obtained is for example 340 it falls between 337.5 and 22.5 and the animation to be performed will be UP.
There can be several ways to use the algorithm in your code. I would simply make a function to be called after the left click which will return the degree to me. So I see which range it falls into and I run the relative animation. Or you can do it all within a procedure as well as in my demo.

As an example:

procedure TMainForm.GetAnimation;
var
  PlayerX, PlayerY,
  DiffXX, DiffYY,
  ArcTanXY, Degrees: Single;
begin
  if (Event.IsMouseButton(mbLeft)) then
  begin
    { PreviousSprite declared as global because it is used elsewhere in the code.       
    You could declare it here }
    PreviousSprite := Player.CurrentAnimation;
    Player.CurrentAnimation.Stop;
    { MouseX, MouseY as above }
    MouseX := Event.Position.X;
    MouseY := Event.Position.y;
    PlayerX := Player.CurrentAnimation.X;
    PlayerY := Player.CurrentAnimation.Y;
    { Using mouse click coordinates and Player position I apply the
      arctangent formula and I convert the value obtained in degrees.
      This way I get the requested direction }
    if (PlayerY < MouseY) then
    begin
      DiffYY := (MouseY - PlayerY);
      if (PlayerX < MouseX) then
      begin
        DiffXX := (MouseX - PlayerX);
        ArcTanXY := Math.arctan2(DiffXX, DiffYY);
        Degrees := Math.radtodeg(ArcTanXY);
      end
        else
        begin
          DiffXX := (PlayerX - MouseX);
          ArcTanXY := Math.arctan2(DiffXX, DiffYY);
          Degrees := 360 - Math.radtodeg(ArcTanXY);
        end;
    end
    else if PlayerY > MouseY then
    begin
      DiffYY := (PlayerY - MouseY);
      if (PlayerX < MouseX) then
      begin
        DiffXX := (MouseX - PlayerX);
        ArcTanXY := Math.arctan2(DiffXX, DiffYY);
        Degrees := 180 - Math.radtodeg(ArcTanXY);
      end
        else
        begin
          DiffXX := (PlayerX - MouseX);
          ArcTanXY := Math.arctan2(DiffXX, DiffYY);
          Degrees := 180 + Math.radtodeg(ArcTanXY);
        end;
    end;
    { For each range there is an animation.
      Ranges can and must be changed according to the environment in which Player moves.
      e.g. a top view would not require changes unlike a front view.
      In the demo Road there is a demonstration. Try to move Player with the default
      values and then with the optimized ones (these are not definitive and can be
      improved but give the idea. }
    if ((Degrees > 337.5) or (Degrees <= 22.5)) then
      // assign your UP animation
    else if ((Degrees > 22.5) and (Degrees <= 67.5)) then
      // UPRIGHT animation
    else if ((Degrees > 67.5) and (Degrees <= 112.5)) then
      // RIGHT animation
    else if ((Degrees > 112.5) and (Degrees <= 157.5)) then
      // DOWNRIGHT animation
    else if ((Degrees > 157.5) and (Degrees <= 202.5)) then
      // DOWN animation
    else if ((Degrees > 202.5) and (Degrees <= 247.5)) then
      // DOWNLEFT animation
    else if ((Degrees > 247.5) and (Degrees <= 292.5)) then
      // LEFT animation
    else if ((Degrees > 292.5) and (Degrees <= 337.5)) then
      // UPLEFT animation
    Player.CurrentAnimation.X := PreviousSprite.X;
    Player.CurrentAnimation.Y := PreviousSprite.Y;
    Player.CurrentAnimation.Play;
  end;
end;

In this example the range values are written directly but as mentioned you can store them in an array.
Keep in mind that you have to modify it according to CGE’s new sprite handling.
One more thing:
as written in the comment, the ranges are perfect for a top view but you could change them at will for each environment of your game so as to hide as much as possible any oblique walkway typical of sprites.

Many thanks, I finally got it working with this code.
Then I had to adjust some of the degree ranges by trial but that was all.
:slight_smile: