In my scene, I have my NavMesh in the center (the yellow one) with 3 cubes set to be tracked. I want the NavMesh to find the closest one with the shortest path to it out of all the cubes and start following it. I have written code to do so, yet it acts weird and I don't see anything wrong with it, but obviously something is. What's happening is when I click play and leave the cubes as is, the NavMesh does, in fact, find the path to the closest cube and start heading towards it (cube3), but when it almost gets to it, the NavMesh takes a hard turn and starts heading towards cube 1 which is obviously not the closer one.
Here is my code. PathLength works just fine. I think the problem lies within the CalcPath function.
float PathLength(Vector3 target){
NavMeshPath path = new NavMeshPath ();
myNavMesh.CalculatePath (target, path);
int i = 1;
float currentPathLength = 0;
Vector3 lastCorner;
Vector3 currentCorner;
lastCorner = path.corners [0];
while (i < path.corners.Length) {
currentCorner = path.corners [i];
currentPathLength += Vector3.Distance (lastCorner, currentCorner);
Debug.DrawLine (lastCorner, currentCorner, Color.red,1f);
lastCorner = currentCorner;
i++;
}
return currentPathLength;
}
void CalcPath(){
Vector3 closestTarget = Vector3.zero;
float lastPathLength = Mathf.Infinity;
float currentPathLength = 0;
foreach (GameObject player in GameObject.FindGameObjectsWithTag("Player")) {
currentPathLength = PathLength (player.transform.position);
if (currentPathLength < lastPathLength) {
closestTarget = player.transform.position;
}
lastPathLength = currentPathLength;
}
myNavMesh.SetDestination (closestTarget);
}
You do have an issue in CalcPath. I'll try to give an example to show you what's wrong. Say the distances to the players are as follows: [5,20,10]. Obviously Player A is closest, but CalcPath will return Player C. I'll go through your loop to show why:
First Iteration:
currentPathLength : 0 -> 5
closestTarget : null -> PlayerA.transform.position
lastPathLength : Mathf.Infinity -> 5
Second Iteration:
currentPathLength : 5 -> 20
closestTarget : PlayerA.transform.position -> PlayerA.transform.position (unchanged)
lastPathLength : 5 -> 20
Third Iteration: (this is where your problem lies)
currentPathLength : 20 -> 10 (this is less than lastPathLength)
closestTarget : PlayerA.transform.position -> PlayerC.transform.position
lastPathLength : 20 -> 10
To fix this issue, instead of storing lastPathLength, store a minimum path length and only change your closestTarget when you have a new minimum.
I was comparing the current path length to the last path length and not storing the shortest path and comparing it. I set a new variable called "closestTargetLength" and compared that to the current path length, and works perfect.
float PathLength(Vector3 target){
NavMeshPath path = new NavMeshPath ();
myNav.CalculatePath (target, path);
int i = 1;
float currentPathLength = 0;
Vector3 lastCorner;
Vector3 currentCorner;
lastCorner = path.corners [0];
while (i < path.corners.Length) {
currentCorner = path.corners [i];
currentPathLength += Vector3.Distance (lastCorner, currentCorner);
Debug.DrawLine (lastCorner, currentCorner, Color.red);
lastCorner = currentCorner;
i++;
}
return currentPathLength;
}
void CalcPath(){
Vector3 closestTarget = Vector3.zero;
float closestTargetLength = Mathf.Infinity;
float lastPathLength = Mathf.Infinity;
float currentPathLength = 0;
foreach (GameObject player in GameObject.FindGameObjectsWithTag("Player")) {
currentPathLength = PathLength (player.transform.position);
if (currentPathLength < closestTargetLength) {
closestTarget = player.transform.position;
closestTargetLength = currentPathLength;
}
lastPathLength = currentPathLength;
}
myNav.SetDestination (closestTarget);
}
Related
I have a raycast that's being rendered every frame based on 2 points, and those 2 points change position each frame.
What I need is a system that doesn't need a direction, or a number of objects, but instead takes in 2 points, and then instantiates or destroys as many objects as necessary to get the instantiated objects from one side to another minus spaceBetweenPoints. If you wanted you could think of it as an Angry Birds Style slingshot HUD, except without gravity, and based on raycasts.
My Script:
public int numOfPoints; // The number of points that are generated (This would need to chnage based one the distance in the end)
public float spaceBetweenPoints; // The spacing between the generated points
private GameObject[] predictionPoints; // The prefab to be gernerated
private Vector2 firstPathStart; // The starting point for the raycast (Changes each frame)
private Vector2 firstPathEnd; // The ending point for the raycast (Changes each frame)
void start()
{
predictionPoints = new GameObject[numOfPoints];
for (int i = 0; i < numOfPoints; i++)
{
predictionPoints[i] = Instantiate(predictionPointPrefab, firePoint.position,
Quaternion.identity);
}
}
void Update
{
Debug.DrawLine(firstPathStart, firstPathEnd, UnityEngine.Color.black);
DrawPredictionDisplay();
}
void DrawPredictionDisplay()
{
for (int i = 0; i < numOfPoints; i++)
{
predictionPoints[i].transform.position = predictionPointPosition(i * spaceBetweenPoints);
}
}
Vector2 predictionPointPosition(float time)
{
Vector2 position = (Vector2)firstPathStart + direction.normalized * 10f * time;
return position;
}
The current system simply takes in a starting position, a direction, and then moves a preset number of objects in that direction based on time. This way of doing it also causes problems because it's endess instead of only going till the end of the raycast: (Pardon my drawing skills)
Blue line = raycast
Black dots = instantiated prefab
Orange dot = raycast orange
Green dot = end of raycast
Notes:
direction is the momentum which I set in the editor, I needed it to put together what I currently have working, but it shouldn't be necessary when running based on points.
If you ask me I would say it is kinda easy if you know little bit of Math trickery. I'm not saying that I'm very good at Math, but once you get it it's kind of easy to pull off next time. Here if I try to explain everything, i won't be able to explain clearly. Take a look as the code below I've commented the whole code so that you can understand easily.
Basically I used a Method called Vector2.Lerp() Liner Interpolation, which means that this method will return value between point1, and point2 based on the value of 3rd argument t which goes from 0 to 1.
public class TestScript : MonoBehaviour
{
public Transform StartPoint;
public Transform EndPoint;
public float spaceBetweenPoints;
[Space]
public Vector2 startPosition;
public Vector2 endPosition;
[Space]
public List<Vector3> points;
private float distance;
private void Update()
{
startPosition = StartPoint.position; //Setting Starting point and Ending point.
endPosition = EndPoint.position;
//Finding the distance between point
distance = Vector2.Distance(startPosition, endPosition);
//Generating the points
GeneratePointsObjects();
Debug.DrawLine(StartPoint.position, EndPoint.position, Color.black);
}
private void OnDrawGizmos()
{
//Drawing the Dummy Gizmo Sphere to see the points
Gizmos.color = Color.black;
foreach (Vector3 p in points)
{
Gizmos.DrawSphere(p, spaceBetweenPoints / 2);
}
}
private void OnValidate()
{
//Validating that the space between two objects is not 0 because that would be Raise an exception "Devide by Zero"
if (spaceBetweenPoints <= 0)
{
spaceBetweenPoints = 0.01f;
}
}
private void GeneratePointsObjects()
{
//Vlearing the list so that we don't iterate over old points
points.Clear();
float numbersOfPoints = distance / spaceBetweenPoints; //Finding numbers of objects to be spawned by dividing "distance" by "spaceBetweenPoints"
float increnment = 1 / numbersOfPoints; //finding the increment for Lerp this will always be between 0 to 1, because Lerp() takes value between 0 to 1;
for (int i = 1; i < numbersOfPoints; i ++)
{
Vector3 v = Vector2.Lerp(startPosition, endPosition, increnment * i); //Find next position using Vector2.Lerp()
points.Add(v);//Add the newlly found position in List so that we can spwan the Object at that position.
}
}
}
Update: Added, How to set prefab on the positions
I just simply Destroyed old objects and Instantiated new Objects. But remember instantiating and Destroying object frequently in your game in unity will eat-up memory on your player's machine. Os I would suggest you to use Object-Pooling. For the reference I'll add a link to tutorial.
private void Update()
{
startPosition = StartPoint.position; //Setting Starting point and Ending point.
endPosition = EndPoint.position;
//Finding the distance between point
distance = Vector2.Distance(startPosition, endPosition);
//Generating the points
GeneratePointsObjects();
//Update: Generating points/dots on all to location;
InstenciatePrefabsOnPositions();
Debug.DrawLine(StartPoint.position, EndPoint.position, Color.black);
}
private void InstenciatePrefabsOnPositions()
{
//Remove all old prefabs/objects/points
for (int i = 0; i < pointParent.childCount; i++)
{
Destroy(pointParent.GetChild(i).gameObject);
}
//Instantiate new Object on the positions calculated in GeneratePointsObjects()
foreach (Vector3 v in points)
{
Transform t = Instantiate(pointPrefab);
t.SetParent(pointParent);
t.localScale = Vector3.one;
t.position = v;
t.gameObject.SetActive(true);
}
}
Hope this helps please see below links for more reference
OBJECT POOLING in Unity
Vector2.Lerp
I hope I understood you right.
First, compute the A to B line, so B minus A.
To get the number of needed objects, divide the line magnitude by the objects' spacing. You could also add the diameter of the prediction point object to avoid overlapping.
Then to get each object position, write (almost) the same for loop.
Here's what I came up with, didn't tested it, let me know if it helps!
public class CustomLineRenderer : MonoBehaviour
{
public float SpaceBetweenPoints;
public GameObject PredictionPointPrefab;
// remove transforms if you need to
public Transform start;
public Transform end;
private List<GameObject> _predictionPoints;
// these are your raycast start & end point, make them public or whatever
private Vector2 _firstPathStart;
private Vector2 _firstPathEnd;
private void Awake()
{
_firstPathStart = start.position;
_firstPathEnd = end.position;
_predictionPoints = new List<GameObject>();
}
private void Update()
{
_firstPathStart = start.position;
_firstPathEnd = end.position;
// using any small value to clamp everything and avoid division by zero
if (SpaceBetweenPoints < 0.001f) SpaceBetweenPoints = 0.001f;
var line = _firstPathEnd - _firstPathStart;
var objectsNumber = Mathf.FloorToInt(line.magnitude / SpaceBetweenPoints);
var direction = line.normalized;
// Update the collection so that the line isn't too short
for (var i = _predictionPoints.Count; i <= objectsNumber; ++i)
_predictionPoints.Add(Instantiate(PredictionPointPrefab));
for (var i = 0; i < objectsNumber; ++i)
{
_predictionPoints[i].SetActive(true);
_predictionPoints[i].transform.position = _firstPathStart + direction * (SpaceBetweenPoints * i);
}
// You could destroy objects, but it's better to add them to the pool since you'll use them quite often
for (var i = objectsNumber; i < _predictionPoints.Count; ++i)
_predictionPoints[i].SetActive(false);
}
}
First off, sorry it this isn't written very well, I've spend hours debugging this and I'm very stressed. I'm trying to make a moving platform in unity that can move between way-points, I don't want to have to have tons of gameobjects in the world taking up valuable processing power though so I'm trying to use something I can just add to the script through the editor.
The only problem is that it seems to be doing this at an incredible speed:
Black = The Camera View, Blue = The platform and where it should be going based on waypoints, Red = What it is currently doing.
I've spend hours trying to find a fix but I have no idea why it's doing this.
My Script on the Platform:
public Vector3[] localWaypoints;
Vector3[] globalWaypoints;
public float speed;
public bool cyclic;
public float waitTime;
[Range(0, 2)]
public float easeAmount;
int fromWaypointIndex;
float percentBetweenWaypoints;
float nextMoveTime;
void Start()
{
globalWaypoints = new Vector3[localWaypoints.Length];
for (int i = 0; i < localWaypoints.Length; i++)
{
globalWaypoints[i] = localWaypoints[i] + transform.position;
}
}
void Update()
{
Vector3 velocity = CalculatePlatformMovement();
transform.Translate(velocity);
}
float Ease(float x)
{
float a = easeAmount + 1;
return Mathf.Pow(x, a) / (Mathf.Pow(x, a) + Mathf.Pow(1 - x, a));
}
Vector3 CalculatePlatformMovement()
{
if (Time.time < nextMoveTime)
{
return Vector3.zero;
}
fromWaypointIndex %= globalWaypoints.Length;
int toWaypointIndex = (fromWaypointIndex + 1) % globalWaypoints.Length;
float distanceBetweenWaypoints = Vector3.Distance(globalWaypoints[fromWaypointIndex], globalWaypoints[toWaypointIndex]);
percentBetweenWaypoints += Time.deltaTime * speed / distanceBetweenWaypoints;
percentBetweenWaypoints = Mathf.Clamp01(percentBetweenWaypoints);
float easedPercentBetweenWaypoints = Ease(percentBetweenWaypoints);
Vector3 newPos = Vector3.Lerp(globalWaypoints[fromWaypointIndex], globalWaypoints[toWaypointIndex], easedPercentBetweenWaypoints);
if (percentBetweenWaypoints >= 1)
{
percentBetweenWaypoints = 0;
fromWaypointIndex++;
if (!cyclic)
{
if (fromWaypointIndex >= globalWaypoints.Length - 1)
{
fromWaypointIndex = 0;
System.Array.Reverse(globalWaypoints);
}
}
nextMoveTime = Time.time + waitTime;
}
return newPos - transform.position;
}
struct PassengerMovement
{
public Transform transform;
public Vector3 velocity;
public bool standingOnPlatform;
public bool moveBeforePlatform;
public PassengerMovement(Transform _transform, Vector3 _velocity, bool _standingOnPlatform, bool _moveBeforePlatform)
{
transform = _transform;
velocity = _velocity;
standingOnPlatform = _standingOnPlatform;
moveBeforePlatform = _moveBeforePlatform;
}
}
void OnDrawGizmos()
{
if (localWaypoints != null)
{
Gizmos.color = Color.red;
float size = .3f;
for (int i = 0; i < localWaypoints.Length; i++)
{
Vector3 globalWaypointPos = (Application.isPlaying) ? globalWaypoints[i] : localWaypoints[i] + transform.position;
Gizmos.DrawLine(globalWaypointPos - Vector3.up * size, globalWaypointPos + Vector3.up * size);
Gizmos.DrawLine(globalWaypointPos - Vector3.left * size, globalWaypointPos + Vector3.left * size);
}
}
}
UPDATE: Upon further testing I found that if the first object in my localWaypoint array is set to 0,0,0 and my 2nd object is set to 1,0,0 then the platform will spiral to the right, making sure to hit the waypoints as it's spiraling, and then spiraling out into nowhere like in the image above. But if I set my first object to 0,0,0 and my second object to -1,0,0 then the object will act the same way as before, but will spiral to the left as displayed in this image. (The second image has also bee updated to display how the platfrom makes sure to hit both waypoints before is spirals out into nowhere).
I've also noticed that if I set both waypoints to 0,0,0 then the platform stays still, these 2 things prove that it has somthing to do with the way the waypoints are being handled and not some other script or parent object interfering.
Using the updated numbers ([0,0,0], [1,0,0]) works in my test app. However, if I put a rotation on the object's Y axis, then I see behavior like you are seeing. In Update, if you change:
transform.Translate(velocity);
to
transform.Translate(velocity, Space.World);
You should see your desired behavior. Note that "transform.Translate(velocity)" is the same as "transform.Translate(velocity, Space.Self)". Your translation is being rotated.
If you are curious, take a look at this for more information on how the values in the transform are applied:
https://gamedev.stackexchange.com/questions/138358/what-is-the-transformation-order-when-using-the-transform-class
Unity has a function Terrain.sampleHeight(point) which is great, it instantly gives you the height of the Terrain underfoot rather than having to cast.
However, any non-trivial project has more than one Terrain. (Indeed any physically large scene inevitably features terrain stitching, one way or another.)
Unity has a function Terrain.activeTerrain which - I'm not making this up - gives you: the "first one loaded"
Obviously that is completely useless.
Is fact, is there a fast way to get the Terrain "under you"? You can then use the fast function .sampleHeight ?
{Please note, of course, you could ... cast to find a Terrain under you! But you would then have your altitude so there's no need to worry about .sampleHeight !}
In short is there a matching function to use with sampleHeight which lets that function know which Terrain to use for a given xyz?
(Or indeed, is sampleHeight just a fairly useless demo function, usable only in demos with one Terrain?)
Is there in fact a fast way to get the Terrain "under you" - so as to
then use the fast function .sampleHeight ?
Yes, it can be done.
(Or indeed, is sampleHeight just a fairly useless demo function,
usable only in demos with one Terrain?)
No
There is Terrain.activeTerrain which returns the main terrain in the scene. There is also Terrain.activeTerrains (notice the "s" at the end) which returns active terrains in the scene.
Obtain the terrains with Terrain.activeTerrains which returns Terrain array then use Terrain.GetPosition function to obtain its position. Get the current terrain by finding the closest terrain from the player's position. You can do this by sorting the terrain position, using Vector3.Distance or Vector3.sqrMagnitude (faster).
Terrain GetClosestCurrentTerrain(Vector3 playerPos)
{
//Get all terrain
Terrain[] terrains = Terrain.activeTerrains;
//Make sure that terrains length is ok
if (terrains.Length == 0)
return null;
//If just one, return that one terrain
if (terrains.Length == 1)
return terrains[0];
//Get the closest one to the player
float lowDist = (terrains[0].GetPosition() - playerPos).sqrMagnitude;
var terrainIndex = 0;
for (int i = 1; i < terrains.Length; i++)
{
Terrain terrain = terrains[i];
Vector3 terrainPos = terrain.GetPosition();
//Find the distance and check if it is lower than the last one then store it
var dist = (terrainPos - playerPos).sqrMagnitude;
if (dist < lowDist)
{
lowDist = dist;
terrainIndex = i;
}
}
return terrains[terrainIndex];
}
USAGE:
Assuming that the player's position is transform.position:
//Get the current terrain
Terrain terrain = GetClosestCurrentTerrain(transform.position);
Vector3 point = new Vector3(0, 0, 0);
//Can now use SampleHeight
float yHeight = terrain.SampleHeight(point);
While it's possible to do it with Terrain.SampleHeight, this can be simplified with a simple raycast from the player's position down to the Terrain.
Vector3 SampleHeightWithRaycast(Vector3 playerPos)
{
float groundDistOffset = 2f;
RaycastHit hit;
//Raycast down to terrain
if (Physics.Raycast(playerPos, -Vector3.up, out hit))
{
//Get y position
playerPos.y = (hit.point + Vector3.up * groundDistOffset).y;
}
return playerPos;
}
Terrain.GetPosition() = Terrain.transform.position = position in world
working method:
Terrain[] _terrains = Terrain.activeTerrains;
int GetClosestCurrentTerrain(Vector3 playerPos)
{
//Get the closest one to the player
var center = new Vector3(_terrains[0].transform.position.x + _terrains[0].terrainData.size.x / 2, playerPos.y, _terrains[0].transform.position.z + _terrains[0].terrainData.size.z / 2);
float lowDist = (center - playerPos).sqrMagnitude;
var terrainIndex = 0;
for (int i = 0; i < _terrains.Length; i++)
{
center = new Vector3(_terrains[i].transform.position.x + _terrains[i].terrainData.size.x / 2, playerPos.y, _terrains[i].transform.position.z + _terrains[i].terrainData.size.z / 2);
//Find the distance and check if it is lower than the last one then store it
var dist = (center - playerPos).sqrMagnitude;
if (dist < lowDist)
{
lowDist = dist;
terrainIndex = i;
}
}
return terrainIndex;
}
It turns out the answer is simply NO, Unity does not provide such a function.
You can use this function to get the Closest Terrain to your current Position:
int GetClosestTerrain(Vector3 CheckPos)
{
int terrainIndex = 0;
float lowDist = float.MaxValue;
for (int i = 0; i < _terrains.Length; i++)
{
var center = new Vector3(_terrains[i].transform.position.x + _terrains[i].terrainData.size.x / 2, CheckPos.y, _terrains[i].transform.position.z + _terrains[i].terrainData.size.z / 2);
float dist = Vector3.Distance(center, CheckPos);
if (dist < lowDist)
{
lowDist = dist;
terrainIndex = i;
}
}
return terrainIndex;
}
and then you can use the function like this:
private Terrain[] _terrains;
void Start()
{
_terrains = Terrain.activeTerrains;
Vector3 start_pos = Vector3.zero;
start_pos.y = _terrains[GetClosestTerrain(start_pos)].SampleHeight(start_pos);
}
public static Terrain GetClosestTerrain(Vector3 position)
{
return Terrain.activeTerrains.OrderBy(x =>
{
var terrainPosition = x.transform.position;
var terrainSize = x.terrainData.size * 0.5f;
var terrainCenter = new Vector3(terrainPosition.x + terrainSize.x, position.y, terrainPosition.z + terrainSize.z);
return Vector3.Distance(terrainCenter, position);
}).First();
}
Raycast solution: (this was not asked, but for those looking for Solution using Raycast)
Raycast down from Player, ignore everything that has not Layer of "Terrain" (Layer can be easily set in inspector).
Code:
void Update() {
// Put this on Player! Raycast's down (raylength=10f), if we hit something, check if the Layers name is "Terrain", if yes, return its instanceID
RaycastHit hit;
if (Physics.Raycast (transform.localPosition, transform.TransformDirection (Vector3.down), out hit, 10f, 1 << LayerMask.NameToLayer("Terrain"))) {
Debug.Log(hit.transform.gameObject.GetInstanceID());
}
}
At this point already, you have a reference to the Terrain by "hit.transform.gameObject".
For my case, i wanted to reference this terrain by its instanceID:
// any other script
public static UnityEngine.Object FindObjectFromInstanceID(int goID) {
return (UnityEngine.Object)typeof(UnityEngine.Object)
.GetMethod("FindObjectFromInstanceID", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)
.Invoke(null, new object[] { goID });
}
But as written above, if you want the Terrain itself (as Terrain object) and not the instanceID, then "hit.transform.gameObject" will give you the reference already.
Input and code snippets taken from these links:
https://answers.unity.com/questions/1164722/raycast-ignore-layers-except.html
https://answers.unity.com/questions/34929/how-to-find-object-using-instance-id-taken-from-ge.html
I am making an object spawn script in which on the start of the script the spawning function is called and in it is a for loop which creates an object each iteration. It first picks a random X position for it and then checks if it is in a range of another prefabs coordinates so they don't spawn too close or worse, one in each other. If it is in the same coordinates as another prefab it will return 0 and this same goes out for the Z axis too. It also picks a random Y axis rotation so it doesn't all face the same direction. After this it spawns the prefab and sets it's coordinates and rotation after which it check if the coordinates in the X or Z axis are 0, and if any of those two are 0 it goes back one iteration and the last object to be spawned is destroyed so it doesn't flood. This works perfectly but when you want to set it to spawn too much objects it floods the RAM because there is nowhere to spawn more objects. I tried finding the highest X position and highest Z position and multiplying them, and setting them both to positive, and then dividing them by the space between the prefabs but this doesn't work as it sets it to a really really high number. How would you fix this?
Script:
using UnityEngine;
using System.Collections;
public class PrefabSpawner : MonoBehaviour {
public int amountOfPrefabs;
public int maxAmountOfPrefabs;
private int currentSpawnedPrefab;
public float spaceBetweenPrefabs;
private float positionX;
private float positionZ;
private float maxPositionX;
private float maxPositionZ;
private float multipliedPosXZ;
private bool previousSpawnHadZero;
public GameObject prefab;
private GameObject point1;
private GameObject point2;
private GameObject currentSpawn;
private Vector2[] positions;
void Start () {
currentSpawnedPrefab = 0;
previousSpawnHadZero = false;
point1 = gameObject.transform.GetChild (0).gameObject;
point2 = gameObject.transform.GetChild (1).gameObject;
if (point1.transform.position.x > point2.transform.position.x)
maxPositionX = point1.transform.position.x;
else
maxPositionX = point2.transform.position.x;
if (point1.transform.position.z > point2.transform.position.z)
maxPositionZ = point1.transform.position.z;
else
maxPositionZ = point2.transform.position.z;
multipliedPosXZ = maxPositionX * maxPositionZ;
if (multipliedPosXZ < 0)
multipliedPosXZ += multipliedPosXZ + multipliedPosXZ;
maxAmountOfPrefabs = Mathf.FloorToInt (multipliedPosXZ / spaceBetweenPrefabs);
if (amountOfPrefabs > maxAmountOfPrefabs)
amountOfPrefabs = maxAmountOfPrefabs;
point1.GetComponent<MeshRenderer> ().enabled = false;
point2.GetComponent<MeshRenderer> ().enabled = false;
gameObject.GetComponent<MeshRenderer> ().enabled = false;
positions = new Vector2[amountOfPrefabs];
SpawnPrefabs (amountOfPrefabs);
}
void SpawnPrefabs (int amount) {
for (int i = 0; i < amount; i++) {
if(previousSpawnHadZero)
i -= 1;
currentSpawn = (GameObject)Instantiate (prefab);
positionX = GetRandomPositionX ();
positionZ = GetRandomPositionZ ();
currentSpawn.transform.position = new Vector3 (positionX, this.transform.position.y + currentSpawn.transform.localScale.y, positionZ);
currentSpawnedPrefab += 1;
if (positionX == 0 || positionZ == 0) {
previousSpawnHadZero = true;
currentSpawnedPrefab -= 1;
Destroy (currentSpawn);
}
if (positionX != 0 && positionZ != 0) {
previousSpawnHadZero = false;
positionX = 0;
positionZ = 0;
}
}
}
IEnumerator Pause () {
yield return null;
}
float GetRandomPositionX () {
//Finds a random position for the X axis and then checks it and returns either 0 if the position is taken or the position if not
}
float GetRandomPositionZ () {
//Finds a random position for the Z axis and then checks it and returns either 0 if the position is taken or the position if not
}
bool CheckPositionAvailable (float pos, int axis) {
//Checks if the position is available.
}
}
Code is really long to debug but the problem is clearly visible and is from the SpawnPrefabs function. Currently, when you instantiate a prefab, you check if the generated position is 0. If 0, you subtract 1 from the i in the for loop then destroy the instantiated object and then start the for loop again from the current loop-1.
So the combination of Instantiate, Destroy and repeating it over again in the for loop is causing the memory issue.
What to do:
You have to re-write the whole function and this will require modification in your whole code too. Do not instantiate and destroy object in that loop unless when needed.
1.In the Start() function, create one prefab.
2.Make it to be invisible in the scene by disabling its mesh/sprite renderer.
3.Use that prefab in the for loop to check if the generated position is valid. If it is valid, you can now create/instantiate an object in the loop.
This prevents instantiating and destroying objects in the loop when you only create objects when if (positionX != 0 && positionZ != 0) .
I created a series of sphere clones in my game. After that I adapted the scale so that they appear smaller. However, now there is a gap between these spheres ... and I would have to change the position of this instatiate game objects. I changed my code already exactly at this position but nothing happens. So please I need your help! How can I do this? I would have very small spheres which are located near together.
Here the code:
using UnityEngine;
using System.Collections;
public class SineWave : MonoBehaviour {
private GameObject plotPointObject;
private int numberOfPoints= 100;
private float animSpeed =1.0f;
private float scaleInputRange = 8*Mathf.PI; // scale number from [0 to 99] to [0 to 2Pi] //Zahl vor Mathf, Anzahl Bön
private float scaleResult = 2.5f; // Y Achse Range
public bool animate = true;
GameObject[] plotPoints;
// Use this for initialization
void Start () {
if (plotPointObject == null) //if user did not fill in a game object to use for the plot points
plotPointObject = GameObject.CreatePrimitive(PrimitiveType.Sphere); //create a sphere
//add Material to the spheres , load material in the folder Resources/Materials
Material myMaterial = Resources.Load("Materials/green", typeof(Material)) as Material;
plotPointObject.GetComponent<MeshRenderer> ().material = myMaterial;
//change the scale of the spheres
//plotPointObject.transform.localScale = Vector3.one * 0.5f ;
plotPointObject.transform.localScale -= new Vector3(0.5f,0.5f,0.5f);
plotPoints = new GameObject[numberOfPoints]; //creat an array of 100 points.
//plotPointObject.GetComponent<MeshRenderer> ().material =Material.Load("blue") as Material
//plotPointObject.transform.localScale -= new Vector3 (0.5F, 0.5F, 0.5F); //neu: change the scale of the spheres
for (int i = 0; i < numberOfPoints; i++)
{
plotPoints[i] = (GameObject)GameObject.Instantiate(plotPointObject, new Vector3(i -
(numberOfPoints/2), 0, 0), Quaternion.identity); //this specifies
what object to create, where to place it and how to orient it
}
//we now have an array of 100 points- your should see them in the hierarchy when you hit play
plotPointObject.SetActive(false); //hide the original
}
Thank you already in advance!
Edit:
As I said in the comment I achieved now to place my spheres without a gap in between. However, as soon as I animate my spheres (with a sine wave) there is still that gap between the spheres. How can I adapt this? Should I copy the code of the Start function in the Update function?
I would be very happy to get some help. Thank you very much!
enter code here void Update()
{
for (int i = 0; i < numberOfPoints; i++)
{
float functionXvalue = i * scaleInputRange / numberOfPoints; // scale number from [0 to 99] to [0 to 2Pi]
if (animate)
{
functionXvalue += Time.time * animSpeed;
}
plotPoints[i].transform.position = new Vector3(i - (numberOfPoints/2), ComputeFunction(functionXvalue) * scaleResult, 0);
//print (plotPointObject.GetComponent<MeshRenderer> ().bounds.size.x);
// put the position information of sphere clone 50 in a vector3 named posSphere
posSphere = plotPoints [50].transform.position;
}
//print position of sphere 50 in console
//print (posSphere);
}
float ComputeFunction(float x)
{
return Mathf.Sin(x);
}
}
I think you could make the Barış solution.
For each new object that you are instantiating, you will set his position to the lasted instantiated position adding the size of the object itself, or whatever distance that you want they have from each other.
var initialPosition = 0;
var distanceFromEachOther = 20;
for (int i = 0; i < numberOfPoints; i++) {
var newPos = new Vector3(initialPosition + (i * distanceFromEachOther), 0, 0);
plotPoints[i] = (GameObject)GameObject.Instantiate(plotPointObject, newPos, Quaternion.identity);
}
That will make a gap between the spheres at X pivot, depending on their size. Change the distanceFromEachOther var, adjusting for your needs.
You could also get the object distance with plotPointObject.GetComponent<MeshRenderer>().bounds.size, so distanceFromEachOther could be, for example distanceFromEachOther = plotPointObject.GetComponent<MeshRenderer>().bounds.size.x + 5. So then you will have the objects with a perfectly distance of 5 from each other.
give this a try:
Transform objectToSpawn;
for (int i = 0; i < numberOfPoints; i++)
{
float someX = 200;
float someY = 200;
Transform t = Instantiate(objectToSpawn, new Vector3(i -(numberOfPoints/2), 0, 0), Quaternion.identity) as Transform;
plotPoints[i] = t.gameObject;
t.position = new Vector(someX, someY);
}