Random Script repeatedly going down - c#

I am busy building a platformer(2d). And I really want procedural generation. I think that I came up with a smart system for spawning the platforms randomly(up for debate). But my platforms keep on picking to randomly spawn down. No matter how many times I restart the game
eg :
here is my code
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlatformSpawner : MonoBehaviour
{
public GameObject[] platform;
public Vector2 SpawnGap;
public float randomspawn;
public float VerticalRandomness;
public float queueTime = 1.5f;
private float time = 0;
public Vector2 startspawn;
public GameObject flag;
Vector2 nextspawn;
Vector2 nextspawncentre;
Vector3 prevspawn;
public int platamount;
int amountspawned;
bool alreadyspawnedflag;
// Start is called before the first frame update
void Start()
{
platamount = Random.Range(5, 15); // random amount of platforms in level
alreadyspawnedflag = false;
}
// Update is called once per frame
void Update()
{
if (time > queueTime) // just for quality of life(so that if I maybe have 10000 platforms I can delay the spawning)
{
if(platamount >= amountspawned) // random amount of platform
{
GameObject go = Instantiate( platform[ Random.Range (0, platform.Length) ] ); // instantiating random object from list, to make it even more random(objects assigned in inspector)
Vector2 randomrange = new Vector2(Random.Range(1f, -1f) * randomspawn, Random.Range(2f, -0.25f/*perferes up, but stil not working*/) * VerticalRandomness);//calculates random position
Debug.Log(randomrange);// it is indeed choosing random pos, even on y but still giong down
nextspawncentre = new Vector2(prevspawn.x + SpawnGap.x + startspawn.x, prevspawn.y + SpawnGap.y + startspawn.y); // calculates next spawn without randomness
nextspawn = new Vector2(nextspawncentre.x + randomrange.x, nextspawncentre.y + randomrange.y);// adds randomness
go.transform.position = transform.position + new Vector3(nextspawn.x, nextspawn.y, 0);// places object at nextspawn variable
time = 0;// resets timer
prevspawn = go.transform.position;
amountspawned += 1;
randomrange = new Vector2(0, 0);// tried reseting the random, but to no success
}
else if(!alreadyspawnedflag)// almost same logic as before just for the flag to reload scene(handeled in another script)
{
GameObject spawnedflag = Instantiate(flag);
spawnedflag.transform.position = new Vector3(nextspawncentre.x + 1, 15, 0f);
alreadyspawnedflag = true;
}
}
time += Time.deltaTime;
}
}
look at comments explained, everything there.
In advance thanks for the help.
ps: Platforms do not have rigidbodies, just sprite renderer and boxcollider2d.

I think the issue is related to your startSpawn being a negative value (at least it looks like that from your first platform). So even if the Y is positive in the randomrange it might be overruled by the negative startSpawn Y value.
Actually I think you don't even add your startSpawn every time at all but rather only using it as the initial position
I think you should simply do e.g.
private bool firstPlatform = true;
and
...
if(firstPlatform)
{
nextspawn = startspawn;
firstPlatform = false;
}
else
{
nextspawncentre = prevspawn + SpawnGap;
nextspawn = nextspawncentre + randomrange;
}
...

Related

Once every 2 seconds, move the object to random coordinates

I want my enemy to move to a random X position between -2.4 and 2.4 every 2 seconds, but he moves in jerks and for a very small distance. I suspect that the problem is in speed * Time.deltaTime, but I don't know how to fix it
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Random=UnityEngine.Random;
public class EnemyController : MonoBehaviour
{
public Transform enemy;
private float random;
[SerializeField] private float speed = 10f;
void Start()
{
StartCoroutine(MoveEnemy());
}
IEnumerator MoveEnemy()
{
while (!MainGame.lose)
{
yield return new WaitForSeconds(2);
random = Random.Range(-2.4f, 2.4f);
Debug.Log(random);
enemy.transform.position = Vector2.MoveTowards(enemy.position,
new Vector3(random, enemy.position.y),
speed * Time.deltaTime);
}
}
}
You are moving the enemy only once every two seconds. To me it sounds like you rather want to constantly move the enemy but only choose a new target position every 2 seconds so e.g.
// you can directly make Start a Coroutine by changing its return type
IEnumerator Start()
{
while (!MainGame.lose)
{
var random = Random.Range(-2.4f, 2.4f);
var targetPosition = new Vector3(random, enemy.position.y);
for(var time = 0f; time < 2f; time += Time.deltaTime)
{
// need to check within here since otherwise it would always try to finish the for loop
if(MainGame.lose)
{
// quits from this entire routine
yield break;
}
enemy.position = Vector2.MoveTowards(enemy.position, targetPosition, speed * Time.deltaTime);
// this basically means continue in the next frame
yield return null;
if(enemy.position == targetPosition)
{
// quits the for loop and basically starts again from var random = ...
break;
}
}
}
}
So for up to two seconds you try to reach this random target with a constant linear movement and you select a new random target position if you either exceed 2 seconds or reached the target before that

Change public Transforms based on collision. Change line renderer points based on public transforms

I'm pretty new and I can't seem to get this to work. Players have a long pole and if they poke certain objects it starts a string-like connection to the next one they poke. These objects are tagged as "PokableObjects", and to poke players will click. I'm going to have hundreds of different pokable objects, and I want the script on the pole to work for all of them.
I think I'm misunderstanding how to reference only the objects being poked. I want the points of a Bezier Curve script, which are public Transforms, to adapt and become whatever "PokableObject" the player clicks.
This is my script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BCurve : MonoBehaviour
{
//curved line renderer stuff
private LineRenderer lineRenderer;
public Transform p0;
public Transform p1;
public Transform p2;
//Object name detection stuff
bool m_Started;
public LayerMask m_LayerMask;
//Respawn Stuff
private float clickCounter;
public GameObject newPoker;
void Start()
{
lineRenderer = GetComponent<LineRenderer>();
m_Started = true;
clickCounter = 0;
}
private void OnTriggerStay(Collider other)
{
if (other.tag == "PokableObject")
{
Collider[] hitColliders = Physics.OverlapBox(gameObject.transform.position, transform.localScale / 2, Quaternion.identity, m_LayerMask);
if (Input.GetMouseButtonDown(0) && (clickCounter == 0))
{
p0 = Collider.gameObject.position;
clickCounter++;
}
else
{
p2 = Collider.gameObject.position;
//find midpoint between p0 & p2 then lower it's Y coordinate by 1
p1 = ((p0.position.x + p2.position.x) * .05f, ((p0.position.y + p2.position.y) * .05f) - 1), (p0.position.z + p2.position.z) * .05f;
//disable current object and spawn a new one so players can repeat
Instantiate(newPoker, transform.position, Quaternion.Euler(0, 0, 0));
GetComponent<BCurve>().enabled = false;
}
}
}
void Update()
{
DrawQuadraticBezierCurve(p0.position, p1.position, p2.position);
}
void DrawQuadraticBezierCurve(Vector3 p0, Vector3 p1, Vector3 p2)
{
lineRenderer.positionCount = 200;
float t = 0f;
Vector3 B = new Vector3(0, 0, 0);
for (int i = 0; i < lineRenderer.positionCount; i++)
{
B = (1 - t) * (1 - t) * p0 + 2 * (1 - t) * t * p1 + t * t * p2;
lineRenderer.SetPosition(i, B);
t += (1 / (float)lineRenderer.positionCount);
}
}
}
All help is much appreciated.
Thanks
First of all never compare float values using ==! Due to floating point (im)precision this might fail even though logically you think it would match.
E.g.
0.2f * 5f / 10f == 1.0f
is not necessarily true because it might actually be 1.0000001 or 0.9999999.
This said anyway it makes no sense to use a float for a counter. You rather want to use
int clickCounter;
Then you have a bunch of , there when calculating p1. Seems like this should have been wrapped in a p1 = new Vector3( .... )
However anyway the p1 you calculate is no Transform but would rather be a Vector3 position. You don't need this as a field! Simply recalculate it later given the two Transforms p0 and p2 within DrawQuadraticBezierCurve and then you can simply make it
var p1 = (p0.position + p2.position) * .05f - Vector3.up;
There is no need for that LayerMask and OverlapBox which result you are not using anywhere at all. You already have a tag to check your hit
Collider.gamObject makes no sense. What you want is access the Transform of the object you are colliding with which simply is the other.Transform
There is no need for GetComponent<BCurve>. This component already is the BCurve so you could simply use
enabled = false;
However, as soon as you get the second point you are disabling the component so Update will never be called again.
Either you want to draw the line only once with the positions in that moment so simply call DrawQuadraticBezierCurve right away and remove your entire Update method.
Or if your objects might continue moving and you want to continuously update the line then keep Update but keep this component enabled.
In both cases you should only call DrawQuadraticBezierCurve once you already have both Transforms.
So all together it could look like this
public class BCurve : MonoBehaviour
{
//curved line renderer stuff
[SerializeField] private LineRenderer lineRenderer;
public Transform start;
public Transform end;
//Respawn Stuff
public GameObject newPoker;
// I would make this adjustable via the Inspctor
[SerializeField] private int positionCount = 200;
private void Start()
{
if(!lineRenderer) lineRenderer = GetComponent<LineRenderer>();
}
private void OnTriggerStay(Collider other)
{
if(start && end)
{
return;
}
// in general rather use CompareTag instead of ==
// the second silently fails in case of typos and is slightly slower
if (other.CompareTag("PokableObject"))
{
// Then you ALWAYS want to check fr the mouse click, not only
// if the counter is 0
if(Input.GetMouseButtonDown(0))
{
// you rather want to check here
if (!start)
{
start = other.transform;
}
// I would add a check though to not enable to click on the same object twice
else if (other.transform != start)
{
end = other.transform;
// If you anyway want the line to be drawn only ONCE with the current positions
// then directly call this here and delete Update completely
//DrawQuadraticBezierCurve(start.position, end.position);
//enabled = false;
// or even
//Destroy(this);
Instantiate(newPoker, transform.position, Quaternion.Euler(0, 0, 0));
}
}
}
}
private void Update()
{
if(start && end)
{
DrawQuadraticBezierCurve(start.position, end.position);
}
}
private void DrawQuadraticBezierCurve(Vector3 p0, Vector3 p2)
{
lineRenderer.positionCount = positionCount;
var inversePositionCount = 1f / positionCount;
var t = 0f;
var p1 = (p0.position + p2.position) * .05f - Vector3.up;
var positions = new Vector3[positionCount];
for (int i = 0; i < lineRenderer.positionCount; i++)
{
var B = (1 - t) * (1 - t) * p0 + 2 * (1 - t) * t * p1 + t * t * p2;
positions[i] = B;
t += inversePositionCount;
}
// Note: It is WAY cheaper to call this and set all positions at once
lineRenderer.SetPositions(positions);
}
}
derHugo gave an amazing answer, and could not have figured this out otherwise! I added a few things and it's working perfectly. The first thing was to make sure a new LineRender object is being created which a new poker object would automatically reference and have the start and end points be reset:
private void Start()
{
if (!lineRenderer) lineRenderer = GetComponent<LineRenderer>();
lineRenderer = Instantiate(lineObject, transform.position, Quaternion.Euler(0, 0, 0)).GetComponent<LineRenderer>();
start = null;
end = null;
destroy = false;
}
The second was to move both the destruction of the current poker and the instantiation of a new poker to a LateUpdate, because the line wasn't being rendered:
private void LateUpdate()
{
if (destroy == true)
{
Instantiate(newPoker, transform.position, transform.rotation).transform.SetParent(GameObject.Find("MainCamera").transform);
Destroy(this.gameObject);
}
}
Again, thank you for the help derHugo!

how to spawn objects in unity using two axis but not in a specific section

Hi im working on a character animation / interaction with the environment. Im trying to spawn rocks of different sizes going from the ground to where the gravity direction is applied.
Im using c# for both of my scripts (character_movements_animation.cs & powerUp.cs)
My question is how to spawn objects around my character and not through it.
Im using the code below:
/* Variables Declaration */
public GameObject rock_Small;
public GameObject rock_Medium;
public GameObject rock_Large;
private float posX, posY, posZ;
private bool checkPos = false;
//Use this for initialization
void Start() {
//Empty for now
}
// Update is called once per frame
void Update() {
if (Random.Range(0, 100) < 10) {
checkPos = false;
posX = this.transform.position.x + Random.Range(-5.0f, 5.0f);
posY = this.transform.position.y;
posZ = this.transform.position.z + Random.Range(-5.0f, 5.0f);
if(posX > 3f && posY > 3f){
checkPos = true;
}
if (checkPos == true) {
Vector3 newPos = new Vector3(posX, posY, posZ);
Instantiate(rock_Small, newPos, rock_Small.transform.rotation);
}
}
}
Also see the example in the figure.
Seems like you want to spawn objects around your character, but avoid the character's own volume.
I'd suggest something like this:
var pos = this.transform.position = Random.insideUnitCircle.normlaized * _distance; //how far away from your character. 1, based on your original code
This will create a ring aorund your character. If you want to randomize things further, you can apply smaller random offsets to this value, eg:
var pos = this.transform.position = Random.insideUnitCircle.normlaized * _distance;
pos += Random.insideUnitCircle * _offset; //how wide the ring should be. 0.1 is probably sufficient.

How to add parallax to objects instantiated in play mode and recreated with camera position in Unity 3d

So my endless 2d game has 2 layers of mountains in the background which I want to add parallax on, the Near layer needs to be slower than actor/camera, & the Far to be slowest. The problem is, I can't directly add script of movement to them as they are instantiated in play mode randomly according to the random theme colors, so they are being created one after other from the script below, but I want to add a movement on them on x axis slower than camera speed, while also letting it recreate continuously at the end of last one.
Here's the script creating new mountains :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PathManager : MonoBehaviour {
public static PathManager Instance;
public GameObject CoinFragments;
public GameObject SlideArrow;
public float parallaxSpeed = 5f; //
private GameObject lastPathObject;
private float PathXPosition;
private GameObject lastMountainFar;
private float MountainFarXPosition;
private GameObject lastMountainNear;
private float MountainNearXPosition;
private float StarXPostion;
private float lastCameraX; //
// Use this for initialization
void Start () {
Instance = this;
}
// Update is called once per frame
void Update()
{
}
private void LateUpdate()
{
// Check for new Near Mountain
if (lastMountainNear != null && GamePlayCameraManager.Instance.MainCamera.transform.position.x > lastMountainNear.transform.position.x)
{
this.GenerateNearMountain();
}
// Check for new Far Mountain
if (lastMountainFar != null && GamePlayCameraManager.Instance.MainCamera.transform.position.x > lastMountainFar.transform.position.x)
{
this.GenerateFarMountain();
}
}
// Start Creating Mountains
public void StartMountainCreation(){
MountainNearXPosition = GamePlayCameraManager.Instance.MainCamera.transform.position.x;
MountainFarXPosition = GamePlayCameraManager.Instance.MainCamera.transform.position.x;
this.GenerateNearMountain();
this.GenerateFarMountain();
}
private void GenerateNearMountain(){
Vector3 MountainPosition = new Vector3(MountainNearXPosition - 4f, -3.6f, 10f);
lastMountainNear = Instantiate(ThemeManager.Instance.SelectedMountainNear, MountainPosition, Quaternion.identity);
MountainNearXPosition = MountainNearXPosition + lastMountainNear.GetComponent<Renderer>().bounds.size.x - 0.01f;
//float deltaX = GamePlayCameraManager.Instance.MainCamera.transform.position.x - lastCameraX;
//lastCameraX = GamePlayCameraManager.Instance.MainCamera.transform.position.x;
//lastMountainNear.transform.position += Vector3.right * (deltaX * parallaxSpeed);
}
private void GenerateFarMountain(){
Vector3 MountainPosition = new Vector3(MountainFarXPosition - 4f, -3.6f, 22f);
lastMountainFar = Instantiate(ThemeManager.Instance.SelectedMountainFar, MountainPosition, Quaternion.identity);
MountainFarXPosition = MountainFarXPosition + lastMountainFar.GetComponent<Renderer>().bounds.size.x - 0.01f;
}
Here's my camera movement script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GamePlayCameraManager : MonoBehaviour {
public static GamePlayCameraManager Instance; // Singleton Instance
public Camera MainCamera; // Main Camera
private Vector3 offset;
// Use this for initialization
void Start () {
Instance = this;
MainCamera.transform.position = new Vector3(0, 0, -10);
}
// Update is called once per frame
void Update () {
}
void LateUpdate()
{
if (GameStateManager.GameState == GameState.Set || GameStateManager.GameState == GameState.Playing)
MainCamera.transform.position = new Vector3(this.offset.x + ActorManager.Instance.Actor.transform.position.x + 0.8f, 0, -10);
}
// Fixed Update Method
void FixedUpdate(){
}
public void FindCameraOffset(){
this.offset = ActorManager.Instance.Actor.transform.position - MainCamera.transform.position + new Vector3(1.5f, 0f, 0f);
}
}
First, you should look into pooling your mountain objects so you don't waste cpu and memory endlessly creating them.
Once you have them pooled in your PathManager, you'll need to first know how far the camera moved in the last frame, and then tell the mountains to move according to that. You are going to have to have a reference to your PathManager in your camera Manager.
In your Camera Manager's LateUpdate:
if (GameStateManager.GameState == GameState.Set || GameStateManager.GameState == GameState.Playing) {
Vector3 newPosition = new Vector3(this.offset.x + ActorManager.Instance.Actor.transform.position.x + 0.8f, 0, -10);
float cameraMoveAmount = newPosition.x - MainCamera.transform.position.x;
MainCamera.transform.position = newPosition;
pathManager.MoveMountains(cameraMoveAmount)
}
Then, in your PathManager, use that moveAmount to change the positions of all of the mountains, as well as where the next one will be created. After this point is where you should check for any need to show new mountains, not in the PathManager's LateUpdate. This is because you need to move the mountains before you check to add new ones, and there is no guarantee which happens first if you have them in different LateUpdate calls.
Something like this:
// How fast the mountain layers are going to move in the direction of the camera.
// 0.0f - will not move with the camera, mountains will slide out of frame at normal speed.
// 1.0f - will move with the camera, no movement will be visible in frame.
public float nearMountainMoveSpeed = 0.5;
public float farMountainMoveSpeed = 0.9f;
. . .
public void MoveMountains(float cameraMoveAmount) {
float nearMountainParallaxMove = nearMountainMoveSpeed * cameraMoveAmount;
float farMountainParallaxMove = farMountainMoveSpeed * cameraMoveAmount;
// Move the near mountains & their next placement
MountainNearXPosition += nearMountainParallaxMove;
foreach (GameObject nearMountain in pooledNearMountains) {
nearMountain.transform.position = nearMountain.transform.position + new Vector3(nearMountainParallaxMove,0f,0f);
}
// Check for new Near Mountain
if (lastMountainNear != null && GamePlayCameraManager.Instance.MainCamera.transform.position.x > lastMountainNear.transform.position.x) {
this.GenerateNearMountain();
}
// Move the far mountains & their next placement
MountainFarXPosition += farMountainParallaxMove;
foreach (GameObject farMountain in pooledFarMountains) {
farMountain.transform.position = farMountain.transform.position + new Vector3(farMountainParallaxMove,0f,0f);
}
// Check for new Far Mountain
if (lastMountainFar != null && GamePlayCameraManager.Instance.MainCamera.transform.position.x > lastMountainFar.transform.position.x) {
this.GenerateFarMountain();
}
}
And as an aside, If you end up changing your code and having a single gameobject with all of the farMountains as children and one with all of the nearMountains as children, then you can just move the transform of those parent gameobjects instead of looping through each individual mountain. The amount you would need to move it is the same, of course.

unity2d: randomly change position of gameobject after it has been looped

So, I am making this little game, where the character constantly moves upwards with an autoscroll camera. The character jumps from platform to platform and as soon as a platform or a background tile is nolonger visible, i loop it back up. I assigned a range to my plattforms in which a randomizer choses a value from so that the player gets an individual set of plattforms every time he or she starts the game. the problem is the looping: since i do the randomizing in the start() functions, the random poision of the plattforms is only assigned once and then looped and looped again and again. so the game gets kinda boring after a few loops with is like after 20 seconds :D
Here is my code:
private float randomFloat = 0;
private int subOrAdd = 0;
// Use this for initialization
void Start () {
subOrAdd = Random.Range(1, 10);
randomFloat = Random.Range(0f, 1.4f);
// randomly add or subtract height of object
if (subOrAdd < 6)
{
this.transform.position = new Vector2(transform.position.x, transform.position.y - randomFloat);
}
else if (subOrAdd >= 6)
{
this.transform.position = new Vector2(transform.position.x, transform.position.y + randomFloat);
}
}
Basically, I am having a hardcoded range and then randomly decide to either add or subtract the number that came out of the range. so how would i make it so, that the objects that get looped always ask for a new position? Because start is only called once as you know and even after looping, the position remains the same. I hope I made myself clear here :)
Any help would be awesome!
Here is the the code that loops the platforms:
public class PlattformLooper : MonoBehaviour {
public float spacingBetweenLoops = 0f;
private void OnTriggerEnter2D(Collider2D collider)
{
if (collider.gameObject.tag == "Plattform")
{
Debug.Log("TRIGGERED Plattform!");
float heightOfBGObj = ((BoxCollider2D)collider).size.y;
Vector3 pos = collider.transform.position;
pos.y += heightOfBGObj * (5*5)+spacingBetweenLoops;
collider.transform.position = pos;
}
}
Just extract your randomization logic into a separate method.
void Start () {
RandomizeHeight()
}
public void RandomizeHeight() {
subOrAdd = Random.Range(1, 10);
randomFloat = Random.Range(0f, 1.4f);
// randomly add or subtract height of object
if (subOrAdd < 6)
{
this.transform.position = new Vector2(transform.position.x, transform.position.y - randomFloat);
}
else if (subOrAdd >= 6)
{
this.transform.position = new Vector2(transform.position.x, transform.position.y + randomFloat);
}
}
Then you can call it whenever you want:
public class PlattformLooper : MonoBehaviour {
public float spacingBetweenLoops = 0f;
private void OnTriggerEnter2D(Collider2D collider)
{
if (collider.gameObject.tag == "Plattform")
{
Debug.Log("TRIGGERED Plattform!");
float heightOfBGObj = ((BoxCollider2D)collider).size.y;
Vector3 pos = collider.transform.position;
pos.y += heightOfBGObj * (5*5)+spacingBetweenLoops;
collider.transform.position = pos;
collider.GetComponent<YourComponent>().RandomizeHeight();
}
}

Categories