My Game's Infinite Platform Generation does Not Work - c#

I have followed a YouTube tutorial on how to make a doodle jump replica in 5 minutes.
The problem is, this tutorial is rushed and does not provide all the information needed and thus I have encountered one big problem.
The infinite random platform generation keeps skewing off to either the left or right side of the screen after a certain point, and I have no clue as to why.
Here is the video tutorial: https://www.youtube.com/watch?v=IUzI95mmbwA
I am just wondering if anyone on here could be kind enough to help me on this small problem as it is for my school project and I am not sure how to fix it.
GameManager:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameManager : MonoBehaviour
{
public GameObject platformPrefab;
public int platformCount = 300;
// Use this for initialization
void Start ()
{
Vector3 spawnPosition = new Vector3();
for (int i = 0; i < platformCount; i++)
{
spawnPosition.y += Random.Range(.5f, 2f);
spawnPosition.x += Random.Range(-5f, 5f);
Instantiate(platformPrefab, spawnPosition, Quaternion.identity);
}
}
}
Platforms being close together issue

So you generate your platforms using
void Start ()
{
Vector3 spawnPosition = new Vector3();
for (int i = 0; i < platformCount; i++)
{
spawnPosition.y += Random.Range(.5f, 2f);
spawnPosition.x += Random.Range(-5f, 5f);
Instantiate(platformPrefab, spawnPosition, Quaternion.identity);
}
}
which basically means that the next platform always has an offset to the one before in a range +/- 5.
This means however, that the next value would always have to counterbalance the current offset in order to be fully balanced in total. Since you use a random and not an equal distribution thiis is not guaranteed!
In order to make sure platforms don't get "skewed" to one side infinitely I would simply make sure they still "fit into the screen" and clamp the position accordingly like e.g.
public class GameManager : MonoBehaviour
{
public GameObject platformPrefab;
public int platformCount = 300;
[SerializeField] private Camera mainCamera;
// Adjust via the Inspector in order to also take platform width into account!
public float platformWidth;
[Min(0)] public float xRange = 5f;
float minX;
float maxX;
// Use this for initialization
void Start ()
{
if(!mainCamera) mainCamera = Camera.main;
// get the screen boarders in world space
minX = mainCamera.ScreenToWorldPoint(new Vector3(0, 0, -mainCamera.transform.position.z)).x + platformWidth / 2f;
maxX = mainCamera.ScreenToWorldPoint(new Vector3(Screen.width, 0, -mainCamera.transform.position.z)).x - platformWidth / 2f;
Vector3 spawnPosition = Vector3.zero;
for (int i = 0; i < platformCount; i++)
{
spawnPosition.y += Random.Range(.5f, 2f);
spawnPosition.x += Random.Range(-xRange, xRange);
// now simply clamp the x position so the platform
// doesn't go over the screen boarders
spawnPosition.x = Mathf.Clamp(spawnPosition.x, minX, maxX);
Instantiate(platformPrefab, spawnPosition, Quaternion.identity);
}
}
}
You could of course also instead of only clamping the position actively counter balance the range and shift it left or right accordingly like e.g.
Vector3 spawnPosition = Vector3.zero;
for (int i = 0; i < platformCount; i++)
{
spawnPosition.y += Random.Range(.5f, 2f);
var min = -5f;
var max = 5f;
var distanceToLeftBorder = spawnPosition.x - minX;
var distanceToRightBorder = maxX - spawnPosition.x;
if(distanceToLeftBorder < xRange)
{
min += xRange - distanceToLeftBorder;
max += xRange - distanceToLeftBorder;
}
else if(distanceToRightBorder < xRange)
{
min -= xRange - distanceToRightBorder;
max -= xRange - distanceToRightBorder;
}
spawnPosition.x += Random.Range(min, max);
// now simply clamp the x position so the platform
// doesn't go over the screen boarders
spawnPosition.x = Mathf.Clamp(spawnPosition.x, minX, maxX);
Instantiate(platformPrefab, spawnPosition, Quaternion.identity);
}
To your final issue the platforms being too close I would take yet another different approach:
Instead of randomly place at offset +/- 5 rather separate the sign from the distance and do
var sign = Mathf.Sign(Random.value);
var distance = Random.Range(platformWidth + SPACING, maxDistance);
spawnPosition.x += distance * sign;
which would make sure that the next platform is at minimum the platform width plus an optional spacing (both positive!) and not any closer to the current platform.
I would then instead of clamping simply check if you would hit the border and switch the sign like
var sign = Mathf.Sign(Random.value);
var distance = Random.Range(platformWidth + SPACING, maxDistance);
if(spawnPosition.x + distance * sign < minX || spawnPosition.x + distance * sign > maxX)
{
sign *= -1;
}
spawnPosition.x += distance * sign;

Related

I am attempting to make game objects spawn at random points along the Y-Axis

I am attempting to make a flappybird style game that is underwater with the PC dodging mines and collecting fish to score points. The issue that I am having is that the mines spawn off screen as intended and fly across the screen, however, they spawn in a straight line. I am following a tutorial as I dont know C# so I am practicing to get knowledge up. But in doing so I am not sure where I am going wrong and google searched yielded no solution.
This is the code for spawning in the mines
private void HandleMineSpawning() {
Timer -= Time.deltaTime;
if (Timer < 0) {
Timer += TimerMax;
float heightEdge = 10f;
float minHeight = offset + heightEdge;
float totalHeight = camSize * 2;
float maxHeight = totalHeight - offset *.5f - heightEdge;
float height = Random.Range(minHeight, maxHeight);
CreateMineOffset(height, offset, mineSpawn);
}
And the code that should create an offset on the Y-axis
private void Awake() {
minelist = new List<Mine>();
TimerMax = 1.5f;
offset = 20;
}
private void CreateMineOffset(float offsetY, float offsetSize, float xPosition) {
CreateMine(offsetY - offsetSize * .5f, xPosition);
CreateMine(camSize * 2f - offsetY - offsetSize * .5f, xPosition);
}
This was written for a 3d game but I am sure you can modify it for a platformer game.
Set the y spread to a high value and set the x to a low value. The Z spread can stay at zero for your game.
Hope this helps.
public GameObject itemsToSpread;
GameObject spawn;
public int numberOfItemsToSpawn;
public float Space = 10;//Distance between game objects
//Offset values go here
public float itemXSpread = 5;
public float itemYSpread = 100;
public float itemZSpread = 0;//Add value here for a 3d distribution
// Start is called before the first frame update
void Start()
{
for (int i = 0; i < numberOfItemsToSpawn; i++)
{
SpreadItem();
}
}
void SpreadItem()
{
Vector3 ranPos = new Vector3(Random.Range(-itemXSpread, itemXSpread) + Space, Random.Range(-itemYSpread, itemYSpread) + Space, Random.Range(-itemZSpread, itemZSpread) + Space) + transform.position;
spawn = Instantiate(itemsToSpread, ranPos, Quaternion.identity);
}
Output:
For your game, try these values.

How to spawn randomly without the next spawning object spawning near

I am doing a personal project which is about an aim trainer. What I'm trying to do is that the object that spawns randomly doesn't spawn near the previous one.
I'm not very good at mathematical logic so what I've tried to do doesn't work. Any ideas, thanks :)
The code I've tried isn't doing what I want, the spawns randoms keep appearing near the last spawn, and I don't want that. What I really want is not to repeat the position of the last spawn.
I tried this:
public class Spawn : MonoBehaviour
{
public Vector3 GerRandomPosition()
{
Vector3 center = boxCollider.center + transform.position;
float minX = center.x - boxCollider.size.x / 2f;
float maxX = center.x + boxCollider.size.x / 2f;
float minY = center.y - boxCollider.size.y / 2f;
float maxY = center.y + boxCollider.size.y / 2f;
auxRandomX = randomX;
randomX = Random.Range(minX, maxX);
auxRandomY = randomY;
randomY = Random.Range(minY, maxY);
float distanceX = Mathf.Abs(auxRandomX - randomX);
while (distanceX < 3.2f)
{
randomX = Random.Range(minX, maxX);
distanceX = Mathf.Abs(auxRandomX - randomX);
}
float distanceY = Mathf.Abs(auxRandomY - randomY);
while (distanceY < 3.2f)
{
randomY = Random.Range(minY, maxY);
distanceY = Mathf.Abs(auxRandomY - randomY);
}
Vector3 randomPosition = new Vector3(randomX, randomY, transform.position.z);
return randomPosition;
}
}
TLDR; Move/Check the previous spawn point with a set distance spacing.
Bruteforce Solution
Vector3 lastSpawnPoint = Vector3.zero; // or somewhere far away for start
const float SPAWN_SPACING = 1f;
public Vector3 GetSpawnPoint(){
// Keep generating a new position until it is far away from the previous spawn point
Vector3 newPoint;
do {
newPoint = GetRandomPosition();
} while (Vector3.Distance(newPoint, lastSpawnPoint) <= SPAWN_SPACING)
// Shouldn't be a long/infinite loop if the play-area is big enough.
lastSpawnPoint = newPoint;
return newPoint;
}
Bruteforce it, works very nicely if your game has a large playing/spawning field.
No Bruteforce Solution
Vector3 lastSpawnPoint = Vector3.zero;
const float MIN_SPAWN_SPACING = 1f;
const float MAX_SPAWN_SPACING = 10f;
public Vector3 GetSpawnPoint(){
if (lastSpawnPoint == Vector3.zero){
return GetRandomPosition();
}
// We generate a direction and spacing,
// and move the spawn-point based on that.
float spacing = Random.Range(MIN_SPAWN_SPACING, MAX_SPAWN_SPACING);
Vector3 direction = Random.insideUnitCircle.normalized;
lastSpawnPoint += (direction * spacing);
return lastSpawnPoint;
}
Works nicely, and you can set a max spawn spacing too.
Though currently it is able to generate anywhere, so you might want to configure this to limit where it can spawn at.

How to Spawn Objects in Unity3D with a Minimum Distance between

I am Programming a random "Stone" Spawner and have a big problem at the moment. I have some ideas how to fix it, but want to know a performance friendly way to do it.
So my way to Spawn the Objects on the Sphere Surface is this one:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpawnObjects : MonoBehaviour
{
public Vector3 centerOfSphere = new Vector3(0, 5000, 0);
public float radiusOfSphere = 5000.0f;
public List<GameObject> stones;
public int stonesToSpawn = 1;
void Start()
{
Vector3 center = transform.position + centerOfSphere; //Setting the center position of the Sphere
//This for loop is spawning the Stones on a random position on Sphere Surface
for (int i = 0; i < stonesToSpawn; i++)
{
Vector3 pos = RandomCircle(center, radiusOfSphere);
Quaternion rot = Quaternion.FromToRotation(Vector3.forward, center - pos);
Instantiate(stones[Random.Range(0, stones.Count)], pos, rot);
}
}
//Method returns a Random Position on a Sphere
Vector3 RandomCircle(Vector3 center, float radius)
{
float alpha = UnityEngine.Random.value * 360;
float beta = UnityEngine.Random.value * 360;
Vector3 pos;
pos.x = radius * Mathf.Cos(beta) * Mathf.Cos(alpha);
pos.y = radius * Mathf.Cos(beta) * Mathf.Sin(alpha);
pos.z = radius * Mathf.Sin(beta);
return pos;
}
}
So thank you for your following explanations! :)
As said to make your live one step easier simply use Random.onUnitSphere so your entire method RandomCircle (change that name btw!) can be shrinked to
private Vector3 RandomOnSphere(Vector3 center, float radius)
{
return center + Random.onUnitSphere * radius;
}
And then in order to have a minimum distance between them there are probably multiple ways but I guess the simplest - brute force - way would be:
store already used positions
when you get a new random position check the distance to already existing ones
keep get new random positions until you have found one, that is not too close to an already existing one
This depends of course a lot on your use case and the amount of objects and the minimum distance etc - in other words I leave it up to you to assure that the requested amount and minimum distance is doable at all with the given sphere radius.
You could always leave an "emergency exit" and give up after e.g. 100 attempts.
Something like e.g.
// Linq offers some handy query shorthands that allow to shorten
// long foreach loops into single calls
using System.Linq;
...
private const int MAX_ATTEMPTS = 100;
public float minimumDistance = 1f;
void Start()
{
var center = transform.position + centerOfSphere;
// It is cheaper to use with square magnitudes
var minDistanceSqr = minimumDistance * minimumDistance;
// For storing the already used positions
// Already initialize with the correct capacity, this saves resources
var usedPositions = new List<Vector3>(stonesToSpawn);
for (int i = 0; i < stonesToSpawn; i++)
{
// Keep track of the attempts (for the emergency break)
var attempts = 0;
Vector3 pos = Vector3.zero;
do
{
// Get a new random position
pos = RandomOnSphere(center, radiusOfSphere);
// increase the attempts
attempts++;
// We couldn't find a "free" position within the 100 attempts :(
if(attempts >= MAX_ATTEMPTS)
{
throw new Exception ("Unable to find a free spot! :'(");
}
}
// As the name suggests this checks if any "p" in "usedPositions" is too close to the given "pos"
while(usedPositions.Any(p => (p - pos).sqrMagnitude <= minDistanceSqr)));
var rot = Quaternion.FromToRotation(Vector3.forward, center - pos);
Instantiate(stones[Random.Range(0, stones.Count)], pos, rot);
// Finally add this position to the used ones so the next iteration
// also checks against this position
usedPositions.Add(pos);
}
}
Where
usedPositions.Any(p => (p - pos).sqrMagnitude <= minDistanceSqr))
basically equals doing something like
private bool AnyPointTooClose(Vector3 pos, List<Vector3> usedPositions, float minDistanceSqr)
{
foreach(var p in usedPositions)
{
if((p - pos).sqrMagnitude <= minDistanceSqr)
{
return true;
}
}
return false;
}
if that's better to understand for you

Unity, C# - randomize gap between spawned prefabs but don't allow them to touch?

Alright, in my game I have a Runner object that moves at a velocity towards the left towards platforms. I have succeeded in spawning a random platform prefab at a pt a semi-random distance away from the last platform.
I do this by spawning the platform a random distance spacing, which the distance the object has moved since last platform was spawned.
public GameObject[] spawnableObjects;
private GameObject prefab;
private float spacing; // spawns every 2 units/metres
public float minY, maxY, minGap, maxGap;
private float oldX = 0; // previous camera position
private float newX = 0; // current camera position
private float distanceTravelled = 0;
private Vector3 spawnPos;
public Transform player; // getting camera reference
public Transform spawnPt;
private float currentY, lastY, currentX, lastX;
void Start()
{
//set beginning y pos
GameEventManager.GameStart += GameStart;
GameEventManager.GameOver += GameOver;
spacing = 20; //random between length of last prefab and max gap?
lastY = 0;
lastX = spawnPt.localPosition.x; //first spawn x pos
}
void Update () {
//Debug.Log (distanceTravelled);
newX = player.transform.localPosition.x;
distanceTravelled += newX - oldX; // if you are goint to the right
// if you're going to the left, do this instead:
// distanceTravelled += oldX - newX;
oldX = newX; // this is very important
if (distanceTravelled >= spacing) {
distanceTravelled = 0;
spawnNewObject ();
}
}
void spawnNewObject () {
// code to spawn. Use the newX + some X offset while declaring your new object's position.
int rand = Random.Range (0, spawnableObjects.Length);
prefab = spawnableObjects[rand];
currentY = lastY + Random.Range(minY, maxY);
currentX = lastX + (prefab.transform.localScale.x + 5f); //randomize 5, 5 is gap
spawnPos = new Vector3((spawnPt.localPosition.x), currentY,0f);
//Debug.Log ("new x:"+currentX);
Instantiate (prefab, spawnPos, Quaternion.identity);
lastY = currentY;
lastX = currentX;
spacing = Random.Range (prefab.transform.localScale.x+3, prefab.transform.localScale.x+5);
Debug.Log("spawn now!! spacing="+spacing);
}
The spawnpt game object moves at the same acceleration/speed as Runner, so it appears as if platforms are moving towards the Runner.
For some reason, the Random.Range (prefab.transform.localScale.x+3, prefab.transform.localScale.x+5); which is the length of the prefab currently being spawned+3 or +4 makes it so the platforms virtually never touch.
My thinking in doing length was if the new platform is being spawned from the empty spawnPt.localPosition.x, if the spacing was atleast the length of the current prefab, they wouldn't touch.
Obviously something is flawed in my logic as if I just do prefab.transform.localScale.x platforms touch. I need a new approach for this.
How can I have my randomly selected prefab spawn with a randomized GAP between itself and the last prefab that was spawned? I have tried everything including Physics spheres (don't want to go that route) but can't create a gap that I can alter.
Is there a way to do this?
Try overlap sphere (untested):
if (!Physics.CheckSphere(spawnPosition, objectRadius)) {
//instantiate object
}
objectRadius is the spherical radius that the object cannot be instantiated within another one
also you can have a Random.insideUnitSphere:
spawnPosition = previous.transform.position + Random.insideUnitSphere * spawnRadius;
I think that will work.

Spawning items at sides of a spline

I have just done the Curves and Splines Tutorials from the catlikecoding.com website (http://catlikecoding.com/unity/tutorials/curves-and-splines/) and after finishing it I came up with an idea. I would like to spawn the items from the SplineDecorator at the same distance from the spline at both sides of the spline. I have tried duplicating the for loop and instantiating the newItem twice but at different position but it doenst work as I want to. Here's the Spline Decorator script that takes a spline and then Instantiate some items along the its path
using UnityEngine;
public class SplineDecorator : MonoBehaviour
{
public BezierSpline spline;
public int frequency;
public float distanceBetweenItems;
public bool lookForward;
public Transform[] items;
private void Awake()
{
if (frequency <= 0 || items == null || items.Length == 0)
{
return;
}
float stepSize = 1f / (frequency * items.Length);
for (int p = 0, f = 0; f < frequency; f++)
{
for (int i = 0; i < items.Length; i++, p++)
{
Transform item = Instantiate(items[i]) as Transform;
Vector3 position = spline.GetPoint(p * stepSize);
item.transform.localPosition = new Vector3(position.x + distanceBetweenItems, position.y, position.z);
if (lookForward)
{
item.transform.LookAt(position + spline.GetDirection(p * stepSize));
}
item.transform.parent = transform;
}
}
}
}
You don't define exactly what "at both sides" means to you, so I'm not sure if this is what you're looking for, but maybe this will get you on the right track.
I replaced the inner loop with the following and got a sort of "race track" feel with distanceBetweenItems = 2:
Transform item = Instantiate(items[i]) as Transform;
Transform item2 = Instantiate(items[i]) as Transform;
Vector3 position = spline.GetPoint(p * stepSize);
Vector3 direction = spline.GetDirection(p * stepSize);
Vector3 cross = Vector3.Cross(direction, Vector3.up);
Vector3 delta = cross.normalized * (distanceBetweenItems/2);
item.transform.localPosition = new Vector3(position.x + delta.x, position.y + delta.y, position.z + delta.z);
item2.transform.localPosition = new Vector3(position.x - delta.x, position.y - delta.y, position.z - delta.z);
if (lookForward)
{
item.transform.LookAt( position + spline.GetDirection(p * stepSize));
item2.transform.LookAt(position + spline.GetDirection(p * stepSize));
}
item.transform.parent = transform;
item2.transform.parent = transform;
What I've done here is use Vector3.Cross() (Unity Documentation) to find the line perpendicular to both the spline and Vector3.up. Then I calculate how far along that line to go by normalizing it and multiplying it by half of distanceBetweenItems. (So that, combined, they're distanceBetweenItems apart.)
If you instead wanted something like tracing the wingtips of a plane flying that spline, you'll need to replace the Vector.up above. The simplest way is to replace it with the 'binormal vector', though there are issues with that. This pdf I just found talks a little about it, and might get you on the right path.
(Goofing around, I was able to get a reasonable approximation by replacing the Vector3 cross = ... line with Vector3 cross = Vector3.Cross(direction, direction - spline.GetDirection(p * stepSize + 0.01f));, though it's screwy on sharp bends, non-mirrored vertices, and at the start/end loop.)

Categories