SOLVED: Vision Cone confusion

Started by Iphigenia, Sun 13/02/2011 23:31:21

Previous topic - Next topic

Iphigenia

Can anyone help me add a vision cone to a patrolling NPC? I've read all I can find in the forum on the subject, but can't get my unmathematical head round it  :-[

The vector method sounds fantastic, but is beyond me unless I can paste some ready-made code straight into my script.

The situation: I have an NPC following a roughly circular patrol route made up of around 9 waypoints. I'd like to have him 'looking' directly in front of him as he goes (vision cone pointed in same direction as his direction of travel). If the player 'collides' with this vision cone there's a chance he's spotted.

Thanks in advance.


Khris

Could you post a link to the thread you found about this?
Might save some reinventing the wheel :)

Iphigenia

These two threads are very relevant: 

http://www.adventuregamestudio.co.uk/yabb/index.php?topic=41690.0

http://www.adventuregamestudio.co.uk/yabb/index.php?topic=38687.0

I've bodged an extremely clumsy solution using another character and the follow command (in effect the following character is the NPC and the (invisible) character in front is his gaze) It kinda works, but I'd much rather use a vision cone that pointed exactly in the direction the NPC was moving (perhaps with some randomness tossed in to simulate occasional glances left and right).

Any idiot-proof advice would be much appreciated. Thanks.

Khris

#3
I remember the thread; there an easier way actually.

What you need is a) the distance between NPC and player and b) the angle of the line NPC-player relative to the NPC's viewing direction.

If both are within a specified limit, the NPC will see the player.

As for the NPC occasionally glancing left and right, that makes stuff a lot more complicated, so I'll skip that for now.

The distance is calculated easily:
Code: ags
bool Close(this Character*, int limit) {
  int x = player.x - this.x;
  int y = player.y - this.y;
  return (x*x + y*y <= limit*limit);
}


To get the NPC's direction of walking, their coordinates have to be queried in intervals.
Code: ags
// those two at top of script
int npc_x[100], npc_y[100];  // previous position
int npc_xd[100], npc_yd[100];  // direction

int dir_timer;

function repeatedly_execute() {

  ....  // rest of code

  if (dir_timer >= GetGameSpeed()*2) {  // might want to try other values here
    dir_timer = 0;
    int i = 0;
    Character*c;  
    while (i < Game.CharacterCount) {
      c = character[i];
      if (c.Room == player.Room && c != player) {
        npc_xd[i] = c.x - npc_x[i];  // calculate vector from position 2 seconds ago to now
        npc_yd[i] = c.y - npc_y[i];
        npc_x[i] = c.x;
        npc_y[i] = c.y;
      }
      i++;
    }
  }
  else dir_timer++;
}


Now we can calculate the angle:
Code: ags
bool InView(this Character*, int limit) {
  float x1 = IntToFloat(player.x - this.x);
  float y1 = IntToFloat(player.y - this.y);
  float l = Maths.Sqrt(x1*x1 + y1*y1);
  if (l == 0.0) return true;
  x1 = x1/l;
  y1 = y1/l;

  float x2 = IntToFloat(npc_xd[this.ID]);
  float y2 = IntToFloat(npc_yd[this.ID]);
  l = Maths.Sqrt(x2*x2 + y2*y2);

  if (l == 0.0) {  // NPC isn't moving
    if (this.Loop == 0) { x2 = 0.0; y2 = 1.0; }  // set direction according to loop
    if (this.Loop == 1) { x2 = -1.0; y2 = 0.0; }
    if (this.Loop == 2) { x2 = 1.0; y2 = 0.0; }
    if (this.Loop == 3) { x2 = 0.0; y2 = -1.0; }
  }
  else {
    x2 = x2/l;
    y2 = y2/l;
  }

  float angle = Maths.RadiansToDegrees(Maths.Arcos(x1*x2 + y1*y2));

  return (angle <= IntToFloat(limit));
}


Now we can at any point do:

Code: ags
  if (cNpc.Close(70) && cNpc.InView(20)) ...    // if player is less than 70 pixels away and within +/-20°


Since you're probably going to do that check in rep_ex, too, you need to put the InView() function above rep_ex.

Iphigenia


Thanks Khris.

I understand a tiny fraction of this so apologies if I'm doing something stupid:

Pasting the code into a very simple empty test room (player + one patrolling NPC) I get a "Array Index out of bounds" message on line:

Code: ags

c = character[i]; 


(index: 24 bounds: 0..23)

There was also an issue with 'Acos' in

Code: ags
float angle = Maths.RadiansToDegrees(Maths.Acos(x1*x2 + y1*y2));



"Not a public member of Maths"

I changed 'acos' to 'Arcos' just to see if it would run (which it did until it hit the Array Index out of bounds)

Khris

#5
Right, somehow I was under the impression that characters were numbered starting at 1.

Replace these three lines in rep_ex:

Code: ags
    int i = 0;   //   <<----- 0!
    Character*c;  
    while (i < Game.CharacterCount) {       ///   < not <=


To explain what the code does:

The rep_ex part looks at the current position of all NPCs every two seconds. The vector from the previous to the current position is then calculated and stored.
Say an NPC was at 115, 170 two seconds ago and is now at 145, 168, then the vector (30; -2) is stored for that NPC. In other words, their current viewing direction is east, with a slight tilt to the north.
Then their current position is stored as previous position, and two seconds later the direction is updated.
This should be accurate enough.

The InView function calculates the vector from the NPC to the player. This is vector 1. The second vector is the current viewing direction of the NPC.
Both of these vectors are then normalized, i.e. divided by their length. The result is a unit vector, a vector pointing in the same direction but with a length of 1.
Then the dot product is calculated, then the Acos of the result. This gives the angle between the two vectors (formula taken from here).

Iphigenia

It seems to work brilliantly for one guard but if I add more I sometimes get a

'Error. Floating Point divide By zero.' message

on line

Code: ags
 x2 = x2/l; 


I'm reproducing this spotting check:   
if (cnameguardhere.Close(70) && cnameguardhere.InView(20))

for each guard. Could that be the problem?

Khris

#7
Damn, that's what happens if you code everything in a message window :)

This will occur if a) the player is at the exact same coordinates as an NPC or if an NPC isn't moving.

Currently, the InView function doesn't cope with a standing character correctly.

Here's an amended version:

Code: ags
bool InView(this Character*, int limit) {
  float x1 = IntToFloat(player.x - this.x);
  float y1 = IntToFloat(player.y - this.y);
  float l = Maths.Sqrt(x1*x1 + y1*y1);
  if (l == 0.0) return true;
  x1 = x1/l;
  y1 = y1/l;

  float x2 = IntToFloat(npc_xd[this.ID]);
  float y2 = IntToFloat(npc_yd[this.ID]);
  l = Maths.Sqrt(x2*x2 + y2*y2);

  if (l == 0.0) {  // NPC isn't moving
    if (this.Loop == 0) { x2 = 0.0; y2 = 1.0; }  // set direction according to loop
    if (this.Loop == 1) { x2 = -1.0; y2 = 0.0; }
    if (this.Loop == 2) { x2 = 1.0; y2 = 0.0; }
    if (this.Loop == 3) { x2 = 0.0; y2 = -1.0; }
  }
  else {
    x2 = x2/l;
    y2 = y2/l;
  }

  float angle = Maths.RadiansToDegrees(Maths.Arcos(x1*x2 + y1*y2));

  return (angle <= IntToFloat(limit));
}

Iphigenia

This line seems to be causing a Type Mismatch (Cannot convert 'float' to 'int'):

Code: ags
 if (l == 0) return true

DoorKnobHandle

Needs to be:

Code: ags

if (l == 0.0) return true;

Iphigenia

Cheers dkh. That did the trick.

I now have eagle-eyed guards pacing around the room!

Thanks for all the help Khris. You're a marvel.

Khris


SMF spam blocked by CleanTalk