Strategic turn based with stealth mechanic - SOLVED

Started by rmonic79, Sat 09/06/2018 11:28:53

Previous topic - Next topic

rmonic79

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.

Khris

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.


rmonic79

#3
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.

Khris

#4
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
Code: ags
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
Code: ags
  if (player.IsVisibleTo(cPatrol1)) ...

rmonic79

#5


i had this error

EDIT:
Seems to be solved this way

Khris

Was a typo:

Code: ags
    float l = Math.sqrt(dx * dx + dy * dy);

rmonic79

#7
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?

rmonic79

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.

Matti

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.

Khris

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:
Code: ags
  float af = Maths.DegreesToRadians(IntToFloat(angle));
  float ex = Math.Cos(af), ey = Math.Sin(af);

The vector towards the player is again
 
Code: ags
  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:
Code: ags
  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()

rmonic79

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.


Khris

The only way to get a division by zero is if the player and enemy have the same coordinates exactly.
After
Code: ags
  float l = Math.sqrt(dx * dx + dy * dy);

insert this:
Code: ags
  if (l == 0) return true;

rmonic79

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.

Code: ags

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);
}


SMF spam blocked by CleanTalk