Hi guys maybe you can help me, i'm trying to do a little variant in my game with strategic turn based mechanics. I can't realize an efficient way to make stealth mechanics.For example i have an overhead map with open building, how can i avoid character to be seen if it's near and in line of sight but on other side of the wall? For one example is simple but the map is near complex. If you need i can post you a screenshot.
Thanks as always.
There's no one answer to this question. What exactly do you mean by "near complex"? What are you using for walls?
Turn-based often also means grid-based, which means walls are along grid lines. According LoS algorithms are all over the internet.
Example: https://www.codeproject.com/Articles/15604/Ray-casting-in-a-2D-tile-based-environment
sorry i mean quite complex, now there's only a walkable area mask imported from sketch, put the walls of the labyrinth as an object i think it's the first thing to do, no there's no grid, the movement are based on dinstance from character.x.y. Interesting article about 2d raycasting by the way.
Ok, so pixel-based? In that case you need to "walk" along the line from enemy to player and check every (or every other) pixel for being a wall, using a loop. Something like
bool SightBlocked(float fx, float fy) {
int x = FloatToInt(fx, eRoundNearest);
int y = FloatToInt(fy, eRoundNearest);
// check if room pixel x, y is blocking
return GetLocationType(x - GetViewportX(), y - GetViewportY()) == eLocationObject;
}
bool IsVisibleTo(this* Character, Character* enemy) {
float x = IntToFloat(enemy.x), y = IntToFloat(enemy.y);
float dx = IntToFloat(this.x - enemy.x);
float dy = IntToFloat(this.y - enemy.y);
float l = Math.sqrt(dx * dx + dy * dy);
// check every 2 pixels
dx /= l / 2.0;
dy /= l / 2.0;
int steps = FloatToInt(l / 2.0, eRoundDown);
for (int i = 0; i < steps; i++) {
x += dx; y += dy;
if (SightBlocked(x, y)) return false;
}
return true;
}
Now you can use
if (player.IsVisibleTo(cPatrol1)) ...
(http://i.imgur.com/HXz7pTGl.png?2) (https://imgur.com/HXz7pTG)
i had this error
EDIT:
Seems to be solved this way
(http://i.imgur.com/CN0JLKYl.png?1) (https://imgur.com/CN0JLKY)
Was a typo:
float l = Math.sqrt(dx * dx + dy * dy);
ahahah perfectly coordinated :)
I saw the answer as soon as i edited the post, by the way it seems to work like a charm, thanks Khris. Just for curiosity in wich way you should work with 2d raycast?
I'm adding cones like characters that follow enemies directions and positions to check if the player is in line of sight along with walls check. I tried with isCollidingWithChar but doesn't work cause the check is on baseline i think, are things overlapping works better but alpha channel count as collision, is there an internal function with pixel perfect collision or can you help me maybe in the same function for walls? Thanks.
Most recent thread about this issue: http://www.adventuregamestudio.co.uk/forums/index.php?topic=55533
There's a pixel perfect collision module, but it seems to be outdated. Alternatively you can try changing the character's BlockingHeight.
It's simpler to do this with math. Afaik you already have a distance check implemented, so all that's left is the angle.
If you have the enemy's line of sight as degree int angle, you can get the normalized vector like this:
float af = Maths.DegreesToRadians(IntToFloat(angle));
float ex = Math.Cos(af), ey = Math.Sin(af);
The vector towards the player is again
float x = IntToFloat(enemy.x), y = IntToFloat(enemy.y);
float dx = IntToFloat(player.x - enemy.x);
float dy = IntToFloat(player.y - enemy.y);
// normalize vector
float l = Math.sqrt(dx * dx + dy * dy);
dx /= l;
dy /= l;
You can now calculate the dot product:
float dot = dx * ex + dy * ey;
If the two vectors point in the same direction, this product is exactly 1. If they point 90 degrees apart, it's 0. In other words, all you need to check is if (dot > 0.5) and the player is inside the cone. Use a value closer to 1 for a narrower cone.
However I'm guessing you also want to display the cones for the player to see? You can for instance draw them on a semi-transparent, not clickable GUI, again using some Math and DrawTriangle()
Sorry Khris but my trigonometric knowledge is old and full of dust. Maybe i forgot to tell that the enemies go around screen (so i had with first function some division by zero if you can help me to avoid them). I can't reproduce the triangle with drawtriangle cause i can't do reverse formula. But it seems that the cone this way is strictly connected with enemy-player distance. there are three player and three enemy by now but i will increase enemy presence.
this is the old ones with things overlapping and cone made with sprite. Just to explain the condition.
(http://i.imgur.com/sjqsNb8l.png) (https://imgur.com/sjqsNb8)
The only way to get a division by zero is if the player and enemy have the same coordinates exactly.
After float l = Math.sqrt(dx * dx + dy * dy);
insert this: if (l == 0) return true;
I think i solved all like i taught using also an old script that i had for npc always look at player in Chronicle of Innsmouth.
function Angolo(float xgiocatore, float xpersonaggio, float ygiocatore, float ypersonaggio)
{
float x;
float y;
float angle;
float degangle;
int angolo;
x= xgiocatore-xpersonaggio;
y= ygiocatore-ypersonaggio;
angle = Maths.ArcTan2(x,y);
degangle=Maths.RadiansToDegrees(angle);
angolo = FloatToInt(degangle);
if (angolo<0) angolo=360+angolo;
return angolo;
}
String direzione(int angolo)
{
if ((angolo>=337&&angolo<=360)||(angolo>=0&&angolo<22))
return "D";
else if (angolo>=22&&angolo<67)
return "RD";
else if (angolo>=67&&angolo<112)
return "R";
else if (angolo>=112&&angolo<157)
return "RU";
else if (angolo>=157&&angolo<202)
return "U";
else if (angolo>=202&&angolo<247)
return "LU";
else if (angolo>=247&&angolo<292)
return "L";
else if (angolo>=292&&angolo<337)
return "LD";
}
bool SightBlocked(float fx, float fy) {
int x = FloatToInt(fx, eRoundNearest);
int y = FloatToInt(fy, eRoundNearest);
// check if room pixel x, y is blocking
return GetLocationType(x - GetViewportX(), y - GetViewportY()) == eLocationObject;
}
bool IsVisibleTo(this Character*, Character* enemy) {
float x = IntToFloat(enemy.x), y = IntToFloat(enemy.y);
float dx = IntToFloat(this.x - enemy.x);
float dy = IntToFloat(this.y - enemy.y);
float l;
l = Maths.Sqrt(IntToFloat(((this.y - enemy.y)*(this.y - enemy.y))+((this.x - enemy.x)*(this.x - enemy.x))));
//check every 2 pixels
if (l == 0.0) return true;
dx /= l / 2.0;
dy /= l / 2.0;
int steps = FloatToInt(l / 2.0, eRoundDown);
for (int i = 0; i < steps; i++) {
x += dx; y += dy;
if (SightBlocked(x, y)) return false;
}
return true;
}
void VerificaAngolo(Character *giocatore, Character *nemico){
angolus=Angolo(IntToFloat(giocatore.x), IntToFloat(nemico.x), IntToFloat(giocatore.y), IntToFloat(nemico.y));
dir=direzione(angolus);
if (distanza(giocatore.x, giocatore.y, nemico.x, nemico.y)<167)
{
if (giocatore.IsVisibleTo(nemico))
{
if (nemico.Loop==0)
{
if (dir=="D")
{
giocatore.SayBackground("Mi ha visto.");
}
else giocatore.SayBackground("");
}
else if (nemico.Loop==1)
{
if (dir=="L")
{
giocatore.SayBackground("Mi ha visto.");
}
else giocatore.SayBackground("");
}
else if (nemico.Loop==2)
{
if (dir=="R")
{
giocatore.SayBackground("Mi ha visto.");
}
else giocatore.SayBackground("");
}
else if (nemico.Loop==3)
{
if (dir=="U")
{
giocatore.SayBackground("Mi ha visto.");
}
else giocatore.SayBackground("");
}
}
}
}
function repeatedly_execute_always()
{
VerificaAngolo(cEgo, cGen3);
VerificaAngolo(cEgo, cGen4);
VerificaAngolo(cEgo, cGen5);
VerificaAngolo(cGen1, cGen3);
VerificaAngolo(cGen1, cGen4);
VerificaAngolo(cGen1, cGen5);
VerificaAngolo(cGen2, cGen3);
VerificaAngolo(cGen2, cGen4);
VerificaAngolo(cGen2, cGen5);
}