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/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)