Search Unity

2D Grid Based Fog of War

Discussion in '2D' started by LiberLogic969, Oct 13, 2015.

  1. LiberLogic969

    LiberLogic969

    Joined:
    Jun 29, 2014
    Posts:
    138
    Hello :) I'm having some trouble figuring out the best way to implement Fog of War in my project.

    My games maps are structured in a grid of 1 unit x 1 unit cells. I've created a system that instantiates a prefab object in each cell. This prefab contains a sprite (black 2 unit x 2 unit circle with faded edges), a CircleCollider2D with isTrigger set to true, and a Script that contains state data for the tile (Visible, Explored, Hidden), an Update, OnTriggerStay2D, and OnTriggerExit2D methods. OnTriggerStay2D checks for collisions of any collider2D with the "Unit" tag and if one occurs sets the state of the tile to Visible. OnTriggerExit2D sets the state to Explored. The Update method uses Color.Lerp to change the color of the sprite depending on the state. So Visible tiles are 100% transparent, Explored Tiles are 50% transparent, and Hidden Tiles are 0% transparent.

    That all works and looks great, but im noticing there's thousands of draw calls every frame, and the game is starting to perform poorly. I've done some googling about this issue and it seems like the most common advice given is to use shaders to manipulate the vertex data of a plane that is placed over the map. I have zero experience with shaders and have literally no clue where to start exploring this option. Out of all the stuff I've read there was never an example given as to how this can be accomplished.

    I'm curious if there is a way for me to optimize my current system to reduce draw calls and do things a little more efficient, or if anyone could help me with the whole vertex color shader business. Any help will be greatly appreciated.

    Thanks for reading! ♥
     
  2. tedthebug

    tedthebug

    Joined:
    May 6, 2015
    Posts:
    2,570
    So every update it sets the colour of every sprite? I don't know if it would make a difference but each time the colour was needed to change you could add the sprite to a list then loop through & change the colour & at the end clear the list. That way it should only update the ones needed & ignore the rest.
     
  3. LiberLogic969

    LiberLogic969

    Joined:
    Jun 29, 2014
    Posts:
    138
    The code currently only transitions the Color of the tile when a unit stays in or exits the tiles CircleCollider2D.
     
  4. LiberLogic969

    LiberLogic969

    Joined:
    Jun 29, 2014
    Posts:
    138
    I hate to do this... but I'm really having trouble... anyone have any ideas?
     
  5. Unexpected_Persona

    Unexpected_Persona

    Joined:
    Jul 23, 2015
    Posts:
    9
    I'd advise looking into procedural mesh generation and vertex shading. A lot more difficult to implement than basic prefabs, unfortunately, but it should run better.
     
  6. LiberLogic969

    LiberLogic969

    Joined:
    Jun 29, 2014
    Posts:
    138
    I honestly have no idea where to begin with something like that. Could you possibly point me in the right direction?
     
  7. mgear

    mgear

    Joined:
    Aug 3, 2010
    Posts:
    9,411
    LiberLogic969 likes this.
  8. LiberLogic969

    LiberLogic969

    Joined:
    Jun 29, 2014
    Posts:
    138
    Thanks :)

    I've (of course) run into a problem... The vertex colors are not actually changing... I used the code example in that other thread to raycast and detect the closest vertices in a radius around the player and then set the alpha of those vertex to 0. This is what that looks like :

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class UnitVision : MonoBehaviour {
    5.  
    6.     public LayerMask layerMask;
    7.     public float radius = 1.0f;
    8.  
    9.     void Update()
    10.     {
    11.         Ray ray = new Ray(transform.position, -Vector3.forward);
    12.         RaycastHit[] hits = Physics.RaycastAll(ray, 500, layerMask);
    13.         Debug.DrawRay(ray.origin, ray.direction * 500, Color.red);
    14.         for (var i = 0; i < hits.Length; i++)
    15.         {
    16.             RaycastHit hit = hits[i];
    17.             MeshFilter filter = hit.collider.GetComponent<MeshFilter>();
    18.             Vector3 relativePoint;
    19.  
    20.             if (filter)
    21.             {
    22.                 relativePoint = filter.transform.InverseTransformPoint(hit.point);
    23.                 FullMesh(filter.mesh, relativePoint, radius);
    24.             }
    25.         }
    26.     }
    27.     void FullMesh(Mesh mesh, Vector3 position, float inRadius)
    28.     {
    29.         Vector3[] vertices = mesh.vertices;
    30.         Vector3[] normals = mesh.normals;
    31.         float sqrRadius = inRadius * inRadius;
    32.         Color[] colours = mesh.colors;
    33.  
    34.         for (int i = 0; i < vertices.Length; i++)
    35.         {
    36.             //getting the vertices around the local point passed
    37.             var sqrMagnitude = (vertices[i] - position).sqrMagnitude;
    38.             //if the vertex is too far away, dont carry on
    39.             if (sqrMagnitude > sqrRadius)
    40.                 continue;
    41.             //set the alpha to 0
    42.             colours[i].a = 0;
    43.         }
    44.         mesh.colors = colours;
    45.     }
    46. }
    I've debugged the code and it all seems to be working right. The raycasts are hitting the mesh and at the right position.

    Im creating the Mesh with the CreatePlane code on the wiki. The mesh containing GameObject has this script attached to it :

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class FogMesh : MonoBehaviour
    5. {
    6.     void Start()
    7.     {
    8.         //getting the mesh
    9.         Mesh mesh = GetComponent<MeshFilter>().mesh;
    10.         Vector3[] vertices = mesh.vertices;
    11.         Color[] colors = new Color[vertices.Length];
    12.  
    13.         //have to filter out whatever game object its attatched too, to make sure the colours have
    14.         //been set correctly before they get altered later, stops invalid array errors where colours
    15.         //return null
    16.         for (int i = 0; i < vertices.Length; i++)
    17.         {
    18.             colors[i] = Color.black;
    19.             colors[i].a = 1;
    20.         }
    21.         mesh.colors = colors;
    22.     }
    23. }
    The mesh has a Material with the standard shader. I've tried messing around with different shaders and textures and nothing seems to work. Any ideas?
     
  9. LiberLogic969

    LiberLogic969

    Joined:
    Jun 29, 2014
    Posts:
    138
    Well I got it working. Had to use a Particle Shader.

    ...

    One more question. How could I smooth out the edges of the revealed area? It looks really nice with a blur applied, but I can't seem to get the blur to only effect the fog of war mesh.
     
  10. mgear

    mgear

    Joined:
    Aug 3, 2010
    Posts:
    9,411
    yes, most shaders wont display vertex colors.

    For smoother effect, instead of setting the color straight to 0, could slowly decrease it from 1.
    tested something similar while ago: http://unitycoder.com/upload/demos/2DFogOfWar/ (webplayer)
     
  11. LiberLogic969

    LiberLogic969

    Joined:
    Jun 29, 2014
    Posts:
    138
    I experimented with smoothing out the color transition using Color.Lerp like I was doing with the Tile Prefab system. It's an improvement but once its clear the edges are very jagged and uneven, it just doesn't look like Fog... Your web demo looks a lot better than what I've currently got going.

    Is there no way to apply a blur effect to a single object in the scene?