Day 15 or 16 (16th Dec = Day 16 right?)
I have been going back over the NavMesh stuff, trying to get it working properly. It all seems to be stemming from the fact the characters are not running on the floor objects properly. They sink in and run on the underlying plane, which actually had the NavMesh baked onto it. Bit tricky.
My idea of instantiating a bunch of maze segments, assembling them, and then baking a navmesh on that wasn’t quite working right. So I am going back over that trying to get a better understanding of how to do it better.
Oh wait I think I fixed it.
Stay tuned. Taking a while to generate 500x500 maze.
Cancelled that, ran a 150x150 maze instead. Don’t have all day!
Things I have done differently…
The parts of the maze are cloned or instantiated under a parent, that Maze objects. I need to put a NavMeshSurface component on the top level of the maze, and modifiers on the walls to make them impassable.
Then after generating the maze, I can get the NavMeshSurface object to generate the mesh then. This frees up a bunch of CPU cycles and seems to allow the agents to work a bit better through the maze.
Previously the agent wouldn’t go through a 20x20, now it belts through a 150x150. So that is good.
They made it to the end!
Code:
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.AI;
/*
This script controls the scene, generates the maze, places end point etc.
*/
public class scenescripts : MonoBehaviour {
public int Maze_Segments_x = 10;
public int Maze_Segments_y = 10;
Room[,] Rooms;
public float Map_Scale_factor = 2;
public GameObject room_template;
public GameObject wall_template;
public GameObject maze_floors;
public GameObject maze_walls;
//public GameObject maze_others;
public NavMeshSurface maze_surface;
public GameObject goal;
//public NPCmove NPC;
float counter;
public float Map_Offset_x = 5f;
public float Map_Offset_y = 5f;
public float Map_Offset_z = 0.5f;
// Use this for initialization
void Start() {
genMaze();
}
// Update is called once per frame
void Update() {
}
class Room
{
public Wall Up;
public Wall Down;
public Wall Left;
public Wall Right;
public bool isVisited;
}
class Wall
{
public Room Room1;
public Room Room2;
public bool passage;
public Wall(Room Room1,Room Room2 )
{
this.passage = false;
this.Room1 = Room1;
this.Room2 = Room2;
}
}
void genMaze()
{
//Array of cells set to right size
Rooms = new Room[Maze_Segments_x, Maze_Segments_y];
List<Wall> Walls = new List<Wall>();
int x, y;
//Build grid of rooms
for (y = 0; y < Maze_Segments_y; y++)
{
for (x = 0; x < Maze_Segments_x; x++)
{
Room tmproom = new Room();
tmproom.isVisited = false;
Rooms[x, y] = tmproom;
}
}
//Run back over array and create walls between all the rooms, putting in the references to rooms etc.
for (y = 0; y < Maze_Segments_y; y++)
{
for (x = 0; x < Maze_Segments_x; x++)
{
// fixed some bugs here in map generation
if (x == 0)
{
Rooms[x, y].Left = new Wall(Rooms[x, y], null);
}
else if (x == Maze_Segments_x - 1)
{
Rooms[x, y].Right = new Wall(Rooms[x, y], null);
}
else
{
Wall tmpwall = new Wall(Rooms[x, y], Rooms[x + 1, y]);
Rooms[x, y].Right = tmpwall;
Rooms[x + 1, y].Left = tmpwall;
}
if (y == 0)
{
Rooms[x, y].Up = new Wall(Rooms[x, y], null);
}
else if (y == Maze_Segments_x - 1)
{
Rooms[x, y].Down = new Wall(Rooms[x, y], null);
}
else
{
Rooms[x, y].Down = new Wall(Rooms[x, y], null);
Wall tmpwall = new Wall(Rooms[x, y], Rooms[x, y + 1]);
Rooms[x, y].Down = tmpwall;
Rooms[x, y + 1].Up = tmpwall;
}
}
}
/*
Modified Prim's algorithm from Wikipedia
1. Start with a grid full of walls.
2. Pick a cell, mark it as part of the maze. Add the walls of the cell to the wall list.
3. While there are walls in the list:
a. Pick a random wall from the list. If only one of the two cells that the wall divides is visited, then:
i. Make the wall a passage and mark the unvisited cell as part of the maze.
ii. Add the neighboring walls of the cell to the wall list.
b. Remove the wall from the list.
*/
//2. Pick a cell, mark it as part of the maze. Add the walls of the cell to the wall list.
// Need to pick even index inside the maze
//NOTE: Generating the maze starting at the [0,0] corner results in a terrible maze
Rooms[1, 1].isVisited = true;
Walls.Add(Rooms[1, 1].Up);
Walls.Add(Rooms[1, 1].Down);
Walls.Add(Rooms[1, 1].Left);
Walls.Add(Rooms[1, 1].Right);
// 3. While there are walls in the list:
do
{
//a. Pick a random wall from the list. If only one of the two cells that the wall divides is visited, then:
int r = Random.Range(0, Walls.Count - 1);
if (Walls[r] == null) { }
else if (Walls[r].Room1 == null || Walls[r].Room2 == null) { }
else if (Walls[r].Room1.isVisited ^ Walls[r].Room2.isVisited) //either visited but not both
{
//i. Make the wall a passage and mark the unvisited cell as part of the maze.
//ii. Add the neighboring walls of the cell to the wall list.
Walls[r].passage = true;
if (!Walls[r].Room1.isVisited)
{
Walls[r].Room1.isVisited = true;
if (!Walls.Contains(Walls[r].Room1.Up)) Walls.Add(Walls[r].Room1.Up);
if (!Walls.Contains(Walls[r].Room1.Down)) Walls.Add(Walls[r].Room1.Down);
if (!Walls.Contains(Walls[r].Room1.Left)) Walls.Add(Walls[r].Room1.Left);
if (!Walls.Contains(Walls[r].Room1.Right)) Walls.Add(Walls[r].Room1.Right);
}
else
{
Walls[r].Room2.isVisited = true;
if (!Walls.Contains(Walls[r].Room2.Up)) Walls.Add(Walls[r].Room2.Up);
if (!Walls.Contains(Walls[r].Room2.Down)) Walls.Add(Walls[r].Room2.Down);
if (!Walls.Contains(Walls[r].Room2.Left)) Walls.Add(Walls[r].Room2.Left);
if (!Walls.Contains(Walls[r].Room2.Right)) Walls.Add(Walls[r].Room2.Right);
}
}
//2. Pick a cell, mark it as part of the maze. Add the walls of the cell to the wall list.
Walls.Remove(Walls[r]);
} while (Walls.Count > 0);
//Done forming maze
GameObject tmpobject;
//Build maze out of 3d objects
for (y = 0; y < Maze_Segments_y; y++)
{
for (x = 0; x < Maze_Segments_x; x++)
{
//No room for this tile! Shouldnt happen anymore.
if (Rooms[x, y] == null)
{
Debug.Log(string.Format("No room created for maze tile: {0},{1}", x, y));
continue;
}
//Create room on this maze tile.
tmpobject = Instantiate(room_template,
new Vector3(Map_Offset_x + (x * Map_Scale_factor) + Map_Scale_factor / 2, Map_Offset_z, Map_Offset_y + (y * Map_Scale_factor) + Map_Scale_factor / 2),
new Quaternion(0, 0, 0, 0),
maze_floors.transform);
// Create four walls as required for each maze tile. Check to make sure it hasn't already been made by neighboring tile.
if (Rooms[x, y].Up != null && !Rooms[x, y].Up.passage)
{
tmpobject = Instantiate(wall_template,
new Vector3(Map_Offset_x + (x * Map_Scale_factor) + Map_Scale_factor / 2, Map_Offset_z, Map_Offset_y + (y * Map_Scale_factor) + Map_Scale_factor / 2),
Quaternion.Euler(0, 270, 0),
maze_walls.transform);
}
if (Rooms[x, y].Right != null && !Rooms[x, y].Right.passage && x == Maze_Segments_x - 1)
{
tmpobject = Instantiate(wall_template,
new Vector3(Map_Offset_x + (x * Map_Scale_factor) + Map_Scale_factor / 2, Map_Offset_z, Map_Offset_y + (y * Map_Scale_factor) + Map_Scale_factor / 2),
Quaternion.Euler(0, 180, 0),
maze_walls.transform);
}
if (Rooms[x, y].Down != null && !Rooms[x, y].Down.passage && y == Maze_Segments_y - 1)
{
tmpobject = Instantiate(wall_template,
new Vector3(Map_Offset_x + (x * Map_Scale_factor) + Map_Scale_factor / 2, Map_Offset_z, Map_Offset_y + (y * Map_Scale_factor) + Map_Scale_factor / 2),
Quaternion.Euler(0, 90, 0),
maze_walls.transform);
}
if (Rooms[x, y].Left != null && !Rooms[x, y].Left.passage)
{
tmpobject = Instantiate(wall_template,
new Vector3(Map_Offset_x + (x * Map_Scale_factor) + Map_Scale_factor / 2, Map_Offset_z, Map_Offset_y + (y * Map_Scale_factor) + Map_Scale_factor / 2),
Quaternion.Euler(0, 0, 0),
maze_walls.transform);
}
}
}
//Place goal at end of the maze
goal.transform.Translate(new Vector3(Map_Offset_x + ((Maze_Segments_x - 1) * Map_Scale_factor) + Map_Scale_factor / 2, Map_Offset_z, Map_Offset_y + ((Maze_Segments_y-1) * Map_Scale_factor) + Map_Scale_factor / 2));
//Generate the nav mesh once after maze built.
maze_surface.BuildNavMesh();
}
}