Hi folks,
Meet Luke:
Luke is a bit of an autist, and so he doesn't know about his surroundings too well. When told to look at something on his right, he sometimes faces to the front â€" or back â€" instead. Sometimes an object is directly behind him, yet looking at it, he turns to the left as if he was only interested in its left edge for some strange reason. See my current game entry to watch him fumbling and bumbling, especially in the tree branch room.
What an awkward fellow this Luke guy is, and AGS makes him that way. Luke is using the standard functions Character.FaceLocation() and Character.FaceObject(), and those functions give results that are at the very least very counterintuitive, not to say plainly wrong.
How come? I've tried to research a bit of context for doing directions in the two-dimensional room, and this is what I've found:
To start off with, AGS doesn't natively know the concept of "looking downwards" as in "looking at your feet" or "looking upwards" as in "looking into the sky". You can code and draw it, of course, but it will be a special animation. Instead, when AGS talks about "down", it means "turned toward the human player (towards the fourth wall)", and when it talks about "up", it means "turned away from the human player (towards the back stage wall)".
Thus, "down" and "up" describe aspects of the depth dimension, not the height dimension.
This is relevant for FaceDirection() and such. No matter what direction you specify, the character will always look straight ahead, but it will be turned in different ways.
Well, FaceObject() seems to often calculate this turn incorrectly.
How calculate a correct turn?
Details inside:
Does AGS do this? No, and as far as I can determine it hasn't done this for decades:
Whole generations of adventure coders must have struggled with those functions and come to the conclusion that they must be broken in some elusive way. Oodles of adventures must contain kludgy workarounds. So there must be a very tenacious reason to keep those broken functions broken or else they would have been fixed long ago. And based on these experiences, I doubt that they can ever be fixed in future, either.
In the light of this it might be better to fix this on the AGS program side and provide drop-in replacements for FaceLocation(), FaceObject(), and FaceCharacter(). This is what I try to offer below:
Perhaps the code might be useful in some way. It should go into GlobalScript.asc, and GlobalScript.h should get some suitable definitions. Or if you keep a module with often-used functions, you could stick it there instead.
The code might still have some bugs.
Code below:
Code: ags
Meet Luke:
Luke is a bit of an autist, and so he doesn't know about his surroundings too well. When told to look at something on his right, he sometimes faces to the front â€" or back â€" instead. Sometimes an object is directly behind him, yet looking at it, he turns to the left as if he was only interested in its left edge for some strange reason. See my current game entry to watch him fumbling and bumbling, especially in the tree branch room.
What an awkward fellow this Luke guy is, and AGS makes him that way. Luke is using the standard functions Character.FaceLocation() and Character.FaceObject(), and those functions give results that are at the very least very counterintuitive, not to say plainly wrong.
How come? I've tried to research a bit of context for doing directions in the two-dimensional room, and this is what I've found:
To start off with, AGS doesn't natively know the concept of "looking downwards" as in "looking at your feet" or "looking upwards" as in "looking into the sky". You can code and draw it, of course, but it will be a special animation. Instead, when AGS talks about "down", it means "turned toward the human player (towards the fourth wall)", and when it talks about "up", it means "turned away from the human player (towards the back stage wall)".
Thus, "down" and "up" describe aspects of the depth dimension, not the height dimension.
This is relevant for FaceDirection() and such. No matter what direction you specify, the character will always look straight ahead, but it will be turned in different ways.
- If a character does FaceDirection(eDirectionUp), it doesn't really look "upwards", it looks upstage instead and you'll see the back of the character's head. If it does "FaceDirection(eDirectionDown)" it looks downstage.
- Similarly, if it does FaceDirection(eDirectionLeftUp) it looks upstage to the left, etc
Well, FaceObject() seems to often calculate this turn incorrectly.
How calculate a correct turn?
Details inside:
Spoiler
The character will always look straight ahead no matter how high the object is above the floor, so the object's height or elevation must be irrelevant information when calculating how the character should be turned. Instead, exactly two specifications should be relevant:
The easier part is calculating whether the object is to the left of the character and how far. You compare the character's and object's X dimensions. However, the relevant point for this comparison should be the middle point of the object's width, and the scaling of the object should be taken into account when determining this middle.
It's harder to find out whether the object is in front of the character:
So to recap, a preliminary outline of a correct algorithm for ch.FaceDirection(o) would be:
Turn to eDirectionAB, where
This preliminary outline must be adapted because the character can turn straight to the right or left and straight upstage or downstage, too. A good predictable calculation might be: Draw a vector in the 2d space from the middle of the baseline of the character to the middle of the baseline of the object. Then turn the vector to the nearest multiple of 45°. Then convert this angle into the direction we are looking for.
Also, some characters can only turn to the straight orthogonal directions, they don't have views for eDirectionLeftUp etc. â€" the calculation above should then use multiples of 90° in these cases.
- Whether the object is to the left or the right of the character.
- Whether the object is upstage or downstage with respect to the character.
The easier part is calculating whether the object is to the left of the character and how far. You compare the character's and object's X dimensions. However, the relevant point for this comparison should be the middle point of the object's width, and the scaling of the object should be taken into account when determining this middle.
It's harder to find out whether the object is in front of the character:
- Usually, whenever AGS tries to find out whether something is "in front of" something else, it uses the baselines: The (geometrically) lower the baseline of a thing is, the more downstage it is considered to be.
- If we use the lower edge of the sprite instead of the baseline, we will get into trouble in a lot of very common situations. Take for instance, a cup object that is lying on a table. The lower edge of the cup sprite will be near the table top, that is "high up". If we use this value as a depth information, we would conclude that the cup is "far away in the distance", which is wrong.
- The only way to tell AGS how distant the object is is setting the baseline of the object. AGS doesn't provide any other way to tell this information. So conversely, AGS should actually consider the baseline for depth calculations whenever it is set; it should not deliberately play dumb and ignore a set baseline, taking the lower edge of the object against better knowledge.
So to recap, a preliminary outline of a correct algorithm for ch.FaceDirection(o) would be:
Turn to eDirectionAB, where
- A is "Up" when the baseline of o is geometrically higher than the baseline of ch, otherwise "Down" (use the lower edge of the sprite iff the baseline isn't set);
- B is "Left" when the middle of o is geometrically to the left of the middle of ch, otherwise "Right" (and consider the scaling when determining the middle).
This preliminary outline must be adapted because the character can turn straight to the right or left and straight upstage or downstage, too. A good predictable calculation might be: Draw a vector in the 2d space from the middle of the baseline of the character to the middle of the baseline of the object. Then turn the vector to the nearest multiple of 45°. Then convert this angle into the direction we are looking for.
Also, some characters can only turn to the straight orthogonal directions, they don't have views for eDirectionLeftUp etc. â€" the calculation above should then use multiples of 90° in these cases.
[close]
Does AGS do this? No, and as far as I can determine it hasn't done this for decades:
- When dealing with objects, it uses their left edge, so FaceObject() makes characters strangely prefer their left-hand side.
- When the objects have a baseline set, it ignores the baseline, so FaceObject() wrongly assumes for a lot of objects that they are in the background when they are on a table etc. instead.
- I haven't analysed the code that far, but I think that the function doesn't cleanly round to nearest multiples of 45° (90°) either; for instance, it seems to prefer the "DownRight" direction unless the lower left corner of the object is nearly directly under the character and only then switches to the "Down" direction.
Whole generations of adventure coders must have struggled with those functions and come to the conclusion that they must be broken in some elusive way. Oodles of adventures must contain kludgy workarounds. So there must be a very tenacious reason to keep those broken functions broken or else they would have been fixed long ago. And based on these experiences, I doubt that they can ever be fixed in future, either.
In the light of this it might be better to fix this on the AGS program side and provide drop-in replacements for FaceLocation(), FaceObject(), and FaceCharacter(). This is what I try to offer below:
- FaceLocationBetter() offers directions in clean 45° multiples and so short-circuits any angle rounding operations that FaceLocation() might do.
- FaceObjectBetter() and FaceCharacterBetter() use the baseline when set.
- FaceObjectBetter() determines the object width whilst considering the scaling and uses the middle of this width. It uses an auxiliary function CurrentSprite() that gets the sprite that the object currently displays.
Perhaps the code might be useful in some way. It should go into GlobalScript.asc, and GlobalScript.h should get some suitable definitions. Or if you keep a module with often-used functions, you could stick it there instead.
The code might still have some bugs.
Code below:
Spoiler
int CurrentSprite(this Object *)
{
readonly int view = this.View;
if (0 == view)
return this.Graphic;
readonly ViewFrame *frame =
Game.GetViewFrame(view, this.Loop, this.Frame);
return frame.Graphic;
}
function FaceLocationBetter(this Character *, int dest_x, int dest_y, BlockingStyle bs)
{
readonly int diff_x = dest_x - this.x;
readonly int diff_y = dest_y - this.y;
int sign_of_diff_x = 2 * (diff_x >= 0) - 1;
int sign_of_diff_y = 2 * (diff_y >= 0) - 1;
readonly int abs_x = diff_x * sign_of_diff_x;
readonly int abs_y = diff_y * sign_of_diff_y;
if (abs_x < abs_y)
{
readonly int tan_val = diff_x * 29 / diff_y; // 12/29 is an approximation for tan 22.5°
if (tan_val >= -12 && tan_val <= 12)
sign_of_diff_x = 0;
}
else
{
readonly int tan_val = diff_y * 29 / diff_x; // 12/29 is an approximation for tan 22.5°
if (tan_val >= -12 && tan_val <= 12)
sign_of_diff_y = 0;
}
return this.FaceLocation(
this.x + 10 * sign_of_diff_x, this.y + 10 * sign_of_diff_y, bs);
}
function FaceObjectBetter(this Character *, Object *o, BlockingStyle bs)
{
int y = o.Y;
if (o.Baseline != 0)
y = o.Baseline;
int x = o.X;
int width = Game.SpriteWidth[o.CurrentSprite()];
if (!o.IgnoreScaling)
width *= GetScalingAt(o.X, o.Y) / 100;
x += width / 2;
this.FaceLocationBetter(x, y, bs);
}
function FaceCharacterBetter(this Character *, Character *ch, BlockingStyle bs)
{
int y = ch.y;
if (ch.Baseline != 0)
y = ch.Baseline;
this.FaceLocationBetter(this.x, y, bs);
}
[close]