Search Unity

Vision based enemy AI problem (for co-op)

Discussion in 'Scripting' started by Josiah_Ironclad, Apr 20, 2020.

  1. Josiah_Ironclad

    Josiah_Ironclad

    Joined:
    Sep 24, 2019
    Posts:
    156
    I'm trying to make a weeping angel mechanic for a co-op game, where the enemy's behaviour is based on if anyone can see it.

    I made this flowchart to visualise it better, cause I've been stuck on this for a few days now.
    upload_2020-4-20_19-32-5.png

    As you can see I've already accounted for neither of the cameras seeing the enemy, by using "OnBecameInvisible", but I've also implemented a blinking mechanic, and each player has a boolean "eyesClosed". This obviously complicates things.

    I started off by making an else statement, checking if "unseen" is false.
    upload_2020-4-20_19-45-22.png

    But from here I'm stuck. I already have a foreach loop running in the same script, to find the nearest player. So I can use that to also check if any of the players have their eyes closed.

    upload_2020-4-20_19-51-23.png

    upload_2020-4-20_19-59-51.png

    But this only helps with the third column in the flowchart.

    The foreach loop cycles through an array. So let's say both cameras see the enemy, and the first player in the array has their eyes closed. This causes "seenByOne" to become false, and therefore the enemy moves closer to the nearest player. But if the second player in the array has their eyes open, the enemy will be moving until they are checked by the loop.

    And currently I'm unsure of how to branch this out so that it accommodates for both possible scenarios.
    I.e. (one camera sees enemy but eyes are closed, the other camera doesn't see enemy), and (both cameras see enemy but both also have eyes closed).
     
    Last edited: Apr 20, 2020
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,697
    First, good for you for making the flowchart. It's a great way to reason about this.

    But if I understand your problem, you want to linearize the flow, not branch it.

    Here are the questions you want to ask:

    - assume nobody can see the angel (set a simple temp "CanSeeMe" boolean to FALSE)
    - iterate all the players and do this:
    ----- is the player's eyes open?
    ----------if yes: is teh player camera facing the angel and in range?
    -------------- then set "CanSeeMe" to TRUE

    Finally at the end of it, choose how to act based on the CanSeeMe boolean
     
  3. Josiah_Ironclad

    Josiah_Ironclad

    Joined:
    Sep 24, 2019
    Posts:
    156
    Sorry, branch wasn't the best term to use. I just meant "develop".

    Unfortunately your suggestion leads to the same problem.

    Let's say player 1 can't see the angel for whatever reason (camera or eyes), but player 2 can (eyes open and camera facing the angel). Every time the iteration lands on player 1, "CanSeeMe" will be set to false, regardless of whether player 2 can see it or not.

    This will cause the angel to move towards the nearest player every frame. Which in visual terms basically ignores player 2's vision.

    Now that's assuming foreach loops run through each object once per frame (unlike For loops). But even if the entire foreach loop runs through all the players before the check is made, if you reverse the vision (player 1 can see angel, player 2 can't), by the end of the frame "CanSeeMe" will be false, even if though player 1 can see the angel.

    If it runs fully within a single frame, I could put a "break;" if the currently iterated player can see the angel, therefore the loop will never reach player 2, and so "CanSeeMe" will be true. But I'd need to setup the network side of the game to test this, and that might take a while. Please let me know if this is the case (foreach loop runs all iterations within 1 frame).
     
    Last edited: Apr 20, 2020
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,697
    You didn't read my pseudocode carefully.

    Regardless of how often you check or even how many frames it takes to get through, when you do this process, do the steps I listed.

    You will NEVER set "CanSeeMe" false except at the very start of the process.

    You will ONLY set "CanSeeMe" true when any one "can see" condition is true.

    If you NEED to span this process over multiple frames (an optimization you probably don't need to worry about yet), do it all with its internal boolean, and only when you have done all players, copy the boolean out to the one actually controlling the angel..
     
    PraetorBlue likes this.
  5. Leonetienne500

    Leonetienne500

    Joined:
    Dec 5, 2016
    Posts:
    130
    Checking if an object is seen is actually really difficult. At least, doing it correctly.
    Here is an obvious "solution" that doesn't work:

    -Raycast from camera to objects position. Why? The objects bounds are way bigger than it's pivot point. If a gorilla would stand centered behind a broomstick, the Raycast
    check would yield "it's behind an object, hence invisible". Same goes for wall cornors or transparent objects like glass.


    Technically you would kind of have to intercept the D3D rendering process to analyze the depth buffers....

    Actually, what you could try, but keep in mind that it performs like S*** AND is not that accurate, is the following.

    Give your Stalker a generous box collider.
    And then have your CheckFunction do something like this: This is complete pseudo code btw, but i know there do exist getters and functions to actually make it work.

    Code (CSharp):
    1. bool DoesCameraSeeStalker(Camera cam)
    2. {
    3.     Ray camToStalkerTopLeftBack = cam.RayAtPoint(stalkerCol.bounds.topleft);
    4.     ...For all 8 cornors
    5.  
    6.     Ray[] rays = {camToStalkerTopLeftBack, ...};
    7.  
    8.     foreach (Ray r in rays)
    9.     {
    10.         Hit hit;
    11.         if (Physics.Raycast(r, out hit))
    12.         {
    13.             if (hit.collider.compareTag("Stalker"))
    14.             {
    15.                 return true; //First raycast that hits aborts all further raycasts reducing lag. Technically, your Stalker.AmISeen() function could also abort its foreach Player iteration as soon as one returns true saving a lot of performance.
    16.  
    17.             }
    18.         }
    19.     }
    20.  
    21. }
    But that does not work great in close cornors. You may would want to interpolate even more Raycasts between those points. Or use a super low poly mesh with a mesh collider on your Stalker to check against each vertex (!!!super low poly).

    But your game is going to lag worse than minecraft rtx if you have lets say 10 stalkers and 10 players
     
    Last edited: Apr 21, 2020
  6. Cannist

    Cannist

    Joined:
    Mar 31, 2020
    Posts:
    64
    I'd probably address this with a bit field. Take a single
    int
    variable. We'll set bit 0 to 1 whenever player one can see the object and use bit 1 likewise for player two. Then we never reset the field completely but only the part for the player that we just checked.

    Code (CSharp):
    1. int seeState
    2.  
    3. // when we determined that player one can see the object set the lowest bit to one
    4. seeState |= 1
    5.  
    6. // when we determined player one cannot see the object set the lowest bit to zero
    7. seeState &= ~1
    8.  
    9. // when we determined that player two can see the object set the bit 1 to one
    10. seeState |= 2
    11.  
    12. // when we determined player one cannot see the object set bit 1 to zero
    13. seeState &= ~2
    14.  
    15.  
    16. // to check if, according to our checks, any player can see the object or not
    17. if (seeState == 0) {
    18.   // no one can see the object
    19. }
     
    Last edited: Apr 21, 2020
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,697
    You're gonna have to set that to zero before you do any of the subsequent bit field adds. :)