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

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;
} }

Related

How to get OnTriggerStay to work on every frame?

so I have this code :
public class ball_physics : MonoBehaviour
{
public Rigidbody ball;
public open2close claw;
public Vector3 offset;
Rigidbody rb;
private float forceMultiplier = 20;
private bool isShoot;
Vector3 start_position;
public path path;
void Start()
{
rb = GetComponent<Rigidbody>();
rb.Sleep();
start_position = transform.position;
}
void Update()
{
// *************
}
void Shoot(Vector3 Force)
{
if (isShoot)
{
print("isshot false");
return;
}
rb.AddForce(new Vector3(Force.x, Force.y,Force.z)* forceMultiplier);
isShoot = true;
path.Instance.HideLine();
}
private void OnTriggerStay(Collider ball)
{
if (isShoot)
{
return;
}
print("ontriggerstay");
if (claw.isClosed && claw.transform.gameObject.tag == "claw" )
{
print("claw");
//rb.Sleep();
transform.position = claw.rightClaw.transform.position + offset;
Vector3 forceInit = (start_position - transform.position);
path.Instance.UpdateTrajectory(forceInit * forceMultiplier, rb, transform.position);
}
}
private void OnTriggerExit(Collider ball)
{
if (claw.isClosed && claw.transform.gameObject.tag == "claw")
{
rb.WakeUp();
}
if (claw.isClosed == false)
{
Shoot(start_position - transform.position);
}
}
}
So on my other codes I have OnStayTrigger, which basically a clawhand grabs a ball and pulls. But this code is suppose to show a trajectory line and shoot. When I shoot the first time it works. But when I try again, The clawhand can grab the ball but it doesnt show the trajectory line nor shoots it. So im guessing this is the code that needs to be fixed. How can I make OnTriggerStay work all the time. I wouldnt want to change any code because it works perfectly. How can I just keep updating the OnTriggerstay so it can work?
At first glance it appears that You never set isShoot back to false, so it would never fire again because in OnTriggerStay you have it return if isShoot = true.

Smoothly change the player's speed when he leaves the zone

I'm trying to make special zone located on ground that accelerates the player, entering which the player's speed increases. When he gets out of it, the speed smoothly returns to the original. But now, when the player leaves the zone, the speed instantly becomes the original. Don't know how to realize it. Please help:)
My code:
PlayerMovement:
[SerializeField] private Rigidbody _rigidoby;
public bool inZone = false;
private void Update()
{
if (inZone == false)
{
_rigidoby.velocity = new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0, Input.GetAxis("Vertical") * Time.deltaTime) * 500;
}
}
SpecialZone:
private Vector3 _cachedVelocity;
private Rigidbody _collisionRigidbody;
[SerializeField] private PlayerMovement _player;
private void OnTriggerStay(Collider other)
{
_player.inZone = true;
if (_collisionRigidbody == null)
{
_collisionRigidbody = other.GetComponent<Rigidbody>();
_cachedVelocity = _collisionRigidbody.velocity;
}
_collisionRigidbody.velocity = new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0, Input.GetAxis("Vertical") * Time.deltaTime) * 1000;
}
private void OnTriggerExit(Collider other)
{
_collisionRigidbody.velocity = _cachedVelocity;
_player.inZone = false;
}
A simple solution can be to gradually decrease the speed in the update once you exit the trigger like this:
private void OnTriggerExit(Collider other)
{
//_collisionRigidbody.velocity = _cachedVelocity;
_player.inZone = false;
}
private void Update()
{
if (inZone == false)
{
_rigidoby.velocity = new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0, Input.GetAxis("Vertical") * Time.deltaTime) * 500;
} else {
If (_collisionRigidbody.velocity > _cachedVelocity;)
_collisionRigidbody.velocity -= 0.01;
}
}
Another could be to launch a coroutine when you exit the trigger.
Alternatively, you could use Vector3.Lerp and set the percent of the speed change in the update like this:
Vector3 _startingSpeedBeforeDeceleration = Vector3.zero;
private void OnTriggerExit(Collider other)
{
//_collisionRigidbody.velocity = _cachedVelocity;
_startingSpeedBeforeDeceleration = _collisionRigidbody.velocity;
_player.inZone = false;
}
float percent = 0f; //from 0 to 1
private void Update()
{
if (inZone == false)
{
_rigidoby.velocity = new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0, Input.GetAxis("Vertical") * Time.deltaTime) * 500;
} else {
If (_collisionRigidbody.velocity > _cachedVelocity;) {
_collisionRigidbody.velocity = Vector3.Lerp(_startingSpeedBeforeDeceleration, _cachedVelocity, percent);
percent += 0,01f;
}
}
}
Not debugged code. Note that I kept the departing speed in the variable _startingSpeedBeforeDeceleration, that is optional, and depends on the movement behaviour you want.
You can play around with Vector3.Lerp for example changing the first argument _startingSpeedBeforeDeceleration as a fixed value, and put there rigidbody speed every time it changes like this Vector3.Lerp(_collisionRigidbody.velocity, _cachedVelocity, percent);, that way you have a smooth start and smooth end, and a top speed change at the middle of the path. Also the percent change can be handled to set the movement behaviour.

Physics2D.OverlapBox shows inconsistent behaviour

Recently I started working with Unity, this is the first time I'm trying to build a 2d platformer.
For some reason, when I press the jump button, there is a random chance that it will actually make the player jump. Probably around 1 in 50 that it actually jumps.
I just can't figure out why its doing that. Do you know what I'm doing wrong here?
using UnityEngine;
public class Player : MonoBehaviour
{
public float movespeed = 5f;
public float jumpforce = 5f;
public Rigidbody2D player;
public LayerMask layerMaskPlatforms;
private float movementHorizontalInput;
private bool jumpInput;
private float lastTimeOnGroundInSeconds = 0f;
private float lastTimePressedJump = 0f;
void OnBecameInvisible()
{
// todo: restart game
}
void Update()
{
movementHorizontalInput = Input.GetAxisRaw("Horizontal");
jumpInput = Input.GetButtonDown("Jump");
}
void FixedUpdate()
{
if (jumpInput)
{
AttemptJump();
lastTimePressedJump = 0.2f;
}
else if (lastTimePressedJump > 0)
{
AttemptJump();
lastTimePressedJump -= Time.deltaTime;
}
if (IsOnGround())
{
lastTimeOnGroundInSeconds = 0.2f;
}
else if (lastTimeOnGroundInSeconds > 0)
{
lastTimeOnGroundInSeconds -= Time.deltaTime;
}
player.velocity = new Vector2(movementHorizontalInput * movespeed * Time.deltaTime * 50f, player.velocity.y);
}
private void AttemptJump()
{
if (lastTimeOnGroundInSeconds > 0)
{
player.AddForce(new Vector2(0, jumpforce), ForceMode2D.Impulse);
lastTimeOnGroundInSeconds = 0;
}
}
private bool IsOnGround()
{
Vector2 groundedCheckPosition = (Vector2)transform.position + new Vector2(0, -0.01f);
var overlapBox = Physics2D.OverlapBox(groundedCheckPosition, transform.localScale, 0, layerMaskPlatforms);
return overlapBox;
}
}
One major issue is that the jump input is being polled every frame (Update), but the jump code is done every several frames (FixedUpdate), so if you press jump, it is most likely the FixedUpdate method will never see that jump, explaining why it happens so rarely.
You'd need to save the jump state (maybe in a boolean), so by the time Fixed Update happens, it knows that that a jump occurred. Then set that jump to false, and do the jump logic.

Unity2D Disable player movement for X seconds

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!

I want to create the ball movement like the one in 'chilly snow' game. I want to curve the ball, but it revolves

I am new to unity and don't know a lot of stuff. I've been watching tutorials and I saw one in which the guy created a replica of famous 'Chilly Snow'. The game is complete but the movement of ball isn't like the one in chilly snow. The ball starts orbiting continuously when I press mouse button. I wanted to know how to create that kind of movement, so that the ball turns left and right in a curve but doesn't go in to an orbit. I googled a lot but wasn't able to find my required result. I would really appreciate if anyone could point me in the right direction. Images are attached.Chilly Snow | Movement of my ball
public class movement : MonoBehaviour {
private float points;
public float playerSpeed;
private float rotationSpeed;
public Text score;
private bool isMovingLeft;
public GameObject player;
public bool isDead;
void Start () {
Time.timeScale = 0;
isDead = false;
isMovingLeft = true;
points = 0;
}
void Update ()
{
if (isDead == false)
{
points += Time.deltaTime;
}
transform.Translate (Vector3.down * playerSpeed * Time.deltaTime);
if (Input.GetMouseButtonDown (0))
{
Time.timeScale = 1;
isMovingLeft = !isMovingLeft;
rotationSpeed += 0.5f * Time.deltaTime;
}
if (Input.GetMouseButton (0))
{
rotationSpeed = 1f;
}
if (isMovingLeft) {
rotationSpeed += 1.5f * Time.deltaTime;
transform.Rotate(0,0,rotationSpeed);
} else
transform.Rotate(0,0, - rotationSpeed);
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Obstacle") {
Die ();
}
}
public void Die()
{
playerSpeed = 0f;
isDead = true;
Invoke ("Restart", 2f);
}
void Restart(){
SceneManager.LoadScene ("Ski_scene_1");
}
void FixedUpdate()
{
score.GetComponent<Text>().text = points.ToString("0");
}
}
Here is how I would approach it without doing a rotation... using your code.
public class movement : MonoBehaviour {
private float points;
public Text score;
public GameObject player;
public bool isDead;
private float currentXSpeed;
private float targetSpeed;
public float maxXSpeed;
public float speedChange;
void Start () {
Time.timeScale = 0;
isDead = false;
isMovingLeft = true;
points = 0;
targetSpeed = maxXSpeed;
}
void Update ()
{
if (isDead == false)
{
points += Time.deltaTime;
}
if(Input.GetMouseButtonDown(0))
{
Time.timeScale = 1;
targetSpeed = -targetSpeed;
}
currentSpeed = mathf.MoveTowards(currentSpeed, targetSpeed, speedChange * Time.deltaTime);
Vector3 movementDirection = new Vector3(currentSpeed, Vector3.down.y * playerSpeed, 0.0f);
transform.Translate (movementDirection * Time.deltaTime);
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Obstacle") {
Die ();
}
}
public void Die()
{
playerSpeed = 0f;
isDead = true;
Invoke ("Restart", 2f);
}
void Restart(){
SceneManager.LoadScene ("Ski_scene_1");
}
void FixedUpdate()
{
score.GetComponent<Text>().text = points.ToString("0");
}
}
You need something like sinusoidal movement or any other graph you fancy.
An example would be for this is like;
gameObject.transform.Translate(Vector3.right * Time.deltaTime*cubeSpeed);
gameObject.transform.position += transform.up * Mathf.Sin (Time.fixedTime * 3.0f ) * 0.1f;
Above pseudo is for 2D graph simulation, can be adapted to your situation.
The object is always moving to right and going up and down while making a sinusoidal movement. Because the up and down speed is not fixed hence you get the sinusoidal or like sinusoidal movement.
In your case, while the object is always going down it will make the sinusoidal movement to left and right.
Your movement is based on the rotation so, if you give this sinusoidal speed as your rotation speed, you can achieve this.
Another aproach can be lerp or slerp
Lerp allows you to make kinda smooth transactions between 2 vectors.
Like moving from pointA to pointB in X seconds.
For rotation you will need Quaternion.Lerp There is a great answer on Unity Answers you can check that if you haven't before.
Hope this helps! Cheers!