Unable to access values from positions component on Line Renderer inUnity - c#

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);
}

Related

SceneManager.LoadScene() is not working in the way i want

In my unity project i have 3 scenes.
Title
Play
Over
For achieving the flow Title -> Play -> Over. I had to start the game from Over scene. Over -> Title -> Play -> Over .. I don't want this. I want it to work when i start the game from Title scene. On doing so i am unable to change from Play -> Over.
for Title scene
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class LoadSceneOnInput : MonoBehaviour {
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
if (Input.GetAxis("Submit") == 1) {
SceneManager.LoadScene("Play");
}
}
}
for Over Scene
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class GameOverInput : MonoBehaviour {
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
if (Input.GetAxis("Submit") == 1) {
SceneManager.LoadScene("Title");
}
}
}
Play's script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class LevelGenerator : MonoBehaviour {
public GameObject floorPrefab;
public GameObject wallPrefab;
public GameObject ceilingPrefab;
public GameObject characterController;
public GameObject floorParent;
public GameObject wallsParent;
// allows us to see the maze generation from the scene view
public bool generateRoof = true;
// number of times we want to "dig" in our maze
public int tilesToRemove = 50;
public int mazeSize;
// spawns at the end of the maze generation
public GameObject pickup;
// this will determine whether we've placed the character controller
private bool characterPlaced = false;
// 2D array representing the map
private bool[,] mapData;
// we use these to dig through our maze and to spawn the pickup at the end
private int mazeX = 4, mazeY = 1;
// Use this for initialization
void Start () {
// initialize map 2D array
mapData = GenerateMazeData();
// create actual maze blocks from maze boolean data
for (int z = 0; z < mazeSize; z++) {
for (int x = 0; x < mazeSize; x++) {
if (mapData[z, x]) {
CreateChildPrefab(wallPrefab, wallsParent, x, 1, z);
CreateChildPrefab(wallPrefab, wallsParent, x, 2, z);
CreateChildPrefab(wallPrefab, wallsParent, x, 3, z);
} else if (!characterPlaced) {
// place the character controller on the first empty wall we generate
characterController.transform.SetPositionAndRotation(
new Vector3(x, 1, z), Quaternion.identity
);
// flag as placed so we never consider placing again
characterPlaced = true;
CreateChildPrefab(floorPrefab, floorParent, x, 0, z);
}
//create floor and ceiling
if(mapData[z, x]){
CreateChildPrefab(floorPrefab, floorParent, x, 0, z);
}else{
if((z > 0 && z < mazeSize - 1) && (x > 0 && x < mazeSize - 1)){
if(!(Random.value > 0.8)){
CreateChildPrefab(floorPrefab, floorParent, x, 0, z);
}
}
}
if (generateRoof) {
CreateChildPrefab(ceilingPrefab, wallsParent, x, 4, z);
}
}
}
// spawn the pickup at the end
var myPickup = Instantiate(pickup, new Vector3(mazeX, 1, mazeY), Quaternion.identity);
myPickup.transform.localScale = new Vector3(0.25f, 0.25f, 0.25f);
}
void Update() {
if(characterController.transform.position.y < -5){
SceneManager.LoadScene("Over");
}
}
// generates the booleans determining the maze, which will be used to construct the cubes
// actually making up the maze
bool[,] GenerateMazeData() {
bool[,] data = new bool[mazeSize, mazeSize];
// initialize all walls to true
for (int y = 0; y < mazeSize; y++) {
for (int x = 0; x < mazeSize; x++) {
data[y, x] = true;
}
}
// counter to ensure we consume a minimum number of tiles
int tilesConsumed = 0;
// iterate our random crawler, clearing out walls and straying from edges
while (tilesConsumed < tilesToRemove) {
// directions we will be moving along each axis; one must always be 0
// to avoid diagonal lines
int xDirection = 0, yDirection = 0;
if (Random.value < 0.5) {
xDirection = Random.value < 0.5 ? 1 : -1;
} else {
yDirection = Random.value < 0.5 ? 1 : -1;
}
// random number of spaces to move in this line
int numSpacesMove = (int)(Random.Range(1, mazeSize - 1));
// move the number of spaces we just calculated, clearing tiles along the way
for (int i = 0; i < numSpacesMove; i++) {
mazeX = Mathf.Clamp(mazeX + xDirection, 1, mazeSize - 2);
mazeY = Mathf.Clamp(mazeY + yDirection, 1, mazeSize - 2);
if (data[mazeY, mazeX]) {
data[mazeY, mazeX] = false;
tilesConsumed++;
}
}
}
return data;
}
// allow us to instantiate something and immediately make it the child of this game object's
// transform, so we can containerize everything. also allows us to avoid writing Quaternion.
// identity all over the place, since we never spawn anything with rotation
void CreateChildPrefab(GameObject prefab, GameObject parent, int x, int y, int z) {
var myPrefab = Instantiate(prefab, new Vector3(x, y, z), Quaternion.identity);
myPrefab.transform.parent = parent.transform;
}
}

How to randomly create a maze

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.

How to Get Coordinates from Hex Map on Click in Unity

I'm making the board game known as Hex. When I click on a tile, it changes to either blue or yellow, but I need to also be able to know what the coordinates of that tile are.
I can't use...
rend.transform.position;
...because the the coordinates I want to receive look similar to this (0,0) being the bottom left and (0,1) above it:
The coordinates in the console are the one's I need to receive.
I printed those out by using the column and row variables when I generated the hex map:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class HexMap : MonoBehaviour
{
// Use this for initialization
void Start()
{
GenerateMap();
}
public GameObject HexPrefab;
public void GenerateMap()
{
for (int column = 0; column < 11; column++)
{
for (int row = 0; row < 11; row++)
{
// Instantiate a Hex
Hex h = new Hex(column, row);
Instantiate(HexPrefab, h.Position(), Quaternion.identity, this.transform);
Debug.Log(column + "," + row);
}
}
}
}
I want to be able to get the coordinates when I click a hex tile using this script here:
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using UnityEngine;
using UnityEngine.Networking;
public class ColorChange : MonoBehaviour {
public Color[]colors; // allows input of material colors in a set sized array
public SpriteRenderer rend; // what are we rendering? the hex
public enum Player {ONE, TWO};
public static Player currentPlayer = Player.ONE;
// Use this for initialization
void Start () {
rend = GetComponent<SpriteRenderer> (); // gives functionality for the renderer
}
void NextPlayer() {
if( currentPlayer == Player.ONE ) {
currentPlayer = Player.TWO;
}
else if( currentPlayer == Player.TWO) {
currentPlayer = Player.ONE;
}
}
// Update is called once per frame
void OnMouseDown () {
// if there are no colors present nothing happens
if (colors.Length == 0)
return;
if (currentPlayer == Player.ONE)
rend.color = colors [0];
else if (currentPlayer == Player.TWO)
rend.color = colors [1];
NextPlayer();
}
Here is the script I use to determine where the hex tiles need to be:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Hex{
public Hex (int q, int r){
this.Q = q;
this.R = r;
}
public readonly int Q; // x
public readonly int R; // y
static readonly float WIDTH_MULTIPLIER = Mathf.Sqrt(3) / 2;
public Vector2 Position(){
float radius = 0.513f;
float height = radius * 2;
float width = WIDTH_MULTIPLIER * height;
float vert = height * 0.75f;
float horiz = width;
return new Vector2(horiz * (this.Q - this.R/2f), vert * this.R);
}
}
I was thinking I need to be able to assign each Hex Model the value of column and row when it is instantiated, but I'm not sure how to do that. I tried several solutions I found online as well as using GetComponent, but I wasn't able to make them work.
If anyone has an idea on how this might be possible I would greatly appreciate it!
The reason why GetComponent is not working in your case is because the Coordinates script is on a child object of the hex prefab. You could access a component on a child object by calling GetComponentInChildren. It would look something like the following:
// Instantiate a Hex
Hex h = new Hex(column, row);
// Instantiate the prefab
GameObject instance = Instantiate(HexPrefab, h.Position(), Quaternion.identity, this.transform);
// Let's find the coorinates component on the hex prefab instance.
Coordinates coordinates = instance.GetComponentInChildren<Coordinates>();
// now you may assign the column and row values to it
// ...
There are many possible approaches to this problem. But just keep in mind where in the prefab hierarchy the components are and you will be fine.

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

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