How to randomly create a maze - c#

I currently have code that opens up all of the doors on my prefabs for rooms/tiles that are not on the edges of my randomly generated map. I would like to modify this code so that not all of the doors are open and it is more of a randomly generated maze. I want every room to be accessible and it is possible to have more than one path through the maze/game world. How can I change my code so it works in this manner? Below is my current code that I want to modify.
using UnityEngine;
public class Room : MonoBehaviour {
public GameObject doorNorth;
public GameObject doorSouth;
public GameObject doorEast;
public GameObject doorWest;
}
using UnityEngine;
using System;
public class mapGenerator : MonoBehaviour {
public int rows;
public int cols;
public GameObject[] gridPrefabs;
private float roomWidth = 50.0f;
private float roomHeight = 50.0f;
public Room[,] grid;//used to keep track of rooms created ,uses two numbers to refer to it in memory
public bool isMapOfDay;
public bool isRandomMap;
public int chosenSeed;
// Use this for initialization
void Start () {
chosenSeed = GameManager.instance.mapSeed;
rows = GameManager.instance.mapRows;
cols = GameManager.instance.mapColumns;
isMapOfDay = GameManager.instance.useMapOfDay;
isRandomMap = GameManager.instance.useRandomMap;
gridPrefabs = GameManager.instance.mapTiles;
}
public GameObject RandomRoomPrefab()//Returns a random room
{
return gridPrefabs [UnityEngine.Random.Range (0, gridPrefabs.Length)];
}
public void GenerateGrid()//used to generate map grid
{
if (isRandomMap == true && isMapOfDay == false) {//sets map to random map based on time
UnityEngine.Random.InitState(DateToInt(DateTime.Now));//sets "random" seed to current time
} else if (isRandomMap == false && isMapOfDay == true) {//sets map to map of day based on numbers in day
UnityEngine.Random.InitState(DateToInt (DateTime.Now.Date));
} else {//if both are selected just use random map
UnityEngine.Random.InitState(DateToInt (DateTime.Now));
}
if (chosenSeed != 0) {//if a specific seed is entered in game manager use this instead
UnityEngine.Random.InitState(chosenSeed);
}
//Clear out the grid
grid = new Room[cols, rows];
GameManager.instance.mapGrid = grid;
//For each grid row...
for (int i=0; i<rows; i++)
{
//for each column in that row
for (int j=0; j<cols; j++)
{
//Figure out the location
float xPosition = roomWidth * j;
float zPosition = roomHeight * i;
Vector3 newPosition = new Vector3 (xPosition, 0.0f, zPosition);
//create a new grid at appropiate location
GameObject tempRoomObj = Instantiate (RandomRoomPrefab (), newPosition, Quaternion.identity)as GameObject;
//set its parent
tempRoomObj.transform.parent = this.transform;
//give the temp room a meaningful name
tempRoomObj.name = "Room_" + j + "," + i;
//Get the room object
Room tempRoom = tempRoomObj.GetComponent<Room> ();
//open doors as needed
if (i == 0) {
//open north doors if on bottom row
tempRoom.doorNorth.SetActive (false);
} else if (i == rows - 1) {
//Otherwise, if doors are on the top row open south doors
tempRoom.doorSouth.SetActive (false);
} else {
//otherwise, this row is in the middle so both north and south open
tempRoom.doorNorth.SetActive (false);
tempRoom.doorSouth.SetActive (false);
}
if (j == 0) {
//if first column then east doors are opened
tempRoom.doorEast.SetActive (false);
} else if (j == cols - 1) {
//Otheriwse, if one last column row open west doors
tempRoom.doorWest.SetActive (false);
} else {
//otherwise, we are in middle so both west and east are opened
tempRoom.doorEast.SetActive (false);
tempRoom.doorWest.SetActive (false);
}
//save it to the grid array
grid [j, i] = tempRoom;//
GameManager.instance.mapGrid=grid;
}
}
}
public int DateToInt(DateTime dateToUse)//adds date and time up and returns it as an int
{
int dateToReturn = dateToUse.Year + dateToUse.Month + dateToUse.Day + dateToUse.Hour +dateToUse.Minute + dateToUse.Second + dateToUse.Millisecond;
return dateToReturn;
}
public void clear()//clears grid
{
for (int c=0; c<GameManager.instance.mapGrid.GetLength(0); c++) {
for (int r=0; r<GameManager.instance.mapGrid.GetLength(1); r++) {
if(GameManager.instance.mapGrid[c,r]!=null)//if not null destroy
{
Destroy(GameManager.instance.mapGrid[c,r].gameObject);
}
}
}
}
}
I believe the change needs to happen in my else statements but I'm not exactly sure how to go about this as I have never made a maze before. Thanks for the help!

Check this blog, it maybe can help you to understand of procedural generation of mazes in Unity.
https://www.raywenderlich.com/82-procedural-generation-of-mazes-with-unity
But in essence you can use Recursive backtracker algorithm. It's relative simple to implement.
Mark your startpoint as visited & choose a random neightbor which is unvisited.
Randomly choose a grid at that starting point and carve a passage through to the near grid(only if the near grid has not been visited yet. It'll be the new grid
Return to the last grid that has uncarved wall and repeat, only if all near grids have been in visited state.
The algorithm ends when the process has backed all the way up to the starting point.
I hope you could clarify a little more about what you are looking for.

Related

Unity 2D - How to make a movement point system in a map based on non-gridded tiles?

I'm making a wargame set on a map in which the player's divisions move across irregular-shaped provinces (a la Paradox) in a turn-based manner. I want to make it so that each turn, the divisions can move an amount of provinces equal to the division's movement points (by default 2).
I'm having trouble (mainly because im a newbie) coding a way for the game to detect which provinces are in range to a division, aside from the ones directly adjacent to the division's current position. Whatever I do, the game either detects all provinces as "in-range" or just doesn't make an accurate calculation.
The provinces are not in a grid system because of their irregular shapes, so I'm using collider overlaps to detect which ones are next to each other. Feel free to tell me if there's a better way to do it.
Here's what I tried (skip to GetTilesInRange):
{
private void Awake()
{
Army = GameObject.FindGameObjectsWithTag("Division");
Map = GameObject.FindGameObjectsWithTag("Tile");
DivisionHeightOffset = new Vector3(0f, -ñDivisionHeightOffsetY, 0f);
}
private void Update()
{
mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
CheckforSelectedDivisions();
}
private void CheckforSelectedDivisions()
{
foreach ( GameObject Division in Army)
{
if (Division.GetComponent<DivisionBehaviour>().Selected && !Checked)
{
Checked = true;
SelectedDivision = Division;
SelectedDivisionPosition = Division.transform.position;
NameOfSelectedDivision = Division.name;
DivisionOverlapPoint = Division.transform.position + DivisionHeightOffset;
DivisionMovementPoints = (Division.GetComponent<DivisionBehaviour>().MovementPoints);
GetCurrentTile();
}
}
if(SelectedDivision != null) { if(!SelectedDivision.GetComponent<DivisionBehaviour>().Selected) {Checked = false;}}
}
private void GetCurrentTile()
{
foreach (GameObject Tile in Map) if (Tile.GetComponent<Collider2D>())
{
if(Tile.GetComponent<Collider2D>().OverlapPoint(DivisionOverlapPoint))
{
CurrentTile = Tile;
Debug.Log(NameOfSelectedDivision + " is in " + CurrentTile.name);
CurrentTilePosition = Tile.transform.position;
CurrentTileBounds = Tile.GetComponent<Collider2D>().bounds;
GetTilesInRange();
}
}
}
** private void GetTilesInRange()
{
i = DivisionMovementPoints;
foreach (GameObject TileB in Map) if (TileB.GetComponent<Collider2D>() && TileB != CurrentTile && !TileB.GetComponent<TileInfo>().Movable && !TileB.GetComponent<TileInfo>().Movable2)
{
if(TileB.GetComponent<Collider2D>().bounds.Intersects(CurrentTileBounds))
{
MovableTile = TileB;
Debug.Log(MovableTile.name + " is in range!");
}**
else if(i > 0 && TileB.GetComponent<Collider2D>().bounds.Intersects(MovableTile.GetComponent<Collider2D>().bounds))
{
i = i - 1;
MovableTile = TileB;
Debug.Log(MovableTile.name + " is in range!");
}
}
}
}
Have navigation info outside of graphical info. You will probably need to store other data on provinces as well, like production, who owns etc.
public class Province // Class to hold province data
{
public int ProvinceID; // Pointer to Building Type
public string Name; // Name of Province
public string Spritename; // Sprite to show capital building
public int BuildingFlags; // Flags for the Province
public List<int> AdjacentProvinces; // List of adjacent Province IDs
public GameObject GO; // Game Object Province sprite
}
You can search each province for neighbors going through the list of its public List AdjacentProvinces;
You could write a function to return a list of adjacent neighbors.
You could then call this function of each of its adjacent neighobors for each 1 movement step you want to add to see if an Province ID is in range

Generating random Path in 2D Grid in C# (Tower Defense)

im trying to create a 2D TowerDefense Game and i´m stuck with creating a random Path between a random start- and endpoint. These two points are located at the top edge and bottom edge respectively.
Currently my code is looking for the direction on the x axis where the end point is. If the path is level with the end point, a straight path is generated to that point.
But I want more variety. The path shouldn't just go left or right and then down. For example, I want something like curves, but the path must not collide.
I hope someone can help me with my Code.
Code:
public class MapGenerator : MonoBehaviour
{
public GameObject mapTile;
[SerializeField] private int mapWidth; //set in the unity environment
[SerializeField] private int mapHeight; //set in the unity environment
public static List<GameObject> mapTiles = new List<GameObject>();
public static List<GameObject> pathTiles = new List<GameObject>();
public static GameObject startTile;
public static GameObject endTile;
private bool reachedX = false;
private bool reachedY = false;
private GameObject currentTile;
private int currentIndex;
private int nextIndex;
public Color startTileColor;
public Color endTileColor;
public Color mapColor;
public Color pathColor;
private void Start()
{
generateMap();
}
//selecting all Tiles at the top Edge
private List<GameObject> getTopEdgeTiles()
{
List<GameObject> edgeTiles = new List<GameObject>();
for (int i = mapWidth * (mapHeight - 1); i < mapWidth * mapHeight; i++)
{
edgeTiles.Add(mapTiles[i]);
}
return edgeTiles;
}
//selecting all Tiles at the bottom Edge
private List<GameObject> getBottomEdgeTiles()
{
List<GameObject> edgeTiles = new List<GameObject>();
for (int i = 0; i < mapWidth; i++)
{
edgeTiles.Add(mapTiles[i]);
}
return edgeTiles;
}
//void for moving down
private void moveDown()
{
pathTiles.Add(currentTile); //adding currentTile to PathTile
currentIndex = mapTiles.IndexOf(currentTile); //getting current Index of Tile in mapTiles
nextIndex = currentIndex - mapWidth; //setting next Index
currentTile = mapTiles[nextIndex]; //setting next currentTile
}
//void for moving left
private void moveLeft()
{
pathTiles.Add(currentTile);
currentIndex = mapTiles.IndexOf(currentTile);
nextIndex = currentIndex - 1;
currentTile = mapTiles[nextIndex];
}
//void for moving right
private void moveRight()
{
pathTiles.Add(currentTile);
currentIndex = mapTiles.IndexOf(currentTile);
nextIndex = currentIndex + 1;
currentTile = mapTiles[nextIndex];
}
private void generateMap()
{
//setup 2D Map
for (int y = 0; y < mapHeight; y++)
{
for (int x = 0; x < mapWidth; x++)
{
GameObject newTile = Instantiate(mapTile);
mapTiles.Add(newTile);
newTile.transform.position = new Vector2(x, y);
}
}
List<GameObject> topEdgeTiles = getTopEdgeTiles();
List<GameObject> bottomEdgeTiles = getBottomEdgeTiles();
int rand1 = Random.Range(0, mapWidth);
int rand2 = Random.Range(0, mapWidth);
startTile = topEdgeTiles[rand1]; //random starting point
endTile = bottomEdgeTiles[rand2]; //random end point
currentTile = startTile;
moveDown();
//starting the path algorithm
bool moving = true;
while (moving)
{
if (!reachedX)
{
if (currentTile.transform.position.x > endTile.transform.position.x)
{
moveLeft();
}
else if (currentTile.transform.position.x < endTile.transform.position.x)
{
moveRight();
}
else
{
reachedX = true;
}
}
else if(!reachedY) {
if (currentTile.transform.position.y > endTile.transform.position.y)
{
moveDown();
}
else
{
reachedY = true;
}
}
else if (reachedX && reachedY)
{
moving = false;
}
}
pathTiles.Add(endTile);
//setting colors for each tile
foreach (GameObject obj in mapTiles)
{
obj.GetComponent<SpriteRenderer>().color = mapColor;
}
foreach (GameObject obj in pathTiles)
{
obj.GetComponent<SpriteRenderer>().color = pathColor;
}
startTile.GetComponent<SpriteRenderer>().color = startTileColor;
endTile.GetComponent<SpriteRenderer>().color = endTileColor;
}
}
This generate something like:
current path generation
An I want something like that:
my imagination
Thank you and Best Regards!
I actually tried a bunch of different ways of producing random paths. They all worked, but in all cases the vast majority of the paths that were generated were not pleasing -- not enough wiggles or not using most of the available area.
This is a way that I think would work well, but it would be a fair bit of code:
Generate about 4 or 5 random points. Make sure you have at least one in each quadrant.
Calculate the Voronoi diagram of those points, along with the entrance and exit.
Try to find a Hamiltonian path through the diagram from the entrance to the exit. If you can't find one, then just take the longest simple path that you found.
In each Voronoi cell, connect the path entry point to the exit point with a smooth curve.
The Hamiltonian path, along with the requirement to have a point in each quadrant, ensures that the path uses the available space well. The number of random points determines how tight the curves are.

What is the fastest method to create and render large number of 2d sprites in Unity?

I am creating an infinite terrain generator and I need to constantly update the terrain as the player moves around.
Everything is fine, but I am having trouble with finding information about the fastest method of creating and rendering sprites on the fly.
Information about sprites:
I am using 1 sprite sheet which has all the frames I need for my terrain. Grass, sand, water etc. all in 1 single .png file. All frames are stored in an array from which I can easily grab them.
Steps I need to do to display my sprite object correctly currently:
Create new object.
Set their position in 2d space.
Add component.
Scale them as needed.
Generated sprites get stored in a GameObject array called chunk. This is the way I am currently generating sprites.
chunk[i] = new GameObject();
chunk[i].gameObject.transform.position = new Vector2(spriteCoordX, spriteCoordY);
chunk[i].AddComponent<SpriteRenderer>();
SpriteRenderer renderer = chunk[i].GetComponent<SpriteRenderer>();
renderer.sprite = tiles[calculatedFrameId]; //Set correct sprite frame.
chunk[i].gameObject.transform.localScale = new Vector2(6.75f , 6.75f);
I don't know, adding component and scaling every single time I want to create a new sprite in code seems redundant and unnecessary and I am sure there is a better way to do that.
To sum up:
I need the best (fastest) possible way to generate large number of sprites, set their frame, position and proper scale.
Cannot help posting this image here, as this is really of thousands of words, thanks #AidenH for the "Object pooling" comment!
I apologize this took me some time to get to... I ended up creating a project where I generate an open world, and I use Perlin Noise to generate portions of the map depending on where the player is(Since I dont have an actual terrain)
I used Object Pooling for this, so I have a World Manager that knows what the world looks like (or in my case it uses Perlin Noise), this WorldManager pools tiles.
NOTE: This is one way to do it, using Object Pooling.
So basically it would look something like this:
public class WorldManager : MonoBehaviour {
// Being cheap here for the time, if you use this route make sure to use a proper singleton pattern
static public WorldManager instance;
[SerializeField] // Created a prefab for all my tile objects
private TileObject tilePrefab;
[SerializeField]
private int StartingTilePool = 300;
[SerializeField] // In my case this list stored 1 sprite, and I just changed that sprite color depending on the value of perlin noise
List<Sprite> terrainSprites;
private Queue<TileObject> objectPool = new Queue<TileObject>();
void Start() {
instance = this; // Again use a proper singleton pattern in your project.
GenerateTilePool();
LoadFirstSetOfTiles();
}
private void LoadFirstSetOfTiles()
{
// my player always starts at 0,0..
for(int x = -SpawnTileBoundry.HorizontalExtents; x <= SpawnTileBoundry.HorizontalExtents; ++x)
{
for(int y = -SpawnTileBoundry.VerticalExtents; y <= SpawnTileBoundry.VerticalExtents; ++y)
{
SetActiveTile(x,y);
}
}
}
private void GenerateTilePool()
{
for(int i =0; i < tilesToGenerate; ++i)
{
TileObject tempTile = Instantiate(tilePrefab);
EnqueTile(tempTile);
}
}
public void EnqueTile(TileObject tile)
{
objectPool.Enqueue(tile);
tile.gameObject.SetActive(false);
}
public void SetActiveTile(int x, int y)
{
TileObject newTile = null;
if(objectPool.count > 0)
{
newTile = objectPool.Dequeue();
}
else
{
// We didn't have enough tiles store in our pool so lets make a new 1.
newTile = Instantiate(tilePrefab);
}
newTile.transform.position = new Vector3(x,y,1); // Used 1 to put it behind my player...
newTile.gameObject.SetActive(true);
// The sprite here would be based off of your world data, where mine is only a white box, I use the second parameters to give it a gray-scaled color.
newTile.UpdateSprite(terrainSprites[0], Mathf.PerlinNoise(x/10.0f, y / 10.0f));
}
}
That is just my WorldManager that handles ObjectPooling for me...
Here is my TileObject
[RequireComponent(typeof(SpriteRenderer))]
public class TileObject : MonoBehaviour {
private SpriteRenderer myRenderer;
private void Awake() {
myRenderer = getComponent<SpriteRenderer>();
}
void Update()
{
if(Mathf.Abs(transform.position.x - Camera.main.transform.position.x) > SpawnTileBoundry.HorizontalExtents || Mathf.Abs(transform.position.y - Camera.main.transform.position.y) > SpawnTileBoundry.VerticalExtents)
{
// With this check the tile knows it is no longer visible,
// I could have used OnBecameInvisible to handle this
// but I added a threshold to the extents, that prevent it so
// players would see tiles "appearing" this caused a nice bug, where
// if you moved just right a row/col of tiles wouldn't spawn.
WorldManager.instance.EnqueTile(this);
}
}
public void UpdateSprite(Sprite sprite)
{
myRenderer.sprite = sprite;
}
public void UpdateSprite(Sprite sprite, float grayColor)
{
UpdateSprite(sprite);
myRenderer.color = new Color(grayColor,grayColor,grayColor,1f);
}
}
Here is my SpawnTileBoundry script:
public class WorldManager : MonoBehaviour {
private int lastX = 0;
private int lastY = 0;
static public int HorizontalExtents;
static public int VerticalExtents;
void Start() {
VerticalExtents = (int)Camera.main.orthograpicSize + 2; // +2 is just my threshold
HorizontalExtents = (int)(VerticalExtents * Screen.width/Screen.height) +3; // +3 is just my threshold you can change these to what you want.
lastX = (int)transform.position.x;
lastY = (int)transform.position.y;
}
void Update() {
int newX = (int)transform.position.x;
int newY = (int)transform.position.y;
HandleNewTileSpawn(lastX - newX, lastY - newY);
}
// This will tell the WorldManager which tiles need to appear
// We are no longer creating new tiles unless we absolutely have to.
// We are also only making new tiles appear in the circumstance that
// we are about to see them.
void HandleNewTileSpawn(int x, int y)
{
if(x != 0)
{
// This code could be refactor to a method so it was less error prone for changes or tweaks...
if(x < 0)
{
for(int i = lastY - VerticalExtents; i < lastY + VerticalExtents; ++i)
{
WorldManager.instance.SetActiveTile(lastX + HorizontalExtents, i);
}
}
else
{
for(int i = lastY - VerticalExtents; i < lastY + VerticalExtents; ++i)
{
WorldManager.instance.SetActiveTile(lastX - HorizontalExtents, i);
}
}
lastX = (int)transform.position.x;
}
if(y != 0)
{
if(lastY < 0)
{
for(int i = lastX - HorizontalExtents; i < lastX + HorizontalExtents; ++i)
{
WorldManager.instance.SetActiveTile(i, lastY + VeritcalExtents);
}
}
else
{
for(int i = lastX - HorizontalExtents; i < lastX + HorizontalExtents; ++i)
{
WorldManager.instance.SetActiveTile(i, lastY - VeritcalExtents);
}
}
lastY = (int)transform.position.y;
}
}
}
I am using the WorldManager to handle my object pooling, yes I am still instantiating quite a few sprites at the beginning but then I eventually stop spawning as there is no need to keep spawning new objects.
Unity doesn't have alot in their documentation in regards to object pooling however they do have a video tutorial that goes over some of the basics of Object Pooling: https://unity3d.com/learn/tutorials/topics/scripting/object-pooling

Unable to access values from positions component on Line Renderer inUnity

Here I'm generating a line renderer with multiple points to produce a wave out of the points and now I need to access some points in that list to do further calculations on it.
The problem is the Vector 2 List that I'm using to add the points to does not show the same points as that of Positions parameter on the line renderer component as I'm also moving the parent component on which this line renderer component is attached and the position on the default line renderer component shows the positional values in World Space which is what I need to access, whereas the Vector 2 list that I've generated shows points in Local Space, where only one value appears to be changing.
How can I access the World space values without moving too much of my current architecture?
I need to access Z axis points on Positions elements on Line Renderer component for comparing the positional values and also get Index of the same points as well.
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
[RequireComponent(typeof(LineRenderer))]
public class tail : MonoBehaviour {
public GameObject tailComponent, diameterBall, playButton, statisticalToolSubLevel;
public float speed;
public float pointSpacing = .01f;
public List<Vector2> points;
LineRenderer line;
public Text maxima, minima, mean, median;
public TapToMeasure otherScript;
// Use this for initialization
void Start () {
line = GetComponent<LineRenderer>();
points = new List<Vector2>();
SetPoint();
}
// Update is called once per frame
void Update () {
if (!playButton.activeSelf)
{
if (Vector3.Distance(points.Last(), diameterBall.transform.position) > pointSpacing)
{
SetPoint();
}
}
/*
int result1 = 0;
result1 = BinarySearchRecursive(points.position.x, otherScript.statisticalMarkerList[0].transform.position.x, 0, points.Count);
*/
if (statisticalToolSubLevel.activeSelf)
{
for(int i=0; i<points.Count; i++)
{
if(Mathf.Approximately(points[i].x, otherScript.statisticalMarkerList[0].transform.position.x))
{
Debug.Log("Task acheieved");
//return i;
}
if (points[i].x < otherScript.statisticalMarkerList[0].transform.position.x)
{
i += 1;
//Debug.Log(i);
}
}
}
}
void SetPoint()
{
points.Add(diameterBall.transform.position);
line.positionCount = points.Count; //count<=100 or whatever number or edge of the room
//line.SetPosition(points.Count - 1, diameterBall.transform.position);
line.SetPosition(points.Count - 1, tailComponent.transform.InverseTransformPoint(diameterBall.transform.position));
}
public static int BinarySearchRecursive(float[] inputArray, float query, int min_idx, int max_idx)
{
if (min_idx > max_idx)
{
return -1;
}
else
{
int mid = (min_idx + max_idx) / 2;
if (query == inputArray[mid])
{
return mid;
}
else if (query < inputArray[mid])
{
return BinarySearchRecursive(inputArray, query, min_idx, mid);
}
else if (min_idx + 1 == max_idx)
{
return mid;
}
else
{
return BinarySearchRecursive(inputArray, query, mid, max_idx);
}
}
}
}
You can use transform.TransformVector(Vector3 point) to convert from the local space of your transform to worldspace.
Thanks to a friend I was able to solve this problem.
All I was needed to do was access those values in the line renderer position component by creating a new different Vector3 array and getting the positions in the SetPoint function.
Additional Code:
Vector3[] finalpositions;
void SetPoint()
{
points.Add(diameterBall.transform.InverseTransformPoint(diameterBall.transform.position));
line.positionCount = points.Count; //count<=100 or whatever number or edge of the room
//line.SetPosition(points.Count - 1, diameterBall.transform.position);
line.SetPosition(points.Count - 1, tailComponent.transform.InverseTransformPoint(diameterBall.transform.position));
finalpositions = new Vector3[points.Count];
line.GetPositions(finalpositions);
}

How do I get a component from a ever changing List of objects?

while working in a 3d endless runner game in unity I came across this issue. I have a List of platforms(segments/roads) that lay in front of the player while the player runs in z direction. I have downloaded a new asset package called Dreamteck splines. So each platform has a spline component attached to it. Once a platform is laid the player grabs the spline and runs according to the pattern of the spline.
Let's say that the player is on the first platform. When the player reaches the end of the first platform's spline, the OnEndReached() event handler is called, which basically says what you want to happen when the spline's endpoint is reached. So I want to know how to I get the next spline once the end is reached.
P = player
As seen in the image above this is what I am trying to accomplish. As a brief description of how platforms are laid is that once the player goes to the next road the one he just passed gets disabled so next time he can reuse the road in front of the player in random manner.
The code: track manager script.
public Segment[] tilePrefabs;
public static Segment newSegment;
public static List<Segment> m_Segments;
public static List<Segment> m_PastSegements;
private int m_SafeSegmentLeft;
private int m_PreSegments = -1;
private float startingSegmentDistance = 4f;
private int startingSafeSegments = 2;
private int amtSegmentsOnScreen = 10;
private float segmentRemovalDistace = -40f;
private float m_TotalWorldDistance;
private float m_CurrentSegmentDistance;
void Update ()
{
while (m_Segments.Count < amtSegmentsOnScreen)
{
SpawnNewSegment();
}
m_TotalWorldDistance += scaledSpeed;
m_CurrentSegmentDistance += scaledSpeed;
if (m_CurrentSegmentDistance > m_Segments[0].worldLength)
{
m_CurrentSegmentDistance -= m_Segments[0].worldLength;
m_PastSegements.Add(m_Segments[0]);
m_Segments.RemoveAt(0);
}
Vector3 currentPos;
Quaternion currentRot;
Transform playerTransform = playerMotor.transform;
m_Segments[0].GetPointAtInWorldUnit(m_CurrentSegmentDistance, out currentPos, out currentRot);
bool needRecenter = currentPos.sqrMagnitude > floatingOriginThreshold;
if (needRecenter)
{
int count = m_Segments.Count;
for (int i = 0; i < count; i++)
{
m_Segments[i].transform.position -= currentPos;
}
count = m_PastSegements.Count;
for (int i = 0; i < count; i++)
{
m_PastSegements[i].transform.position -= currentPos;
}
m_Segments[0].GetPointAtInWorldUnit(m_CurrentSegmentDistance, out currentPos, out currentRot);
}
playerTransform.rotation = currentRot;
playerTransform.position = currentPos;
for (int i = 0; i < m_PastSegements.Count; i++)
{
if ((m_PastSegements[i].transform.position - currentPos).z < segmentRemovalDistace)
{
m_PastSegements[i].Cleanup();
m_PastSegements.RemoveAt(i);
i--;
}
}
}
public void SpawnNewSegment()
{
int useSegment = Random.Range(0, tilePrefabs.Length);
if (useSegment == m_PreSegments)
{
useSegment = (useSegment + 1) % tilePrefabs.Length;
}
Segment segmentToUse = tilePrefabs[useSegment];
newSegment = Instantiate(segmentToUse, Vector3.zero, Quaternion.identity);
Vector3 currentExitPoint;
Quaternion currentExitRotation;
if (m_Segments.Count > 0)
m_Segments[m_Segments.Count - 1].GetPointAt(1.0f, out currentExitPoint, out currentExitRotation);
else
{
currentExitPoint = transform.position;
currentExitRotation = transform.rotation;
}
newSegment.transform.rotation = currentExitRotation;
Vector3 entryPoint;
Quaternion entryRotation;
newSegment.GetPointAt(0.0f, out entryPoint, out entryRotation);
Vector3 pos = currentExitPoint + (newSegment.transform.position - entryPoint);
newSegment.transform.position = pos;
newSegment.manager = this;
newSegment.transform.localScale = new Vector3((Random.value > 0.5f ? -1 : 1), 1, 1);
newSegment.objectRoot.localScale = new Vector3(1.0f / newSegment.transform.localScale.x, 1, 1);
if (m_SafeSegmentLeft <= 0)
SpawnObstacle(newSegment);
else
m_SafeSegmentLeft -= 1;
m_Segments.Add(newSegment);
}
The player script
//Current tile segment;
private Segment currentSegment;
//Spline Follower
private SplineFollower follower
//For Dreamteck spline -->
private Segment nextSegment;
void Start()
{
playerCollider = GetComponent<CapsuleCollider>();
anim = GetComponent<Animator>();
follower = GetComponent<SplineFollower>();
moveLane = currentLane;
follower.onEndReached += Follower_onEndReached;
}
private void Follower_onEndReached()
{
currentSegment = nextSegment;
follower.computer = currentSegment.spline;
}
void OnTriggerEnter(Collider col)
{
nextSegment = col.GetComponentInParent<Segment>();
}
The segment script : Attached to each road/ platform
public SplineComputer spline;
public static Segment next;
SplinePoint[] points;
void Start()
{
spline = GetComponentInChildren<SplineComputer>();
spline.space = SplineComputer.Space.Local;
points = spline.GetPoints();
if (points.Length == 0)
return;
}
At the moment I use colliders, each road has a box collider component. Once the player reach end of the platform it does get the next spline component. It works but sometimes it fails to recognize the next spline and use the same spline which causes the player to run the same platform that he passed again and again.
So I'm out of ideas. So came here to find a solution or advice. Help would be appreciated.
In this case I would simply store my possible segments in a List then when I reached the end get the next segment and move the current 1st segment to the end or where ever you want to move it in the list.
That is
public Segment currentSegment;
public List<Segment> segments;
void OnEndReached()
{
//Put the completed segmetn back in the list ... probably at the end or randomized anywhere but at the start
segments.Insert(segments.Count-1, currentSegment);
//Now get the next segment
currentSegment = segments[0];
segments.RemoveAt(0);
}
In this model you have a simple List which represents the order your segments will appear in, you always set the current segment to the next in the list e.g. index 0 and you put them back in the list when done... putting them at the end or if you want to randomize order slotting them in anyware except index 0 e.g.
segments.Insert(UnityEngine.Random.Range(1, segments.Count), currentSegment);
Note that I removed the segment I am on from the list ... the list just represents the upcoming order which in runner games I find it handy to know that e.g. so I can reset things, change attributes of the segments based on performance, score, etc.
Using OnTriggerEnter, or OnTriggerEnter2D if you're dealing with 2D colliders, should do the job. But as you say you already are working with colliders, I assume this is what you have tried.
You could try:
OnTriggerStay
A Raycast down to the ground object. In this link is a 2D Example: https://kylewbanks.com/blog/unity-2d-checking-if-a-character-or-object-is-on-the-ground-using-raycasts
You can also raycast with 3D objects.
What you would be using it for in this case is basically to shoot a "laser" into the ground below your player and grab an object there based on which layers you tell it to hit. So if you have a layer called "Ground" which your platform is part of, then it can only return objects from that layer.
Just remember to raycast often so it's updated to reflect the game.

Categories