void Start() { // FIXME is new object the way to go?? Or scriptable object without new keyword?? Or static?? MazeFrameCreator mazeFrameCreator = new MazeFrameCreatorMeshIcosahedron(2, 4) { Scale = new Vector3(10, 10, 10), Jitter = 0f }; mazeFrame = mazeFrameCreator.GenerateEmptyMazeFrame(); for (int i = 0; i <= 3; i++) { mazeFrame.SetActiveQuadrant(i); mazeFrameCreator.GenerateMaze(ref mazeFrame); mazeFrame.SetOnShortestPath(mazeFrame.Quadrants[i], i); // HACKY } mazeFrame.ActivateAllNodes(); //mazeFrame.KeepOnlyShortestPathConnections(); mazeFrame.AddOffsetStartAndEndNodes(Vector3.Scale(Vector3.forward, mazeFrameCreator.Scale) * 3, true); mazeFrame.AddPathSegments(); //mazeFrameSplines = new MazeFrameSplines(mazeFrame); //GameObject mazeObjects = null; //MazePopulator.PopulateWithSplineFittedBars(mazeFrame, ref mazeFrameSplines, ref mazeObjects, new Vector3(2, .15f, 1)); //GameObject player = null; //GameObject cameraRig = null; //MazePopulator.PlacePlayerAndCamera(ref player, ref cameraRig, mazeFrame.StartNode.Position + (Vector3.Scale(Vector3.forward, mazeFrameCreator.Scale) * 1.5f)); }
private void DestroyCurrentLevel() { if (!LevelIsPresent) { return; } mazeFrame = null; mazeFrameSplines = null; gravityFrameController = null; Destroy(mazeObjects); if (player != null) { player.SetActive(false); } if (cameraRig != null) { cameraRig.SetActive(false); } if (endPortal != null) { endPortal.SetActive(false); } if (cubeOfDeath != null) { Destroy(cubeOfDeath); } if (mindWarpTriggers != null) { Destroy(mindWarpTriggers); } //Destroy(player); //Destroy(cameraRig); //Destroy(cubeOfDeath); //Destroy(endPortal); }
/// <summary> /// Initializes a new gravity frame from maze frame fitted splines. /// </summary> /// <param name="mazeFrame">Maze frame.</param> /// <param name="mazeFrameSplines">Maze frame fitted splines.</param> /// <param name="closebyDistFac"> Units of max(mazeFrame.Scale) at which nodes are considered close by.</param> /// <param name="junctionDistFac"> Units of max(mazeFrame.Scale) at which nodes are considered close to junction.</param> public GravityFrameController(MazeFrame mazeFrame, MazeFrameSplines mazeFrameSplines, float junctionDistFac, float planeSize) { //float currCloseByDist = mazeFrame.Scale.ComponentMax() * closebyDistFac; float currJunctionDist = mazeFrame.Scale.ComponentMax() * junctionDistFac; GenerateGravityFrameFromSplines(mazeFrame, mazeFrameSplines, currJunctionDist, planeSize); }
/// <summary> /// Updates the Virus for the next frame to be drawn. /// </summary> /// <param name="time">Time elapsed during gameplay.</param> /// <param name="frame"> The frame that is currently drawn, used for storing playSessions for evaluation</param> /// <returns>An updated version of the frame-parameter, with all the relevant information of this Virus</returns> internal override MazeFrame Update(GameTime time, MazeFrame frame) { if (Vector2.Distance(maze.player.Position, Position) < 100.0f && maze.LettersCollected() && maze.GetSpeechManager().WordWasSaid(maze.requiredWord)) { maze.resetGame(true); } return(frame); }
/// <summary> /// Generate maze from maze frame. /// </summary> /// <returns>A maze object containing the maze.</returns> /// <param name="mazeFrame">Maze frame.</param> public void GenerateMaze(ref MazeFrame mazeFrame) { // Build maze //BuildMazeFrameUsingRecursiveBacktracking(ref mazeFrame); BuildMazeFrameUsingHuntAndKill(ref mazeFrame, 50, 0.50f); // Label all nodes LabelNodesWRTPath(ref mazeFrame); }
/// <summary> /// Method to generate maze. /// </summary> /// <returns>The maze.</returns> public MazeFrame GenerateMaze() { // Initialize mazeFrame MazeFrame mazeFrame = GenerateEmptyMazeFrame(); // Build maze and label GenerateMaze(ref mazeFrame); return(mazeFrame); }
/// <summary> /// Generate maze from maze frame, inside quadrant specified by <paramref name="quadrantInd"/>. /// </summary> /// <returns>A maze object containing the maze.</returns> /// <param name="mazeFrame">Maze frame.</param> /// <param name="quadrantInd">Index of quadrant in which to build maze.</param> public void GenerateMaze(ref MazeFrame mazeFrame, int quadrantInd) { // Active quadrant mazeFrame.SetActiveQuadrant(quadrantInd); // Build maze and label GenerateMaze(ref mazeFrame); // Reactivate all nodes mazeFrame.ActivateAllNodes(); }
/// <summary> /// Updates the Letter for the next frame to be drawn. /// </summary> /// <param name="time">Time elapsed during gameplay.</param> /// <param name="frame"> The frame that is currently drawn, used for storing playSessions for evaluation</param> /// <returns>An updated version of the frame-parameter, with all the relevant information of this Letter</returns> internal override MazeFrame Update(GameTime time, MazeFrame frame) { if (Vector2.Distance(maze.player.Position, Position) < 50) // If close we pick up the letter { pickedUp = true; } frame.Letters.Add(new LetterInfo() { Position = Position, PickedUp = pickedUp, Text = LetterString }); // Add Session information return(frame); }
public void PopulateWithSplineFittedShape(MazeFrame mazeFrame, ref MazeFrameSplines mazeFrameSplines, ref GameObject mazeObjects, Vector3 resize, string baseShape) { int objPerCurveSegment = 2; // First, fit splines to maze path segments mazeFrameSplines = new MazeFrameSplines(mazeFrame); // Set color list to use for paths List <Color> colorList = new List <Color>(); GetColorBasedOnPathIndex(new List <int>(1), ref colorList); // Create parent object to hold maze objects mazeObjects = new GameObject("mazeObjects"); mazeObjects.layer = 10; mazeObjects.transform.position = Vector3.zero; mazeObjects.transform.rotation = Quaternion.identity; // Load and resize prefab //string prefabObjName = baseShape + "/" + baseShape + "-20-" + objN; string prefabObjName = baseShape + "/" + baseShape + "-20-1"; GameObject prefabInst = Instantiate(Resources.Load("Prefabs/" + prefabObjName) as GameObject); Vector3 mazeObjectPrefabSize = prefabInst.GetComponent <MeshRenderer>().bounds.size; if (!resize.ComponentsAreApproxEqualTo(mazeObjectPrefabSize)) { Utilities.MeshResize(prefabInst, Vector3.Scale(resize, prefabInst.GetComponent <MeshRenderer>().bounds.size.ComponentInverse())); mazeObjectPrefabSize = prefabInst.GetComponent <MeshRenderer>().bounds.size; } // Create objects and add meshes to fit StartCoroutine(CreateSplineFittedMeshesCoroutine(mazeObjects, mazeFrameSplines, prefabInst, objPerCurveSegment, colorList)); //CreateSplineFittedMeshesCoroutine(mazeObjects, mazeFrameSplines, prefabInst, objPerCurveSegment, colorList); //// Add lights to the sides //GameObject mazeLightObjects = new GameObject("mazeLightObjects"); //mazeLightObjects.transform.position = Vector3.zero; //mazeLightObjects.transform.rotation = Quaternion.identity; //// Load prefab //string prefabLightName = "PathObjectLightBar"; //GameObject prefabLightInst = Instantiate(Resources.Load("Prefabs/" + prefabLightName) as GameObject); //float spacing = 1f; //// Add lights //AddPathObjectLights(mazeLightObjects, mazeFrameSplines, prefabLightInst, spacing, mazeObjectPrefabSize, colorList); }
public void PlaceMindWarpTriggers(ref GameObject triggerParent, MazeFrame mazeFrame, float scale, float triggerProb, int intensity) { if (triggerParent != null) { Object.Destroy(triggerParent); triggerParent = null; } triggerParent = new GameObject("MindWarpTriggers"); GameObject prefabInst = Object.Instantiate(Resources.Load("Prefabs/" + "MindWarpTrigger")) as GameObject; prefabInst.GetComponent <SphereCollider>().radius = scale / 2f; Transform parent = triggerParent.GetComponent <Transform>(); foreach (MazeNode node in mazeFrame.Nodes) { if (node == mazeFrame.StartNode || node == mazeFrame.EndNode || node.Identifier.Contains("entry") || node.Identifier.Contains("exit")) { continue; } // Place trigger if (node.ConnectedNeighbors.Count >= 3) { GameObject trigger = Object.Instantiate(prefabInst, parent); trigger.transform.position = node.Position; trigger.name = "trigger-" + node.Identifier; Vector3[] neighborPosition = new Vector3[node.ConnectedNeighbors.Count]; for (int i = 0; i < node.ConnectedNeighbors.Count; i++) { neighborPosition[i] = node.ConnectedNeighbors[i].Position; } trigger.GetComponent <MindWarpController>().NeighborPosition = neighborPosition; } } Object.Destroy(prefabInst); // Set statics MindWarpController.TriggerProb = triggerProb; MindWarpController.IntensityLevel = intensity; }
/// <summary> /// Generate gravity frame from maze frame object, should only be used when paths between nodes are perfectly straight. /// </summary> /// <param name="mazeFrame">MazeFrame object.</param> private void GenerateGravityFrameFromMazeObj(MazeFrame mazeFrame, float closebyDist) { // Generate gravity frame based on maze nodes gravityFrame = new List <GravityNode>(mazeFrame.Nodes.Count); // First create base list, then add neighbors foreach (MazeNode mazeNode in mazeFrame.Nodes) { // Create gravity node from maze node GravityNode gravNode = new GravityNode(mazeNode.Position, mazeNode.Identifier); gravityFrame.Add(gravNode); } for (int i = 0; i < gravityFrame.Count; i++) { foreach (MazeNode neighbor in mazeFrame.Nodes[i].ConnectedNeighbors) { gravityFrame[i].neighbors.Add(gravityFrame.Find(x => x.identifier == neighbor.Identifier)); } } // Add closeby list to each node AddClosebyNodes(closebyDist); }
private Dictionary <string, Vector3> PopulateWithShape(MazeFrame mazeFrame, float fracConnSpace, int nNodesPerConnectionObj, string baseShape, string connShape) { // Set space allowed for connection as fraction of average connection length between nodes fracConnSpace = Mathf.Clamp01(fracConnSpace); float connSpace = (mazeFrame.Scale[0] + mazeFrame.Scale[1] + mazeFrame.Scale[2]) / 3 * fracConnSpace; // Prep storing of object center line nodes //int nNodesPerConnectionObj = 3; int nNodesPerBaseObj = 2; int nNodes = (mazeFrame.NumberOfConnections() * nNodesPerBaseObj) + (mazeFrame.NumberOfUniquePairwiseConnections() * nNodesPerConnectionObj); Dictionary <string, Vector3> objCenterLineNodes = new Dictionary <string, Vector3>(nNodes); // Load prefabs and set parent for game objects GameObject prefabCyl = Resources.Load("Prefabs/" + baseShape) as GameObject; GameObject prefabCylConn = Resources.Load("Prefabs/" + connShape) as GameObject; GameObject mazeObjects = new GameObject("mazeObjects"); mazeObjects.transform.position = Vector3.zero; mazeObjects.transform.rotation = Quaternion.identity; mazeObjects.AddComponent <MeshFilter>(); mazeObjects.AddComponent <MeshRenderer>(); mazeObjects.GetComponent <MeshRenderer>().material = prefabCyl.GetComponent <MeshRenderer>().sharedMaterial; // First, go through each node and built appropriately bended connections foreach (MazeNode node in mazeFrame.Nodes) { // Check whether we're at a dead end if (node.ConnectedNeighbors.Count <= 1) { continue; } // Place connection object and pass center line nodes to main dictionary Dictionary <string, Vector3> newCenterLineNodes = PlaceShapeConnectionAtJunction(mazeObjects, node, prefabCylConn, connSpace, nNodesPerConnectionObj); Dictionary <string, Vector3> .KeyCollection keys = newCenterLineNodes.Keys; foreach (string key in keys) { objCenterLineNodes.Add(key, newCenterLineNodes[key]); } // Increment global counter MazeObjectsPlaced++; } // Then, build the base connections (store visit record) Dictionary <string, bool> isVisited = new Dictionary <string, bool>(mazeFrame.Nodes.Count); foreach (MazeNode nd in mazeFrame.Nodes) { isVisited.Add(nd.Identifier, false); } foreach (MazeNode node in mazeFrame.Nodes) { foreach (MazeNode neighbor in node.ConnectedNeighbors) { // Check whether all connections involving this neighbor have been built already if (isVisited[neighbor.Identifier]) { continue; } // set object properties Vector3 position = (node.Position + neighbor.Position) / 2; float scale = Vector3.Distance(node.Position, neighbor.Position); scale = scale - connSpace; Quaternion rot = prefabCyl.transform.rotation * Quaternion.FromToRotation(Vector3.forward, Vector3.Normalize(neighbor.Position - node.Position)); // Check dead end ends/start/end if (node.ConnectedNeighbors.Count == 1 || node.Identifier == "start" || node.Identifier == "end") { position = position + ((node.Position - neighbor.Position).normalized * (connSpace / 4)); scale = scale + (connSpace / 2); } if (neighbor.ConnectedNeighbors.Count == 1 || neighbor.Identifier == "start" || neighbor.Identifier == "end") { position = position + ((neighbor.Position - node.Position).normalized * (connSpace / 4)); scale = scale + (connSpace / 2); } // Place object and set scale GameObject cyl = Object.Instantiate(prefabCyl, position, rot, mazeObjects.transform); cyl.transform.localScale = new Vector3(cyl.transform.localScale.x, cyl.transform.localScale.y, cyl.transform.localScale.z * scale); cyl.name = "base-" + node.Identifier + "-" + neighbor.Identifier; cyl.AddComponent <MeshCollider>(); cyl.GetComponent <MeshCollider>().convex = true; // Add two nodes (start/end) to storage with naming scheme: base-<nodeid>-<neighid> // If on dead end ends/start/end --> nodes at the extremes // If not --> shift nodes from the extremes to the center (as the conn nodes start at the extremes as well Vector3 nodeGravPos; Vector3 neighbGravPos; if (node.ConnectedNeighbors.Count == 1 || node.Identifier == "start" || node.Identifier == "end") { nodeGravPos = position + (node.Position - neighbor.Position).normalized * (scale / 2); } else { nodeGravPos = position + (node.Position - neighbor.Position).normalized * (scale / 4); } if (neighbor.ConnectedNeighbors.Count == 1 || neighbor.Identifier == "start" || neighbor.Identifier == "end") { neighbGravPos = position + (neighbor.Position - node.Position).normalized * (scale / 2); } else { { neighbGravPos = position + (neighbor.Position - node.Position).normalized * (scale / 4); } } objCenterLineNodes.Add("base-" + node.Identifier + "-" + neighbor.Identifier, nodeGravPos); objCenterLineNodes.Add("base-" + neighbor.Identifier + "-" + node.Identifier, neighbGravPos); } // Set current node as visisted isVisited[node.Identifier] = true; // Increment global counter MazeObjectsPlaced++; } //Utilities.MergeChildrenOfParent(mazeObjects); return(objCenterLineNodes); }
/// <summary> /// Updates the MazeObject for the next frame to be drawn. /// </summary> /// <param name="time">Time elapsed during gameplay.</param> /// <param name="frame"> The frame that is currently drawn, used for storing playSessions for evaluation</param> /// <returns>An updated version of the frame-parameter, with all the relevant information of this MazeObject</returns> internal abstract MazeFrame Update(GameTime time, MazeFrame frame);
/// <summary> /// Label all nodes according to whether they are (1) on the shortest path, (2) are on a loop to the shortest path, /// (3) are on a path resulting in a dead end, and (4) are not connected to the maze. /// All labels are inclusive, i.e. the starting point of a dead end is a node labeled as on the shorted path. /// The shortest path is found using Dijkstra's algorithm. /// </summary> /// <returns>List of nodes with path labels.</returns> /// <param name="mazeFrame">List of nodes.</param> protected void LabelNodesWRTPath(ref MazeFrame mazeFrame) { // // This function labels each node according to the following booleans: // // onShortestPath - whether node is on the path found using psuedo Dijkstra's method // onLoop - whether node is on path that diverges from the shortest path, but returns to it eventually // onDeadEnd - whether the node is on a dead end, defined as a path that (1) diverges from the shortest path and doesn't return // notConnectedToPath - whether node has a connection on path or is superfluous visual filler // (junction nodes have more than one label) // Find shortest path from beginning to end using Dijkstra's algorithm // From wikipedia/other: /* * 1) Mark all nodes unvisited, mark selected initial node with a current distance of 0 and the rest with infinity. * 2) Set the non-visited node with the smallest current distance as the current node C. * 3) For each neighbour N of current node C: add the current distance of C with the weight of the edge * connecting C-N. If it's smaller than the current distance of N, set it as the new current distance of N. * 4) Mark the current node C as visited. * 5) If end not touched yet, and if there are non-visited nodes, go to step 2. */ // Check start/end node active status if (!mazeFrame.StartNode.IsActive || !mazeFrame.EndNode.IsActive) { throw new System.Exception("Start/end nodes are required to be active."); } // Prep storage of labels and initilaze as false int activeNodeCount = 0; foreach (MazeNode nd in mazeFrame.Nodes) { if (nd.IsActive) { activeNodeCount++; } } Dictionary <string, bool> onShortestPath = new Dictionary <string, bool>(activeNodeCount); Dictionary <string, bool> onDeadEnd = new Dictionary <string, bool>(activeNodeCount); Dictionary <string, bool> onLoop = new Dictionary <string, bool>(activeNodeCount); Dictionary <string, bool> notConnectedToPath = new Dictionary <string, bool>(activeNodeCount); foreach (MazeNode nd in mazeFrame.Nodes) { if (nd.IsActive) { onShortestPath.Add(nd.Identifier, false); onDeadEnd.Add(nd.Identifier, false); onLoop.Add(nd.Identifier, false); notConnectedToPath.Add(nd.Identifier, false); } } // Find start and end node MazeNode startNode = mazeFrame.StartNode; MazeNode endNode = mazeFrame.EndNode; // Create bookkeeping for visits and set all nodes to unvisitied Dictionary <string, bool> isVisited = new Dictionary <string, bool>(mazeFrame.Nodes.Count); foreach (MazeNode nd in mazeFrame.Nodes) { if (nd.IsActive) { isVisited.Add(nd.Identifier, false); } else { isVisited.Add(nd.Identifier, true); } } // Ignore nodes without neighbors foreach (MazeNode nd in mazeFrame.Nodes) { if (nd.ConnectedNeighbors.Count == 0) { isVisited[nd.Identifier] = true; } } // Set distance to start, and initialize at infinity Dictionary <string, float> distToStart = new Dictionary <string, float>(activeNodeCount); foreach (MazeNode nd in mazeFrame.Nodes) { if (nd.IsActive) { distToStart.Add(nd.Identifier, float.PositiveInfinity); } } // Start search MazeNode currentNode; distToStart[startNode.Identifier] = 0; int count = 0; bool endFound = false; while (!endFound) { // Set current unvisited node based on distance to start int currentNodeInd = -1; float lastDist = float.PositiveInfinity; for (int i = 0; i < mazeFrame.Nodes.Count; i++) { if (!isVisited[mazeFrame.Nodes[i].Identifier]) { float currDist = distToStart[mazeFrame.Nodes[i].Identifier]; if (currDist <= lastDist) { currentNodeInd = i; lastDist = currDist; } } } currentNode = mazeFrame.Nodes[currentNodeInd]; // Find unvisited neighbors of current node List <MazeNode> unvisitedNeighbors = new List <MazeNode>(); foreach (MazeNode nb in currentNode.ConnectedNeighbors) { if (isVisited[nb.Identifier] == false) { unvisitedNeighbors.Add(nb); } } // For each neighbor, set distToStart as min of (currentNode disttostart + dist to current) and neighbor disttostart foreach (MazeNode nb in unvisitedNeighbors) { // Distance in case of non-equidistant nodes //float distToCurrentNode = Vector3.Distance(currentNode.position, nb.position); // Distance in case of equidistant nodes float distToCurrentNode = 1; // Update neighbor distToStart[nb.Identifier] = Mathf.Min((distToStart[currentNode.Identifier] + distToCurrentNode), distToStart[nb.Identifier]); } // Set current node to visisted isVisited[currentNode.Identifier] = true; // end reached? endFound |= currentNode == endNode; // Safety check count++; if (count > mazeFrame.Nodes.Count) { throw new System.Exception("Finding shortest path loop did not terminate correctly."); } } // Store shortest path length in maze frame now that we know it int shortestPathLength = (int)distToStart[endNode.Identifier]; // Backtrack along distToStart and label each node as being on the shortest path currentNode = endNode; onShortestPath[currentNode.Identifier] = true; count = 0; while (currentNode != startNode) { // Find neighbor with shortest distance to start MazeNode nextNodeOnPath = currentNode; foreach (MazeNode nb in currentNode.ConnectedNeighbors) { if (nb.IsActive && distToStart[nb.Identifier] < distToStart[nextNodeOnPath.Identifier]) // There is ALWAYS a node closer than the current one { nextNodeOnPath = nb; } } // Update and continue currentNode = nextNodeOnPath; onShortestPath[currentNode.Identifier] = true; // Safety check count++; if (count > mazeFrame.Nodes.Count) { throw new System.Exception("Finding shortest path loop did not terminate correctly."); } } /* Label each node as onLoop/onDeadEnd/notConnectedToPath as follows: * * 1) Find all nodes that are adjacent to the path * 2) For each of these: * 3) Create stack to keep unlabeled nodes, and set allLabeled flag * 4) While !allLabeled * 5) Create list of unlabeled neighbors of current node that are not on the stack * 6) If current node has 1+ unlabeled neighbors --> explore * 7) Add current node to stack and set an unlabeled neighbor not on the stack as current node * 8) If current node has 0 unlabeled neighbors not on the stack --> label and backtrack * 9) If current node has only 1 connected neighbor --> onDeadEnd * 10) If more than one connected neighbors, and one was onLoop --> onLoop * 11) If more than one connected neighbors, and one of them was onShortestPath (or two if at seed node) --> onLoop (loop connection found!) * 12) If more than one connected neighbors, and no onLoop/onShortestPath --> onDeadEnd (backtracking from only dead ends) * 13) If stack is not empty, pop node and set as current node, go to 5) * 14) If stack is empty, all nodes are labeled, set allLabeled to true; * * 15) Set all junctions by finding all nodes adjacent to onDeadEnd/onLoop, and set them likewise. * 16) Set all unlabeled nodes to notConnectedToPath */ // First, find unlabeled nodes that are connected to the path (1) List <MazeNode> unlabeledSeedNode = new List <MazeNode>(); for (int i = 0; i < mazeFrame.Nodes.Count; i++) { if (mazeFrame.Nodes[i].IsActive && !onShortestPath[mazeFrame.Nodes[i].Identifier]) { foreach (MazeNode nb in mazeFrame.Nodes[i].ConnectedNeighbors) { if (nb.IsActive && onShortestPath[nb.Identifier]) { unlabeledSeedNode.Add(mazeFrame.Nodes[i]); break; } } } } // Start the search (2) for (int inode = 0; inode < unlabeledSeedNode.Count; inode++) { // set seed node MazeNode seedNode = unlabeledSeedNode[inode]; if (onDeadEnd[seedNode.Identifier] || onLoop[seedNode.Identifier]) // check whether node was touched from a previous cycle below { continue; } // Create stack for to be labeled nodes Stack <MazeNode> nodesToLabel = new Stack <MazeNode>(); // FIXME should I initialize with conservative estimate? // Search from the starting node until the stack is empty (all are labeled) (4) count = 0; currentNode = seedNode; bool allLabeled = false; while (!allLabeled) { // Parse current neighbors List <MazeNode> unlabeledNeighborsNotOnStack = new List <MazeNode>(); bool hasOnShortestPath = false; bool hasOnLoop = false; int shortestPathCount = 0; foreach (MazeNode nb in currentNode.ConnectedNeighbors) { if (nb.IsActive) { if (onShortestPath[nb.Identifier]) { shortestPathCount++; } else if (onLoop[nb.Identifier]) { hasOnLoop = true; } else if (onDeadEnd[nb.Identifier]) { } else { if (!nodesToLabel.Contains(nb)) { unlabeledNeighborsNotOnStack.Add(nb); } } } } // If we're at the seed node, the first onShortestPath node is ignored (it's the hook), otherwise any one is fine if (currentNode == seedNode) { hasOnShortestPath = shortestPathCount > 1; } else { hasOnShortestPath = shortestPathCount > 0; } // Move forward or label and backtrack // If current node has an unlabeled node not on the stack --> explore (6) if (unlabeledNeighborsNotOnStack.Count > 0) { // Add current node to stack and set first neighbor not on stack (order doesn't matter) as current node foreach (MazeNode nb in unlabeledNeighborsNotOnStack) { nodesToLabel.Push(currentNode); currentNode = nb; break; } } else // If there are no more unlabeled nodes that are not on the stack, we can label and backtrack { int activeConnectedNeighborsCount = 0; foreach (MazeNode nb in currentNode.ConnectedNeighbors) { if (nb.IsActive) { activeConnectedNeighborsCount++; } } if (activeConnectedNeighborsCount <= 1) // easiest case { onDeadEnd[currentNode.Identifier] = true; } else if (hasOnLoop || (hasOnShortestPath && currentNode != seedNode)) { onLoop[currentNode.Identifier] = true; } else { onDeadEnd[currentNode.Identifier] = true; } // Backtrack if there are still unlabeled, otherwise end if (nodesToLabel.Count != 0) { currentNode = nodesToLabel.Pop(); } else { allLabeled = true; } } // Safety check count++; if (count > mazeFrame.Nodes.Count * 2) { throw new System.Exception("Labeling nodes loop did not terminate correctly."); } } } // Set junctions // onDeadEnd junctions List <MazeNode> nodesNeighboringOnDeadEnd = new List <MazeNode>(); List <MazeNode> nodesNeighboringOnLoop = new List <MazeNode>(); for (int i = 0; i < mazeFrame.Nodes.Count; i++) { if (!mazeFrame.Nodes[i].IsActive) { continue; } // Every node neighboring an onDeadEnd is a junction for a dead ending path foreach (MazeNode nb in mazeFrame.Nodes[i].ConnectedNeighbors) { if (nb.IsActive && onDeadEnd[nb.Identifier]) { nodesNeighboringOnDeadEnd.Add(mazeFrame.Nodes[i]); break; } } // Only nodes that are onShortestPath neighboring an onLoop can be an onLoop junction if (onShortestPath[mazeFrame.Nodes[i].Identifier]) { foreach (MazeNode nb in mazeFrame.Nodes[i].ConnectedNeighbors) { if (nb.IsActive && onLoop[nb.Identifier]) { nodesNeighboringOnLoop.Add(mazeFrame.Nodes[i]); break; } } } } foreach (MazeNode nd in nodesNeighboringOnDeadEnd) { onDeadEnd[nd.Identifier] = true; } foreach (MazeNode nd in nodesNeighboringOnLoop) { onLoop[nd.Identifier] = true; } // Find remaining nodes, and label them as notConnectedToPath foreach (MazeNode nd in mazeFrame.Nodes) { if (nd.IsActive && !onShortestPath[nd.Identifier] && !onLoop[nd.Identifier] && !onDeadEnd[nd.Identifier]) { notConnectedToPath[nd.Identifier] = true; } } // Assign elements to maze frame int shortestPathInd = 0; bool indNotFound = true; while (indNotFound) { if (mazeFrame.ShortestPathInd.Contains(shortestPathInd)) { shortestPathInd++; } else { indNotFound = false; } } mazeFrame.SetShortestPathLength(shortestPathLength, shortestPathInd); mazeFrame.SetOnShortestPath(onShortestPath, shortestPathInd); mazeFrame.SetOnDeadEnd(onDeadEnd); mazeFrame.SetOnLoop(onLoop); mazeFrame.SetNotConnectedToPath(notConnectedToPath); }
/// <summary> /// Build connections inside maze frame using hunt and kill algorithm. /// </summary> /// <param name="mazeFrame">Maze frame object.</param> /// <param name="huntAfterSegmentLength">Enter hunting mode after segment has grown to this length in world units, /// defaults to unlimited (uses smalles Scale value.</param> /// <param name="huntProbability">Probability of starting hunt when it arrives, defaults to 0.50.</param> protected void BuildMazeFrameUsingHuntAndKill(ref MazeFrame mazeFrame, float huntAfterSegmentLength = float.PositiveInfinity, float huntProbability = 0.50f) { if (mazeRandGen == null) { mazeRandGen = new ConsistentRandom(); } if (randGen == null) { randGen = new ConsistentRandom(); } // Build maze using hunt and kill /* 1) Make the initial node the current node and mark it as visited * 2) While there are unvisited nodes * 1) If the current node has any neighbours which have not been visited * 1) Choose randomly one of the unvisited neighbours * 2) Remove the wall between the current node and the chosen node * 3) Make the chosen node the current node and mark it as visited and add to targets * 2) Find random unvisited node next to visited node * 1) Make it the current node and mark it as visited and add to targets */ // Check start/end node active status if (!mazeFrame.StartNode.IsActive || !mazeFrame.EndNode.IsActive) { throw new System.Exception("Start/end nodes are required to be active."); } // Set storage of visit flags and create list for hunt to target int nNodesVisited = 0; Dictionary <string, bool> isVisited = new Dictionary <string, bool>(mazeFrame.Nodes.Count); foreach (MazeNode nd in mazeFrame.Nodes) { if (nd.IsActive) { isVisited.Add(nd.Identifier, false); } else { isVisited.Add(nd.Identifier, true); nNodesVisited++; } } List <MazeNode> targets = new List <MazeNode>(Mathf.RoundToInt(mazeFrame.Nodes.Count / 2f)); // half of full maze should be more than enough // Ignore nodes without neighbors foreach (MazeNode nd in mazeFrame.Nodes) { if (nd.AllNeighbors.Count == 0) { isVisited[nd.Identifier] = true; nNodesVisited++; } } // Determine huntAfterNConnections int huntAfterNConnections = Mathf.RoundToInt(huntAfterSegmentLength / Scale.ComponentMin()); //Initialize near startNode MazeNode currentNode = mazeFrame.StartNode; isVisited[currentNode.Identifier] = true; nNodesVisited++; targets.Add(currentNode); int count = 0; int nConnectionsMade = 0; while (nNodesVisited < mazeFrame.Nodes.Count) { // See if there are unvisited neighbors List <MazeNode> unvisitedNeighbors = new List <MazeNode>(); foreach (MazeNode nb in currentNode.AllNeighbors) { if (!isVisited[nb.Identifier]) { unvisitedNeighbors.Add(nb); } } if (nConnectionsMade >= huntAfterNConnections) { if (mazeRandGen.NextDouble() < huntProbability) { nConnectionsMade = 0; } } if (unvisitedNeighbors.Count > 0 && nConnectionsMade < huntAfterNConnections) { // Select random neighbor (2) MazeNode selNode = unvisitedNeighbors[mazeRandGen.Next(unvisitedNeighbors.Count)]; // Add as connected neighbor to current node object(3) and vice versa currentNode.AddConnectionByReference(selNode); // Switch to new node(4) and record visit currentNode = selNode; isVisited[currentNode.Identifier] = true; nNodesVisited++; targets.Add(currentNode); nConnectionsMade++; } else { // Find random unvisited node neighboring a visited node List <int> targetInd = new List <int>(targets.Count); for (int i = 0; i < targets.Count; i++) { targetInd.Insert(i, i); } bool found = false; Stack <MazeNode> targetsToRemove = new Stack <MazeNode>(); int huntCount = 0; while (!found) { // sample a random target node int randInd = targetInd[mazeRandGen.Next(targetInd.Count)]; MazeNode currTarget = targets[randInd]; // See if target has unvisited neighbors unvisitedNeighbors = new List <MazeNode>(); foreach (MazeNode nb in currTarget.AllNeighbors) { if (!isVisited[nb.Identifier]) { unvisitedNeighbors.Add(nb); } } // if it has no unvisited neighors, remove target from list and go again, otherwise, pick random unvisited neighbor if (unvisitedNeighbors.Count == 0) { targetInd.Remove(randInd); targetsToRemove.Push(currTarget); } else { // Select random neighbor and set as currentNode currentNode = currTarget; found = true; } // Reset connections counter nConnectionsMade = 0; // Safety check huntCount++; if (huntCount > targets.Count) { throw new System.Exception("Hunt did not terminate correctly."); } } // Remove nodes from targets that had no unvisisted neigbors while (targetsToRemove.Count > 0) { targets.Remove(targetsToRemove.Pop()); } } // Safety check count++; if (targets.Count == 0 || count > Mathf.Pow(mazeFrame.Nodes.Count, 2)) { throw new System.Exception("Hunt and kill loop did not terminate correctly."); } } // Sanity check foreach (MazeNode nb in mazeFrame.Nodes) { if (!isVisited[nb.Identifier]) { throw new System.Exception("Hunt and kill loop did not terminate correctly."); } } }
/// <summary> /// /// Build connections inside maze frame using recursive backtracking algorithm. /// </summary> /// <param name="mazeFrame">Maze frame object.</param> protected void BuildMazeFrameUsingRecursiveBacktracking(ref MazeFrame mazeFrame) { if (mazeRandGen == null) { mazeRandGen = new ConsistentRandom(); } if (randGen == null) { randGen = new ConsistentRandom(); } // Build maze using recursive backtracking // From wikipedia: /* 1) Make the initial node the current node and mark it as visited * 2) While there are unvisited nodes * 1) If the current node has any neighbours which have not been visited * 1) Choose randomly one of the unvisited neighbours * 2) Push the current node to the stack * 3) Remove the wall between the current node and the chosen node * 4) Make the chosen node the current node and mark it as visited * 2) Else if stack is not empty * 1) Pop a node from the stack * 2) Make it the current node */ // Check start/end node active status if (!mazeFrame.StartNode.IsActive || !mazeFrame.EndNode.IsActive) { throw new System.Exception("Start/end nodes are required to be active."); } // Set storage of visit flags and create stack for visisted nodes int nNodesVisited = 0; Dictionary <string, bool> isVisited = new Dictionary <string, bool>(mazeFrame.Nodes.Count); foreach (MazeNode nd in mazeFrame.Nodes) { if (nd.IsActive) { isVisited.Add(nd.Identifier, false); } else { isVisited.Add(nd.Identifier, true); nNodesVisited++; } } Stack <MazeNode> nodeTrack = new Stack <MazeNode>(mazeFrame.Nodes.Count); // FIXME does preallocating help performance here? // Ignore nodes without neighbors foreach (MazeNode nd in mazeFrame.Nodes) { if (nd.AllNeighbors.Count == 0) { isVisited[nd.Identifier] = true; nNodesVisited++; } } //Initialize near startNode MazeNode currentNode = mazeFrame.StartNode; isVisited[currentNode.Identifier] = true; nNodesVisited++; while (nNodesVisited < mazeFrame.Nodes.Count) { // See if there are unvisited neighbors List <MazeNode> unvisitedNeighbors = new List <MazeNode>(); foreach (MazeNode nb in currentNode.AllNeighbors) { if (!isVisited[nb.Identifier]) { unvisitedNeighbors.Add(nb); } } if (unvisitedNeighbors.Count != 0) { // Add current node to stack (1) nodeTrack.Push(currentNode); // Select random neighbor (2) //MazeNode selNode = RandomlySelectNodeWithBiasTowardsForwards(currentNode, unvisitedNeighbors); //Node selNode = RandomlySelectNodeWithBiasTowardsNode(currentNode, unvisitedNeighbors, endNode.AllNeighbors[0], 2,mazeRandGen); MazeNode selNode = unvisitedNeighbors[mazeRandGen.Next(unvisitedNeighbors.Count)]; // Add as connected neighbor to current node object(3) and vice versa currentNode.AddConnectionByReference(selNode); // Switch to new node(4) and record visit currentNode = selNode; isVisited[currentNode.Identifier] = true; nNodesVisited++; } else if ((unvisitedNeighbors.Count == 0) && (nodeTrack.Count != 0)) { currentNode = nodeTrack.Pop(); } // Safety check if ((nodeTrack.Count == 0) && (nNodesVisited > mazeFrame.Nodes.Count)) { throw new System.Exception("Recursive backtracking loop did not terminate correctly."); } } // Sanity check foreach (MazeNode nb in mazeFrame.Nodes) { if (isVisited[nb.Identifier] == false) { throw new System.Exception("Recursive backtracking loop did not terminate correctly."); } } }
private IEnumerator BuildNewMaze() { float mazeFrameElementsBuilt = 0; mazeFrameMeshesArePresent = false; // Initialize maze creator MazeFrameCreator mazeFrameCreator = null; switch (mazeShape) { case 0: { mazeFrameCreator = new MazeFrameCreatorSquare3D(mazeSize, nQuadrants) { Scale = mazeScale, Jitter = mazeJitter }; break; } case 1: { mazeFrameCreator = new MazeFrameCreatorMeshIcosahedron(nDivisions, nQuadrants) { Scale = mazeScale, Jitter = mazeJitter }; break; } } // Set random seed selector System.Random randGen = new ConsistentRandom(); // Randomize order of difficulties int[] randomQuadrantIndices = Utilities.RandomIndices(nQuadrants); // Generate maze! if (nQuadrants != 0 && nQuadrants != difficulties.Length) { throw new System.ArgumentException("When using quadrants, nQuadrants and nDifficulties should be equal."); } List <MazeFrame> singleMazeFrames = new List <MazeFrame>(difficulties.Length); for (int iDifficulty = 0; iDifficulty < difficulties.Length; iDifficulty++) { List <MazeFrame> singleFrameSections = new List <MazeFrame>(nSections); for (int iSections = 0; iSections < nSections; iSections++) { // Create sections int quadrantInd; //if (nQuadrants != 0) { quadrantInd = iDifficulty; } if (nQuadrants != 0) { quadrantInd = randomQuadrantIndices[iDifficulty]; } else { quadrantInd = 0; } MazeFrame currSection = null; int currDifficulty = difficulties[iDifficulty]; int currSeedInd = randGen.Next(seedData[quadrantInd].seeds[currDifficulty].Length); mazeFrameCreator.RandomSeed = seedData[quadrantInd].seeds[currDifficulty][currSeedInd]; //mazeFrameCreator.RandomSeed = seedData[quadrantInd].seeds[iDifficulty][iSections]; if (nQuadrants == 0) { currSection = mazeFrameCreator.GenerateMaze(); } else { currSection = mazeFrameCreator.GenerateEmptyMazeFrame(); mazeFrameCreator.GenerateMaze(ref currSection, quadrantInd); currSection.SetOnShortestPath(currSection.Quadrants[quadrantInd], 0); // HACKY } //currSection.KeepOnlyShortestPathConnections(); //currSection.AddPathSegments(); // DEBUG if (nQuadrants != 0) { Debug.Log(currSection.GetNIntersectionsOnPath()[0] + " " + currSection.GetDifficulty(quadrantInd)[0] + " - " + seedData[quadrantInd].difficulties[currDifficulty][currSeedInd]); } else { Debug.Log(currSection.GetNIntersectionsOnPath()[0] + " " + currSection.GetDifficulty()[0]); } // DEBUG singleFrameSections.Add(currSection); mazeFrameElementsBuilt++; LevelBuiltProgressPercentage = (mazeFrameElementsBuilt / (difficulties.Length * nSections)) / 2; yield return(null); } MazeFrame currSingleFrame; if (singleFrameSections.Count > 1) { //MazeFrame currSingleFrame = MazeFrame.Concatenate(singleFrameSections, mazeFrameCreator.Scale, mazeDirection); currSingleFrame = MazeFrame.CombineShrink(singleFrameSections, shrinkFactor); } else { currSingleFrame = singleFrameSections[0]; } currSingleFrame.AddOffsetStartNode(Vector3.Scale(mazeDirection, mazeScale) * startOffsetFactor, true); currSingleFrame.AddOffsetEndNode(Vector3.Scale(mazeDirection, mazeScale) * endOffsetFactor, true); for (int iInc = 0; iInc < iDifficulty; iInc++) { currSingleFrame.IncrementShortestPathIndices(); } singleMazeFrames.Add(currSingleFrame); } if (singleMazeFrames.Count > 1) { mazeFrame = MazeFrame.Merge(singleMazeFrames); } else { mazeFrame = singleMazeFrames[0]; } //mazeFrame.ConnectUnconnectedNodes(); mazeFrame.AddPathSegments(); yield return(null); // Populate maze and get return splines and maze objects mazePopulator.PopulateWithSplineFittedBars(mazeFrame, ref mazeFrameSplines, ref mazeObjects, objectScaling); //mazePopulator.PopulateWithSplineFittedCylinders(mazeFrame, ref mazeFrameSplines, ref mazeObjects, objectScaling); // Wait till population is complete while (!mazeFrameMeshesArePresent) { if (mazePopulator.MazeObjectsPlaced == mazeFrameSplines.SplineSegments.Count) { mazeFrameMeshesArePresent = true; } yield return(null); } // Create and Initialize gravity frame gravityFrameController = new GravityFrameController(mazeFrame, mazeFrameSplines, gravJunctionDistFactor, gravPlaneWidth); yield return(null); // Place others PlacePlayerAndCamera(); PlaceEndPortal(); PlaceCubeOfDeath(); PlaceMindWarpTriggers(); }
/// <summary> /// Fit splines to maze frame as a new instance of <see cref="MazeFrameSplines"/>. /// </summary> /// <param name="mazeFrame">Maze frame.</param> public MazeFrameSplines(MazeFrame mazeFrame) { if (mazeFrame.PathSegments == null) { throw new System.ArgumentException("Maze frame needs to have path segments in order to fit splines to it."); } SplineSegments = new List <SplineSegment>(mazeFrame.Nodes.Count); // decent estimate ShortestPathInd = mazeFrame.ShortestPathInd; StartPoint = mazeFrame.StartNode.Position; EndPoint = mazeFrame.EndNode.Position; // First, create all junction splines float junctionSpace = .5f; List <MazeNode> junctionList = new List <MazeNode>(mazeFrame.Nodes.Count / 2); foreach (MazeNode node in mazeFrame.Nodes) { if (node.ConnectedNeighbors.Count >= 3) { junctionList.Add(node); } } foreach (MazeNode junction in junctionList) { for (int ineighb1 = 0; ineighb1 < (junction.ConnectedNeighbors.Count - 1); ineighb1++) { for (int ineighb2 = ineighb1 + 1; ineighb2 < junction.ConnectedNeighbors.Count; ineighb2++) { // Create spline MazeNode neighb1 = junction.ConnectedNeighbors[ineighb1]; MazeNode neighb2 = junction.ConnectedNeighbors[ineighb2]; if (IsNodeConnectedToOrientedUp(junction)) { if (!IsNodePositionOrientedUp(neighb1) && !IsNodePositionOrientedUp(neighb2)) { continue; } } Vector3[] pArray = { neighb1.Position + (junction.Position - neighb1.Position) * (1 - junctionSpace), junction.Position, junction.Position, neighb2.Position + (junction.Position - neighb2.Position) * (1 - junctionSpace) }; Spline spline = new Spline(pArray); // Set start/end normals based on node identity if (IsNodePositionOrientedUp(neighb1)) { spline.SetRMFStartNormal(Vector3.up); } else { spline.SetRMFStartNormal(mazeFrame.Center - pArray[0]); } if (IsNodePositionOrientedUp(neighb2)) { spline.SetRMFEndNormal(Vector3.up); } else { spline.SetRMFEndNormal(mazeFrame.Center - pArray[3]); } // Create new spline segment //List<int> newShortestPathInd = new List<int>(neighb1.shortestPathInd); //foreach (int ind in neighb2.shortestPathInd) { newShortestPathInd.AddIfNotPresent(ind); } //List<int> newShortestPathInd = new List<int>(); //foreach (int ind in neighb1.shortestPathInd) //{ if (neighb2.shortestPathInd.Contains(ind)) { newShortestPathInd.AddIfNotPresent(ind); } } //foreach (int ind in neighb2.shortestPathInd) //{ if (neighb1.shortestPathInd.Contains(ind)) { newShortestPathInd.AddIfNotPresent(ind); } } List <string> identifiers = new List <string>(3) { junction.Identifier, neighb1.Identifier, neighb2.Identifier }; SplineSegments.Add(new SplineSegment(spline, identifiers, junction.shortestPathInd, neighb1.shortestPathInd, neighb2.shortestPathInd, true)); } } } // Then, add all regular segments foreach (List <MazeNode> path in mazeFrame.PathSegments) { // Skip path if it only consists of two junctions if (path.Count == 2 && path[0].ConnectedNeighbors.Count >= 3 && path[1].ConnectedNeighbors.Count >= 3) { continue; } // Create spline Vector3[] pArray = new Vector3[path.Count]; for (int i = 0; i < path.Count; i++) { pArray[i] = path[i].Position; } if (path[0].ConnectedNeighbors.Count >= 3) { pArray[0] = pArray[0] + (pArray[1] - pArray[0]) * junctionSpace; } if (path[path.Count - 1].ConnectedNeighbors.Count >= 3) { pArray[path.Count - 1] = pArray[path.Count - 1] + (pArray[path.Count - 2] - pArray[path.Count - 1]) * junctionSpace; } Spline spline = new Spline(pArray); // Set start/end normals based on node identity if (IsNodePositionOrientedUp(path[0])) { spline.SetRMFStartNormal(Vector3.up); } else { spline.SetRMFStartNormal(mazeFrame.Center - pArray[0]); } if (IsNodePositionOrientedUp(path[path.Count - 1])) { spline.SetRMFEndNormal(Vector3.up); } else { spline.SetRMFEndNormal(mazeFrame.Center - pArray[path.Count - 1]); } // Get shortestPathInd, from an in-between node in path if possible List <int> newShortestPathInd = new List <int>(); if (path.Count >= 3) // use first non-junction node { newShortestPathInd = new List <int>(path[1].shortestPathInd); } else if (path.Count == 2) // use union of only two nodes { foreach (int ind in path[0].shortestPathInd) { if (path[1].shortestPathInd.Contains(ind)) { newShortestPathInd.AddIfNotPresent(ind); } } foreach (int ind in path[1].shortestPathInd) { if (path[0].shortestPathInd.Contains(ind)) { newShortestPathInd.AddIfNotPresent(ind); } } } // Create new spline segment List <string> identifiers = new List <string>(path.Count); foreach (MazeNode node in path) { identifiers.Add(node.Identifier); } SplineSegments.Add(new SplineSegment(spline, identifiers, newShortestPathInd, path[0].shortestPathInd, path[path.Count - 1].shortestPathInd, false)); } // Finally, define start/end neighbors foreach (SplineSegment baseSeg in SplineSegments) { // For start Vector3 currStart = baseSeg.StartPoint; foreach (SplineSegment otherSeg in SplineSegments) { if (baseSeg == otherSeg) { continue; } if (otherSeg.StartPoint.ComponentsAreApproxEqualTo(currStart)) { baseSeg.startNeighbors.AddIfNotPresent(otherSeg); otherSeg.startNeighbors.AddIfNotPresent(baseSeg); } if (otherSeg.EndPoint.ComponentsAreApproxEqualTo(currStart)) { baseSeg.startNeighbors.AddIfNotPresent(otherSeg); otherSeg.endNeighbors.AddIfNotPresent(baseSeg); } } // For end Vector3 currEnd = baseSeg.EndPoint; foreach (SplineSegment otherSeg in SplineSegments) { if (baseSeg == otherSeg) { continue; } if (otherSeg.StartPoint.ComponentsAreApproxEqualTo(currEnd)) { baseSeg.endNeighbors.AddIfNotPresent(otherSeg); otherSeg.startNeighbors.AddIfNotPresent(baseSeg); } if (otherSeg.EndPoint.ComponentsAreApproxEqualTo(currEnd)) { baseSeg.endNeighbors.AddIfNotPresent(otherSeg); otherSeg.endNeighbors.AddIfNotPresent(baseSeg); } } } }
/// <summary> /// Generate gravity frame from splines fitted to maze frame. /// </summary> /// <param name="mazeFrameSplines">Splines fitted to maze frames.</param> ///// <param name="closebyDist"> Distance at which nodes are considered close by.</param> /// <param name="junctionDist"> Distance at which nodes are considered close to junction.</param> private void GenerateGravityFrameFromSplines(MazeFrame mazeFrame, MazeFrameSplines mazeFrameSplines, float junctionDist, float planeWidth) { // Generate gravity frame based on maze frame fitted splines int nodeCount = 0; foreach (MazeFrameSplines.SplineSegment splineSeg in mazeFrameSplines.SplineSegments) { nodeCount += splineSeg.spline.NSamplesToUse; } gravityFrame = new List <GravityNode>(nodeCount); // Generate sub frame to use later List <List <GravityNode> > segmentSubFrame = new List <List <GravityNode> >(mazeFrameSplines.SplineSegments.Count); // Create nodes for each splines for (int iSpline = 0; iSpline < mazeFrameSplines.SplineSegments.Count; iSpline++) { segmentSubFrame.Add(new List <GravityNode>(mazeFrameSplines.SplineSegments[iSpline].spline.NSamplesToUse)); GravityNode prevNode = null; for (int i = 0; i < mazeFrameSplines.SplineSegments[iSpline].spline.NSamplesToUse; i++) { Vector3 currPos = mazeFrameSplines.SplineSegments[iSpline].spline.GetPointOnSpline(mazeFrameSplines.SplineSegments[iSpline].spline.SampleIndToT(i)); Vector3 currTang = mazeFrameSplines.SplineSegments[iSpline].spline.GetTangentToPointOnSpline(mazeFrameSplines.SplineSegments[iSpline].spline.SampleIndToT(i)).normalized; Vector3 currNorm = mazeFrameSplines.SplineSegments[iSpline].spline.DefaultGetNormalAtT(mazeFrameSplines.SplineSegments[iSpline].spline.SampleIndToT(i)).normalized; Vector3 cross = Vector3.Cross(currTang, currNorm); Vector3[] planePoints = new Vector3[2] { currPos + (cross * (planeWidth / 2f)), currPos - (cross * (planeWidth / 2f)) }; GravityNode node = new GravityNode(currPos, iSpline + "-" + currPos.ToString(), planePoints); if (prevNode != null) { node.neighbors.Add(prevNode); prevNode.neighbors.Add(node); } // Determine if near junction if (mazeFrameSplines.SplineSegments[iSpline].startNeighbors.Count > 0) { if (Vector3.Distance(currPos, mazeFrameSplines.SplineSegments[iSpline].spline.GetPointOnSpline(0)) < junctionDist) { node.nearJunction = true; } } if (mazeFrameSplines.SplineSegments[iSpline].endNeighbors.Count > 0) { if (Vector3.Distance(currPos, mazeFrameSplines.SplineSegments[iSpline].spline.GetPointOnSpline(1)) < junctionDist) { node.nearJunction = true; } } // Add to frame gravityFrame.Add(node); prevNode = node; // Add spline segment sub-frame as well segmentSubFrame[iSpline].Add(node); } } // Create closeby list from, and connect, neighboring splines for (int iSeg = 0; iSeg < mazeFrameSplines.SplineSegments.Count; iSeg++) { // Add frame to its own closeby List <GravityNode> currSubFrame = segmentSubFrame[iSeg]; foreach (GravityNode node in currSubFrame) { node.closeby.AddRange(currSubFrame); } // Start/end of spline List <List <MazeFrameSplines.SplineSegment> > segList = new List <List <MazeFrameSplines.SplineSegment> >(2) { mazeFrameSplines.SplineSegments[iSeg].startNeighbors, mazeFrameSplines.SplineSegments[iSeg].endNeighbors }; for (int iStartEnd = 0; iStartEnd <= 1; iStartEnd++) { foreach (MazeFrameSplines.SplineSegment neighSeg in segList[iStartEnd]) { // Get sub frame index List <GravityNode> neighSegSF = segmentSubFrame[mazeFrameSplines.SplineSegments.IndexOf(neighSeg)]; // Add to closeby list foreach (GravityNode node in currSubFrame) { node.closeby.AddRange(neighSegSF); } // Connect start/end switch (iStartEnd) { case 0: { if (Vector3.Distance(currSubFrame[0].position, neighSegSF[0].position) < Vector3.Distance(currSubFrame[0].position, neighSegSF[neighSegSF.Count - 1].position)) { currSubFrame[0].neighbors.Add(neighSegSF[0]); } else { currSubFrame[0].neighbors.Add(neighSegSF[neighSegSF.Count - 1]); } break; } case 1: { if (Vector3.Distance(currSubFrame[currSubFrame.Count - 1].position, neighSegSF[0].position) < Vector3.Distance(currSubFrame[currSubFrame.Count - 1].position, neighSegSF[neighSegSF.Count - 1].position)) { currSubFrame[currSubFrame.Count - 1].neighbors.Add(neighSegSF[0]); } else { currSubFrame[currSubFrame.Count - 1].neighbors.Add(neighSegSF[neighSegSF.Count - 1]); } break; } } } } } }
/// <summary> /// Updates the maze for a new frame /// </summary> /// <param name="time">Time elapsed during gameplay</param> /// <returns>A frame describing everything if interest for the SessionManager</returns> internal Frame Update(GameTime time) { MazeFrame frame = new MazeFrame(); // Update player frame = player.Update(time, frame); if (player.moving && game.BCIManager.IsConcentrating()) // Moving players cant concentrate { game.BCIManager.StopConcentrating(); } else if (!player.moving && !game.BCIManager.IsConcentrating()) // Non-moving players should start concentrating { game.BCIManager.StartConcentrating(); } //Update virus frame = Virus.Update(time, frame); // Update antibodies and prepare for possible deletion. toDelete = new List <Antibody>(); foreach (Antibody antibody in antibodies) { frame = antibody.Update(time, frame); } // Update all letters foreach (Letter letter in Letters) { frame = letter.Update(time, frame); } // If the player is moving, we might add some antibodies (if we havent reached the maximum and some small chance is present) if (player.moving && rand.Next(1000) < 10 && antibodies.Count < MAX_ANTIBODIES) { // Get a random position for the antibody to appear at. Should be within a specific range of the player. float angle = MathHelper.TwoPi * (float)rand.NextDouble() - MathHelper.Pi; float distance = rand.Next(100, (int)(WindowWidth / 2.0f) - 20); Vector2 abPosition = new Vector2(player.Position.X + ((float)Math.Sin((double)angle) * distance), player.Position.Y + ((float)Math.Cos((double)angle) * distance)); antibodies.Add(new Antibody(this, abPosition, Keywords.AntibodyWords[rand.Next(Keywords.AntibodyWords.Length)], TimeSpan.FromSeconds(rand.Next(5, 10)).TotalMilliseconds)); } // Delete all antibodies that died. foreach (Antibody ab in toDelete) { antibodies.Remove(ab); } // Reset the player if he died. if (player.died) { game.gameover(false); } // Set information to the frame (for session manager) frame.BCIValue = GetBCIManager().ConcentrationLevel; frame.LeanForward = GetMovementManager().MovesForward(); frame.LeanLeft = GetMovementManager().MovesLeft(); frame.LeanRight = GetMovementManager().MovesRight(); return(frame); }
public void PopulateWithSplineFittedBars(MazeFrame mazeFrame, ref MazeFrameSplines mazeFrameSplines, ref GameObject mazeObjects, Vector3 resize) { PopulateWithSplineFittedShape(mazeFrame, ref mazeFrameSplines, ref mazeObjects, resize, "MazeBar"); }
public Dictionary <string, Vector3> PopulateWithBars(MazeFrame mazeFrame, float fracConnSpace, int nNodesPerConnectionObj) { return(PopulateWithShape(mazeFrame, fracConnSpace, nNodesPerConnectionObj, "MazeBar", "MazeBarConn")); }
/// <summary> /// Updates the Antibody for the next frame to be drawn. /// </summary> /// <param name="time">Time elapsed during gameplay.</param> /// <param name="frame"> The frame that is currently drawn, used for storing playSessions for evaluation</param> /// <returns>An updated version of the frame-parameter, with all the relevant information of this Antibody</returns> internal override MazeFrame Update(GameTime time, MazeFrame frame) { // Case Attached Antibody if (attached) { timeSinceLevelRaise += time.ElapsedGameTime.Milliseconds; // Timer for next level raise. if (timeSinceLevelRaise > MillisecondsTimer) // Required time since last Level Raise has passed. { RaiseStage(); // Raise level. timeSinceLevelRaise = 0; } // If the player has only been concentrated for a short while we reset the required concentration time. // This is due to the fact that, after lowering a stage, the player has to concentrate even longer to lower another stage. // But since concentration can be lost, this could be a big punishment for players. // As such, we only require the minimum concentration time every time the player has lost concentration. if (!maze.GetBCIManager().IsConcentratedFor(TimeSpan.FromSeconds(0.1).TotalMilliseconds)) { requiredConcentration = TimeSpan.FromSeconds(CONCENTRATION_TIME_PER_STATE).TotalMilliseconds; } // If concentrated long enough the stage should be lowered. if (maze.GetBCIManager().IsConcentratedFor(requiredConcentration)) { LowerStage(); } } // Case the antibody is too far from the player or the killword was said. else if (Vector2.Distance(Position, maze.player.Position) > 400.0f || (Vector2.Distance(Position, maze.player.Position) < 150.0f && maze.GetSpeechManager().WordWasSaid(killWord))) { maze.toDelete.Add(this); // Delete the antibody. } else // Case antibody movement { Vector2 movementDirection = Vector2.Normalize(maze.player.Position - Position); // Move in the direction of the player. Vector2 oldPosition = Position; bool moving = false; for (float i = Speed; i > 0 && !moving; i -= 0.2f) // Move by speed, unless collision occurs which results in slower movement. { moving = true; Position.X += movementDirection.X * i; // Move in the direction at the given speed Position.Y += movementDirection.Y * i; // In case of collision reset the movement. Matrix abTransformMatrix = Matrix.CreateTranslation(new Vector3(-maze.antibodyTextures[stage].Width / 2.0f, -maze.antibodyTextures[stage].Height / 2.0f, 0.0f)) * Matrix.CreateTranslation(new Vector3(Position, 0.0f)); if (maze.HitsMazeWall(abTransformMatrix, (int)maze.antibodyTextures[stage].Width, (int)maze.antibodyTextures[stage].Height, maze.ABCollisionData)) { Position = oldPosition; // Reset movement } } // Antibody should become attached due to short distance to player. if (Vector2.Distance(Position, maze.player.Position) < 20) { attached = true; Position = Position - maze.player.Position; // Calculate relative position to the player and store position as such. relativeAngle = (float)Math.Atan2(Position.X, -Position.Y) - maze.player.rotation; // Calculate the relative angle to the player ship. Used for rotating along. maze.player.LowerSpeed(); timeSinceLevelRaise = 0; requiredConcentration = TimeSpan.FromSeconds(CONCENTRATION_TIME_PER_STATE).TotalMilliseconds; // Set required concentration time. } } // Give frame all relevant information. frame.Antibodies.Add(new AntibodyInfo() { Position = Position, Attached = attached, Killword = killWord, State = stage }); return(frame); }
private IEnumerator CreateMazesFromSeeds() { // Parse difficulty float[,] difficultyBounds = new float[nDifficulties, 2]; float difficultyStartPad = difficultyLimits[0]; float difficultyEndPad = 1 - difficultyLimits[1]; float diffRange = 1 - (difficultyStartPad + difficultyEndPad); float currDiffSize = difficultySize * diffRange; float diffSpacing = (diffRange - (nDifficulties * currDiffSize)) / (nDifficulties - 1); if (diffSpacing < 0) { throw new System.Exception("Overlapping difficulties."); } for (int i = 0; i < (nDifficulties); i++) { difficultyBounds[i, 0] = difficultyStartPad + (currDiffSize + diffSpacing) * i; difficultyBounds[i, 1] = difficultyBounds[i, 0] + currDiffSize; } string boundsDisp = difficultyBounds[0, 0] + ">x<=" + difficultyBounds[0, 1]; for (int i = 1; i < nDifficulties; i++) { boundsDisp = boundsDisp + " | " + difficultyBounds[i, 0] + ">x<=" + difficultyBounds[i, 1]; } Debug.Log(boundsDisp); //seedList[0] = new List<int>(nMazes); // difficulty <=.20 //seedList[0] = new List<int>(nMazes); // difficulty >.20 <=.40 //seedList[0] = new List<int>(nMazes); // difficulty >.40 <=.60 //seedList[0] = new List<int>(nMazes); // difficulty >.60 <=80 //seedList[0] = new List<int>(nMazes); // difficulty >.80 <=1 // Set random seed int mainRandomSeed = System.Environment.TickCount; ConsistentRandom mainRandomGenenerator = new ConsistentRandom(mainRandomSeed); // Setup maze creators MazeFrameCreator mazeFrameCreator = null; switch (mazeShape) { case 0: { mazeFrameCreator = new MazeFrameCreatorSquare3D(mazeSize, nQuadrants); break; } case 1: { mazeFrameCreator = new MazeFrameCreatorMeshIcosahedron(divisions, nQuadrants); break; } } mazeFrameCreator.Scale = mazeScale; // Used in hunt and kill for determining when to hunt // Create level from new seeds int ittMax = 1; if (nQuadrants != 0) { ittMax = nQuadrants; } for (int iQuadrant = 0; iQuadrant < ittMax; iQuadrant++) { // Setup seed/difficulty list List <List <int> > seedList = new List <List <int> >(nDifficulties); for (int i = 0; i < nDifficulties; i++) { seedList.Add(new List <int>(nMazes)); } List <List <float> > difficultyList = new List <List <float> >(nDifficulties); for (int i = 0; i < nDifficulties; i++) { difficultyList.Add(new List <float>(nMazes)); } bool done = false; int count = 0; while (!done) { // set new seed int mazeSeed = mainRandomGenenerator.Next(); mazeFrameCreator.RandomSeed = mazeSeed; // get maze MazeFrame mazeFrame = mazeFrameCreator.GenerateEmptyMazeFrame(); if (nQuadrants != 0) { mazeFrame.SetActiveQuadrant(iQuadrant); } mazeFrameCreator.GenerateMaze(ref mazeFrame); // Save according to difficulty float difficulty; if (nQuadrants == 0) { difficulty = mazeFrame.GetDifficulty()[0]; } else { difficulty = mazeFrame.GetDifficulty(iQuadrant)[0]; } for (int i = 0; i < nDifficulties; i++) { if (difficulty > difficultyBounds[i, 0] && difficulty <= difficultyBounds[i, 1] && seedList[i].Count < nMazes) { seedList[i].Add(mazeSeed); difficultyList[i].Add(difficulty); } } // Check end condition done = true; for (int i = 0; i < nDifficulties; i++) { done = done && seedList[i].Count == nMazes; } // safety check count++; if (count == count * 100) { throw new System.Exception("Something went wrong."); } // Display progress string disp; if (nQuadrants == 0) { disp = count + ": " + seedList[0].Count; } else { disp = "quadrant" + (iQuadrant + 1) + "of" + nQuadrants + " | " + count + ": " + seedList[0].Count; } for (int i = 1; i < nDifficulties; i++) { disp = disp + ", " + seedList[i].Count; } Debug.Log(disp + " | d = " + difficulty.ToString("0.0000")); // Yield WaitForSecondsRealtime wait = null; if (wait == null) { wait = new WaitForSecondsRealtime(0.0001f); } if ((count % 10) == 0) { yield return(wait); } } // Convert to SeedData object if (nDifficulties == 5) { seedData = new SeedData(seedList[0].ToArray(), seedList[1].ToArray(), seedList[2].ToArray(), seedList[3].ToArray(), seedList[4].ToArray(), difficultyList[0].ToArray(), difficultyList[1].ToArray(), difficultyList[2].ToArray(), difficultyList[3].ToArray(), difficultyList[4].ToArray(), mazeSize, mazeFrameCreator.GetType().FullName); } else if (nDifficulties == 4) {// Convert to SeedData object seedData = new SeedData(seedList[0].ToArray(), seedList[1].ToArray(), seedList[2].ToArray(), seedList[3].ToArray(), difficultyList[0].ToArray(), difficultyList[1].ToArray(), difficultyList[2].ToArray(), difficultyList[3].ToArray(), mazeSize, mazeFrameCreator.GetType().FullName); } else if (nDifficulties == 3) {// Convert to SeedData object seedData = new SeedData(seedList[0].ToArray(), seedList[1].ToArray(), seedList[2].ToArray(), difficultyList[0].ToArray(), difficultyList[1].ToArray(), difficultyList[2].ToArray(), mazeSize, mazeFrameCreator.GetType().FullName); } else if (nDifficulties == 2) {// Convert to SeedData object seedData = new SeedData(seedList[0].ToArray(), seedList[1].ToArray(), difficultyList[0].ToArray(), difficultyList[1].ToArray(), mazeSize, mazeFrameCreator.GetType().FullName); } else { throw new System.Exception("Requested nDifficulty not implemented due to the stupid save part."); } // Save to disk string creatorName = ""; string filePath; switch (mazeShape) { case 0: { creatorName = mazeFrameCreator.GetType().FullName + "-" + mazeSize.ToString() + "-" + mazeScale.ToString(); break; } case 1: { creatorName = mazeFrameCreator.GetType().FullName + "-" + divisions.ToString() + "-" + mazeScale.ToString(); break; } } if (nQuadrants != 0) { filePath = filePathBase + "-" + creatorName + "-" + "quadrant" + (iQuadrant + 1) + "of" + nQuadrants + ".json"; } else { filePath = filePathBase + "-" + creatorName + ".json"; } SeedDataController.SaveSeedData(seedData, filePath); } Debug.Log("Done!"); Debug.Break(); #if UNITY_EDITOR UnityEditor.EditorApplication.isPlaying = false; #endif }