How can I generate stairs? - c#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GenerateStairs : MonoBehaviour
{
public GameObject stairsPrefab;
public int delay = 3;
public int stairsNumber = 5;
public int stairsHeight = 0;
public Vector3 stairsPosition;
public Vector2 stairsSize;
// Use this for initialization
void Start ()
{
StartCoroutine(BuildStairs());
}
// Update is called once per frame
void Update ()
{
}
private IEnumerator BuildStairs()
{
for (float i = 0; i <= stairsSize.x; i++)
{
for (float k = 0; k <= stairsSize.y; k++)
{
stairsPosition = new Vector3(i, stairsHeight, k);
GameObject stairs = Instantiate(stairsPrefab, stairsPosition, Quaternion.identity);
stairs.transform.localScale = new Vector3(stairsSize.x, 1 , stairsSize.y);
stairsHeight += 1;
yield return new WaitForSeconds(delay);
}
}
}
private void CalculateNextStair()
{
}
}
I messed it up. For example I want to build 5 stairs but the loops are over the stairs size and not number of stairs.
Second it's creating 10 sets of stairs not 5 stairs:
Another problem is how can I make that each stair will be build slowly ? Now it's just Instantiate slowly with delay but how can I generate each stair with delay?
Screenshot of the script inspector:
My current code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GenerateStairs : MonoBehaviour
{
public GameObject stairsPrefab;
public float delay = 0.3f;
public int stairsNumber = 5;
public int stairsPositions = 0;
public int stairsStartPositionHeight = 0;
public float stairsScalingHaight = 1;
public Vector2 stairsPosition;
public Vector2 stairsSize;
// Use this for initialization
void Start()
{
StartCoroutine(BuildStairs());
}
// Update is called once per frame
void Update()
{
}
private IEnumerator BuildStairs()
{
for (float i = 0; i <= stairsNumber; i++)
{
// x=0f, y=z=stairsHeight
stairsPosition = new Vector3(0f, stairsPositions, stairsPositions);
GameObject stairs = Instantiate(
stairsPrefab,
stairsPosition,
Quaternion.identity);
stairs.transform.localScale = new Vector3(
stairsSize.x,
stairsScalingHaight,
stairsSize.y);
stairsStartPositionHeight += 1;
yield return new WaitForSeconds(delay);
}
}
private void CalculateNextStair()
{
}
}

There's no reason to loop over the size of the stairs at all; you want to loop over stairsNumber, which is yet unused in your code.
Also, you don't need to change the x component of your stairs' positions. Keep it at 0f (or whatever you need).
The y and z components of your stairs positions (relative to the starting point) should both be factors of stairHeight. In this particular case, you want them to be equal to stairHeight, so that you get "square" step shapes.
private IEnumerator BuildStairs()
{
for (int i = 0; i <= stairsNumber ; i++) {
// x=0f, y=z=stairsHeight
stairsPosition = new Vector3(0f, stairsHeight, stairsHeight);
GameObject stairs = Instantiate(
stairsPrefab,
stairsPosition,
Quaternion.identity);
stairs.transform.localScale = new Vector3(
stairsSize.x,
1f ,
stairsSize.y);
stairsHeight += 1f;
yield return new WaitForSeconds(delay);
}
}
If you change stairSize to be a Vector3, then you can just use stairSize directly as the localScale, and increment stairsHeight by stairsSize.y instead of just 1f.
If you want to offset the starting position of your stairs, you need to include an offset. I recommend keeping it separate from the height counter until you need to add them.
Also, if you want to have rectangular sized steps, keep a widthFactor to multiply by the height to find how far each step moves horizontally.
Combining these changes might look like this:
Vector3 stairSize;
float stepWidthFactor=1f;
Vector3 stairsStartPosition;
private IEnumerator BuildStairs() {
for (int i = 0; i <= stairsNumber ; i++) {
stairsPosition = new Vector3(
stairsStartPosition.x,
stairsStartPosition.y + stairsHeight,
stairsStartPosition.z + stairsHeight*stepWidthFactor);
GameObject stairs = Instantiate(
stairsPrefab,
stairsPosition,
Quaternion.identity);
stairsHeight += stairsSize.y;
stairs.transform.localScale = stairSize;
yield return new WaitForSeconds(delay);
}
}

Related

Set behaviours(script a) controlled by booleans(script b) based on Time.time (flocking system)

I am working on a flocking system in Unity and am new to c#. I am working with 2 scripts - 1 that manages the overall flock (FlockTest) and the other that manages particle behaviour (FlockParticleBehaviour). I have followed a tutorial which has public boolean values that control seeking behaviour in FlockParticleBehaviour through FlockTest. In play mode, I can toggle these booleans to change the goal seeking behaviour. However, I want to automate this toggling based on time (To add it to an AR session). I have added an if statement to void Update() in the FlockTest and when I hit play, the seekGoal and obedient boolean boxes switch on and off but nothing happens to the particles. I have tried using an invoke method which didn't work(no errors but boxes dont switch on and off) and thought about trying a coRoutine but I am not sure this will work since I don't want to stop and start my script. I am at a loss as to how to get the particles obeying the boolean in update. Am I meant to be referencing in my particle behaviour script's flock function? Very new so would love some help if anyone knows a better way forward!
FlockTest script (contains if statement)
using System.Collections.Generic;
using UnityEngine;
public class FlockTest : MonoBehaviour
{
public GameObject[] particles;
public GameObject particlePrefab;
public int particleCount = 10;
public Vector3 range = new Vector3(5,5,5);
public Vector3 innerLimit = new Vector3(1,1,1);
public bool seekGoal = true;
public bool obedient = true;
public bool willful = false;
[Range(0, 200)]
public int neighbourDistance =50;
[Range(0,2)]
public float maxForce = 0.5f;
[Range(0,5)]
public float maxvelocity = 2.0f;
// Start is called before the first frame update
void Start()
{
int time = (int)Time.time;
particles = new GameObject[particleCount];
for(int i = 0; i < particleCount; i++)
{
Vector3 particlePos = new Vector3(Random.Range(-range.x, range.x), Random.Range(-range.y, range.y), Random.Range(-range.z, range.z));
particles[i] = Instantiate(particlePrefab, this.transform.position + particlePos, Quaternion.identity) as GameObject;
particles[i].GetComponent<FlockParticleBehaviour>().manager = this.gameObject;
}
}
void Update()
// the toggles in the inspector are changing but nothing is happening with the particles.
{
int time = (int)Time.time;
if(time == 3f) {
seekGoal = false;
obedient = false;
willful = true;
}
if(time == 6f)
{
seekGoal = true;
obedient = true;
willful = false;
}
}
}
FlockParticleBehaviour script
using System.Collections.Generic;
using UnityEngine;
public class FlockParticleBehaviour : MonoBehaviour
{
public GameObject manager;
public Vector3 location = Vector3.zero;
public Vector3 velocity;
Vector3 goalPos = Vector3.zero;
Vector3 currentForce; //this is a current force position. pushes particle around by adding all the other forces
// Start is called before the first frame update
void Start()
{
velocity = new Vector3(Random.Range(0.01f, 0.1f), Random.Range(0.01f, 0.1f), Random.Range(0.01f, 0.1f));
location = new Vector3(this.gameObject.transform.position.x, this.gameObject.transform.position.y, this.gameObject.transform.position.z);
}
Vector3 seek(Vector3 target)
{
return(target - location);
}
void applyForce(Vector3 f)
{
Vector3 force = new Vector3(f.x, f.y, f.z);
if(force.magnitude > manager.GetComponent<FlockTest>().maxForce)
{
force = force.normalized;
force *= manager.GetComponent<FlockTest>().maxForce;
}
this.GetComponent<Rigidbody>().AddForce(force);
if(this.GetComponent<Rigidbody>().velocity.magnitude > manager.GetComponent<FlockTest>().maxvelocity)
{
this.GetComponent<Rigidbody>().velocity = this.GetComponent<Rigidbody>().velocity.normalized;
this.GetComponent<Rigidbody>().velocity *= manager.GetComponent<FlockTest>().maxvelocity;
}
Debug.DrawRay(this.transform.position, force, Color.white);
}
Vector3 align()
{
float neighbourdist = manager.GetComponent<FlockTest>().neighbourDistance;
Vector3 sum = Vector3.zero;
int count = 0;
foreach (GameObject other in manager.GetComponent<FlockTest>().particles)
{
if(other == this.gameObject) continue;
float d = Vector3.Distance(location, other.GetComponent<FlockParticleBehaviour>().location);
if (d < neighbourdist) {
sum += other.GetComponent<FlockParticleBehaviour>().velocity;
count++;
}
}
if (count >0)
{
sum /= count;
Vector3 steer = sum - velocity;
return steer;
}
return Vector3.zero;
}
Vector3 cohesion()
{
float neighbourdist = manager.GetComponent<FlockTest>().neighbourDistance;
Vector3 sum = Vector3.zero;
int count = 0;
foreach (GameObject other in manager.GetComponent<FlockTest>().particles)
{
if(other == this.gameObject) continue;
float d = Vector3.Distance(location, other.GetComponent<FlockParticleBehaviour>().location);
if(d < neighbourdist)
{
sum += other.GetComponent<FlockParticleBehaviour>().location;
count++;
}
}
if (count > 0)
{
sum /= count;
return seek(sum);
}
return Vector3.zero;
}
void flock()
{
location = this.transform.position;
velocity = this.GetComponent<Rigidbody>().velocity;
if(manager.GetComponent<FlockTest>().obedient && Random.Range(0,50) <=1)
{
Vector3 ali = align();
Vector3 coh = cohesion();
Vector3 gl;
if(manager.GetComponent<FlockTest>().seekGoal)
{
gl = seek(goalPos);
currentForce = gl + ali +coh;
}
else
currentForce = ali + coh;
currentForce = currentForce.normalized;
}
if(manager.GetComponent<FlockTest>().willful && Random.Range(0,50)<=1)
{
if(Random.Range(0,50)<1) //change direction
currentForce = new Vector3(Random.Range(0.01f, 0.1f), Random.Range(0.01f, 0.1f),Random.Range(0.01f, 0.1f));
}
applyForce(currentForce);
}
// Update is called once per frame
void Update()
{
flock();
goalPos = manager.transform.position;
}
}
Several points:
it is much easier and cleaner to set your flock manager directly as FlockTest, not GameObject to avoid GetComponent calls.
I cannot understand what you want to achieve by calling (int)Time.time and comparing it later with 3 and 6. Time.time returns the number of seconds that passed from the start of the application. So your code in Update method of FlockTest script will not have any chance to be called after the seventh second of your game passed. So obedient will always be true and willful will always be false after the seventh second.
Your Random.Range(0, 50) <= 1 is quite a low chance. It will return an int value from 0 to 49, so it is only a 2% chance that your changes in FlockTest will apply to FlockParticleBehaviour instance. Is it what you wanted to get? You can try to remove this random from the if statement to make this chance 100% and check if this is an issue.
Right now it seems like the chance of changing something is too low to see it in several seconds of the game. As I've said above, after the seventh second your bool values will never change.

How can I move multiple objects in multiple positions using lerp and using speed?

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Movements : MonoBehaviour
{
public List<Transform> objectsToMove = new List<Transform>();
public List<Vector3> positions = new List<Vector3>();
public int amountOfPositions = 30;
public int minRandRange, maxRandRange;
public bool randomPositions = false;
public bool generateNewPositions = false;
public float duration = 5f;
public bool pingPong = false;
private void Start()
{
if (generateNewPositions || (positions.Count == 0 && amountOfPositions > 0))
GeneratePositions();
StartCoroutine(MoveBetweenPositions(duration));
}
private void GeneratePositions()
{
for(int i = 0; i < amountOfPositions; i++)
{
if (randomPositions)
{
var randPosX = UnityEngine.Random.Range(minRandRange, maxRandRange);
var randPosY = UnityEngine.Random.Range(minRandRange, maxRandRange);
var randPosZ = UnityEngine.Random.Range(minRandRange, maxRandRange);
positions.Add(new Vector3(randPosX, randPosY, randPosZ));
}
else
{
positions.Add(new Vector3(i, i, i));
}
}
}
IEnumerator MoveBetweenPositions(float duration)
{
for(int i = 0; i < positions.Count; i++)
{
float time = 0;
Vector3 startPosition = objectsToMove[0].position;
while(time < duration)
{
transform.position = Vector3.Lerp(startPosition, positions[i], time / duration);
time += Time.deltaTime;
yield return null;
}
objectsToMove[0].position = positions[i];
}
}
}
Now I have one object to move in the editor a cube. but if I have for example 20 cubes and I want that the first cube will move to the first position(waypoint) then after duration of 5 or any duration the next cube will move to the first position then the first cube will move to the second position and son all the cubes will move to the next position/s with speed and duration.
So each 5 seconds another cube start to move between the positions and each cube will move smooth with speed value between the positions.
For example the duration is 5 and also the speed is 5.
Later I want to use enum or something like that to be able to choose modes for example ping pong or backward or stop(each cube will stop in the last waypoint) or continue so the cubes will continue move between the waypoints nonstop.
Seems like this:
while(time < duration)
{
transform.position = Vector3.Lerp(startPosition, positions[i], time/duration);
time += Time.deltaTime;
yield return null;
}
should be this:
while(time < duration)
{
objectsToMove[i].position = Vector3.Lerp(startPosition, positions[i], time / duration);
time += Time.deltaTime;
yield return null;
}
if i understood your problem right.

Why when switching text on TextMesh it's switching only for one object?

The script is attached to empty gameobject :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SwitchText : MonoBehaviour
{
public GameObject[] objectsTexts;
private bool isDisplaying = false;
private Vector3[] lastFwd;
private float curAngleX = 0;
private GameObject[] objectsWithText;
public void Init()
{
objectsWithText = GameObject.FindGameObjectsWithTag("ObjectToAddText");
lastFwd = new Vector3[objectsWithText.Length];
for(int i = 0; i < objectsWithText.Length; i++)
{
lastFwd[i] = objectsWithText[i].transform.forward;
}
}
private void Update()
{
if (isDisplaying == false)
{
StartCoroutine(UpdateDisplayPhrase(objectsWithText));//objectsTexts[i].name, objectsWithText[i].GetComponent<TextMesh>()));
}
}
private bool myApproximation(float a, float b, float tolerance)
{
return (Mathf.Abs(a - b) < tolerance);
}
private IEnumerator UpdateDisplayPhrase(GameObject[] objects)//string text, TextMesh t)
{
// block multiple calls
//if (isDisplaying) yield break;
isDisplaying = true;
for (int i = 0; i < objectsWithText.Length; i++)
{
var curFwd = objectsWithText[i].transform.forward;
// measure the angle rotated since last frame:
var ang = Vector3.Angle(curFwd, lastFwd[i]);
if (myApproximation(ang, 179f, 1f) == true)
{
var t = objectsWithText[i].GetComponent<TextMesh>();
var text = objectsTexts[i].name;
// you can simply yield IEnumerators so they are executed and at the same time
// wait until finished
yield return StartCoroutine(FadeTextTo(0, 0.6f, t));//FadeTextToZeroAlpha(1, t);
t.text = text;
// NOTE: you pass in 0
// -> DIVIDING BY 0 ?!
yield return StartCoroutine(FadeTextTo(1, 0.6f, t));//FadeTextToFullAlpha(0, t);
}
// when done reset the blocking flag
//isDisplaying = false;
}
}
public IEnumerator FadeTextTo(float targetAlpha, float maxDuration, TextMesh textMesh)
{
// more efficient to get both colors beforehand
var fromColor = textMesh.color;
var toColor = new Color(fromColor.r, fromColor.g, fromColor.b, targetAlpha);
// this is optional ofcourse but I like to do this in order to
// always have the same fading speed even if it is already slightly faded into one direction
var actualDuration = maxDuration * Mathf.Abs(fromColor.a - toColor.a);
var passedTime = 0f;
while (passedTime < actualDuration)
{
var lerpFactor = passedTime / actualDuration;
// now the huge advantage is that you can add ease-in and -out if you like e.g.
//lerpFactor = Mathf.SmoothStep(0, 1, lerpFactor);
textMesh.color = Color.Lerp(fromColor, toColor, lerpFactor);
// avoid overshooting
passedTime += Mathf.Min(Time.deltaTime, actualDuration - passedTime);
yield return null;
}
// just to be sure in the end always set it once
textMesh.color = toColor;
}
}
I'm calling the Init method in another script in the Start after adding text first time to the gameobjects.
Using a break point in the SwitchText script on the line :
StartCoroutine(UpdateDisplayPhrase(objectsWithText));
objectsWithText contain 3 objects also objectsTexts contain 3 objects.
The problem is that the objectsWithText not rotating yet bit in the Update it's starting the Coroutine already.
The main goal is to switch the text on the TextMesh objects to the names of the objects in objectsTexts.
This script rotate the objects :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Rotate : MonoBehaviour
{
public GameObject[] objectsToRotate;
public float duration = 5f;
private void OnMouseDown()
{
StartCoroutine(StartRotationOfObjects());
}
private IEnumerator StartRotationOfObjects()
{
for (int i = 0; i < objectsToRotate.Length; i++)
{
// Random wait period before rotation starts
if (i == 0)
{
yield return new WaitForSeconds(0);
}
else
{
yield return new WaitForSeconds(Random.Range(0, 2f));
}
StartCoroutine(Rotates(objectsToRotate[i].transform, duration));
}
}
private IEnumerator Rotates(Transform objectToRotate, float duration)
{
Quaternion startRot = objectToRotate.rotation;
float t = 0.0f;
while (t < duration)
{
t += Time.deltaTime;
objectToRotate.rotation = startRot * Quaternion.AngleAxis(t / duration * 360f, Vector3.up);
yield return null;
}
objectToRotate.rotation = startRot;
}
}

Unity Moving Instantiated Objects

The code below works Except for the Instancated objects moving on their own. I want my Instancated objects to move back and fourth between pointA and pointB at a speed 0.5f.
Note: Im not trying to use the commented code in Start() and Update() because this file is attached to the camera.
With current code: My objectList objects are moving as expected just not their instantiated objects. I would like the Instantiated objects to move like ping-pong with their off-set
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObjectsSpawner : MonoBehaviour {
public GameObject[] objectList;
public GameObject[] objectSpawns;
int val;
public float speed = 0.5f;
Vector3 pointA;
Vector3 pointB;
// Use this for initialization
void Start () {
val = PlayerPrefs.GetInt("CannonPowerVal");
addToList();
for (int i = 1; i < objectSpawns.Length; i++){
pointA = new Vector3(-3.8f, objectSpawns[i].transform.localPosition.y, 0);
pointB = new Vector3(3.8f, objectSpawns[i].transform.localPosition.y, 0);
}
//pointA = new Vector3(-3.8f, transform.localPosition.y, 0);
//pointB = new Vector3(3.8f, transform.localPosition.y, 0);
}
// Update is called once per frame
void Update()
{
//PingPong between 0 and 1
float time = Mathf.PingPong(Time.time * speed, 1);
//transform.position = Vector3.Lerp(pointA, pointB, time);
for (int i = 1; i < objectSpawns.Length; i++)
{
objectSpawns[i].transform.position = Vector3.Lerp(pointA, pointB, time);
}
}
public void addToList(){
objectSpawns = new GameObject[val];
int max = objectList.Length;
int counter = 8; // set first object out of screen sight
// Adds Scene Objects from objectList to objectSpawns
// Size of SceneObjects determined by CannonPowerVal
for (int i = 0; i < PlayerPrefs.GetInt("CannonPowerVal"); i++){
objectSpawns.SetValue(objectList[Random.Range(0, max)], i);
// Random x spawn(-2.8f, 2.8f)
Instantiate(objectSpawns[i], new Vector2(transform.localPosition.x + Random.Range(-2.8f,2.8f), transform.localPosition.y + counter), Quaternion.identity);
counter = counter + 5;
}
}
}
after instantiating the object, you forgot to add the reference to the list of objectSpawns.
In addToList() method do:
GameObject object = Instantiate(objectSpawns[i],...);
objectSpawns.Add(object)

How can I add/destroy new objects to existing formation?

In the manager:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FormationsManager : MonoBehaviour
{
public Transform squadMemeberPrefab;
public int numberOfSquadMembers = 20;
public int columns = 4;
public int gaps = 10;
public Formations formations;
private int numofmembers;
// Use this for initialization
void Start()
{
numofmembers = numberOfSquadMembers;
formations.Init(numberOfSquadMembers, columns, gaps);
GenerateSquad();
}
// Update is called once per frame
void Update()
{
if (numofmembers != numberOfSquadMembers)
{
GenerateSquad();
}
}
private void GenerateSquad()
{
Transform go = squadMemeberPrefab;
for (int i = 0; i < formations.newpositions.Count; i++)
{
go = Instantiate(squadMemeberPrefab);
go.position = formations.newpositions[i];
go.tag = "Squad Member";
go.transform.parent = gameObject.transform;
}
}
}
And the Formations script:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Formations : MonoBehaviour
{
public List<Vector3> newpositions;
private int numberOfSquadMembers;
private int columns;
private int gaps;
private List<Quaternion> quaternions;
private Vector3 FormationSquarePositionCalculation(int index)
{
float posX = (index % columns) * gaps;
float posY = (index / columns) * gaps;
return new Vector3(posX, posY);
}
private void FormationSquare()
{
newpositions = new List<Vector3>();
quaternions = new List<Quaternion>();
for (int i = 0; i < numberOfSquadMembers; i++)
{
Vector3 pos = FormationSquarePositionCalculation(i);
Vector3 position = new Vector3(transform.position.x + pos.x, 0, transform.position.y + pos.y);
newpositions.Add(position);
}
}
public void Init(int numberOfSquadMembers, int columns, int gaps)
{
this.numberOfSquadMembers = numberOfSquadMembers;
this.columns = columns;
this.gaps = gaps;
FormationSquare();
}
}
What I want to do is in the FormationsManager in the Update not only just calling GenerateSquad but to add the new once to the last/next position of the existing already formation.
void Update()
{
if (numofmembers != numberOfSquadMembers)
{
GenerateSquad();
}
}
If the value of numberOfSquadMembers is 20 first time and then I changed it to 21 add new object to the end of the formation and same if I change the value of numberOfSquadMembers for example from 20 to 19 or from 21 to 5 destroy the amount of objects from the end and keep the formation shape.
The soldiers the last line is on the right side.
So if I change the value to add more then add it to the right and if I change to less destroy from the right side. The most left line of soldiers is the first.
It is possible if you keep GameObject instances inside FormationsManager class, and then reuse them in GenerateSquad method.
In FormationsManager class, add and modify code as follows.
public GameObject squadMemeberPrefab;
List<GameObject> SquadMembers = new List<GameObject>();
void Update()
{
if (numofmembers != numberOfSquadMembers)
{
numofmembers = numberOfSquadMembers;
formations.Init(numberOfSquadMembers, columns, gaps);
GenerateSquad();
}
}
private void GenerateSquad()
{
Transform go = squadMemeberPrefab;
List<GameObject> newSquadMembers = new List<GameObject>();
int i = 0;
for (; i < formations.newpositions.Count; i++)
{
if (i < SquadMembers.Count)
go = SquadMembers[i];
else
{
go = Instantiate(squadMemeberPrefab);
newSquadMembers.Add(go);
}
go.position = formations.newpositions[i];
go.tag = "Squad Member";
go.transform.parent = gameObject.transform;
}
for (; i < SquadMembers.Count; i++)
Destroy(SquadMembers[i]);
SquadMembers = newSquadMembers;
}
However, I recommend you to consider GameObject Pool (Object Pool), which can thoroughly resolve such object recycle problem. For this purpose, you can use ClientScene.RegisterSpawnHandler. Go to this Unity Documentation page and search text "GameObject pool". You can see an example code there.

Categories

Resources