How coroutine work with OnTriggerEnter2D? - c#

I'm learning C# and unity, one of my recent issue is having my OnTriggerEnter2D() function work. After a lot of test i managed to resolve my issues but i'm not sure at 100% if i understood.
So basically i'm checking if an object touch another one and if this is not the case i rotate the object :
for (var i = 0; i < 4; i++)
{
if (!door.connected)
{
go.transform.Rotate(0f, 0f, 90f, Space.Self);
}
}
This one didnt work and after a lot of debug i decided to use a coroutine with a 0.5s delay right after the go.transform.Rotate(0f, 0f, 90f, Space.Self); and this work.
If i'm right this work because the delay let enought time for my OnTriggerEnter2D() function to detect the collision or this have nothing to do with the OnTriggerEnter2D() ? If this is the case, everytime i'm going to check for collision i'll need to use a coroutine ?
Edit 1 : I have this code in another script
public void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Door"))
{
this.connected = true;
}
}
So the previous loop will indeed make my object rotate but never set my public variable "connected" to true, like the collision never occur, except for the one spawning at the right spot. But when i use a coroutine it does work, why ?

You definitely don't need a coroutine to rotate an object in OnTriggerEnter2D.
I imagine what's happening, based on the code snippet you've shown us, is that it's almost instantly rotating the object 90 degrees 4 times (that's a full 360 degree rotation) and it therefore looks like it's not moving at all.
Edit (in response to Edit 1):
The reason your code works when you add a coroutine is because the four 90 degree rotations don't all happen in a single frame then. What happens is:
It rotates the door 90 degrees then kicks off a coroutine which last a few frames.
Since the door has rotated between frames, the other script gets the chance to detect any collisions
If it detects one with the door, it can set the connected flag to
true which prevents go.transform.Rotate from being called in any subsequent
frames.
Compare this to the sequence without the coroutine which would be:
It rotates the door 90 degrees, then it does it again, then again, then again... then it moves on to the next frame.
Since, as far as all other GameObjects are concerned, the door has
NOT moved between frames no new collisions are detected and your
connected flag remains false.
The solution to your problem isn't using a coroutine, though. The solution is to remove your for loop. In the Update method, if the door is not connected rotate it a single degree step. On the next update, if the door still isn't connected, do it again. Eventually, within 4 updates, it will have become connected and it will no longer rotate every frame.

If i'm right this work because the delay let enought time for my OnTriggerEnter2D() function to detect the collision or this have nothing to do with the OnTriggerEnter2D() ?
No it has nothing to do with OnTriggerEnter2D. As you can see your collision is working and triggered.
What happens is that you rotate 4 times about 90° which sums up to 360° within one frame => You will note no difference.
A Coroutine in Unity is basically an IEnumerator. Every IEnumerator at some point uses the yield keyword. So what a Coroutine in Unity does is basically:
Each frame call MoveNext on the given IEnumerator until it either terminates or reaches the next yield statement. (There are some special cases like e.g. WaitUntilEndOfFrame...)
So translated you could say the yield means something like
"Interrupt" this routine
Render the current frame
Continue the routine from here in the next frame
Btw. what you are doing via e.g.
yield return new WaitForseconds(3f);
basically equals doing something like
var timePassed = 0f;
while(timePassed < 3f)
{
yield return null;
timePassed += Time.deltaTime;
}
So: No for Collision detection you do not need to use a Coroutine; BUT whenever you want to "stretch" some codes execution over multiple frames you should use one.
What Unity does internally when you declare OnTriggerEnter2D as an IEnumerator basically equals something like
private void OnTriggerEnter2D(Collider2D other)
{
StartCoroutine(RotationAnimation());
}
private IEnumerator RotationAnimation()
{
// ...
}
so you still have to be very careful that you don't get concurrent routines!
If what you want would actually be a smooth rotation you would e.g. use
private bool alreadyRotating;
// adjust this via the Inspector
[SerializeField] float rotationAnglePerSecond = 90f;
private void OnTriggerEnter2D(Collider2D other)
{
// avoid concurrent routines
if(!alreadyRotating) StartCoroutine(RotationAnimation());
}
private IEnumerator RotationAnimation()
{
// block concurrent routine
if(alreadyRotating) yield break;
alreadyRotating = true;
var initialOrientation = go.transform.rotation;;
// rotate until you reach 360°
var rotated = 0f;
while(rotated < 360f)
{
// get amount of rotation within this frame
var rotate = Time.deltaTime * rotationAnglePerSecond;
go.transform.Rotate(0f, 0f, 90f);
rotated += rotate;
yield return null;
}
// reset to initial orientation
go.transform.rotation = initialOrientation;
// when done allow the next routine
alreadyRotating = false;
}

Related

Unity. Fixed Update isn't sensing my jump input well. Normal update launches my character to random heights when space is pressed

I don't know what's up. I usually do my jumping on the update function. This is my first big game though other than other prototypes or bad game attempts I've done.
This is my jump code
void FixedUpdate()
{
if (isDead == false)
{
if (Input.GetKeyDown(KeyCode.Space))
{
if (OnGround == true)
{
rb.velocity= new Vector2 (rb.velocity.x, jumpforce * Time.deltaTime);
}
}
}
}
I sometimes miss a few jumps on fixed update. But when I put it in my update function it jumps so low sometimes, like two or three times and then it launches me so high. It's just so random and weird. Is there anything I can do about this or will I just have to deal with fixedupdate? I even tried lateupdate if that matters.
The general rule of thumb is to place all keyboard/mouse input into the Update method and not the FixedUpdate method. The reason for this is because FixedUpdate is not a frame by frame process, but is called at fixed time increments which can be more or less calls in a certain frame period.
Input data is determined every frame, so in FixedUpdate where it can run in-between frames, skip frames, etc. you have the possibility of losing input. Instead, move your input code to the Update method which is called every frame. You will no longer lose input.
The issue with setting your physics calls in Update is because all physics code should be placed in FixedUpdate. As Update can run faster or slower than the physics system in your game, placing physics-based calls in it can have unexpected results.
Unity:
FixedUpdate should be used instead of Update when dealing with Rigidbody. For example when adding a force to a rigidbody, you have to apply the force every fixed frame inside FixedUpdate instead of every frame inside Update. Tell me more...
The solution is to cache your input from Update and then set using the resulting input from this method in FixedUpdate to determine your physics.
private bool justJumped = false;
void FixedUpdate()
{
if(justJumped)
{
justJumped = false;
rb.AddForce(Vector2.up * jumpForce);
}
}
void Update()
{
if (!justJumped && Input.GetKeyDown(KeyCode.Space) && OnGround && !isDead)
{
justJumped = true;
}
}
I would also recommend not setting velocity directly as if any other outside forces are acting on your player, they will be disregarded. Instead, use AddForce. I swapped your direct velocity set with an AddForce. I would also not multiply by Time.deltaTime. That value is just a float value for how much time has passed since the last frame. It should be used when doing movement in small increments such as with a Lerp. If you want to set velocity directly, remove the Time.deltaTime product and decrease your jumpforce by a lot. You will find the outcome to be the same.

How Can I Gradually Add More Force To An Object To Increase It's Speed Regardless of It's Direction?

I'm new in Unity. I am trying to make a 2D game similar to Pong. However, I want to increase more speed to Ball over time to make it harder. I set the gravity scale of the Ball to zero so that it doesn't fall down.
I added a force and bouncy Physics element to the ball. So it bounces back from walls and it goes to different directions.
Here is a screenshot of Game I'm working on:
MY QUESTION IS:
How can I add more force to the ball regardless of which direction it
bounces back?
<Note: I tried putting it inside FixedUpdate () method but the ball goes crazy because of constantly executing same function every frame. I was thinking of adding more force to the ball over time by using InvokeRepeating ( ) method later on to set time interval. If there is better idea of using other techniques, giving me a little advice will help me a lot>
Thank you !
I would recommend using a Coroutine or an InvokeRepeating. I would also recommend changing your code a bit.
rbBall.AddForce(rbBall.transform.right * ballForce)
The above snippet will add the ballForce in the direction the rbBall is moving.
Now for the two example snippets.
Coroutine
private float timeBeforeAddForce = 5f;
private void Start()
{
StartCoroutine(GradualAddForceToBall());
}
private IEnumerator GradualAddForceToBall()
{
// wait for 5 seconds
yield return new WaitForSeconds(timeBeforeAddForce);
// add the speed
rbBall.AddForce(rbBall.transform.right * ballForce)
// call the coroutine again
StartCoroutine(GradualAddForceToBall());
}
InvokeRepeating
private void Start()
{
InvokeRepeating("GradualAddForceToBall", 0.0f, timeBeforeAddForce);
}
private void GradualAddForceToBall()
{
rbBall.AddForce(rbBall.transform.right * ballForce)
}
If you want to change the current time of how long the speed is applied, I would go with the Coroutine as you can gradually decrease the timeBeforeAddingForce every time it enters the Coroutine.
I found the answer. You can force an object to be a specific speed while keeping its same movement direction - normalize the velocity (which sets the value to have a mangitude of 1) and then multiply it by your desired speed:
Here is the code:
public float currentSpeed = 5f;
void FixedUpdate()
{
//This will let you adjust the speed of ball using normalization
rbBall.velocity = rbBall.velocity.normalized * currentSpeed;
}
Adjust the currentSpeed variable to change it's speed.
""

Unity2D: implementing a running effect to my character

I'm trying to achieve the same effect done Knuckles does in the new game Sonic mania, the effect can be seen here (2:18 - 2:25). So far I duplicated my main player and lowered the duplicated players alpha so that it looks a bit transparent, I also added the script below on the duplicated player to give the duplicated player some distance to the original player; however I wasn't quite sure how I can make the duplicated players slowly return the original player when the player isn't moving! I attempt using animation but it didn't look as good as what was shown in the video, I also tired to shorten the distance over Time.deltaTime however it still didn't look effective! Is there a better way of attempting the same effect shown in the video?? Thank you :)
public GameObject Player;
public float distance = 0.75f;
// Use this for initialization
void Start () {
distance = 0.42f;
}
// Update is called once per frame
void Update () {
transform.position = (transform.position - Player.transform.position).normalized * distance + Player.transform.position;
}
You can try using TrailRenderer.
Here's the documentation: https://docs.unity3d.com/Manual/class-TrailRenderer.html
You can activate it during the run effect and deactivate it otherwise.

StartCoroutine with IEnumerator including Time.deltaTime sometimes infinite loops

I'm hoping someone can help me with the issue I'm having creating my 2D game. I'm using Unity to create an Android application. I'm moving my character (a goat) across the screen using a grid system (GridMove - http://wiki.unity3d.com/index.php/GridMove).
I included the Global and Move script in the following links :
Global : http://codeshare.io/i6BDn
Move : http://codeshare.io/JHDAs
In the move Update function there is a StartCoroutine which moves the goat to a certain position (based on the grid size which is 0.5).
The transform.position gets set with a Vector3.Lerp and the Time.deltaTime. On my computer it works fine, but when I start opening programs or attach the debugger the goat seems to keep looping in the same position. This also happens on a low end phone or even an Samsung Galaxy s4.
Is there a way to stop the goat from resetting his transform.position or a way to check it? I can't seem to pinpoint where it goes wrong.
Looping goat position :
If you need more information, just let me know.
while (t <= 1f)
Lets think about this for a second. You want to move him from position 0f to position 1f. If he reaches 1f, the code will not stop running because the condition of position == 1f still satisfies the while loop condition.
instead it should be like this:
float threshold = Single.Epsilon;
while ((1f - t) > threshold)
Bigger problem:
//Done with moving, set bool to false to "release" the update function
t = 0;
isMoving = false;
yield return 0;
Do not end your Ienumerator with a yield return 0. It does the exact same thing as yield return null, and will loop the IEnumerator all over again. You just reset your variables as well so it will start all over from position 0f. Instead you should end the IEnumerator by just terminating the code. It will not loop over again:
//Done with moving, set bool to false to "release" the update function
t = 0;
isMoving = false;

Mega-man 2 Camera help (Unity)

So I have tried several ways to fix a Mega-man 2 camera without actually figuring out a good way of doing it. So I would love if someone could give me some tips for I could solve this issue.
The problem: As I have written I want a Mega-man 2 camera (2D). The thing from it that I have not been able to solve is how to move it in Y like they do in Mega-man 2. What happens is that the camera does not move at all in Y, unless you go outside "camera bounds" and then it "freezes" the player and moves the camera down, see this video: https://youtu.be/NP697v8WtkU?t=5m2s (Hopefully it starts where the camera does this, otherwise go to 5:02).
But I can't get this to work in any good ways for me. What I have tried so far is, I have triggers at the top and bottom of the camera and when the player hit's one of them I start a coroutine where the camera should Lerp, however, it moves too much, so if I need to move it twice in the Y it would be all messed up. (My code is at the bottom but as I said, I need some help how I can fix it)
So please, if anyone have any idea how I can solve this, please let me know. I've been trying for several days without getting it to work perfectly.
public void ChangeY()
{
StartCoroutine(MoveCameraDown());
}
IEnumerator MoveCameraDown()
{
Vector3 CurrentCameraPos = Camera.main.transform.position;
Vector3 NewCameraPos = new Vector3(Camera.main.transform.position.x, Camera.main.transform.position.y - 5.6f, Camera.main.transform.position.z);
for (float t = 0.0f; t < 1; t += Time.deltaTime / 1.5f /* TIME IT SHOULD TAKE TO MOVE THE CAMERA DOWN */)
{
Camera.main.transform.position = Vector3.Lerp(CurrentCameraPos, NewCameraPos, t);
yield return null;
}
}
And as I have already written, I call the ChangeY() when I hit one of the triggers. This is just my testing case if you are wondering why some numbers are hardcoded and all that. But as I said, I have not been able to fix this so if anyone can help me I would really appreciate it.
It is actually easy to do. You asked for some tips and I will give you one.
Assuming you have already built your 2D world just like in the video you posted. There are many ways to solve this problem but I show you the way I consider to be the easiest. I have 5 steps for you to follow.
You need a value you can use each time to move down. Based on your code, you used -5.6f each time. The go down value should never be the same each time when the player touches the bottom trigger. It should be generated during run time.
This will be addressed in step 3.
1) If the player triggers the camera bottom trigger, first thing you should do is to stop the player, by disabling gravity then setting the rigidbody velocity and torque to zero.
For example inside your OnTriggerEnter function attched to the camera:
void OnTriggerEnter(Collider other) {
Rigidbody rgbd;
rgbd= other.gameObject.GetComponent<Rigidbody> ();
rgbd.useGravity = false;
rgbd.velocity = Vector3.zero;
rgbd.angularVelocity = Vector3.zero;
}
2) Immediately throw Ray cast downward(-y) using Physics.Raycast.
The origin position of the raycast should be the position of the player. For example,
GameObject gameOBjectHitFromRaycast;//Store Game Object detected from the raycast
RaycastHit hit;
if (Physics.Raycast(player.transform.position, Vector3.down, out hit)){
gameOBjectHitFromRaycast = hit.collider.gameObject;
}
The information of the object below the player will be stored in RaycastHit.
Use RaycastHit to get the information about the object that the player is about to land on.
3) After getting the information of that object the player is about to land on, get the y-axis position of that object then get the y-axis position of the player. Add them together then divide the result by 2. That is your new y position. The new position you need to move the camera to. For example,
Vector3 cameraNewPos = new Vector3(player.transform.position.x,(gameOBjectHitFromRaycast.transform.position.y+
camera.transform.position.y)/2,player.transform.position.z);
Basically, what we did here is to find the middle of the player's current position and the the landing position so that we can position the camera right in the middle of it. That should be do it but you can also subtract another number from the y-xis if that is not enough.
4) Use a coroutine function to move the camera smoothly to the new y axis position. Inside the corutine function, there should be public bool variable that will be used to determine if this function is done running(if camera is done moving).
Replace this function with the current camera move function you have
bool cameraMoving = false; //This variable can be used to determine when the camera is done moving
private IEnumerator MoveCameraDown (Vector3 toLocationPos, float timeToFinish)
{
float runTime = 0; //Dont change
float timer = 0; //Dont change
float tempDeltaTime;
cameraMoving = true;
//Will keep looping until camera is done moving
while (timer<timeToFinish) {
//Translate camera
Camera.main.transform.position = Vector3.Lerp (Camera.main.transform.position, toLocationPos, runTime);
tempDeltaTime = Time.deltaTime; //Get delta time once to be used multiple times to improve performance
runTime += tempDeltaTime / timeToFinish;
timer += tempDeltaTime; //Increement To exit loop
yield return null;
}
//Camera is done moving
cameraMoving = false;
}
Finally, to move the camera, you can just start the coroutine and pass in the new position calcluation in step 3 and also how much time you want it to take to do the moving. For example, we can pass in the cameraNewPos from step 3 and 2 for seconds it will take to move the camera.
StartCoroutine (MoveCameraDown (cameraNewPos , 2f));
5) When the camera is done moving, enable rigidbody gravity of the player with
Rigidbody rgbd;
rgbd= player.GetComponent<Rigidbody> ();
rgbd.useGravity = true;

Categories

Resources