I have a project in my collage for traffic car simulation in unity, and the one of the project requirements is to let the user enter the number of the cars, and each one of them should driving like AI_Driver, and i wrote a script to spawning cars with Instantiate function , i spawned 1000 car and when i am spawning a car, i am making some changes like (the position of the spawned car on the y axis),
the problem is some of the cars has been edited correctly exactly what i want but, the others not applying the changes with the position
.
and here is the script:
public class VehicleSpawner : MonoBehaviour
{
public GameObject[] vehiclePrefabs;
public int vehiclesToSpawn;
public bool navON = false;
public bool aStarON = true;
void Start()
{
StartCoroutine(SpawnMany(0));
}
IEnumerator SpawnMany(int count)
{
while (count < vehiclesToSpawn)
{
GameObject obj = Instantiate(vehiclePrefabs[1]);
GameObject[] spawningPints = GameObject.FindGameObjectsWithTag("spawningPoint");
Transform child = spawningPints[Random.Range(0, spawningPints.Length)].transform;
WaypointNavigator nav = obj.GetComponent<WaypointNavigator>();
nav.spawner = this;
nav.priority = Random.Range(0, 2 * vehiclesToSpawn);
if (!aStarON)
{
nav.currentWaypoint = child.GetComponent<Waypoint>();
nav.navON = navON;
}
else
{
Transform dest = transform.GetChild(Random.Range(0, transform.childCount - 1));
nav.start = child.GetComponent<Waypoint>();
nav.end = dest.GetComponent<Waypoint>();
nav.priority = Random.Range(0, 10000);
nav.aStarON = aStarON;
}
// change in the position
obj.transform.position = new Vector3(
child.GetComponent<Waypoint>().GetPosition().x,
child.GetComponent<Waypoint>().GetPosition().y + 0.5f,
child.GetComponent<Waypoint>().GetPosition().z
);
yield return new WaitForSeconds(0.05f);
count++;
}
}
}
Please any one can help??
Cache spawningPints. Never use Find, because it just runs foreach gameObject in the hierarchy.
Try first to change position, then add Nav component (it might cache position and teleport car back after you change position throw transform).
Related
So I am trying to make a top-down tycoon game where a truck gives you crates and you sell the crates for money. I have a script for the trucks and it's working just fine except for the first part. So I coded it that when the trucks are spawned 2 crates spawn and are parented to the truck game object, and the issue is that when I run the game, the objects spawn and all, but they aren't parented to the trucks, I have 6 trucks in the scene and only one gets their crate parented. Can someone help?
Code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CarScript : MonoBehaviour
{
private float speed = 0.01f;
private Vector3 dropoffLocation;
private bool hasCrates = true;
private float dropCratesCooldown = 500f;
GameObject cratess;
private float crateUpgrade = 1;
public GameObject crateUpgrade1;
public GameObject crateUpgrade2;
public GameObject crateUpgrade3;
public GameObject crateUpgrade4;
private void Start()
{
dropoffLocation = new Vector3(3.5f, -7f, 0);
crateUpgrade1 = GameObject.Find("Boxes In Truck 1");
crateUpgrade2 = GameObject.Find("Boxes In Truck 2");
crateUpgrade3 = GameObject.Find("Boxes In Truck 3");
crateUpgrade4 = GameObject.Find("Boxes In Truck max");
if (crateUpgrade == 1)
{
Instantiate(crateUpgrade1);
crateUpgrade1.transform.parent = transform;
//crateUpgrade1.transform.position = new Vector3(0, 0, 0);
}
}
// Update is called once per frame
void Update()
{
if (transform.position.x > 40)
{
Destroy(gameObject);
}
if (transform.position.x > dropoffLocation.x && hasCrates)
{
dropCratesCooldown--;
if (dropCratesCooldown < 0)
{
cratess = transform.GetChild(0).gameObject;
Destroy(cratess);
hasCrates = false;
}
transform.Translate(0, 0, 0);
}
else
{
transform.Translate(speed, 0, 0);
}
}
}
btw unity doesn't even show me that its an error
Thanks in advance!
Instantiate returns the instantiated game object. That's what you want to assign as a child of the truck. The way you're doing it now, you just keep assigning and reassigning crateUpgrade1's parent. That's the reason that only one of your trucks get a crate parented to it.
This is how it should be:
GameObject crate = Instantiate(crateUpgrade1);
crate.transform.parent = transform;
One other thing; the standard way to instantiate new objects is to create a prefab asset in the project, then assign a reference to it to CarScript, and then instantiate instances of that prefab. So the crateUpgrade1 = GameObject.Find("Boxes In Truck 1"); part is a little weird.
This is how it's usually done
public class CarScript : MonoBehaviour
{
// Assign this to your "crate" prefab in the Unity Inspector
[SerializeField] private GameObject cratePrefab;
private void Start()
{
if (crateUpgrade == 1)
{
GameObject crate = Instantiate(this.cratePrefab);
crate.transform.parent = transform;
}
}
}
Ive been working for a few days on this problem, trying to create a square grid of tiles, using Unity Tilemaps seems to be the most efficient way, and it has a lot built in already.
I want to highlight the tiles around the player so that he will see what the legal moves are.
I use a second Tilemap for this, on top of the basemap(ground/grass).
The second Tilemap, the highlightsMap is made up of invisible Tiles, which will turn into highlighted Tiles when a Physics.SphereOverlap occurs, or a Physics2D.CircleAll
To detect every Tile, I have added a box collider on an empty object and I made a grid of these colliders on top of the Tilemap grid, so that:
Box Collider at Position (2,4,0) is exactly on top of Tile at Position(2,4,0)
This should be the most straightforward way to handle this, as you can change a Tile, using Tilemap.SetTile(Vector3Int Pos, Tile tile)
The problem is very strange however. The colliders have the correct positional values to be able to reference the tiles exactly underneath them, just through that position data. As explained above, and I have double checked this, the collider empties have the exact same position as the tiles underneath them, no conversion is needed.
The problem is that the tiles are not highlighting around the player as expected, instead a few next to the player are highlighted and others arent, the Physics.OverlapSphere I am using only works on 3D Colliders, which is why I added them as another grid on top of everything.
Using Physics2D.CircleAll, unfortunately does not detect any 2D colliders on the tiles themselves(Sprite, or Grid), not sure if that is intended to work like that anyway.
Collision Grid Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;
public class scr_ColliderGrid : MonoBehaviour
{
public GameObject eCollider;
public Tile invisibleTile;
public Tile highlightTile;
public Tilemap highlightMap;
public int xSize, ySize;
private Vector3Int[] gridArray; // Tilemaps use Vector3Int BUT only use (X, Y, 0) !!!!!!!
private int xC = 0, yC = 0, i = 0;
private Tile[] tiles;
private Vector3Int previous;
private void Start()
{
gridArray = new Vector3Int[xSize * ySize];
tiles = new Tile[xSize * ySize];
GenerateCollisionGrid();
}
private void GenerateCollisionGrid()
{
for (xC = 0; xC < xSize; xC++)
{
for (yC = 0; yC < ySize; yC++)
{
Vector3Int newPos = new Vector3Int(xC, yC, 0); // 2, 4, 0
Vector3Int newColPos = new Vector3Int(xC, yC, 0); // 2, 4, 0 //This used to be different values, but now they are exactly the same.
if (invisibleTile != null)
{
Tile tile = Instantiate(invisibleTile, newPos, Quaternion.identity, transform);
tiles[i] = tile;
GameObject col = Instantiate(eCollider, newColPos, Quaternion.identity, transform);
}
gridArray[i] = newPos;
i++;
}
}
highlightMap.SetTiles(gridArray, tiles);
highlightMap.SetTile(new Vector3Int(2,4,0), highlightTile); // 2,4,0 //Test to see if positions are the same. (collider and tiles)
}
Player Highlight Legal Moves Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;
public class scr_TankMoves : MonoBehaviour
{
public Tile highlightTile;
public TileBase[] highlightTiles;
public Tilemap highlightMap;
public int maxMoveTiles;
public bool highlight;
private Vector3Int previous, previousLeft, previousRight, previousForward, previousAft, previousVect;
private Vector3Int[] previousVectors;
void Start()
{
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.W))
{
transform.localPosition = new Vector3(transform.localPosition.x,
transform.localPosition.y + 1,
transform.localPosition.z );
}
if (Input.GetKeyDown(KeyCode.S))
{
transform.localPosition = new Vector3(transform.localPosition.x,
transform.localPosition.y - 1,
transform.localPosition.z );
}
if (Input.GetKeyDown(KeyCode.D))
{
transform.localPosition = new Vector3(transform.localPosition.x + 1,
transform.localPosition.y,
transform.localPosition.z);
}
if (Input.GetKeyDown(KeyCode.A))
{
transform.localPosition = new Vector3(transform.localPosition.x - 1,
transform.localPosition.y,
transform.localPosition.z);
}
}
void LateUpdate()
{
if (highlight)
HighlightMoves();
else EraseHighlights();
}
private void HighlightMoves()
{
previousVectors = new Vector3Int[1];
highlightMap.SetTiles(previousVectors, null);
int tMax = 0;
Collider[] legalTiles = Physics.OverlapSphere(transform.position, maxMoveTiles/2);
previousVectors = new Vector3Int[legalTiles.Length];
foreach (Collider col in legalTiles)
{
//Vector3 conversionVector = new Vector3(col.transform.localPosition.x, col.transform.localPosition.y, col.transform.localPosition.z);
Vector3Int tileVector = Vector3Int.FloorToInt(col.transform.position);
previousVectors[tMax] = tileVector;
tMax++;
}
highlightMap.SetTiles(previousVectors, highlightTiles);
}
private void EraseHighlights()
{
//highlightMap.SetTile(previousForward, null);
//highlightMap.SetTile(previousAft, null);
//highlightMap.SetTile(previous, null);
//highlightMap.SetTile(previousRight, null);
//highlightMap.SetTile(previousLeft, null);
highlightMap.SetTiles(previousVectors, null);
}
private void OnDrawGizmos()
{
Gizmos.DrawWireSphere(transform.position, maxMoveTiles/2);
}
}
}
If you open a new 3D Project in Unity and setup a grid with a Tilemap, called highlightMap in my code example, you should be able to recreate this exactly, using the 2 scripts.
Everything is oriented in 2D, this means x+ is Right y+ is forward and z+ is up/dephth (unused by tilemaps).
The empty Collider prefab I have is an empty GO with pos 0,0,0. it has another empty GO Child, which has the Box Collider Component, and the transform value of this child is 0.5,0.5,0.5, so that the Collider is centered on top of each tile.
This is after 0 moves, so just pressed Play:
This is after 1 move forward, (y+1):
Instead of going by colliders at all afaik you could also just use GridLayout.WorldToCell like e.g.
Tilemap yourTileMap;
// see https://docs.unity3d.com/ScriptReference/Tilemaps.Tilemap-layoutGrid.html
var grid = yourTileMap.layoutGrid;
// The "Grid" implements "GridLayout"
// see https://docs.unity3d.com/ScriptReference/Grid.html
var currentCellPos = grid.WorldToCell(transform.position);
var currentCell = yourTileMap.GetTile(currentCellPos);
Then if you need to get multiple tiles I would probably go the "lazy/stupid" way and just do something like e.g.
var currentCellPos = grid.WorldToCell(transform.position);
var currentCell = yourTileMap.GetTile(currentCellPos);
var leftCellPos = grid.WorldToCell(transform.position - transform.right * someRange);
var leftCell = yourTileMap.GetTile(leftCellPos);
var rightCellPos = grid.WorldToCell(transform.position + transform.right * someRange);
var rightCell = yourTileMap.GetTile(rightCellPos);
var frontCellPos = grid.WorldToCell(transform.position + transform.forward * someRange);
var frontCell = yourTileMap.GetTile(frontCellPos);
var backCellPos = grid.WorldToCell(transform.position - transform.forward * someRange);
var backCell = yourTileMap.GetTile(backCellPos);
highlightTiles[tMax] = Instantiate(highlightTile, col.transform.parent.position, Quaternion.identity, transform);
I dont know why, but apparently I didnt instantiate the tiles themselves into an Array of tiles, so everytime I was ''placing'' them on the grid, it was actually placing an empty array.
Now its fixed! Just need to find a nice way to erase all after a move and im set!
I would like to change/adjust the shape/dimensions of several cloned objects within the scene view by adjusting one. This object could be say a quad or a line renderer that needs to be extended. For example as one game objects line renderer is extended (using the mouse) in the scene view, all other clones are affected. I know that it's a lot simpler to adjust shape/dimensions of one object before cloning it, also this change can be made on a prefab and applied to all, but I need to see the dynamic changes as they happen to make my design process more effective.
I would also like to turn this functionality on and off, when the need arises. Please see how I created my clones in this question.
Note that I don't want to achieve this at runtime.
How can I adjust shape/dimensions of one clone to affect all other
clones in the scene view
Synchronize properties when modifications to the component are made. To work with the existing code-base, we'll also need to clone the modified object to ensure that it's not destroyed when we re-create the other objects.
This object could be say a quad or a line renderer that needs to be
extended
So we want to know when any property on any component has been modified. With custom scripts it's trivial with OnValidate, but with a sealed component such LineRenderer it's a little trickier. Fortunately, since we're working with the Editor we have access to some of its core features.
Specifically, we can hook into the Editor's Undo.postprocessModifications event to get a callback whenever modifications are made to the scene. This delegate will provide us with an UndoPropertyModification array that will contain a list of modified component properties which we can use to synchronize the other objects via EditorUtility.CopySerializedIfDifferent.
CircleSpawn
[ExecuteInEditMode]
public class CircleSpawn : MonoBehaviour
{
public List<GameObject> Objects;
public GameObject OriginalObject;
public GameObject PreviousObject;
public GameObject ActiveObject;
public SpawnData Data;
private void OnEnable ()
{
if (Objects == null) Objects = new List<GameObject>();
// Register modification event
Undo.postprocessModifications += OnPropertyModification;
}
private void OnDisable ()
{
// Deregister modification event
Undo.postprocessModifications -= OnPropertyModification;
}
private UndoPropertyModification[] OnPropertyModification (
UndoPropertyModification[] modifications)
{
// Iterate through modifications
foreach (var mod in modifications)
{
var trg = mod.currentValue.target as Component;
if (trg)
{
// Filter only those objects that we've created
if (Objects.Contains(trg.gameObject))
{
// Clone the object and make it 'active'
if (!ActiveObject.Equals(trg.gameObject))
{
SetActiveObj(Instantiate(trg.gameObject));
ActiveObject.name = OriginalObject.name;
ActiveObject.hideFlags =
HideFlags.DontSaveInBuild | HideFlags.HideInHierarchy;
ActiveObject.SetActive(false);
}
// Synchronize the other object properties
foreach (var obj in Objects)
{
var type = mod.currentValue.target.GetType();
var comp = obj.GetComponent(type);
if (comp == null)
comp = obj.AddComponent(type);
EditorUtility.CopySerializedIfDifferent(trg, comp);
}
UpdateTransforms();
break;
}
}
}
return modifications;
}
public void SetActiveObj (GameObject active)
{
// Destroy the active object
if (!OriginalObject.Equals(ActiveObject) &&
PreviousObject && !PreviousObject.Equals(ActiveObject))
DestroyImmediate(ActiveObject);
ActiveObject = active;
}
public void UpdateObjects ()
{
// Destroy old objects
foreach (var obj in Objects) DestroyImmediate(obj);
Objects.Clear();
var steps = 360.0f / Data.Count;
var angle = 0f;
// Instantiate new objects
for (var i = 0; i < Data.Count; i++)
{
var rot = Quaternion.Euler(0f, 0f, Data.Angle + angle);
var pos = rot * Vector3.right * Data.Radius;
var obj = Instantiate(ActiveObject, transform.position + pos, rot);
obj.SetActive(true);
Objects.Add(obj);
angle += steps;
}
}
public void UpdateTransforms ()
{
var steps = 360.0f / Objects.Count;
var angle = 0f;
// Set transforms based on Angle and Radius
for (var i = 0; i < Objects.Count; i++)
{
var rot = Quaternion.Euler(0f, 0f, Data.Angle + angle);
var pos = rot * Vector3.right * Data.Radius;
Objects[i].transform.position =
transform.position + pos;
Objects[i].transform.rotation = rot;
angle += steps;
}
}
}
CircleSpawnEditor
[CustomEditor(typeof(CircleSpawn))]
public class CircleSpawnEditor : Editor
{
public override void OnInspectorGUI ()
{
GUI.enabled = !EditorApplication.isPlaying;
var spawner = (CircleSpawn)target;
// Draw object field
EditorGUILayout.LabelField("Object");
spawner.OriginalObject = (GameObject)EditorGUILayout.ObjectField(
spawner.OriginalObject, typeof(GameObject), true);
if (!spawner.OriginalObject) return;
// Restore original object
if (GUILayout.Button("Revert") || !spawner.ActiveObject ||
!spawner.OriginalObject.Equals(spawner.PreviousObject))
{
// Store data reference
spawner.Data = spawner.OriginalObject.GetComponent<SpawnData>();
if (!spawner.Data) return;
spawner.SetActiveObj(spawner.OriginalObject);
spawner.PreviousObject = spawner.OriginalObject;
spawner.UpdateObjects();
}
// Draw numeric sliders
EditorGUILayout.LabelField("Radius"); // Set as required
spawner.Data.Radius = EditorGUILayout.Slider(spawner.Data.Radius, 0f, 100f);
EditorGUILayout.LabelField("Angle"); // Set as required
spawner.Data.Angle = EditorGUILayout.Slider(spawner.Data.Angle, 0f, 360f);
EditorGUILayout.LabelField("Count"); // Set as required
spawner.Data.Count = EditorGUILayout.IntSlider(spawner.Data.Count, 0, 36);
// Update objects on Count slider change
if (spawner.Data.Count != spawner.Objects.Count)
spawner.UpdateObjects();
// Update transforms on Angle or Radius slider change
if (!Mathf.Approximately(spawner.Data.Angle, spawner.Data.LastAngle) ||
!Mathf.Approximately(spawner.Data.Radius, spawner.Data.LastRadius))
{
spawner.Data.LastAngle = spawner.Data.Angle;
spawner.Data.LastRadius = spawner.Data.Radius;
spawner.UpdateTransforms();
}
}
}
SpawnData
public class SpawnData : MonoBehaviour
{
public int Count;
public float Radius, LastRadius, Angle, LastAngle;
}
I've refactored the code a little, but for the most part, changes are minimal
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.
I'm been making an endless runner game. I have a set of tiles (4 atm) which I spawn randomly. For optimization purposes I decided instead of instantiating and destroying game objects once the player run though it do disable them and once a when a tile is needed to spawn from the deactivated tiles. I made two lists active and deactivate which will do the above scenario. I managed to disable them but I don't know how to add them to the disable list and spawn them when. needed. How can I achieve it.
Here's the code and what I did so far.
TileManager.cs
void Update ()
{
//Vector3 offset = new Vector3(0f, 0f, Time.time * speed);
//GameObject go;
//go.transform.SetParent(transform);
if (playerTransform.position.z - safeZone > (spawnZ - amtOfTilesOnScreen * tileLength))
{
SpawnTile();
DeleteTile();
}
}
private void DeleteTile()
{
//Destroy(activeTiles[0]);
activeTiles[0].SetActive(false);
activeTiles.RemoveAt(0);
//deactivatedTiles.RemoveAt(0);
}
private void SpawnTile(int prefabIndex = -1)
{
GameObject go;
if (prefabIndex == -1)
{
go = Instantiate(tilePrefabs[RandomPrefabIndex()]) as GameObject;
}
else
{
go = Instantiate(tilePrefabs[prefabIndex]) as GameObject;
}
go.transform.SetParent(transform);
go.transform.position = Vector3.forward * spawnZ;
spawnZ += tileLength;
activeTiles.Add(go);
//go.SetActive(false);
//if (deactivatedTiles != null)
//{
// Debug.Log("Has a Tile");
// deactivatedTiles[0].SetActive(true);
//}
//Debug.Log(deactivatedTiles);
}
A help would be greatly appreciated.
Instead of deactivating and then re-activating your tiles, you could just move them from the back to the front when needed, like this:
|---|---|---|---|
|---|---|---|---|
|
V
|---|---|---|---|
|---|---|---|---|
(The first picture is the old state, and the second one is the state after). Instead of
if (playerTransform.position.z - safeZone > (spawnZ - amtOfTilesOnScreen * tileLength)) {
SpawnTile();
DeleteTile();
}
Do something like
if (playerTransform.position.z - safeZone > (spawnZ - amtOfTilesOnScreen * tileLength)) {
if (activeTiles.Count() < (NumberOfTilesThatFitOnScreen + 1)) {
var spawnedTile = SpawnTileAtFront();
activeTiles.Add(spawnedTile);
} else {
var movedTile = activeTiles[0];
MoveTileToFront(movedTile);
//Puts the moved tile at the end of the array, so the next tile that will be moved is always the one in back
activeTiles.RemoveAt(0);
activeTiles.Add(movedTile);
}
}
and then implement these two methods like so:
private void MoveTileToFront(GameObject tile)
{
tile.transform.position = Vector3.forward * spawnZ;
spawnZ += tileLength;
}
private GameObject SpawnTileAtFront(int prefabIndex = -1)
{
GameObject go;
if (prefabIndex == -1)
{
go = Instantiate(tilePrefabs[RandomPrefabIndex()]) as GameObject;
}
else
{
go = Instantiate(tilePrefabs[prefabIndex]) as GameObject;
}
go.transform.SetParent(transform);
MoveTileToFront(go);
return go;
}
Set NumberOfTilesThatFitOnScreen to the number of tiles that completely fill the screen.
Now your game should keep spawning new tiles until there is enough so that if you alter the last or first tile, it won't show on the screen. Then, once this amount is reached, the game should take the last tile (which isn't on screen) and move it to the front (which also shouldn't be on the screen). Then you're character will encounter this tile again, and another off-screen tile will be moved to the front, and so on. Ideally, it would look like the character is on an infinite path
Just move the tile from the active list to the deactivate list, like this:
private void DisposeActiveTile(int index)
{
GameObject unused_tile = activeTiles[index];
activeTiles.RemoveAt(index);
deactivatedTiles.Add(unused_tile);
}
private GameObject GetLastUnusedTile()
{
if(deactivatedTiles.Count == 0)
return null;
int last_index = deactivatedTiles.Count - 1;
GameObject unused_tile = deactivatedTiles[last_index];
deactivatedTile.RemoveAt(last_index);
return unused_tile;
}
When removing an object from the list, the object is not destroyed if it is referenced by another variable.