I've just started exploring unity & c# working together, and accidently I faced a next problem:
I have a fish. Fish was supposed to go from left to right, then change its direction and go from right to left. I haven't finished that moving part yet, but I was going to do it with timer. So timer is active, fish starts to move, timer stops, changing direction, timer resets, fish starts to move etc.
I want to flip my sprite, so it will face correct direction. It doesn't work from Elapsed function and I don't understand why. If you have any ideas, please share
using System.Timers;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FishBehavior : MonoBehaviour
{
public float moveSpeed;
private int direction; //for left -1, for right 1
private Timer timer;
private SpriteRenderer img;
private Rigidbody2D rb;
void Start()
{
img = GetComponent<SpriteRenderer>();
rb = GetComponent<Rigidbody2D>();
direction = 1;
timer = new Timer();
timer.Elapsed += TimerElapsed;
ChangeTimerOptions();
}
private void Move()
{
//moving
}
private void ChangeDirection()
{
direction *= -1;
img.flipX = !img.flipX; //Doesn't work!
//stop movement
}
private void ChangeTimerOptions()
{
System.Random rand = new System.Random();
timer.Interval = rand.Next(3000, 8000);
timer.Enabled = true;
Move();
}
private void TimerElapsed(object source, ElapsedEventArgs e)
{
ChangeDirection();
ChangeTimerOptions();
}
}
The issue is that the callback for TimerElapsed is on a different thread than that of Unity. Meaning any sort of calls you make inside of it to methods that would normally work in Unity will not. I would recommend instead using a Coroutine or an Invoke.
To briefly explain what both are, a Coroutine is a special method that completes small amounts of work over multiple frames. The method can also be suspended for certain amounts of time to wait to perform code down the line. An Invoke or InvokeRepeating is a call to perform a method after a certain amount of time or to continually make a call to a method after a set start time and set amount of time.
If you are also eventually incorporating a movement and randomizing your wait time, I would consider using a Coroutine over an Invoke so you can handle all movement/flip logic from the same main call.
public float moveSpeed;
private int direction; //for left -1, for right 1
private Timer timer;
private SpriteRenderer img;
private Rigidbody2D rb;
// store the coroutine in case a duplicate call occurs somehow
private Coroutine MoveAndFlip = null;
void Start()
{
img = GetComponent<SpriteRenderer>();
rb = GetComponent<Rigidbody2D>();
direction = 1;
// detect if any coroutine is already running
CleanUpMoveAndFlipCoroutine();
MoveAndFlip = StartCoroutine(MoveAndFlipAsset());
}
private void Move()
{
//moving
}
private void ChangeDirection()
{
direction *= -1;
img.flipX = !img.flipX;
//stop movement
}
private IEnumerator MoveAndFlipAsset()
{
// instead of milliseconds, use seconds
float randomTimeInterval = Random.Range(3.0f, 8.0f);
while(true)
{
// can do movement here - depending on how you would like to apply motion it would change how
// it is implemented such as directly changing velocity, using a Vector3.Lerp, AddForce, etc.
// wait the time to flip
yield return new WaitForSeconds(randomTimeInterval);
// now flip
ChangeDirection();
}
}
private void CleanUpMoveAndFlipCoroutine()
{
if (MoveAndFlip != null)
StopCoroutine(MoveAndFlip);
}
If you would like an Invoke example to the above implementation, here is how you could approach it.
void Start()
{
img = GetComponent<SpriteRenderer>();
rb = GetComponent<Rigidbody2D>();
direction = 1;
Invoke("ChangeDirection", RandomTimeToFlip());
}
private float RandomTimeToFlip()
{
return Random.Range(3.0f, 8.0f); ;
}
private void ChangeDirection()
{
direction *= -1;
img.flipX = !img.flipX;
//stop movement
// start our method again after a set amount of time
Invoke("ChangeDirection", RandomTimeToFlip());
}
Edit: Apparently, you are able to use the TimerElapsed by changing the Timer's SynchronizingObject reference, but I am not exactly sure what to assign it to. I would still recommend using one of the two methods I described above though.
Related
I add my codes below. What is my fault, can anyone help me?
I want to when SpawnRandomBall function run two times, spawnInternal turn into spawnInternal2. So I create a new variable, called 'check'. The variable increase when SpawnRandomBall function run. I set the variable as a public. In this way I can see that 'check' variable increase or doesn't increase. 'Check' variable is increasing without problem. When the veriable value equal to 3, it must be 'else if' run. But unfortunately it doesn't work.
I guess problem is I run my codes in Start() function. But I don't know how can I do properly.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpawnManagerX : MonoBehaviour
{
public GameObject[] ballPrefabs;
private float spawnLimitXLeft = 14.5f;
private float spawnLimitXRight = 24;
private float spawnPosY = 10;
private float startDelay = 1.0f;
private float spawnInterval = 4.0f;
private float spawnInterval2 = 2.0f;
public int check;
// Start is called before the first frame update
void Start()
{
if (check <= 2)
{
InvokeRepeating("SpawnRandomBall", startDelay, spawnInterval);
}
else if (check > 2)
{
InvokeRepeating("SpawnRandomBall", startDelay, spawnInterval2);
}
}
// Spawn random ball at random x position at top of play area
void SpawnRandomBall ()
{
// Generate random ball index and random spawn position
Vector3 spawnPos = new Vector3(-21, spawnPosY, Random.Range(spawnLimitXLeft, spawnLimitXRight));
int ballIndex = Random.Range(0, 3);
// instantiate ball at random spawn location
Instantiate(ballPrefabs[ballIndex], spawnPos, ballPrefabs[ballIndex].transform.rotation);
check += 1;
}
}
I want to change SpawnInternal variable into SpawnInternal2
The situation you describe is that you want a method to run. And for the first two iterations, you’d like one particular delay interval, and then after that you’d like a different delay interval. With that being the case, I would suggest you’re using the wrong tools. I would personally start a Coroutine like this:
public class SpawnManagerX : MonoBehaviour
{
public GameObject[] ballPrefabs;
private float spawnLimitXLeft = 14.5f;
private float spawnLimitXRight = 24;
private float spawnPosY = 10;
private float startDelay = 1.0f;
private float spawnInterval = 4.0f;
private float spawnInterval2 = 2.0f;
public int check;
void Start()
{
StartCoroutine ( SpawnRandomBall () );
}
// Spawn random ball at random x position at top of play area
IEnumerator SpawnRandomBall ()
{
while ( true )
{
// yielding here will produce an initial delay when the coroutine is run.
yield return new WaitForSeconds (
(check++ < 2) ? spawnInterval : spawnInterval2 );
// Generate random ball index and random spawn position
var spawnPos = new Vector3(-21, spawnPosY, Random.Range(spawnLimitXLeft, spawnLimitXRight));
var ballIndex = Random.Range(0, 3);
// instantiate ball at random spawn location
Instantiate( ballPrefabs[ballIndex], spawnPos, ballPrefabs[ballIndex].transform.rotation );
}
}
}
It’s also possible to run the Start method as a coroutine as well, but I feel, especially for newcomers, that the intent can sometimes be lost.
What this is saying is, start a coroutine, and in that coroutine, keep looping while (true) (which means “forever”). The line that change the amount of time that it waits is the last line, the “return”. We WaitForSeconds and the number of seconds we wait is based on the current count value. NB: written on phone but not tested.
Edited to create an initial delay in the coroutine.
If Start gets called once then the else if is never run. The code doesn't just wait at the else if waiting for check to update.
You just need to manage what code gets run in the SpawnRandomBall method.
You're going to need to try something like this this:
// Start is called before the first frame update
void Start()
{
InvokeRepeating("SpawnRandomBall", startDelay, spawnInterval2);
}
// Spawn random ball at random x position at top of play area
void SpawnRandomBall()
{
check++;
if (check == 1 || check == 3)
return;
// Generate random ball index and random spawn position
Vector3 spawnPos = new Vector3(-21, spawnPosY, Random.Range(spawnLimitXLeft, spawnLimitXRight));
int ballIndex = Random.Range(0, 3);
// instantiate ball at random spawn location
Instantiate(ballPrefabs[ballIndex], spawnPos, ballPrefabs[ballIndex].transform.rotation);
}
I'm trying to reduce the gravity scale of my flappy bird game for approximately 1 second at start time. I want to give the player a little time cushion before he falls more quickly.
I originally put the function in the start method (does a timer work in a non-update function?) I then put it in the update function.
Regardless, the timer doesn't seem to trigger, and the gravityscale doesnt go to normal (1).
You'll also see my comment where I was trying to lerp the gravity scale instead. Is it possible to lerp a gravity scale?
void Update()
{
Jump();
Rotation();
PlayerHalt();
}
private void PlayerHalt()
{
float gravityTimer = 0f;
gravityTimer += Time.deltaTime;
if (gravityTimer <= 1)
{
rb.gravityScale = .2f;
//rb.gravityScale = Mathf.Lerp(.2f, 1f, gravityTimer / 1f);
}
else
{
rb.gravityScale = 1f;
}
}
You must use IEnumerator that acts like a Timer, at the bottom of the code you will see a simple Gravity Tweener.
private IEnumerator TweenGravity(float target, float duration)
{
var baseGravity = rigidbody2D.gravityScale;
var progress = 0f;
while (progress < 1f)
{
progress += Time.deltaTime/duration;
rigidbody2D.gravityScale = Mathf.Lerp(baseGravity, target, progress);
yield return new WaitForEndOfFrame();
}
}
public Rigidbody2D rigidbody2D; // setup your rigidbody on inspector
private void Start() => StartCoroutine(TweenGravity(1f, 1f)); // how to call IEnumerator
How To Reverting gravity?
You can use an Action to give a callback at the end of the timer for reverting or other command.
private IEnumerator TweenGravity(float target, float duration, Action OnFinish = null)
{
///....
OnFinish?.Invoke(); // this will call after wait progress finish
}
private void Start() => StartCoroutine(TweenGravity(1f, 1f, () => Debug.Log("On Finish callback")));
Here's a method that uses a simple time delay mechanism by taking note of the start time and measuring the current elapsed interval. You can see it in the Delay class below.
Apart from Time (which you could easily replace with DateTime or even Environment.TickCount) it's essentially technology-neutral. No nasty Unity coroutines necessary (which if used incorrectly are akin to Application.DoEvents()).
Delay class:
public class Delay
{
private float _lastInterval;
/// <summary>
/// The timeout in seconds
/// </summary>
/// <param name="timeout"></param>
private Delay(float timeout)
{
Timeout = timeout;
_lastInterval = Time.time;
}
public float Timeout { get; }
public bool IsTimedOut => Time.time > _lastInterval + Timeout;
public void Reset()
{
_lastInterval = Time.time;
}
public static Delay StartNew(float delayInSeconds)
{
return new Delay(delayInSeconds);
}
}
Because it uses Unity's Time.time member it can be safely used from both Update and FixedUpdate.
Usage
private Delay _delay;
private RigidBody rb;
void Start()
{
rb = ...
rb.gravityScale = .2f; // reduce the gravity scale
_delay = Delay.StartNew(1f); // One second
}
void Update()
{
if (_delay.IsTimedOut)
{
rb.gravityScale = 1f;
}
.
.
.
}
Render throttling / Emulate a periodic timer
It can also be used in scenarios that demand reoccurring opperations spaced by a given frequency.
e.g. I use it a-lot for throttling processing or updates for rendering where I don't want to update things every frame but maybe every 100 ms. I can achieve this by setting a Delay whereby I update rendering to the screen/buffer/RenderTexture/Texture2D like so:
private void Update()
{
if (!_delay.IsTimedOut)
{
// not yet time
return;
}
// perform rendering here
_delay.Reset();
}
I'm trying to make the game spawn enemies every random period of time so I'm using yield return new WaitForSeconds(...) in Coroutine and as the title says, I can use while(true) inside the Coroutine and call the Coroutine in the void Start() and the spawning thing works just fine. But when i removed the while(true) and call the Coroutine in void Update(), the enemies are spawned crazily without the delay. Why is this??
Because I guess you don't wait but rather start a new concurrent routine every frame so after the delay has passed once at the beginning you now get one call for each frame!
A Coroutine does not delay the outer code which starts it.
Either use e.g.
[SerializeField] private float delay;
private void Start()
{
StartCoroutine(Routine);
}
private IEnumerator Routine()
{
while(true)
{
yield return new WaitForSeconds(delay);
DoSomething();
}
}
or also directly
// If you make Start an IEnumerator then Unity automatically runs it as a Coroutine
private IEnumerator Start()
{
while(true)
{
yield return new WaitForSeconds(delay);
DoSomething();
}
}
Or if you want to go for Update the equivalent would require a counter like e.g.
private float timer;
private void Start()
{
timer = delay;
}
private void Udpate()
{
timer -= Time.deltaTime;
if(timer <= 0)
{
DoSomething();
timer = delay;
}
}
I wouldn't mix both things ;)
And personally I find Coroutines better to read and maintain most of the times.
I have a spawner object. Every time a gameobject is spawned, I want that object to move randomly (wandering). The problem in my script is that the gameobject movement is very random (jittery). How can I solve this?
void Start ()
{
InvokeRepeating("SpawnNPC", Spawntime, Spawntime);
}
// Update is called once per frame
void Update () {
population = GameObject.FindGameObjectsWithTag("NPCobject");
for (int i = 0; i < population.Length; i++)
{
getNewPosition();
if (population[i].transform.position != pos)
{
population[i].transform.position = Vector3.MoveTowards(population[i].transform.position, pos, .1f);
}
}
}
void getNewPosition()
{
float x = Random.Range(-22, 22);
float z= Random.Range(-22, 22);
pos = new Vector3(x, 0, z);
}
I made the New randomize vector in different method, because I plan to change it with pathfinder function and make it in different thread/task.
You are choosing a new direction every single frame. That will always be very jittery. You wan't to only change direction after, at least, a small interval. Here is a link to one way to do that from Unity's website. https://docs.unity3d.com/ScriptReference/MonoBehaviour.InvokeRepeating.html
What about using Navigation? As you said wandering, I thought it would give you a nice result and also make your code simple.
The following screenshot is a sample with Navigation. The moving game objects are also changing their direction nicely, although it cannot be seen in the sample because the game object is capsule...
Ground game object in the sample program has NavMesh. See here to build NavMesh.
Agent game object has NavMeshAgent Component. See here to set it up.
Th Behaviour class below is for Agent game object.
using UnityEngine;
using UnityEngine.AI;
public class NavAgentBehaviour : MonoBehaviour {
public Transform[] Destinations { get; set; }
// Use this for initialization
void Start ()
{
InvokeRepeating("changeDestination", 0f, 3f);
}
void changeDestination()
{
var agent = GetComponent<NavMeshAgent>();
agent.destination = Destinations[Random.Range(0, Destinations.Length)].position;
}
}
The next Behaviour class is just for spawning the Agent and setting up the destinations. On Unity, set it to whatever game object in the scene, and allocate game objects to the fields.
using UnityEngine;
public class GameBehaviour : MonoBehaviour {
public GameObject Agent;
public Transform SpawnPoint;
public Transform Destination1;
public Transform Destination2;
public Transform Destination3;
// Use this for initialization
void Start()
{
Agent.SetActive(false);
InvokeRepeating("Spawn", 0f, 2f);
}
void Spawn() {
var newAgent = Instantiate(Agent);
newAgent.GetComponent<NavAgentBehaviour>().Destinations = new[] { Destination1, Destination2, Destination3 };
newAgent.transform.position = SpawnPoint.position;
newAgent.SetActive(true);
}
}
Increase the number of destination, to make the moves look more random. By the way, the destinations do not need to be specified by game objects, which is only for making it easy to see the sample program's behaviour.
The source of the jitteriness comes from the fact that you are updating the position to move every frame so your objects never have a consistent location to move to. I would instead suggest attaching a new script to each of your objects that individually handles their movement. In that script you could do something like the following, which has a delay to keep the target position for more than 1 frame.
float delaytimer;
Vector3 pos;
void Start () {
getNewPosition(); // get initial targetpos
}
void Update () {
delaytimer += Time.deltaTime;
if (delaytimer > 1) // time to wait
{
getNewPosition(); //get new position every 1 second
delaytimer = 0f; // reset timer
}
transform.position = Vector3.MoveTowards(transform.position, pos, .1f);
}
void getNewPosition()
{
float x = Random.Range(-22, 22);
float z= Random.Range(-22, 22);
pos = new Vector3(x, 0, z);
}
You are changing the direction they are moving in every frame, thats what is causing the jitter.
You could wait a few moments before you change the direction, perhaps something like this.
// Outside update
float betweenChanges = 2;
float lastChange = 0;
// Inside update
if(Time.realtimeSinceStartup > lastChange)
{
// Change directions
// ...
lastChange = Time.realTimeSinceStart + betweenChanges;
}
You could also solve this by using InvokeRepeating or a Coroutine.
If you dont want all the NPC's to change direction at the same time and still plan on controlling every NPC from the same class like in your example, you should perhaps add a timer for each NPC instance instead and use that to decide when to change its direction.
A better idea would be to let each NPC have its own Movement-script.
I am learning Unity from a Swift SpriteKit background where moving a sprite's x Position is as straight forward as an running an action as below:
let moveLeft = SKAction.moveToX(self.frame.width/5, duration: 1.0)
let delayAction = SKAction.waitForDuration(1.0)
let handSequence = SKAction.sequence([delayAction, moveLeft])
sprite.runAction(handSequence)
I would like to know an equivalent or similar way of moving a sprite to a specific position for a specific duration (say, a second) with a delay that doesn't have to be called in the update function.
gjttt1's answer is close but is missing important functions and the use of WaitForSeconds() for moving GameObject is unacceptable. You should use combination of Lerp, Coroutine and Time.deltaTime. You must understand these stuff to be able to do animation from Script in Unity.
public GameObject objectectA;
public GameObject objectectB;
void Start()
{
StartCoroutine(moveToX(objectectA.transform, objectectB.transform.position, 1.0f));
}
bool isMoving = false;
IEnumerator moveToX(Transform fromPosition, Vector3 toPosition, float duration)
{
//Make sure there is only one instance of this function running
if (isMoving)
{
yield break; ///exit if this is still running
}
isMoving = true;
float counter = 0;
//Get the current position of the object to be moved
Vector3 startPos = fromPosition.position;
while (counter < duration)
{
counter += Time.deltaTime;
fromPosition.position = Vector3.Lerp(startPos, toPosition, counter / duration);
yield return null;
}
isMoving = false;
}
Similar Question: SKAction.scaleXTo
The answer of git1 is good but there is another solution if you do not want to use couritines.
You can use InvokeRepeating to repeatedly trigger a function.
float duration; //duration of movement
float durationTime; //this will be the value used to check if Time.time passed the current duration set
void Start()
{
StartMovement();
}
void StartMovement()
{
InvokeRepeating("MovementFunction", Time.deltaTime, Time.deltaTime); //Time.deltaTime is the time passed between two frames
durationTime = Time.time + duration; //This is how long the invoke will repeat
}
void MovementFunction()
{
if(durationTime > Time.time)
{
//Movement
}
else
{
CancelInvoke("MovementFunction"); //Stop the invoking of this function
return;
}
}
You can use co-routines to do this. To do this, create a function that returns type IEnumerator and include a loop to do what you want:
private IEnumerator foo()
{
while(yourCondition) //for example check if two seconds has passed
{
//move the player on a per frame basis.
yeild return null;
}
}
Then you can call it by using StartCoroutine(foo())
This calls the function every frame but it picks up where it left off last time. So in this example it stops at yield return null on one frame and then starts again on the next: thus it repeats the code in the while loop every frame.
If you want to pause for a certain amount of time then you can use yield return WaitForSeconds(3) to wait for 3 seconds. You can also yield return other co-routines! This means the current routine will pause and run a second coroutine and then pick up again once the second co-routine has finished.
I recommend checking the docs as they do a far superior job of explaining this than I could here