I'm currently working on an N-Body Simulation to try out some new things, one of which being the event system in Unity.
The problem I'm facing is, although it once worked ( I believe perfectly ), it now only activates around 60 times.
What I'm doing is spawning a given amount of Planets in a loop, each time this happens an Event is triggered which sends the current planet's GameObject to a different script which adds it to a List so that it knows on which GameObjects it needs to do calculations.
Here are the two important snippets of code:
public class PlanetSpawnHandler : MonoBehaviour
{
public int startPlanetCount;
public float spawnRadius;
public GameObject planetPrefab;
public float startForce;
GameObject[] sunArray;
//Event and EventArgs
public event EventHandler<OnPlanetSpawnedArgs> OnPlanetSpawned;
public class OnPlanetSpawnedArgs : EventArgs
{
public GameObject planetArg;
}
//Start is called before the first frame update
void Start()
{
sunArray = GameObject.FindGameObjectsWithTag("Sun");
foreach (GameObject sun in sunArray)
{
OnPlanetSpawned?.Invoke(this, new OnPlanetSpawnedArgs { planetArg = sun });
}
for (int o = 0; o < sunArray.Length; o++)
{
float x = sunArray[o].transform.position.x;
float y = sunArray[o].transform.position.y;
float z = sunArray[o].transform.position.z;
for (int i = 0; i < startPlanetCount; i++)
{
//Calculations for Planet Spawning
Vector3 instantiatePos = new Vector3(x - UnityEngine.Random.Range(-spawnRadius, spawnRadius), y - UnityEngine.Random.Range(-spawnRadius, spawnRadius), z - UnityEngine.Random.Range(-spawnRadius, spawnRadius));
GameObject planet = Instantiate(planetPrefab, instantiatePos, Quaternion.identity);
Rigidbody rb = planet.GetComponent<Rigidbody>();
rb.AddForce(startForce * rb.mass, startForce * rb.mass, startForce * rb.mass);
//Triggering the Event
OnPlanetSpawned?.Invoke(this, new OnPlanetSpawnedArgs { planetArg = planet });
//Debug.Log("Spawned Planet");
}
}
}
And the listener:
public class Attraction: MonoBehaviour
{
List<GameObject> planetList = new List<GameObject>();
private void OnEnable()
{
//Subscribing to Event
PlanetSpawnHandler planetSpawnHandler = GetComponent<PlanetSpawnHandler>();
planetSpawnHandler.OnPlanetSpawned += AddPlanetToList;
//Debug.Log(planetSpawnHandler);
}
// Update is called once per frame
private void FixedUpdate()
{
//Shitty code, pls ignore
foreach (GameObject planet in planetList)
{
if (planet == null)
continue;
else
{
foreach (GameObject otherPlanet in planetList)
{
if (planet == otherPlanet)
continue;
else
{
if (otherPlanet == null)
continue;
else
{
Rigidbody planetRb = planet.GetComponent<Rigidbody>();
Rigidbody otherPlanetRb = otherPlanet.GetComponent<Rigidbody>();
Vector3 direction = planetRb.position - otherPlanetRb.position;
float distance = direction.magnitude;
float forceMagnitude = (planetRb.mass * otherPlanetRb.mass) / Mathf.Pow(distance, 2) * 1000f;
Vector3 force = direction.normalized * forceMagnitude;
otherPlanetRb.AddForce(force);
}
}
}
}
}
}
//Function for Event
public void AddPlanetToList(object sender, PlanetSpawnHandler.OnPlanetSpawnedArgs planet )
{
Debug.Log("AddPlanet called");
planetList.Add(planet.planetArg);
Debug.Log(planetList.Count + " In List");
}
As I said, the problem is that the listener only receives 60 messages from the Publisher, although I'm sure it once worked perfectly.
If you need any other information, tell me and I will provide it
The problem has been resolved, issue was with a different script for some reason
Your listener has a function, FixedUpdate, with a comment above it saying Update is called once per frame. Your comment no longer matches the function you're using.
You want to change that function to Update instead:
// Update is called once per frame
private void Update()
{
...
}
FixedUpdate is for frame-independent calculations, and is called 60 times per second. Note that physics calculations often make use of Time.fixedDeltaTime in this function, if that's relevant to you.
Update is called once per frame and usually uses Time.deltaTime.
Related
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!
I am making a tower defense game and everytime I summon an enemy, i add 1 to the enemyCount variable. and everytime the enemy reaches the endpoint, I subtract 1 to the same variable. Default value is zero.
For some reason the variable gives different values in different parts of the script. I have 2 other scripts that has access to enemyCount. One is the PlayerUI script which just displays it, and the other is the enemyMovement script which is suppose to subtract 1 to it everytime the enemy object is destroyed.
The value of enemyCount in PlayerUI is 0, and it updates when the subtraction happens so it becomes -1.
Now when i run the game again, it retains the value of -1, and when subtracted to -2, its -2 that is displayed on the next run.
The value of enemyCount when I log it in the update function inside the script is that it updates to +1 when the enemy is summoned but doesnt update when the enemy is destroyed.
Can anyone point out what i did wrong?
WaveSpawner script:
public Transform enemyPrefab;
private Transform[] spawners;
private bool summonWave = false;
private float countDown = 2;
private int enemyPerWave = 1;
private int waveCtr = 0;
private int enemyCount;
void Awake() {
spawners = new Transform[transform.childCount];
for (var i = 0; i < spawners.Length; i++) {
spawners[i] = transform.GetChild(i);
}
}
void Start() {
enemyCount = 0;
}
void Update() {
if(countDown <= 0) {
summonWave = true;
countDown = 999;
}
if (summonWave) {
StartCoroutine(waveSpawner());
}else {
countDown -= Time.deltaTime;
}
Debug.Log(enemyCount); //GIVES THE RIGHT VALUE BUT DOESNT SUBTRACT 1 WHEN ENEMY IS DESTROYED
}
public int getEnemyCount() { //Accessed by the PlayerUI script. just to display it onscreen.
Debug.Log(enemyCount); // GIVES A VALUE OF N-1
return enemyCount;
}
public void reduceEnemyCount() { //Accessed by the EnemyMovement script.
enemyCount--; //IF THE ENEMY REACHES THE END, THIS FUNCTION IS CALLED AND THEN THE ENEMY OBJECT IS DESTROYED
}
IEnumerator waveSpawner() {
waveCtr++;
summonWave = false;
for (int i = 0; i < enemyPerWave; i++) {
summonEnemies();
yield return new WaitForSeconds(0.3f);
}
}
void summonEnemies() {
enemyCount += 1; //ADDS 1 TO THE VARIABLE EVERY TIME I SPAWN AN ENEMY UNIT. IN THIS CASE JUST 1
Instantiate(enemyPrefab, spawners[0].position, spawners[0].rotation);
//Instantiate(enemyPrefab, spawners[1].position, spawners[1].rotation);
//Instantiate(enemyPrefab, spawners[2].position, spawners[2].rotation);
//Instantiate(enemyPrefab, spawners[3].position, spawners[3].rotation);
}
}
EnemyMovement script:
public WaveSpawner spawner;
private float speed = 35f;
private int numberOfCycles = 2;
private Transform target;
private Transform startingPoint;
private int cycleCtr = 0;
private bool isDestroyed;
void Start() {
target = getClosestWaypoint(Waypoints.points);
startingPoint = target;
isDestroyed = false;
}
void Update() {
Vector3 dir = target.position - transform.position;
transform.Translate(dir.normalized * speed * Time.deltaTime, Space.World);
if (Vector3.Distance(transform.position, target.position) <= 0.2f && !isDestroyed) {
if (cycleCtr == numberOfCycles) {
spawner.reduceEnemyCount(); //subtracts the enemyCount in WaveSpawner
isDestroyed = true;
Destroy(gameObject); //destroying the enemy unit
} else {
if (target.name == startingPoint.name) {
cycleCtr++;
}
target = getToNextWaypoint(target);
}
}
}
PlayerUI script:
public Text enemyCount_UI;
[SerializeField]
private WaveSpawner spawner;
private int enemyCount_var;
void Start() {
enemyCount_UI.text = "Enemy count: 0";
enemyCount_var = 0;
}
void Update() {
enemyCount_var = spawner.getEnemyCount();
enemyCount_UI.text = "Enemy count: " + enemyCount_var.ToString();
}
I believe it is because you have assigned WaveSpawner prefab to EnemyMovement prefab and PlayerUI object. And you also probably have instance of WaveSpawner added somewhere in the scene.
Let's call WaveSpawner (from prefab) a PrefabSpawner and WaveSpawner (from scene) a SceneSpawner. When game is started, SceneSpawner is created. It has its own value of enemy count. When you spawn a prefab of EnemyMovement it is referencing PrefabSpawner, not SceneSpawner. What happens, is that SceneSpawner always incremets its own enemyCount, while EnemyMovement decreases count on PrefabSpawner. Because PlayerUI is also accessing PrefabSpawner it sees its value instead of SceneSpawner.
Try changing the spawning to this:
void summonEnemies() {
enemyCount += 1; //ADDS 1 TO THE VARIABLE EVERY TIME I SPAWN AN ENEMY UNIT. IN THIS CASE JUST 1
var enemyInstnace = Instantiate(enemyPrefab, spawners[0].position, spawners[0].rotation);
enemyInstance.spawner = this;
}
Rather than having WaveSpawner script on both PlayerUI and EnemyMovement, you'll want to make sure they are accessing the same instance of the script.
If you haven't already, create an empty game object to be your "wave spawner" in the scene and drop the WaveSpawner script on that.
Then update PlayerUI and EnemyMovement to get a reference to that game object, and then its WaveSpawner script
public Text enemyCount_UI;
[SerializeField]
private GameObject waveSpawnerGameObject;
private WaveSpawner spawner;
private int enemyCount_var;
void Start() {
enemyCount_UI.text = "Enemy count: 0";
enemyCount_var = 0;
}
void Awake()
{
// Get the reference here
spawner = waveSpawnerGameObject.GetComponent<WaveSpawner>();
}
void Update() {
enemyCount_var = spawner.getEnemyCount();
enemyCount_UI.text = "Enemy count: " + enemyCount_var.ToString();
}
And do the same for your enemy movement and enemy prefabs
This question already has answers here:
While loop freezes game in Unity3D
(2 answers)
Closed 3 years ago.
I have tried to use Z and X to adjust the ambientspeed while in game but when i press them it crashes completely im new to unity and c# so not sure what is wrong.
using UnityEngine;
using System.Collections;
[RequireComponent(typeof(Rigidbody))]
public class Movement : MonoBehaviour
{
public float AmbientSpeed = 250.0f;
public float RotationSpeed = 150.0f;
private Rigidbody _rigidBody;
// Use this for initialization
void Start()
{
_rigidBody = GetComponent<Rigidbody>();
}
// Update is called once per frame
void Update()
{
while (Input.GetKeyDown("z"))
{
AmbientSpeed = AmbientSpeed - 1;
if (AmbientSpeed < 0)
{
AmbientSpeed = 0;
}
}
while (Input.GetKeyDown("x"))
{
AmbientSpeed = AmbientSpeed + 1;
if (AmbientSpeed > 250)
{
AmbientSpeed = 250;
}
}
Debug.Log(AmbientSpeed);
}
void FixedUpdate()
{
UpdateFunction();
}
void UpdateFunction()
{
Quaternion AddRot = Quaternion.identity;
float roll = 0;
float pitch = 0;
float yaw = 0;
roll = Input.GetAxis("Roll") * (Time.fixedDeltaTime * RotationSpeed);
pitch = Input.GetAxis("Pitch") * (Time.fixedDeltaTime * RotationSpeed);
yaw = Input.GetAxis("Yaw") * (Time.fixedDeltaTime * RotationSpeed);
AddRot.eulerAngles = new Vector3(-pitch, yaw, -roll);
_rigidBody.rotation *= AddRot;
Vector3 AddPos = Vector3.forward;
AddPos = _rigidBody.rotation * AddPos;
_rigidBody.velocity = AddPos * (Time.fixedDeltaTime * AmbientSpeed);
if(Vector3.Dot(transform.up, Vector3.down) > 0)
{
_rigidBody.AddForce(0, 0, -10);
}
}
}
Im sorry if i respond late as won't be back on PC till Monday but looking for help with this. Most of the code is from a flight tutorial but the throttle kind of thing in the update() is from me. The script is applied to a rigid-body with box collides .
You're crashing because you get stuck in an infinite loop when the Update method runs and reaches the first while statement:
// Wrong
void Update()
{
while (Input.GetKeyDown("z")) { }
}
A while statement will loop until the condition becomes false. The condition will never become false for the specific Update() call you clicked z.
Update() executes once every frame. So if you want to check if someone is holding down "z" in THIS frame, you use if:
// Correct
void Update()
{
if (Input.GetKey("z")) { }
}
This will give you the effect that you intended to get with while:
while user holds down "z" execute this code".
because the Update() will act like the loop you're trying to get while to achieve.
Another thing; by your usage of while I assumed that you want something to happen every frame that the z key is held down. GetKeyDown will only be true for the ONE frame that you clicked it. If you want to check if it is being held down for any number of frames you should use GetKey:
if (Input.GetKey(KeyCode.Z)) {
// Executes every frame Z is being held down
}
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.
This question already has answers here:
How to make the script wait/sleep in a simple way in unity
(7 answers)
Closed 4 years ago.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GateControl : MonoBehaviour
{
public Transform door;
public float doorSpeed = 1.0f;
public bool randomDoorSpeed = false;
[Range(0.3f, 10)]
public float randomSpeedRange;
private Vector3 originalDoorPosition;
// Use this for initialization
void Start()
{
originalDoorPosition = door.position;
}
// Update is called once per frame
void Update()
{
if (randomDoorSpeed == true && randomSpeedRange > 0.3f)
{
StartCoroutine(DoorSpeedWaitForSeconds());
}
door.position = Vector3.Lerp(originalDoorPosition,
new Vector3(originalDoorPosition.x, originalDoorPosition.y, 64f),
Mathf.PingPong(Time.time * doorSpeed, 1.0f));
}
IEnumerator DoorSpeedWaitForSeconds()
{
doorSpeed = Random.Range(0.3f, randomSpeedRange);
yield return new WaitForSeconds(3);
}
}
Making StartCoroutine inside the Update is a bad idea. But I want that it will take one random speed when running the game then will wait 3 seconds and change to a new random speed then wait another 3 seconds and change for another new random speed and so on.
And while it's waiting 3 seconds to keep the current speed constant until the next change.
Update:
This is what I tried:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GateControl : MonoBehaviour
{
public Transform door;
public float doorSpeed = 1.0f;
public bool randomDoorSpeed = false;
public bool IsGameRunning = false;
[Range(0.3f, 10)]
public float randomSpeedRange;
private Vector3 originalDoorPosition;
// Use this for initialization
void Start()
{
IsGameRunning = true;
originalDoorPosition = door.position;
StartCoroutine(DoorSpeedWaitForSeconds());
}
// Update is called once per frame
void Update()
{
door.position = Vector3.Lerp(originalDoorPosition,
new Vector3(originalDoorPosition.x, originalDoorPosition.y, 64f),
Mathf.PingPong(Time.time * doorSpeed, 1.0f));
}
IEnumerator DoorSpeedWaitForSeconds()
{
var delay = new WaitForSeconds(3);//define ONCE to avoid memory leak
while (IsGameRunning)
{
if (randomDoorSpeed == true && randomSpeedRange > 0.3f)
doorSpeed = Random.Range(0.3f, randomSpeedRange);
yield return delay;
}
}
}
But there are two problems.
The first problem is that every 3 seconds, when it's changing the speed of the door, it's also changing the door position from its current position. So it looks like the door position is jumping to another position and then continue from there. How can I make that it will change the speed meanwhile the door keep moving from its current position ?
Second problem is how can I change the randomDorrSpeed flag so it will take effect while the game is running? I Want that if randomDorrSpeed is false use the original speed of the door (1.0) and if it`s true use the random speed.
You already know that the coroutine should start from Start:
void Start()
{
//initialization
StartCoroutine(DoorSpeedWaitForSeconds());
}
So make the coroutine a loop with the proper terminate condition:
IEnumerator DoorSpeedWaitForSeconds()
{
var delay = new WaitForSeconds(3);//define ONCE to avoid memory leak
while(IsGameRunning)
{
if(randomDoorSpeed == true && randomSpeedRange > 0.3f)
doorSpeed = Random.Range(0.3f, randomSpeedRange);
if(!randomDoorSpeed)
doorSpeed = 1;//reset back to original value
yield return delay;//wait
}
}
For the other question you asked, if you think about it you can't possibly use ping pong with dynamic speed based on Time.time. You need to change it like this:
bool isRising = true;
float fraq = 0;
void Update()
{
if (isRising)
fraq += Time.deltaTime * doorSpeed;
else
fraq -= Time.deltaTime * doorSpeed;
if (fraq >= 1)
isRising = false;
if (fraq <= 0)
isRising = true;
fraq = Mathf.Clamp(fraq, 0, 1);
door.position = Vector3.Lerp(originalDoorPosition,
new Vector3(originalDoorPosition.x, originalDoorPosition.y, 64f),
fraq);
}
You can solve the original problem without coroutines:
public float timeBetweenChangeSpeed = 3f;
public float timer = 0;
void Update ()
{
// Add the time since Update was last called to the timer.
timer += Time.deltaTime;
// If 3 seconds passed, time to change speed
if(timer >= timeBetweenChangeSpeed)
{
timer = 0f;
//Here you call the function to change the random Speed (or you can place the logic directly)
ChangeRandomSpeed();
}
}
And about opening and closing doors. Here is a script I used to control doors in a maze game I worked in:
You need to set empty gameObjects with to set the boundaries, that is until what point you want to move the door one opening or when closing. You place this empty gameobjects in your scene and link them to the scrip in the correct field. The script will take the transform.position component on it's own. There is also a trigger enter to activate the door when a character approaches. If you dont need that part, I can edit the code tomorrow.
You can use this script also to move platforms, enemies... in general anything which can move in a straight line.
using UnityEngine;
using System.Collections;
public class OpenDoor : MonoBehaviour {
// define the possible states through an enumeration
public enum motionDirections {Left,Right};
// store the state
public motionDirections motionState = motionDirections.Left;
//Variables for State Machine
bool mOpening = false;
bool mClosing = false;
//bool mOpened = false;
//OpenRanges to open/close the door
public int OpenRange = 5;
public GameObject StopIn;
public GameObject StartIn;
//Variables for Movement
float SpeedDoor = 8f;
float MoveTime = 0f;
int CounterDetections = 0;
void Update () {
// if beyond MoveTime, and triggered, perform movement
if (mOpening || mClosing) {/*Time.time >= MoveTime && */
Movement();
}
}
void Movement()
{
if(mOpening)
{
transform.position = Vector3.MoveTowards(transform.position, StopIn.transform.position, SpeedDoor * Time.deltaTime);
if(Vector3.Distance(transform.position, StopIn.transform.position) <= 0)
mOpening = false;
}else{ //This means it is closing
transform.position = Vector3.MoveTowards(transform.position, StartIn.transform.position, SpeedDoor * Time.deltaTime);
if(Vector3.Distance(transform.position, StartIn.transform.position) <= 0)
mClosing = false;
}
}
// To decide if door should be opened or be closed
void OnTriggerEnter(Collider Other)
{
print("Tag: "+Other.gameObject.tag);
if(Other.gameObject.tag == "Enemy" || Other.gameObject.tag == "Player" || Other.gameObject.tag == "Elevator")
{
CounterDetections++;
if(!mOpening)
Opening();
}
}
void OnTriggerStay(Collider Other)
{
if(Other.gameObject.tag == "Elevator")
{
if(!mOpening)
Opening();
}
}
void OnTriggerExit(Collider Other)
{
if(Other.gameObject.tag == "Enemy" || Other.gameObject.tag == "Player")
{
CounterDetections--;
if(CounterDetections<1)
Closing();
}
}
void Opening()
{
mOpening = true;
mClosing = false;
}
void Closing()
{
mClosing = true;
mOpening = false;
}
}
Using timer and setting an interval. The delegate event will fire every time the interval is reached.
var t = new Timer {Interval = 3000};
t.Elapsed += (sender, args) => { /* code here */};