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).
That highly depends on what exactly is needed from the translation:
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;
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):
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.
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
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.
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
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;
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 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
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;
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.
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.
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.