StackOverflow error when trying to repeatedly check a variable - c#

Hey before I start I want to let everyone know I'm not only new to this forum but Unity and C# itself so I apologize if there is an easy solution or other silly mistakes.
Alright so basically what I am trying to do is switch the gravity of my player when they hit space, to achieve this I am checking the players transform.position.y and seeing if it is at it's designated height and if it's not I add force.
The area of code:
private void ChangeGravity()
{
if (rb.position.y >= 10f)
{
SAF = false;
rb.constraints = RigidbodyConstraints.FreezePositionY | RigidbodyConstraints.FreezePositionZ;
}
else
{
rb.AddForce(0, VerticalForce * Time.deltaTime, 0);
ChangeGravity();
}
}
For clarification SAF is a precautionary measure so that the player can't spam the space button.
Also VerticalForce = 2f and through my testing I have determined that it is possible for the if statement to be true (this test was by setting the y to 10)
Now here's the error:
StackOverflowException
UnityEngine.Rigidbody.AddForce (Vector3 force, ForceMode mode)
UnityEngine.Rigidbody.AddForce (Single x, Single y, Single z) (at C:/buildslave/unity/build/Runtime/Dynamics/ScriptBindings/Dynamics.bindings.cs:171)
PlayerMovement.ChangeGravity () (at Assets/Scripts/PlayerMovement.cs:21)
PlayerMovement.ChangeGravity () (at Assets/Scripts/PlayerMovement.cs:22)
(The final line repeats a bunch but I cut that out)
The Entire Script: The Script
EDIT
I finally found a very helpful tutorial that i wouldnt have found without you guys on how to reverse gravity of an object, this makes this question obsolete, thank you for your time i'm sorry i didn't find this before making the question.

Your method does not return, it calls itself which in turn calls itself again.
Since calling a function allocates some memory on the thread’s stack, the stack is soon overflowed since it has a space limit of a few MegaBytes.
Call this method in while loop, break the loop when the condition is met. So in each iteration of the loop the method is returned and the call stack won’t grow.
while (true)
{
if (rb.position.y >= 10f)
{
SAF = false;
rb.constraints = RigidbodyConstraints.FreezePositionY | RigidbodyConstraints.FreezePositionZ;
break; //break the loop since condition is met
}
else
{
rb.AddForce(0, VerticalForce * Time.deltaTime, 0);
continue; //the condition is not met, so the loop goes on
}
}

Related

How to smoothly turn character controller in X Direction

Overview
I'm making an endless runner game. In this game, I have 5 lines, I want to player smoothly switch lines something like this Ref Link
In my case, I have everything the same but instead of a car, I have a player with PlayerController attached to it.
I'm changing the player line on Button click and also on IPointerDownHandler & IPointerUpHandler
Code
Full Code
[SerializeField] private List<Vector3> lines; // 5 lines in my case. Vector3 (0,0,0) and so on ...
private int flag;
Vector3 currLine;
private void ChangeLines ()
{
// Getting Inputs
if (Input.GetKey(KeyCode.LeftArrow)) { flag = -1; }
else if (Input.GetKey(KeyCode.RightArrow)) { flag = 1; }
else flag = 0;
if (flag > 0) MoveRight ();
else if (flag < 0) MoveLeft ();
}
//I used two approaches to moving but both are not working as indented
// 1 _ using DoTween
// 2 _ using Vector3.Lerp ()
private void MoveRight ()
{
// some input delay for Ipointers
if (inputDelay > 0) return;
if (currLine == lines [lines.Count - 1]) return; // can't move right anymore
transform.DoRotate (new Vector3(0, 45, 0) , 0.2f); // rotate player toward target
transform.DoMoveX (currLine.X, 0.3f) // 0.3f is coming from inspector
.SetEase (Ease.Linear) // i almost tried all Ease
.OnComplete ( ()=> DoTween.DoRotate (new Vector3(0, 0, 0) , 0.2f));
// using Lerp
LookAt (new Vector3 (currLine.x,Y,Z));
transform.position = Vector3.Lerp(transform.position, new Vector3(currLine.x, ..,..), lineChangeCurve
.Evaluate(Time.deltaTime * lineChangeSpeed));
}
private void MoveLeft ()
{
// same code as MoveRight
}
Problem
The code I wrote is prettier much working. the player is changing lines and also rotating towards the line but I'm unable to figure out what should i need to do to make this effect look like a reference.
Can you tell me how can I achieve the same smoother effect as the reference for my player?
Here is the link that I made so far
3D Assets link
Player lines distance :
new Vector3 (-8, 0,0)
new Vector3 (-4, 0,0)
new Vector3 (0, 0,0)
new Vector3 (4, 0,0)
new Vector3 (8, 0,0)
Thanks in Advance
You seem to be mixing two different animation techniques.
You do
transform.DoMoveX (currLine.X, 0.3f)
.SetEase (Ease.Linear)
...
which starts a tween animation
and then you also do Evaluate on the lerp which seems to be a bit redundant.
Your issue with the Lerp and Evaluate is
a) that you pass in
Time.deltaTime * lineChangeSpeed
which basically means
lineChangeSpeed / frame rate (e.g. 60)
=> This is a way to small value and will most probably basically mean you don't move at all (depending on your used curve)
You want to call this every frame and pass in a value increasing from 0 to 1 here (e.g. in a Coroutine)
b) you call it only exactly once. This will not result in any movement at all .. or at least only in a single step in the first frame => which probably causes a little "hiccup"
=> get rid of the lerp line all together. At best it interferes with the tween animation and is probably the cause of it looking somewhat off
To the question about keeping the button pressed.
You have nothing to prevent this in
private void ChangeLines ()
{
// Getting Inputs
if (Input.GetKey(KeyCode.LeftArrow)) { flag = -1; }
else if (Input.GetKey(KeyCode.RightArrow)) { flag = 1; }
else flag = 0;
if (flag > 0) MoveRight ();
else if (flag < 0) MoveLeft ();
}
First of all the flag seems quite redundant. And then you would
Either change to GetKeyDown so only the first frame where the button goes down is handled
Or add a flag that ignores all input while an animation is already running
Or even both (depending on your desired UX)
So e.g.
private bool alreadyAnimating;
private void ChangeLines ()
{
if(alreadyAnimating) return;
if (Input.GetKey(KeyCode.LeftArrow))
// Or as said maybe even better
if (Input.GetKeyDown(KeyCode.LeftArrow))
{
MoveLeft();
}
else if (Input.GetKey(KeyCode.RightArrow))
// same here
else if (Input.GetKeyDown(KeyCode.RightArrow))
{
MoveRight();
}
}
and then block and reset the flag when done with moving
alreadyAnimating = true;
transform.DoRotate (new Vector3(0, 45, 0) , 0.2f);
transform.DoMoveX (currLine.X, 0.3f)
.SetEase (Ease.Linear)
.OnComplete (()=>
{
DoTween.DoRotate (new Vector3(0, 0, 0) , 0.2f)
.OnComplete(() => { alreadyAnimating = false; })
});
I used Lerp() for these kind of conditions and it worked well all the time. As I saw your character correctly turns but it look like a snap turn. I couldn't spot an obvious error in your code. Play a little more with the rotation time and you will get your desired result. Sorry this must be a comment but dont have the reps.
The idea of the animation for switching lines is pretty simple. All you need is an animation of turning the main character to the side and back. Let's say the line change animation takes 1 second. Then you need that when moving to the left lane, the main character makes a turn to the left in 0.5 seconds and the next 0.5 seconds turns straight again. While the gameobject moves into the adjacent line, the main character will make a small smooth turn and the animation will look better. This is how it is done in the example you showed in the question. In your video, instead of this animation, the main character abruptly turns to a certain angle and returns to its original position abruptly back when it reaches the required line.

Lerp brings object to unrelated position

I've been trying to make a script that moves the selected object smoothly up and down, which seems simple enough, but I was pretty confused when my script kept putting the object in a weird Y position.
I finally got it to work, after trying half of the day, and then scrapping the code, but one question still remains unanswered in my head. Why does the following script not work?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Spin : MonoBehaviour
{
string UpOrDown;
Vector3 originpos;
Vector3 goalUp;
Vector3 goalDown;
void start()
{
originpos = transform.position;
goalUp = originpos + new Vector3(0, 0.05f, 0);
goalDown = originpos - new Vector3(0, 0.05f, 0);
UpOrDown = "Up";
}
string TweenLerp(Vector3 destination, float time, string UpOrDownResult) {
while (transform.position != destination)
{
transform.position = Vector3.Lerp(transform.position, destination, time);
}
return UpOrDownResult;
}
void Update()
{
if (UpOrDown == "Up")
UpOrDown = TweenLerp(goalUp, 0.125f, "Down");
else if (UpOrDown == "Down")
UpOrDown = TweenLerp(goalDown, 0.125f, "Up");
}
}
Sorry if the script seems kind of stupid or with crappy logic, im still kind of new to c#, unity and vectors, so trying to figure out how it works.
so your code works fine, and everything is correct, the reason it's not working is that you called the Start function start(), lowercase, which makes unity not call it. To make this code work all you need is to substitute start() with Start().
Some extra notes to be aware of Lerp and the way you are using it: The way you're doing this animation does work, but not in the way that I think you intended, here's why:
Lerp stands for linear interpolation, and the way it works is that you give a start value (transform.position in your case), an end value (destination), and a value (t) between 0 and 1 that indicates if your value should be more to the start or to the end if t = 0, the result will be the start value, if t = 1, the result will be the end value.
The reason why this works is that everytime you call this lerp function with a value grater than 0, the result value is bit closer to the destination. and if you keep doing this many and many times, eventually it'll get to the destination, but not with linear speed, it will start very fast and end very slow
In your code, you are repeatedly calling this function until the transform.position is equal to the destination using a parameter t called time, the problem is that this "time" value does NOT affect the time of the animation. Since you're doing all of the lerps to get to the destination in one single while loop, they all happen in the same frame, which means that this function is doing the same as transform.position = destination but with a lot of unnecessary Lerp calculations. Btw, if you set the time to 0 in this case you get an infinite loop and you'll need to restart unity.
If you want to have the animations actually happen thru some time and not in one single frame, you should call this Lerp in the Update, so that in each frame it moves a little bit. Your Uptade could look like this:
void Update()
{
if (transform.position == goalUp)
UpOrDown = "Down";
else if (transform.position == goalDown)
UpOrDown = "Up";
transform.position = Vector3.Lerp(transform.position, UpOrDown == "Up" ? goalUp : goalDown, time);
}
One more thing: this time parameter actually makes more sense interpreted as speed, since the lower the value of time, the slower the animation will happen.
Sorry for the size of the answer, hope I have helped!

OverlapCircleAll optimization

I've made boids in unity but when trying to render a 1000 of them the performance is really bad, in my update function i use Physics.OverlapCircleAll to check all surroundiing boids. Is there any way to do this more optimized? Here is my update function:
void Update()
{
Collider2D[] hitColliders = Physics2D.OverlapCircleAll(Position, radius,layerMask.value);
List<Boid> boids = hitColliders.Select(o => o.GetComponent<Boid>()).ToList();
boids.Remove(this);
Flock(boids.ToArray());
}
Absolutely! Physics.OverlapCircleAll creates a lot of garbage every time it is called. What you're looking for is Physics.OverlapCircleNonAlloc, which will not create any garbage as it uses a buffer:
Collider2D[] hitsBuffer = new Collider2D[30]; //limit the amout of possible boid interations
void Update()
{
int numHits = Physics2D.OverlapCircleNonAlloc(Position, radius, hitsBuffer, layerMask.value);
Flock(hitsBuffer,numHits);
}
void Flock(Collider2D[] hitsBuffer, int numHits){
for(int i = 0; i < numHits; i++){
var boid = hitsBuffer[i].GetComponent<Boid>();
if(boid == this)
continue;
//flocking algorith here
}
}
Note how in the above code no additional arrays are created each frame, which is quite expensive. To check how much time is being spent where check out the Profiler:
Orange is 'Physics', working out the overlaps
Cyan is 'Scripts', calcuations in code, ie the flocking algorithm
Dark green is 'GarbageCollector', handling arrays created and destroyed each frame
PS If not already, ensure that the boids are using a CircleCollider2D, this is the easiest for Unity to calculate.
PPS You may want to double check that if(boid == this) actually gets called. I thought that Physics.Overlap... ignores this collider.

Physics inconsistent even when using FixedUpdate() or Time.DeltaTime()

I want to move a rigidBody2D for a fixed amount of time with a fixed amount of velocity, when the player presses a button. I am calling the method in FixedUpdate() and the results are not consistent. Sometimes the rigidBody will travel more and sometimes less (Here is a relevant part of my code how i went about do this)
void Update()
{
GetPlayerInput();
}
private void FixedUpdate()
{
GroundCheck();
ApplayNormalPlayerMovement();
ApplyMove(); // This is the method of interest - I tried calling this in Update() with Time.DeltaTime - still inconsistent results.
MoveCooldownCounter(); // I tried calling this in Update() - inconsistent results also
}
IEnumerator MoveRB()
{
savedVelocityX = rb.velocity.x;
rb.gravityScale = 0;
savedXinput = xInput;
while (moveTimer >= 0)
{
if (xInput != 0)
xInput = 0;
cc.sharedMaterial = noFriction;
if (facingRight)
{
rb.velocity = new Vector2(moveSpeed, .0f); // I tried multiplying the moveSpeed by Time.DeltaTime and FixedDeltaTime - still inconsistent results.
} else
{
rb.velocity = new Vector2(-moveSpeed, .0f);
}
moveTimer -= Time.fixedDeltaTime;
yield return null;
}
moveTimer= moveDuration;
rb.gravityScale = gravity;
rb.velocity = new Vector2 (savedVelocityX, .0f);
xInput = savedXinput;
moveCooldownInternal -= Time.deltaTime; // I tried changing this to a specific float - still inconsistent results in physics..
}
private void MoveCooldownCounter()
{
if (moveCooldownInternal != moveCooldown)
{
moveCooldownInternal -= Time.deltaTime;
if (moveCooldownInternal <= 0)
{
moveCooldownInternal = moveCooldown;
}
if (moveCooldownInternal == moveCooldown && isGrounded)
canMove = true;
}
}
private void ApplyMove()
{
if (b_Fire3 && canMove)
{
StartCoroutine("MoveRB");
canMove= false;
}
}
Side note: right now i experience player input loss on occasions because i call this ApplyMove() in FixedUpdate() (will have to find a workaround for that as well - naturally if I can call the ApplyMove in Update() and get consistent results than this issue would be fixed).
Pleas excise my newbiness, i am trying to learn :)
Thank you in advance!
Add comment
Unity is a game engine. All game engines are based on the game loop - however Unity somewhat hides if from you, behind the 3 Update functions.
Not all parts are equally important. Drawing is generally skipped when the GPU is not ready or the FPS are capped. And Drawing and physics are generally calculated independantly of one another (unless you really messed up the development).
With Physics and Animations, you generally want to stuff to be consistent to the Game Tick Counter, not real time. All movement of game pieces and all animation should count in game ticks. Real Time is a nice figure and all, but ticks can have wildly different processing times. Especially since the CPU side drawing work also has to run in the same thread.
Just figure out your maximum ticks/second. And if you want to run something to run for "roughly" 10 ingame seconds, you multiply that value. It does not mater if the game on this machine needs 2 seconds for 200 ticks or 2 minutes for 200 ticks - with the number of ticks, the results will be consistent (unless the math itself is inprecise, like floating point math).
If it's not multiplayer game, it does not have to be hackproof and thus easiest fix is to capture the first frame of the button pressed and apply the force only on that frame, because of that we have to capture input on the Update function and also there immadietly apply the force even though it's suggested to do physics stuff in fixed update, but if you use delta time in update function for calculation it should be fine!
Now I think you know how to do that but I'll write it anyway we can either use apply force or set the velocity,
void Update()
{
if(Input.GetKeyDown("w"))
rb.velocity = new Vector2(rb.velocity.x, rb.velocity.y + 5f*Time.deltaTime);
}
Note: I didn't test it but it should be allright since GetKeyDown returns true only on the first frame when the button was pressed,
If it was just GetKey and in case of jump we would check the grounding of player, we could be during jump and still be grounded on the second frame even if we jumped because of not really precise ground checking.
PS. Use fixedDeltaTime in fixedUpdate method!

Make loop wait for action to complete

Im working in Unity3d (this is more a C# question, so I doubt that is an issue). Im working on a movement system like you would find in Civilization. I have a loop setup so that you can move 2 squares per turn. This works fine. I click on a square 10 blocks away and it takes 5 turns to get there. Now im trying to make the pawn lerp between blocks. I have got lerp to work, problem is, it jumps from the current tile to the 1st tile, then transitions to the 2nd tile where its supposed to be. I used a coroutine to make this work instead of the update function (as the update would cause it to just lerp to the final destination instead of from current, to first, to second). So what im running into is the loop that goes through each move the pawn has, isnt waiting for the coroutine to complete before continuing its own loop. Here is the code
public void MoveNextTile()
{
float remainingMoves = moveSpeed;
while (remainingMoves > 0)
{
if (currentPath == null)
return;
//Get the cost from current tile to next tile
remainingMoves -= map.CostToEnterTile(currentPath[0].x, currentPath[0].y, currentPath[1].x, currentPath[1].y);
//Move us to the next tile in the sequence
toVector = map.TileCoordToWorldCoord(currentPath[1].x, currentPath[1].y);
Vector3 fromVec = transform.position;
StartCoroutine(MoveObject(fromVec, toVector, 1.0f));
//transform.position = map.TileCoordToWorldCoord(currentPath[1].x, currentPath[1].y);
//Remove the old current tile
this.tileX = currentPath[0].x;
this.tileY = currentPath[0].y;
currentPath.RemoveAt(0);
if (currentPath.Count == 1)
{
this.tileX = currentPath[0].x;
this.tileY = currentPath[0].y;
currentPath = null;
}
}
}
IEnumerator MoveObject(Vector3 source, Vector3 target, float overTime)
{
float startTime = Time.time;
while (Time.time < startTime + overTime)
{
transform.position = Vector3.Lerp(source, target, (Time.time - startTime) / overTime);
yield return null;
}
transform.position = target;
}
I know this is a noob question. I just never have needed to do this in C# before. Thank in advance for all the help
I suggest you research into how coroutines work.
Your coroutine doesn't execute fully and then return to complete the rest of your MoveNextTile function. It actually executes up until the first yield statement and then continues execution of MoveNextTile. Each subsequent frame will continue to run one 'step' of the coroutine until the next yield statement in an attempt to replicate asynchronous methods.
What you want to do is tell your program to explicitly wait for your coroutine to finish. To do so;
yield return StartCoroutine(MoveObject(fromVec, toVector, 1.0f));
Of course, you can only use this statement inside of an IEnumerator. So you would have to change your void MoveNextTile function to IEnumerator MoveNextTile. You end up with something as follows;
public IEnumerator MoveNextTile() {
// Ommited
while (remainingMoves > 0) {
//Ommited
yield return StartCoroutine(MoveObject(fromVec, toVector, 1.0f));
// Now MoveNextTile will continue after your MoveObject coroutine finishes.
}
}

Categories

Resources