TITANIUM      CORE

My Role:


Tool Creation(Level Design Tool),

Shaders,

VFX System.




Project Length: 10 weeks

Team Size: 12 members

Engine:Unity

Language: C#

Titanium Core is a tactical game, X-Com-like  set in an alternative historical universe in which the Roman Empire never fell,  and instead expanded even farther an succeded in the quest of conquering the entire world. 

The player would have to lead  a gruop of brave celtic fighter in the quest of liberating their beloved island of Brittania from the tirannical control of the Roman Empire.



Level Design Tool:


This tool has as its goal the improvement of the workflow of designers, and artist to create and test new levels. This tools is design to be use in pair with Houdini. 

The tool allows the designers and artists to create a new grid plane of variable size and paint on each grid cell using the drawing section of the tool,  once the drawing is complete the drawing data can be saved into a json file. 

By using the selection tool the designer can select an area that has the same color and have a building, street, or ground be created of the dimension of the previously colored area.

The data saved on the json file  will also be used by the players and the AI navigation system as well as the cover system to know walkable areas aswell as which cells provide cover.

This tool was created in collaboration with another programmer.

MapCreation Examples
MP_01
Example 2
MP_02

Code:

          
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Remoting.Messaging;
using HoudiniEngineUnity;
using TMPro.EditorUtilities;
using UnityEngine;
using UnityEditor;
using UnityEngine.Diagnostics;

namespace UnityEngine.Rendering.PostProcessing
{
    public class LevelEditorWindow : EditorWindow
{
    private class EditorGridCell
    {
        private Color _color;
        public int ColorIndex;
        public int[] Vertices;
        public GridCell.CellType GridManagerType => ColorIndexToCellType[ColorIndex];
        //Implement a non walkable cell type
        private static GridCell.CellType[] ColorIndexToCellType => new[]
        {
            GridCell.CellType.Empty, GridCell.CellType.FullCover, GridCell.CellType.Empty, GridCell.CellType.HalfCover,
            GridCell.CellType.PlayerSpawn, GridCell.CellType.EnemySpawn, GridCell.CellType.Empty, GridCell.CellType.NonWalkable
        };
    }
    
    [SerializeField] private int _gridSize;
    [SerializeField] private float _cellScale;
    [SerializeField] private Material _gridMaterial;
    [SerializeField] private int _drawingColorIndex;
    [SerializeField] private GameObject _gridObject;
    [SerializeField] private Mesh _gridMesh;
    [SerializeField] private int _toolSelection;

    [SerializeField] private Transform _selectionParent;

    //[SerializeField] private Texture2D[] SelectableColors;
    [SerializeField] private Transform _buildingParent;

    private EditorGridCell[,] _grid;
    private List< Vector2Int> _currentSelection;
    private const string EditorPrefsKey = "LevelEditorWindowData";
    private const string GridGameObjectName = "LevelEditor_Grid";
    private const int ScreenMenusVerticalOffset = 36;
    private static Color[] DrawingColors => new[]
        {Color.white, Color.red, Color.black, Color.blue, Color.cyan, Color.magenta, Color.yellow, Color.green};

    private Dictionary< Color, int> _colorToIndexHelper;
    //Generate Grid From JSON
    [SerializeField]private TextAsset GridDataJSON;
    private Vector2Int? _temporaryMouseSelectionPoint = null;
    private bool _isControlHeld = false;
    
    #region Tooltips
    //Tools
    private GUIContent GenerateCont = new GUIContent("Generate", "This button will open the generation tools");
    private GUIContent DrawCont = new GUIContent("Draw", "This button will open the drawing tools");
    private GUIContent SelectionCont = new GUIContent("Selection", "This button will open the selection tools");
    //Generation tools
    private GUIContent gridObjCont = new GUIContent("Grid Object: ","Drag and drop from the hierarchy the level grid you want to edit, or click on the icon on the right side of this field to choose one. pressing on the 'GenerateMesh' button will add one automatically.");
    private GUIContent gridSizeCont = new GUIContent("Grid Size:", "Type here the desired grid size.");
    private GUIContent gridScaleCont = new GUIContent("Cell Scale:", "Type the desired cell scale.");
    private GUIContent gridDataJSONCont = new GUIContent("Grid data to load:",
        "Drag and drop a text file containing grid data to be read for the layout info.");
    private GUIContent gridMaterialCont = new GUIContent("Grid material",
        "The material will be applied to the grid mesh automatically when the generate button is pressed.");
    private GUIContent generateGridMeshButtonCont = new GUIContent("Generate Grid Mesh",
        "Press this button to generate a whole grid mesh with a blank layout, or if there is already a selected mesh in the GridObject field, reset the mesh to its default values.");
    private GUIContent generateGridMeshFromJSONCont = new GUIContent("Generate mesh from JSON file",
        "(For this button to work a file containing grid data is required!)Generate a new grid mesh from the data given from the -Grid data to load- field.");
   //Drawing Tools
    private GUIContent DrawingToolsLabel1 = new GUIContent("Select which type of cell to draw, by left clicking you fill a cell with the chosen color.");
    private GUIContent DrawingToolsLabel2 = new GUIContent(" By right clicking you clear the cell.");                                                      
    private GUIContent ClearCont = new GUIContent("Clear", "Clear cell and resume the default color");
    private GUIContent BuildingCont = new GUIContent("Building", "The cells painted in this color will be recognised as buildings.");
    private GUIContent RoadCont = new GUIContent("Road", "The cells painted in this color will be recognised as road tiles");
    private GUIContent CoverCont = new GUIContent("Half Cover", "The cells painted in this color will be recognised as half cover tiles");
    private GUIContent PlayerSpawnCont = new GUIContent("Player Spawn", "The cells painted in this color will be assigned to be the player spawn in the level");
    private GUIContent EnemySpawnCont = new GUIContent("Enemy Spawn", "The cells painted in this color will be assigned to be enemy spawn points in the map");
    private GUIContent GroundReliefCont = new GUIContent("Ground Relief", "The cell painted in this color will highlight where to have a relief in the ground");
    private GUIContent NonWalkableCont = new GUIContent("NonWalkable", "The cell painted with this color will represent non-walkable areas");
    private GUIContent GenerateGridDataCont = new GUIContent("Generate Grid Data From Mesh",
        "This button will create and save a JSON file containing the vertex and color data of the level."); 
    //Selection Tools
    private GUIContent CreateFromSelectionButton = new GUIContent("Create asset from selection", "After selecting a colored zone on the map press this to load a Houdini asset from your assets directory.");
    private GUIContent ClearSelectionButton = new GUIContent("Clear Selection","by clicking this button the last selection made will be cancel");
    #endregion
    
            
        
          
 [MenuItem("LevelEditor/MainWindow #L")]
    private static void Init()
    {
        GetWindow< LevelEditorWindow>().Show();
    }

    //Window drawing
    private void OnGUI()
    {
        ShowBasicTools();
    }

    private void ShowBasicTools()
    {
        //InputFields
        EditorGUI.BeginChangeCheck();
        _gridObject = EditorGUILayout.ObjectField(gridObjCont, _gridObject, typeof(GameObject), true) as GameObject;
        bool valueChanged = EditorGUI.EndChangeCheck();
        if (valueChanged)
            GetMeshValues();


        //Tools
        EditorGUILayout.Space();
        GUIContent[] toolOptions = {GenerateCont, DrawCont,SelectionCont};
        _toolSelection = GUILayout.Toolbar(_toolSelection, toolOptions);

        EditorGUILayout.Space();

        if (_toolSelection == 0)
            ShowGenerationTools();
        else if (_toolSelection == 1)
            ShowDrawingTools();
        else if (_toolSelection == 2)
            ShowSelectionTools();
    }

    private void ShowGenerationTools()
    {
        
        _gridSize = EditorGUILayout.IntField(gridSizeCont, _gridSize);
        _cellScale = EditorGUILayout.FloatField(gridScaleCont, _cellScale);
        GridDataJSON = EditorGUILayout.ObjectField(gridDataJSONCont, GridDataJSON, typeof(TextAsset), false) as TextAsset;
        _gridMaterial =
            EditorGUILayout.ObjectField(gridMaterialCont, _gridMaterial, typeof(Material), false) as Material;
        if (GUILayout.Button(generateGridMeshButtonCont))
        {
            _gridMaterial = new Material(Shader.Find("Particles/Standard Surface"));
            GenerateGrid(_gridSize, _cellScale);
        }

        if (GUILayout.Button(generateGridMeshFromJSONCont))
        {
            _gridMaterial = new Material(Shader.Find("Particles/Standard Surface"));
            GenerateGridFromJson(GridDataJSON);
        }
    }

    private void ShowDrawingTools()
    {
        EditorGUILayout.LabelField(DrawingToolsLabel1);
        EditorGUILayout.LabelField(DrawingToolsLabel2);
        
        GUIContent[] drawingColorOptions = {ClearCont,BuildingCont, RoadCont, CoverCont, PlayerSpawnCont, EnemySpawnCont, GroundReliefCont, NonWalkableCont};
        GUILayout.BeginVertical("Box");
        _drawingColorIndex = GUILayout.SelectionGrid(_drawingColorIndex, drawingColorOptions, 2);

        //_drawingColorIndex = GUILayout.Toolbar(_drawingColorIndex, drawingColorOptions);     
        GUILayout.EndVertical();
        EditorGUILayout.Space();
        if (GUILayout.Button(GenerateGridDataCont))
            GenerateGridData();
    }

    private void ShowSelectionTools()
    {
        string label = _currentSelection == null
            ? "Click on a block of colored cells to make a selection"
            : "You have made a selection";
        EditorGUILayout.LabelField(label);

        EditorGUILayout.Space();

        if (GUILayout.Button(CreateFromSelectionButton))
            GenerateHoudiniFromSelection();

        if (GUILayout.Button(ClearSelectionButton))
            SetNewSelection(null);
    }

    //Scene Update
    private void OnSceneGui(SceneView sceneView)
    {
        if (_gridObject == null)
            return;
        if (_gridMesh == null)
            _gridMesh = _gridObject.GetComponent< MeshFilter>().sharedMesh;
        if (_toolSelection == 1)
        {
            UpdateDrawing(sceneView.camera);
            UpdateBoxDrawing(sceneView.camera);
        }
        else if (_toolSelection == 2)
        {
            UpdateSelection(sceneView.camera);
        }

        if (_gridObject != null && Selection.activeGameObject == _gridObject)
        {
            if (_gridObject != null) _gridObject.SetActive(true);
            if (_buildingParent != null) _buildingParent.gameObject.SetActive(false);
        }

        if (_buildingParent != null && Selection.activeGameObject == _buildingParent.gameObject)
        {
            if (_gridObject != null) _gridObject.gameObject.SetActive(false);
            if (_buildingParent != null) _buildingParent.gameObject.SetActive(true);
        }
    }
            
        
          
//Grid drawing
    private void UpdateDrawing(Camera sceneCamera)
    {
        if (Event.current.type != EventType.MouseDown)
            return;
        //Check hit
        (bool hit, Vector2Int gridPosition) = RaycastGrid(sceneCamera);
        if (!hit)
            return;

        int colorIndex = Event.current.button == 0 ? _drawingColorIndex : 0;
        ColorGridCell(gridPosition, colorIndex);
        
        
    }
    private void UpdateBoxDrawing(Camera sceneCamera)
    {
        if (Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.LeftControl)
        {
            _isControlHeld = true;
        }

        if (Event.current.type == EventType.KeyUp && Event.current.keyCode == KeyCode.LeftControl )
        {
            _isControlHeld = false;
            _temporaryMouseSelectionPoint = null;
        }

        if (_isControlHeld && Event.current.type == EventType.MouseDown && Event.current.button == 0)
        {
            (bool hitInfo, Vector2Int gridPos) = RaycastGrid(sceneCamera);
            if (!hitInfo) return;
            
            if (_temporaryMouseSelectionPoint == null)
            {
                _temporaryMouseSelectionPoint = gridPos;
                Debug.Log("first Click");
            }
            else
            {
                Debug.Log("Second click");
                ColorGridBox(_temporaryMouseSelectionPoint.GetValueOrDefault(), gridPos);
                _temporaryMouseSelectionPoint = null;
            } 
        }
    }

    private void ColorGridCell(Vector2Int gridPos, int colorIndex)
    {
        _grid[gridPos.x, gridPos.y].ColorIndex = colorIndex;
        Color[] colors = _gridMesh.colors;
        foreach (int vi in _grid[gridPos.x, gridPos.y].Vertices)
        {
            colors[vi] = DrawingColors[colorIndex];
        }
        Undo.RecordObject(_gridMesh, "Updated mesh colors");
        _gridMesh.colors = colors;
    }
    private void GenerateGridData()
    {
        GridCell[,] grid = new GridCell[_gridSize, _gridSize];

        for (int y = 0; y < _gridSize; y++)
        {
            for (int x = 0; x < _gridSize; x++)
            {
                grid[x, y] = new GridCell
                {
                    Position = new Vector2Int(x, y),
                    Occupied = false,
                    Type = _grid[x, y].GridManagerType,
                    ColorIndex = _grid[x,y].ColorIndex
                };
                Debug.Log($"Cell: {grid[x, y].Position}, {grid[x, y].Type} {_grid[x, y].ColorIndex}");
            }
        }

        string filePath = EditorUtility.SaveFilePanelInProject("Choose file path", "GridData", "json", "");
        GridManager.SaveGridData(grid, _gridSize, filePath);
    }
    //Grid selection
    private void UpdateSelection(Camera sceneCamera)
    {
        if (Event.current.type != EventType.MouseDown || Event.current.button != 0)
            return;
        //Check hit
        (bool hit, Vector2Int gridPosition) = RaycastGrid(sceneCamera);

        if (!hit)
            return;

        int searchColor = _grid[gridPosition.x, gridPosition.y].ColorIndex;
        if (searchColor == 0)
            return;
        HashSet< Vector2Int> selectedCells = new HashSet< Vector2Int>();
        HashSet< Vector2Int> openSet = new HashSet< Vector2Int> {gridPosition};

        while (openSet.Count > 0)
        {
            Vector2Int current = openSet.First();

            Vector2Int[] neighbours = GetNeighbours(current);
            foreach (Vector2Int neighbour in neighbours)
            {
                if (OnGrid(neighbour) && _grid[neighbour.x, neighbour.y].ColorIndex == searchColor &&
                    !openSet.Contains(neighbour) && !selectedCells.Contains(neighbour))
                    openSet.Add(neighbour);
            }

            openSet.Remove(current);
            selectedCells.Add(current);
        }

        SetNewSelection(selectedCells.ToList());

        Vector2Int[] GetNeighbours(Vector2Int current)
        {
            Vector2Int right = current + Vector2Int.right;
            Vector2Int left = current + Vector2Int.left;
            Vector2Int up = current + Vector2Int.up;
            Vector2Int down = current + Vector2Int.down;
            return new[] {right, left, up, down};
        }
    }
    private void SetNewSelection(List< Vector2Int> newSelection)
    {
        _currentSelection = newSelection;
    }
    private void GenerateHoudiniFromSelection()
    {
        string[] extensions = {"HDAs", "otl,hda,otllc,hdalc,otlnc,hdanc"};
        string hdaPath = EditorUtility.OpenFilePanelWithFilters("Load Houdini Digital Asset",
            HEU_PluginSettings.LastLoadHDAPath, extensions);
        HEU_PluginSettings.LastLoadHDAPath = Path.GetDirectoryName(hdaPath);
        GameObject go = HEU_HAPIUtility.InstantiateHDA(hdaPath, Vector3.zero,
            HEU_SessionManager.GetOrCreateDefaultSession(), false);
        if (go != null)
        {
            HEU_EditorUtility.SelectObject(go);
            if (_buildingParent == null)
            {
                _buildingParent = new GameObject("BuildingParent").transform;
                Undo.RegisterCreatedObjectUndo(_buildingParent.gameObject, "Added building parent object");
            }

            go.transform.parent = _buildingParent;
        }
        else
        {
            Debug.Log("Failed to load hda asset");
            return;
        }

        Vector3[] vertices = new Vector3[_currentSelection.Count * 4];
        int[] triangles = new int[_currentSelection.Count * 6];

        int vi = 0, ti = 0;

        foreach (Vector2Int gridPos in _currentSelection)
        {
            int[] vertexIndices = _grid[gridPos.x, gridPos.y].Vertices;

            //Vertices
            vertices[vi + 0] = _gridMesh.vertices[vertexIndices[0]];
            vertices[vi + 1] = _gridMesh.vertices[vertexIndices[1]];
            vertices[vi + 2] = _gridMesh.vertices[vertexIndices[2]];
            vertices[vi + 3] = _gridMesh.vertices[vertexIndices[3]];
            //Triangle 1
            triangles[ti + 0] = vi;
            triangles[ti + 1] = vi + 2;
            triangles[ti + 2] = vi + 1;
            //Triangle 2
            triangles[ti + 3] = vi + 1;
            triangles[ti + 4] = vi + 2;
            triangles[ti + 5] = vi + 3;
            vi += 4;
            ti += 6;
        }

        GameObject subObject = new GameObject("Selection");
        Undo.RegisterCreatedObjectUndo(subObject, "New sub mesh");

        if (_selectionParent == null)
        {
            _selectionParent = new GameObject("SelectionParent").transform;
            Undo.RegisterCreatedObjectUndo(_selectionParent.gameObject, "Added selection parent object");
            _selectionParent.gameObject.SetActive(false);
        }

        subObject.transform.parent = _selectionParent;

        MeshRenderer renderer = subObject.AddComponent< MeshRenderer>();
        MeshFilter filter = subObject.AddComponent< MeshFilter>();
        Mesh mesh = new Mesh {vertices = vertices, triangles = triangles};

        filter.mesh = mesh;


        HEU_HoudiniAssetRoot asset = go.GetComponent< HEU_HoudiniAssetRoot>();
        List< HEU_InputNode> list = asset._houdiniAsset.GetInputNodes();
        list[0].AddInputEntryAtEnd(subObject);

        subObject.transform.position += Vector3.down * 0.01f;
        go.transform.position += Vector3.up * 0.01f;

        asset._houdiniAsset.RequestCook(true, true, false, true);
        //DestroyImmediate(subObject);
    }
   
            
        
          
     //Grid generation
    private void GenerateGridFromJson(TextAsset jsonFile)
    {
        GridManager.GridDataForJson _gridData = JsonUtility.FromJson< GridManager.GridDataForJson>(jsonFile.text);
        _gridObject = new GameObject(GridGameObjectName);
        Undo.RegisterCreatedObjectUndo(_gridObject, $"Created{_gridObject.name}");
        MeshFilter mFilter = _gridObject.AddComponent< MeshFilter>();
        MeshRenderer mRenderer = _gridObject.AddComponent< MeshRenderer>();
       
        _gridSize = _gridData.GridSize;
        _grid = new EditorGridCell[_gridSize,_gridSize];

        for (int i = 0, y = 0, j = 0; y < _gridData.GridSize; y++)
        {
            for (int x = 0; x < _gridData.GridSize; x++, i += 4, j++)
            {
                _grid[x, y] = new EditorGridCell
                {
                    Vertices = new[] {i, i + 1, i + 2, i + 3},
                    ColorIndex = _gridData.Collection.Grid[j].ColorIndex
                };
            }
        }
        _gridMesh = new Mesh();
        GenerateMesh(_gridMesh, _gridSize, _cellScale);
        mFilter.mesh = _gridMesh;
        mRenderer.material = _gridMaterial;
        for (int y = 0; y < _gridSize; y++)
        {
            for (int x = 0; x < _gridSize; x++)
            {
                ColorGridCell(new Vector2Int(x,y), _grid[x,y].ColorIndex);
            }
        }
    }
    private void GenerateGrid(int size, float scale)
    {
        if (size == 0 || scale < float.Epsilon || _gridMaterial == null)
        {
            Debug.LogWarning("Not all values are valid for mesh generation");
            return;
        }

        if (_gridObject != null)
            DestroyImmediate(_gridObject);

        _gridObject = new GameObject(GridGameObjectName);
        Undo.RegisterCreatedObjectUndo(_gridObject, $"Created {_gridObject.name}");
        MeshRenderer renderer = _gridObject.AddComponent< MeshRenderer>();
        MeshFilter meshFilter = _gridObject.AddComponent< MeshFilter>();


        _gridMesh = new Mesh();
        _grid = new EditorGridCell[size, size];
        GenerateMesh(_gridMesh, size, scale);
        meshFilter.mesh = _gridMesh;
        renderer.material = _gridMaterial;

        _grid = new EditorGridCell[size, size];
        for (int y = 0, i = 0; y < size; y++)
        {
            for (int x = 0; x < size; x++, i += 4)
            {
                _grid[x, y] = new EditorGridCell
                {
                    ColorIndex = 0,
                    Vertices = new[] {i, i + 1, i + 2, i + 3}
                };
            }
        }
    }
    private void GenerateMesh(Mesh mesh, int size, float scale)
    {
        Vector3[] vertices = new Vector3[size * size * 4];
        Color[] colors = new Color[vertices.Length];
        Vector2[] uv = new Vector2[vertices.Length];
        Vector3[] normals = new Vector3[vertices.Length];
        {
            for (int y = 0, i = 0; y < size; y++)
            {
                for (int x = 0; x < size; x++, i += 4)
                {
                    vertices[i] = new Vector3(x, 0, y) * scale;
                    vertices[i + 1] = vertices[i] + Vector3.right * scale;
                    vertices[i + 2] = vertices[i] + Vector3.forward * scale;
                    vertices[i + 3] = vertices[i + 2] + Vector3.right * scale;

                    normals[i] = normals[i + 1] = normals[i + 2] = normals[i + 3] = Vector3.up;
                    colors[i] = colors[i + 1] = colors[i + 2] = colors[i + 3] = Color.white;

                    uv[i] = new Vector2((float) x / size, (float) y / size);
                    uv[i + 1] = new Vector2((float) (x + 1) / size, (float) y / size);
                    uv[i + 2] = new Vector2((float) x / size, (float) (y + 1) / size);
                    uv[i + 3] = new Vector2((float) (x + 1) / size, (float) (y + 1) / size);

                    _grid[x, y] = new EditorGridCell
                    {
                        Vertices = new[] {i, i + 1, i + 2, i + 3},
                        ColorIndex = _grid[x,y] == null ? 0 : _grid[x,y] .ColorIndex
                    };
                }
            }
        }
        int[] triangles = new int[size * size * 6];
        {
            for (int y = 0, t = 0, i = 0; y < size; y++)
            {
                for (int x = 0; x < size; x++, t += 6, i += 4)
                {
                    //Triangle 1
                    triangles[t] = i;
                    triangles[t + 1] = i + 2;
                    triangles[t + 2] = i + 1;
                    //Triangle 2
                    triangles[t + 3] = i + 1;
                    triangles[t + 4] = i + 2;
                    triangles[t + 5] = i + 3;
                }
            }
        }


        mesh.vertices = vertices;
        mesh.uv = uv;
        mesh.normals = normals;
        mesh.colors = colors;
        mesh.triangles = triangles;
    }
    private void GetMeshValues()
    {
        _gridMesh = _gridObject.GetComponent< MeshFilter>().sharedMesh;
        _grid = new EditorGridCell[_gridSize, _gridSize];
        for (int y = 0, i = 0; y < _gridSize; y++)
        {
            for (int x = 0; x < _gridSize; x++, i += 4)
            {
                _grid[x, y] = new EditorGridCell
                {
                    Vertices = new[] {i, i + 1, i + 2, i + 3},
                };
                Color color = _gridMesh.colors[_grid[x, y].Vertices[0]];
                _grid[x, y].ColorIndex = GetColorIndex(color);
            }
        }
    }
    private int GetColorIndex(Color color)
    {
        if (color == Color.white) return 0;
        if (color == Color.red) return 1;
        if (color == Color.black) return 2;
        if (color == Color.blue) return 3;
        if (color == Color.cyan) return 4;
        if (color == Color.magenta) return 5;
        if (color == Color.yellow) return 6;
        if (color == Color.green) return 7;

        return 0;
    }
    private void ColorGridBox(Vector2Int inputPos1, Vector2Int inputPos2)
    {
        Vector2Int _pos1 = new Vector2Int(Mathf.Min(inputPos1.x, inputPos2.x), Mathf.Min(inputPos1.y, inputPos2.y));
        Vector2Int _pos2 = new Vector2Int(Mathf.Max(inputPos1.x, inputPos2.x), Mathf.Max(inputPos1.y, inputPos2.y));

        for (int y = _pos1.y; y < _pos2.y+1; y++)
        {
            for (int x = _pos1.x; x < _pos2.x+1; x++)
            {
                Vector2Int pos = new Vector2Int(x,y);
                ColorGridCell(pos, _drawingColorIndex);
            }
        }
    }
            
        
          
        //Window helpers
    private void OnEnable()
    {
        SceneView.duringSceneGui += OnSceneGui;
        string data = EditorPrefs.GetString(EditorPrefsKey, JsonUtility.ToJson(this, false));
        JsonUtility.FromJsonOverwrite(data, this);

        if (_gridObject == null)
        {
            _gridObject = GameObject.Find("LevelEditor_Grid");
            _gridMaterial = new Material(Shader.Find("Particles/Standard Surface"));
        }
        else
        {
            return;
        }

        GetMeshValues();
    }

    private void OnDisable()
    {
        SceneView.duringSceneGui -= OnSceneGui;
        string data = JsonUtility.ToJson(this, false);
        EditorPrefs.SetString(EditorPrefsKey, data);
    }

    //Helper methods
    private (bool, Vector2Int) RaycastGrid(Camera sceneCamera)
    {
        Vector2 mousePos = Event.current.mousePosition;
        Ray ray = sceneCamera.ScreenPointToRay(new Vector3(mousePos.x,
            Screen.height - mousePos.y - ScreenMenusVerticalOffset));
        Plane gridPlane = new Plane(Vector3.up, Vector3.zero);
        bool hit = gridPlane.Raycast(ray, out float enterDistance);
        if (!hit)
            return (false, Vector2Int.zero);
        Vector3 worldPosition = ray.origin + ray.direction * enterDistance;
        Vector2Int gridPosition = new Vector2Int(Mathf.FloorToInt(worldPosition.x / _cellScale),
            Mathf.FloorToInt(worldPosition.z / _cellScale));
        if (!OnGrid(gridPosition))
            return (false, Vector2Int.zero);

        return (true, gridPosition);
    }

    private bool OnGrid(Vector2Int gridPosition)
    {
        return !(gridPosition.x < 0 || gridPosition.y < 0 || gridPosition.x >= _gridSize ||
                 gridPosition.y >= _gridSize);
    }
}

} 
            
        

Shaders:

I created a couple of shaders that are supposed to help the player understand when the ability Phase Shift from one of the charachters is activated, as well as the shader that indicates whenever an enemy has been selected for targeting.


Code:

          
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

Shader "Unlit/S_HolographicShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Color ("Color", Color) = (0,0,1,1)
        _Thickness("Thickness", float) = 1000
        _Frequency("Frequency", float) = 1000
        _Bias("Bias(Gap in between the lines)", Range(-1,1)) = 0
    }
    SubShader
    {
        Tags { "Queue"="Transparent" "RenderType"="Transparent" }
        Cull Off
        ZWrite Off
        LOD 100
        Blend SrcAlpha One

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(2)
                float4 vertex : SV_POSITION;
                float4 objVertex : TEXCOORD1;
            };
            
            fixed4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _Thickness;
            float _Frequency;
            float _Bias;

            v2f vert (appdata v)
            {
                v2f o;
                o.objVertex = mul(unity_ObjectToWorld, v.vertex);
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                col =_Color * max(0,cos(i.objVertex.y *_Thickness +_Time.x * _Frequency) + _Bias);
                col *=1 - max(0,cos(i.objVertex.x * _Thickness +_Time.x * _Frequency) + _Bias);
                col *=1 - max(0,cos(i.objVertex.z * _Thickness +_Time.x * _Frequency) + _Bias);

                return col;
            }
            ENDCG
        }
    }
}

            
        
          
            Shader "Unlit/DamageAoE"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Color ("Color", Color) = (0,0,1,1)
        _Thickness("Thickness", float) = 1000
        _Frequency("Frequency", float) = 1000
        _Bias("Bias(Gap in between the lines)", Range(-1,1)) = 0
    }
    SubShader
    {
        Tags { "Queue"="Geometry" /*"RenderType"="Transparent"*/ }
        //Cull Front      
        //ZWrite On
        
        LOD 100
        //Blend SrcAlpha One

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(2)
                float4 vertex : SV_POSITION;
                float4 objVertex : TEXCOORD1;
            };
            
            fixed4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _Thickness;
            float _Frequency;
            float _Bias;

            v2f vert (appdata v)
            {
                v2f o;
                o.objVertex = mul(unity_ObjectToWorld, v.vertex);
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                col =_Color * max(0,cos(i.objVertex.y *_Thickness * _Frequency) + _Bias);
                return col;
            }
            ENDCG
        }
    }
}

            
        

VFX System:

I created a symple VFX manager that would listen to when a certain action happeneds and play the correspondent Effect.


Code:

          
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.Assertions;
using UnityEngine;

public class Particle_Manager : MonoBehaviour
{

    private List< ParticleSystem> _particles=  new List< ParticleSystem>();

    private void Awake()
    {
        //Add Assault rifle shot action
        BulletManager.OnFire += shootAction =>
        {
            Debug.Log("Doing the particle");
            if (shootAction.Weapon.Type == Weapon.WeaponType.Sniper)
            {
                SpawnMuzzleFlash(shootAction);
            }
        };

        BulletManager.OnFire += shootAction =>
        {
            if (shootAction.Weapon.Type == Weapon.WeaponType.ShotGun)
            {
                SpawnMuzzleFlash(shootAction);
            }
        };
        
        BulletManager.OnFire += shootAction =>
        {
            if (shootAction.Weapon.Type == Weapon.WeaponType.GrenadeLauncher)
            {
                SpawnMuzzleFlash(shootAction);
            }
        };
        
        BulletManager.OnFire += shootAction =>
        {
            if (shootAction.Weapon.Type == Weapon.WeaponType.AssaultRifle)
            {
                SpawnMuzzleFlash(shootAction);
            }
        };
        BulletManager.OnFire += shootAction =>
        {
            if (shootAction.Weapon.Type == Weapon.WeaponType.Pistol)
            {
                SpawnMuzzleFlash(shootAction);
            }
        };
        BulletManager.OnFire += shootAction =>
        {
            if (shootAction.Weapon.Type == Weapon.WeaponType.LaserRifle)
            {
                SpawnMuzzleFlash(shootAction);
            }
        };
        BulletManager.OnFire += shootAction =>
        {
            if (shootAction.Weapon.Type == Weapon.WeaponType.EnemyRanged)
            {
                SpawnMuzzleFlash(shootAction);
            }
        };
        BulletManager.OnFire += shootAction =>
        {
            if (shootAction.Weapon.Type == Weapon.WeaponType.EnemyMelee)
            {
                SpawnMuzzleFlash(shootAction);
            }
        };

    }

    private void SpawnMuzzleFlash(ShootAction shootAction)
    {
        Quaternion rotation = shootAction.Shooter.Data.WorldRepresentation.transform.rotation;
        Vector3 shootingPosition = GridManager.Instance.GetCell(shootAction.FiringPosition).WorldPos + rotation * shootAction.Weapon.Offset;
        SpawnParticle(shootAction.Weapon.MuzzleFlashEffect, shootingPosition, rotation);
    }

    private void SpawnParticle(GameObject particlePrefab, Vector3 pos, Quaternion rot)
    {
        GameObject go = ObjectPool.Instantiate(particlePrefab, pos, rot);
        _particles.Add(go.GetComponentInChildren< ParticleSystem>());
    }

    private void Update()
    {
        for (int i = _particles.Count - 1; i >= 0; i--)
        {
            if (!_particles[i].IsAlive())
            {
                
                Destroy(_particles[i].gameObject);
                _particles.RemoveAt(i);
            }
        }
    }
}