Skip to content

Instantly share code, notes, and snippets.

@SorraTheOrc
Last active April 3, 2023 17:11
Show Gist options
  • Save SorraTheOrc/8706e8cfa122d57e509272bf163d6125 to your computer and use it in GitHub Desktop.
Save SorraTheOrc/8706e8cfa122d57e509272bf163d6125 to your computer and use it in GitHub Desktop.
Extension to CiDy that will allow you to create a NavMesh to guide pedestrians around the city. Any NavMesh driven character can be used. I provide some basic open source pedestrian code (see https://www.patreon.com/posts/64257587 and https://youtu.be/nzzS40XHWA8) but it's not required. If you don't use my Character controller code you will need…
/*
Extension to CiDy that will allow you to create a NavMesh to guide pedestrians around the city.
Any NavMesh driven character can be used. I provide some basic open source pedestrian code
(see https://www.patreon.com/posts/64257587 and https://youtu.be/nzzS40XHWA8) but it's not
required. If you don't use my Character controller code you will need to make a couple of
small changes to the scripts (see comments) and build your own Custom Editor base on my code
below.
Add CiDyPedestrians to a convenient object in your scene, setup a couple of parameters and
click the "Build NavMesh" button.
*/
#if CiDy
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using WizardsCode.BackgroundAI; // Remove if not using my character controller code
using CiDy;
namespace WizardsCode.CiDYExtension
{
/// <summary>
/// Extends the Wizards Code Spawner with features to automatically configure the NavMesh Areas in a
/// CiDy generated city.
/// </summary>
public class CiDyPedestrians : Spawner // If not using my character controller code use MonoBehaviour rather than Spawner
{
[SerializeField, Tooltip("The CiDy graph object that manages the generation of the city.")]
CiDyGraph cidyGraph;
[SerializeField, Tooltip("The name of the navmesh area to use for roads and junctions.")]
public string roadAreaName = "Road";
[SerializeField, Tooltip("The name of the navmesh area to use for road crossings.")]
public string crossingAreaName = "Crossing";
[SerializeField, Tooltip("The name of the navmesh area to use for pavement/sidewalk.")]
public string pavementAreaName = "Pavement";
}
}
#endif
#if CiDy
using CiDy;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.AI;
using WizardsCode.Character;
namespace WizardsCode.CiDYExtension
{
[CustomEditor(typeof(CiDyPedestrians), true)]
public class CiDyPedestriansEditor : SpawnerEditor // If not using my character controller code use CustomEditor rather than SpawnerEditor, but you are on your own implementing that editor ;-)
{
private CiDyGraph graph;
private CiDyPedestrians peds;
protected override void OnEnable()
{
base.OnEnable();
SerializedProperty serializedGraph = serializedObject.FindProperty("cidyGraph");
graph = (CiDyGraph)serializedGraph.objectReferenceValue;
peds = (CiDyPedestrians)target;
}
public bool IsNavMeshSetup
{
get
{
bool isValid = true;
isValid &= NavMesh.GetAreaFromName(peds.roadAreaName) >= 0;
isValid &= NavMesh.GetAreaFromName(peds.crossingAreaName) >= 0;
isValid &= NavMesh.GetAreaFromName(peds.pavementAreaName) >= 0;
return isValid;
}
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
serializedObject.Update();
if (!IsNavMeshSetup)
{
EditorGUILayout.LabelField("Please add the three NavMesh Areas (" + peds.roadAreaName + ", " + peds.crossingAreaName + ", " + peds.pavementAreaName + ") as defined above.");
}
else
{
if (graph != null)
{
if (GUILayout.Button("Configure CiDy"))
{
// NOTE this assumes that you have configured NavMesh Areas for Road, Pedestrian Crossing and Pavement
// Road should have a higher cost than Pedestrian crossing, with Pavement having the lowest
// If you don't know how to do this see https://youtu.be/nzzS40XHWA8
//TODO Setup necessary NavMeshAreas if not already present
//as can be seen here (CiDY calls it a Sidewalk, I call it a Pavement)
ConfigureRoads();
ConfigureIntersections();
ConfigurePavements();
UnityEditor.AI.NavMeshBuilder.BuildNavMeshAsync();
}
}
}
serializedObject.ApplyModifiedProperties();
}
private void ConfigurePavements()
{
List<CiDyCell> cells = graph.cells;
for (int i = 0; i < cells.Count; i++)
{
//TODO is there a more robust way of getting the sidewalks?
Transform sidewalk = cells[i].transform.Find("SideWalk");
if (sidewalk != null)
{
SetNavMeshArea(sidewalk.gameObject, peds.pavementAreaName);
}
}
}
private void ConfigureIntersections()
{
List<CiDyEdge> edges = graph.graphEdges;
for (int i = 0; i < edges.Count; i++)
{
SetNavMeshArea(edges[i].v1.intersection, peds.roadAreaName);
SetNavMeshArea(edges[i].v2.intersection, peds.roadAreaName);
}
}
private void ConfigureRoads()
{
List<GameObject> roads = graph.roads;
for (int i = 0; i < roads.Count; i++)
{
SetNavMeshArea(roads[i], peds.roadAreaName);
// Configure crossings
List<Transform> decals = roads[i].GetComponent<CiDyRoad>().decals;
for (int idx = 0; idx < decals.Count; idx++)
{
SetNavMeshArea(decals[idx].gameObject, peds.crossingAreaName);
}
}
}
/// <summary>
/// Set the navmesh area on all MeshRenderer children of an object.
/// Also set isStatic to true.
/// </summary>
/// <param name="obj"></param>
/// <param name="nameOfNavMeshArea"></param>
private static void SetNavMeshArea(GameObject obj, string nameOfNavMeshArea)
{
MeshRenderer[] renderers = obj.GetComponentsInChildren<MeshRenderer>();
for (int idx = 0; idx < renderers.Length; idx++)
{
GameObjectUtility.SetNavMeshArea(renderers[idx].gameObject, NavMesh.GetAreaFromName(nameOfNavMeshArea));
renderers[idx].gameObject.isStatic = true;
}
}
}
}
#endif
@SorraTheOrc
Copy link
Author

@chris52580 I assume you are the same person asking for help on my Discord. If not then hop over to http://bit.ly/WizardsCodeDiscord and we'll get you going.

For those coming in the future....

We've identified some issue with the currently documented approach (at the time of this message). These seem to be caused in newer version of Unity. By the time you read this we will hopefully have resolved it and updated the instructions. If not, checking out the source from GitHub rather than importing using the package manager works fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment