Unity2D Disable player movement for X seconds - unity3d

I am trying to disable a player movement for 1 second when it collides with a specific object. The object collides and it detects that just fine however currently the entire game freezes and does not return.
Ive implemented a 0 value (as found in other examples) to the X and Y movements with a timer. This freezes the game entirely and I have to kill process and reopen Unity.
public Boolean frozen = false;
float shipDisabledTimer = 1f;
// Start is called before the first frame update
void Start()
{
SetUpMoveBoundaries();
gameSession = FindObjectOfType<GameSession>();
}
// Update is called once per frame
void Update()
{
Move(frozen);
Fire();
if (SceneManager.GetActiveScene() == SceneManager.GetSceneByName("Game"))
{
if (Input.GetKey(KeyCode.Escape))
SceneManager.LoadScene("Game Over");
}
}
public void Move(Boolean Frozen)
{
UnityEngine.Debug.Log("Update1 - " + Frozen);
var deltaX = Input.GetAxis("Horizontal") * Time.deltaTime * moveSpeed;
var deltaY = Input.GetAxis("Vertical") * Time.deltaTime * moveSpeed;
var newXPos = Mathf.Clamp(transform.position.x + deltaX, xMin, xMax);
var newYPos = Mathf.Clamp(transform.position.y + deltaY, yMin, yMax);
transform.position = new Vector2(newXPos, newYPos);
if (frozen == true)
{
float timer = 0f;
timer += Time.deltaTime;
UnityEngine.Debug.Log(frozen);
while (timer < shipDisabledTimer)
{
newXPos = 0;
newYPos = 0;
}
disableship(frozen = false);
}
}
public Boolean disableship(Boolean frozen)
{
return frozen;
}
private void OnTriggerEnter2D(Collider2D other)
{
UnityEngine.Debug.Log(other.gameObject.name);
if (other.gameObject.name.Equals("Orb Shot(Clone)"))
{
UnityEngine.Debug.Log("Orb hit player");
//StartCoroutine(countdownTimer());
disableship(frozen = true);
}
}
The ship alone should freeze. No other game object should freeze. All scores, counters, enemies should continue onward. Only the player X,Y are disabled for 1 second.

This
while (timer < shipDisabledTimer)
{
newXPos = 0;
newYPos = 0;
}
entirely blocks your game's main thread since the timer nor shipDisabledTime are not changed within the loop so it will never end.
There are a few ways you could go here.
Invoke
Invoke allows you to call a method after a certain delay so you could easily do something like
public void Freeze()
{
frozen = true;
Invoke("Unfreeze"; shipDisabledTime);
}
private void Unfreeze()
{
frozen = false;
}
Coroutine
A Coroutine is like a temporary Update method. Simplest way in your case is using WaitForSeconds and do something like
public void Freeze()
{
StartCoroutine (FreezeRoutine());
}
private IEnumerator FreezeRoutine()
{
frozen = true;
yield return new WaitForSeconds(shipDisabledTime);
frozen = false;
}
simple timer
Or you could use a simple timer but in Update (not a while loop) like
private float timer = -1;
public void Freeze()
{
frozen = true;
timer = shipDisabledTime;
}
private void Update()
{
if(timer > 0)
{
timer -= Time.deltaTime;
if(timer <= 0)
{
timer = -1;
frozen = false;
}
}
...
}
And then either way you simply don't take any movement input in the meantime
public void Move()
{
if(frozen) return;
...
}
Except for the Invoke solution you can also extend them and decided whether you want to e.g. stack multiple freezes, ignore them while already frozen or simply start the timers over.
General note: In c# rather simply use bool it's basically the same but easier to read and write ;)
Also note that this call
disableship(frozen = false);
...
public Boolean disableship(Boolean frozen)
{
return frozen;
}
Is pretty strange ... first of all this method does absolutely nothing than just return the same value you pass in as parameter .. you are hiding the frozen field with a same named parameter so this does nothing!
Second your method returns a Boolean but you are not assigning it to anything.
If you want to change the value simply set it using frozen = XY but don't pass this in as parameter further.
Aaand avoid calling Debug.Log every frame .. it will slow down your app even in a build where you don't even see the log!

Related

Unity - Decreasing float var with "Time.deltaTime" never going down

I'm try to create a function to stop player control, but when I pass the duration as a parameter in the function, the variable keeps ina huge number never really decreasing.
public void StopAllPlayerControl(float duration) {
stopPlayerDuration = duration;
stopPlayerDuration -= Time.deltaTime;
//stop here what you need.
playerInput.enableMovement = false;
if (stopPlayerDuration <= 0) {
//restore here to normal state
playerInput.enableMovement = true;
}
}
And Im calling this funct in a Update method like this
StopAllPlayerControl(3);
Image here to show that stopPlayerDuration getsm huge but stays in those big numbers never really decreasing. huge number
I used a combination of both answers, organized my logic better and, in this case, I used a coroutine.
This function starts the coroutine and I can call it and control it with a bool
public void startStopPlayerCo(float dur) {
//function to start stop player courutine from anywhere.
StartCoroutine(StopPlayerCo(dur));
}
This is the coroutine that works well now
public IEnumerator StopPlayerCo(float dur) {
while (startCO) {
GetComponent<PlayerMove>().enabled = false;
yield return new WaitForSeconds(dur);
//reset
GetComponent<PlayerMove>().enabled = true;
startCO = false;
}
}
And I call it in Update Method like this:
startStopPlayerCo(2) //2 seconds, or any var for desired time
Try this:
float startTime = 0;
float duration = 0;
public void StopAllPlayerControl(float duration)
{
this.duration = duration;
if(playerInput.enableMovement)
{
startTime = Time.time;
playerInput.enableMovement = false;
}
if(Time.time - startTime >= this.duration)
{
playerInput.enableMovement = true;
}
}
Explaination
Step 1
Firstly, we need to check if movement is already enabled. If it is, we mark the point that we started the timer, and disable movement. We also store our duration in a variable, and reference it locally with the `this` keyword.
if(playerInput.enableMovement)
{
startTime = Time.time;
playerInput.enableMovement = false;
}
Step 2
If the timer exceeds the duration then movement is enabled.
if(Time.time - startTime < duration)
{
playerInput.enableMovement = true;
}
It is also good practice to enclose this method in a boolean lock to prevent it from being called again while the timer is running, unless you want such a feature to be allowed.

Unity: Input.GetButtonDown is not reliably detected in Update?

I have a player script that I'm trying to implement jumping in. The problem is that Input.GetButtonDown("Jump") doesn't consistently fire in Update. I know this is a problem when it's in FixedUpdate. Here is my code.
Note: I put [SerializeField] on pressingJump and holdingJump so I could see what's happening. pressingJump is inconsistent (as expected) but holdingJump works perfectly.
using UnityEngine;
public class Player : MonoBehaviour
{
public float fallMultiplier = 15f;
public float lowJumpMultiplier = 10f;
public float walkSpeed = 20f;
public float jumpSpeed = 15f;
public bool canMove = true;
public bool canJump = true;
Rigidbody rb;
float horizontalInput;
bool doJump;
bool pressingJump;
bool holdingJump;
void Start()
{
rb = GetComponent<Rigidbody>();
}
void Update()
{
CaputureInput();
}
void CaputureInput()
{
if (canMove) {
horizontalInput = Input.GetAxisRaw("Horizontal");
}
pressingJump = Input.GetButtonDown ("Jump");
doJump = pressingJump && canJump;
holdingJump = Input.GetButton ("Jump") && canJump;
}
void FixedUpdate()
{
Move();
Jump();
}
void Move()
{
rb.velocity = new Vector2(horizontalInput * walkSpeed, rb.velocity.y);
}
void Jump()
{
if (doJump) {
rb.velocity += Vector3.up * jumpSpeed;
doJump = false;
}
if (rb.velocity.y < 0) {
rb.velocity += Vector3.up * Physics.gravity.y * (fallMultiplier - 1) * Time.deltaTime;
} else if (rb.velocity.y > 0 && !holdingJump) {
rb.velocity += Vector3.up * Physics.gravity.y * (lowJumpMultiplier - 1) * Time.deltaTime;
}
}
}
Ok found the solution here: https://www.reddit.com/r/Unity3D/comments/9nr2sd/inputgetbuttondown_and_getbuttonup_dont_work/e7ogta1?utm_source=share&utm_medium=web2x
This was the problem I was having.
Edit: Comment pointed out a link might not be accepted. Here is the relevant content from link. It points out why the issue is occurring and how to fix it.
What you end up with, with the code you've written, is essentially the
same as when you do input checking in FixedUpdate.
This is because Update runs several times in between every
FixedUpdate. Now, what this means for your code in this case is that
yes, you do capture the state of ButtonDown("Jump") during Update, but
think about this: what happens to button_down/up_jump if another
Update happens before a FixedUpdate? Both GetButtonDown and
GetButtonUp are functions that are true only for the one frame in
which the keypress/-release happened. In other words, this is
happening
Update runs -> button_down_jump is false
user presses "Jump".
Update runs -> button_down_jump is true
Update runs -> button_down_jump is false
FixedUpdate runs -> nothing happens, because of button_down_jump's
state
Now, you've actually had a few cases where you press/unpress "jump" at
just the right time to manage to get anything printed at all. This
will only happen given that the following happens:
User un-/press "jump"
Update runs (ONCE)
FixedUpdate runs
What you actually want to do is something like this:
void Update() {
if(Input.GetButtonDown("jump")) {
button_down_jump = true; //this captures the state of the boolean beyond the one frame, as it's only set whenever the input
event happens
}
if(Input.GetButtonUp("jump")) {
button_up_jump = true;
} }

Move rigidbody from point a to b [duplicate]

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

MissingReferenceException: Error after destroying a moving game object

In my game some objects move every few seconds and others don't. I have created a the method below to handle the movement of moving objects.
private IEnumerator moveObject(GameObject movingObject, float posX, float duration) {
if(movingObject != null) {
float elapsedTime = 0;
Vector3 startPos = movingObject.transform.position;
Vector3 endpos = new Vector3(posX, objectPositionY, camera.nearClipPlane);//hole.transform.position;
while(movingObject != null && elapsedTime < duration) {
movingObject.transform.position = Vector3.Lerp(startPos, endpos, elapsedTime / duration);
elapsedTime += Time.deltaTime;
yield return null;
}
}
}
Also I destroy the moving objects using the code below:
for (int i = 0; i < holeObjects.Count; i++) {
Debug.Log ("countingholeobjectindex"+ i );
if (col.gameObject.Equals (holeObjects [i].getHoleObject ())) {
Debug.Log ("condition satisfied!" );
foreach( HoleObjectSetup holeItem in holeObjects )
{
if (holeItem.getHoleObjectType () == HoleObjectSetup.HoleObjectType.moving) {
int holeItemIndex = holeObjects.IndexOf(holeItem);
holeItem.StopAllCoroutines();
holeItem.DestroyHoleObject ();
//holeObjects.RemoveAll(item=>item==null);
//holeObjects.RemoveAt(holeItemIndex);
}
}
}
Any time I destroy a movingObject (mostly, when it's moving), I get the error
MissingReferenceException: The object of type 'GameObject' has been destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.
pointing to the line
movingObject.transform.position = Vector3.Lerp(startPos, endpos, elapsedTime / duration);
in the movingObject method (provided above). I tried adding another nullity check to surround the contents of the while loop, but that made Unity3d unresponsive, leaving me with the only option of force quitting. How can I resolve this issue?
UPDATE
movingObject is called in the coroutine below:
private IEnumerator MovementLogic(GameObject movingObject)
{
// keep going until deactivated or disabled
float currentPos = movingObject.transform.position.x;
while(filledPositions.Contains(currentPos))
{
float newPos = getNextPos(currentPos);
if(freePositions.Contains(newPos))
{
freePositions.Add (currentPos);
filledPositions.Remove (currentPos);
freePositions.Remove (newPos);
filledPositions.Add (newPos);
yield return moveObject(movingObject, newPos, movementDuration);
//update positions
yield return new WaitForSeconds(movementDelay);
currentPos = newPos;
}
else
{
// if a valid hole wasn't found, try the other direction next frame
ReverseDirection();
}
}
yield return null;
//}
}
UPDATE 2
The code for DestroyHoleObject() which is in HoleObjectSetup is:
public void DestroyHoleObject () {
Destroy (currentHoleObject);
}
and
public void createHoleObjectType (GameObject holeObject, HoleObjectType holeObjectType, Vector3 holeObjectPosition, bool isCollider2DEnabled) {
currentHoleObject = (GameObject)Instantiate (holeObject, holeObjectPosition, Quaternion.identity);
setHoleObjectType (holeObjectType);
}
Solve the problem by adding
Debug.Log
statements everywhere. It will take a few seconds to find out the exact point in the cycle that is causing problems.
Secondly,
Sometimes a cheap solution to these problems is, use
DestroyImmediate
rather than
Destroy
This might give you a quick fix.
Thirdly. Be more sophisticated about coroutines:
Something like ..
void OnEnable()
{
Debug.Log("enable ..");
}
void OnDisable()
{
StopCoroutine("Whatever");
.. or perhaps ..
StopAllCoroutines();
}
Moreover, ideally you should "handle it manually".
Consider the object you are getting rid of.
That class, should have a function something like this:
void BeingDestroyed()
{
.. carefully write your own code here to shut down the object ..
}
... and actually call that explicitly when you are about to destroy it.
It's not that easy to add, destroy stuff in games. It's often very complex.
Could be the object destroys itself: perform any needed shutdown code, wait a frame or two if that is relevant to you, remove from any lists, and then "destroy self".
You can't just "leave coroutines alone": you have to manage them carefully by hand.

Lifting platforms is not working as it should

I would like to make some lifting platforms in my game, so if the platform went down, the characters can't go over it. I have written a script for it, but for some reason the "lifting up" is not working as intended. It won't go back to its starting place, but it will go a bit below. And for some reason it won't go smoothly to the place where it should, just "teleport" there and done. I thougt multiplying Time.deltaTime with a const will help, but it is the same.
Here is my code, any help would be appreciated:
public class LiftingPlatform : MonoBehaviour {
private Transform lift;
private bool isCanBeLifted;
private float timeToLift;
public float timeNeededToLift = 5f;
private Vector3 startPos;
private Vector3 downPos;
private Vector3 shouldPos;
private bool isDown;
public GameObject[] collidingWalls;
// Use this for initialization
void Start () {
lift = transform;
isCanBeLifted = true;
timeToLift = 0f;
isDown = false;
startPos = transform.position;
downPos = new Vector3(startPos.x, startPos.y - 5f, startPos.z);
}
// Update is called once per frame
void Update () {
timeToLift += Time.deltaTime;
if (timeToLift >= timeNeededToLift) {
if (isCanBeLifted) {
if (isDown) {
shouldPos = Vector3.Lerp(startPos, downPos, Time.deltaTime * 10);
lift.position = new Vector3(shouldPos.x, shouldPos.y, shouldPos.z);
isDown = true;
}
else if (!isDown) {
shouldPos = Vector3.Lerp(downPos, new Vector3(startPos.x, startPos.y, startPos.z), Time.deltaTime * 10);
lift.position = new Vector3(shouldPos.x, shouldPos.y, shouldPos.z);
isDown = false;
}
}
timeToLift = 0;
}
if (!isDown) {
for (int i = 0; i < collidingWalls.Length; i++) {
collidingWalls[i].SetActive(true);
}
}
else if (isDown) {
for (int i = 0; i < collidingWalls.Length; i++) {
collidingWalls[i].SetActive(false);
}
}
}
void OnTriggerEnter(Collider collider) {
if (collider.tag == "Player" || collider.tag == "Enemy") {
isCanBeLifted = false;
}
}
void OnTriggerExit(Collider collider) {
if (collider.tag == "Player" || collider.tag == "Enemy") {
isCanBeLifted = true;
}
}
}
These lifting platforms are a child of another Platforms object.
It doesn't look like you are updating the object's position every frame. You are only checking if the total time passed is greater than the time needed to lift, and then updating the position to a value that is dependent on the delta time (using the Vector3.Lerp function).
What I would do is in the update step, if timeToLift is greater then timeNeededToLift, subtract the latter from the former and invert the value of isDown. Then, in your Vector3.Lerp, make the third argument (timeToLift / timeNeededToLift) instead of (Time.deltaTime * 10). Can you try that and see if it works?
The third argument for Vector3.Lerp is the "blending factor" between the two vectors, 0 is the first vector, 1 is the second, and 0.5 is in between. If the total time is greater than the time needed to lift, but the delta time is not greater than 1, it will get the position of the platform using a blending factor of less than 1, resulting in a platform that didn't move fully.