I'm working on a test project to experiment with Rigidbody in Unity. I worked on horizontal movement and jump actions, but I have a problem. Input.GetKeyDown() seems to not catch my key down event most of the time. I tried looking at possible solutions that suggested catching key inputs in Update() and corresponding with Rigidbody interactions with FixedUpdate(). When I tried this, I saw little to no improvement. Here is the script I'm working on right now:
public class PlayerScript : MonoBehaviour
{
[SerializeField] private float jumpConstant = 5.0f;
[SerializeField] private int walkSpeed = 10;
private bool jumpDown;
private float horizontalInput;
private Rigidbody rbComponent;
// Start is called before the first frame update
void Start()
{
rbComponent = GetComponent<Rigidbody>();
}
// Update is called once per frame
void Update()
{
CheckIfJumpKeyPressed();
GetHorizontalInput();
}
void FixedUpdate()
{
JumpIfKeyPressed();
MoveHorizontal();
}
void CheckIfJumpKeyPressed()
{
jumpDown = Input.GetKeyDown(KeyCode.Space);
}
void JumpIfKeyPressed()
{
if (jumpDown)
{
jumpDown = false;
rbComponent.AddForce(Vector3.up * jumpConstant, ForceMode.VelocityChange);
Debug.Log("Jumped!");
}
}
void GetHorizontalInput()
{
horizontalInput = Input.GetAxis("Horizontal");
}
void MoveHorizontal()
{
rbComponent.velocity = new Vector3(horizontalInput * walkSpeed, rbComponent.velocity.y, 0);
}
}
Thank you in advance.
You are overwriting your input if an extra frame occurs between your input and the physics frame. You should make sure a lack of input does not overwrite a detected input:
void CheckIfJumpKeyPressed()
{
jumpDown |= Input.GetKeyDown(KeyCode.Space);
}
or, equivalently:
void CheckIfJumpKeyPressed()
{
if (!jumpDown)
{
jumpDown = Input.GetKeyDown(KeyCode.Space);
}
}
Or even:
void CheckIfJumpKeyPressed()
{
if (Input.GetKeyDown(KeyCode.Space))
{
jumpDown = true;
}
}
Whichever you find most readable.
Related
I'm a newbie in unity & c# . The FixedUpdate function does some Rigidbody action (here pushing the cube over the z axis)
However the scene works fine in development (cube starts at z = 0 )
The problem is in build , the cube starts at certain distance means z = 5 or 6
According to my understanding , I believe this is caused due to FixedUpdate being fired for some milli seconds before it notice Time.timeScale = 0f in PauseGame function
And when it notice this it behave as expected.
But then on Restart function when being called up (called by a button) using SceneManager.LoadScene function cube starts with z= 0 in build.
clip at development and clip with bug at build
where did I go wrong, thanks in advance.
image of bug at build, cube starts at certain position
image of development which working fine as expected cube starts at z = 0
public class GameController : MonoBehaviour
{
public GameObject tapToStart;
private void Start()
{
tapToStart.SetActive(true);
PauseGame();
}
private void Update()
{
StartGame();
}
public void Restart()
{
SceneManager.LoadScene("Game");
}
public void PauseGame()
{
Time.timeScale = 0f;
}
public void StartGame()
{
tapToStart.SetActive(false);
Time.timeScale = 1f;
}
}
public class PlayerScript : MonoBehaviour
{
public new Rigidbody rigidbody;
public float force;
private void Update(){}
private void FixedUpdate()
{
rigidbody.AddForce(0, 0, force * Time.deltaTime);
}
}
You are correct! FixedUpdate is called at relatively'fixed' intervals. It doesn't matter if something is done in your other code and you need it to get updated immediately. FixedUpdate will just keep pressing on at a a regular interval.
You can verify your suspicions by placing the line:
rigidbody.AddForce(0, 0, force * Time.deltaTime);
Inside your Update() function.
Now, onto the solution. I suggest that create place the following code in your currently empty Update() function:
if(Time.timeScale = 0f){
rigidbody.velocity = Vector3.zero;
rigidbody.angularVelocity = Vector3.zero;
}
Now, as soon as you press the pause button, it will be detected on the next frame of Update() and the object will be frozen. This will likely occur even before FixedUpdate is called. Of course, if you want to 'restore' the velocity and angular velocity when you unpause, you will need to store that data, and then set it back up when you unpause the game.
The issue has been solved .. I can't pause on start,
need to create a Boolean in PlayerScript.
public class PlayerScript : MonoBehaviour
{
public new Rigidbody rigidbody;
public float force;
public bool isGameStart; // this Boolean
private void Update(){}
private void FixedUpdate()
{
if (isGameStart)
{
rigidbody.AddForce(0, 0, force * Time.deltaTime);
}
}
}
and using that bool value , we need to use rigidbody component.
and in StartGame function in GameController we must assign true to that bool value
public class GameController : MonoBehaviour
{
public GameObject tapToStart;
public PlayerScript playerScript;
private void Start()
{
tapToStart.SetActive(true);
PauseGame();
}
private void Update()
{
StartGame();
}
public void Restart()
{
SceneManager.LoadScene("Game");
}
public void PauseGame()
{
Time.timeScale = 0f;
}
public void StartGame()
{
tapToStart.SetActive(false);
Time.timeScale = 1f;
playerScript.isGameStart = true;
}
}
this worked for me , this solution was given by a Youtuber "Unity city".. credits to him.
I am doing a school project in Unity. My team and I decided to make an endless runner 2D game. However, because it is the first time I use C#, I don't know how to make my player's speed accelerate when collide with a game object in Unity. I only know how to destroy the player's health when a collision happens. Please help me! Thank you!
it's quite hard to give answers without seeing any code you've written, but as it's 2D and you've already got the collision damage working, you've probably used an OnCollisionEnter(), well it's very similar: you test if you've collided (which you've already done), then you add force to your player using a rigidbody2d, probably something along the lines of:
public Rigidbody2D rb;
void OnCollisionEnter2D(Collision2D collision)
{
rb.AddForce(direction * force, ForceMode2D.Impulse); // the ForceMode2D is
// optional, it's just so that
// the velocity change is sudden.
}
This should work.
If you have a GameManager that stores the game speed you can also do that too:
private float gameSpeedMultiplier = 0.5f;
void OnCollisionEnter(Collision collision)
{
if(collision.gameObject.CompareTag("Tag of the collided object")
{
GameManager.Instance.gameSpeed += gameSpeedMultiplier;
}
}
public class PlayerContoler : MonoBehaviour
{
public static PlayerContoler instance;
public float moveSpeed;
public Rigidbody2D theRB;
public float jumpForce;
private bool isGrounded;
public Transform GroundCheckPoint;
public LayerMask whatIsGround;
private bool canDoubleJump;
private Animator anim;
private SpriteRenderer theSR;
public float KnockbackLength, KnockbackForce;
private float KnockbackCounter;
public float bonceForce;
public bool stopImput;
private void Awake()
{
instance = this;
}
// Start is called before the first frame update
void Start()
{
anim = GetComponent<Animator>();
theSR = GetComponent<SpriteRenderer>();
}
// Update is called once per frame
void Update()
{
if(!PauseMenu.instance.isPause && !stopImput)
{
if (KnockbackCounter <= 0)
{
theRB.velocity = new Vector2(moveSpeed * Input.GetAxis("Horizontal"), theRB.velocity.y);
isGrounded = Physics2D.OverlapCircle(GroundCheckPoint.position, .2f, whatIsGround);
if (isGrounded)
{
canDoubleJump = true;
}
if (Input.GetButtonDown("Jump"))
{
if (isGrounded)
{
theRB.velocity = new Vector2(theRB.velocity.x, jumpForce);
AudioManager.instance.PlaySFX(10);
}
else
{
if (canDoubleJump)
{
theRB.velocity = new Vector2(theRB.velocity.x, jumpForce);
canDoubleJump = false;
AudioManager.instance.PlaySFX(10);
}
}
}
if (theRB.velocity.x < 0)
{
theSR.flipX = true;
}
else if (theRB.velocity.x > 0)
{
theSR.flipX = false;
}
} else
{
KnockbackCounter -= Time.deltaTime;
if(!theSR.flipX)
{
theRB.velocity = new Vector2(-KnockbackForce, theRB.velocity.y);
} else
{
theRB.velocity = new Vector2(KnockbackForce, theRB.velocity.y);
}
}
}
anim.SetFloat("moveSpeed", Mathf.Abs(theRB.velocity.x));
anim.SetBool("isGrounded", isGrounded);
}
public void Knockback()
{
KnockbackCounter = KnockbackLength;
theRB.velocity = new Vector2(0f, KnockbackForce);
anim.SetTrigger("hurt");
}
}
I'm not sure why but my .addforce on my rigidbody isn't working.
I have tried following the official unity addforce tutorial.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ArrowController : MonoBehaviour
{
public Rigidbody2D rb;
public float speed = 5.0f;
public Vector2 pos;
void Start()
{
rb = GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void Update()
{
faceMouse();
testForClick();
}
void faceMouse()
{
Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector3 differance = GameObject.Find("gunArm").transform.position - mousePos;
float gunAngle = Mathf.Atan2(differance.y, differance.x) * Mathf.Rad2Deg;
GameObject.Find("gunArm").transform.rotation = Quaternion.Euler(0, 0, gunAngle);
}
void testForClick()
{
if (Input.GetMouseButtonDown(0))
{
print("click");
rb.AddForce(transform.forward);
}
}
}
I expect arrow to have force added to it in the forwards direction but it just prints out "click" (The message I added to ensure the mouse-click was working).
I'm not sure why but I created a test script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Test : MonoBehaviour
{
public Rigidbody2D rb;
public float speed = 20.0f;
// Start is called before the first frame update
void Start()
{
rb = GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
print("click");
rb.AddForce(transform.right * speed, ForceMode2D.Impulse);
}
rotate();
}
private void rotate()
{
}
}
I also edited my old script to this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ArrowController : MonoBehaviour
{
public Rigidbody2D rb;
public float speed = 50.0f;
public Vector2 pos;
private void Start()
{
rb = GetComponent<Rigidbody2D>();
}
private void Update()
{
faceMouse();
testForClick();
}
void FixedUpdate()
{
if (doForce == true)
{
doForce = false;
rb.AddForce(transform.forward * speed, ForceMode2D.Impulse);
}
}
private bool doForce;
private GameObject gunArm;
private Camera cam;
private void faceMouse()
{
// try to reuse the reference
if (!cam) cam = Camera.main;
var mousePos = cam.ScreenToWorldPoint(Input.mousePosition);
// try to re-use the reference
if (!gunArm) gunArm = GameObject.Find("gunArm");
var difference = rb.transform.position - mousePos;
var gunAngle = Mathf.Atan2(difference.y, difference.x) * Mathf.Rad2Deg;
rb.transform.rotation = Quaternion.Euler(0, 0, gunAngle);
}
void testForClick()
{
if (Input.GetMouseButtonDown(0))
{
print("click");
// only set the flag
doForce = true;
}
}
void place()
{
}
}
and the test worked by itself with no rotation and on the main script only the rotation worked so i tried having both scripts active at the same time and it started working, thanks for all the help on this issue.
Despite the fact that isn't working is a quite weak description:
First of all you should do it in FixedUpdate but get the input in Update.
Second reduce the Find calls in Update .. very inefficient. It would be better to reference them via the Inspector if possible. Otherwise maybe in Start .. the way I show here is the last resort with lazy initialization assuming your script might be spawned later on runtime
Additionally (thanks to EricOverflow) you might want to rather pass ForceMode.Impulse to AddForce since you add the force only once and not continuesly.
public class ArrowController : MonoBehaviour
{
public Rigidbody2D rb;
public float speed = 5.0f;
public Vector2 pos;
// store and re-use references!
// would be better to already reference them via drag&drop
// in the Inspector
[SerializeField] private GameObject gunArm;
[SerializeField] private Camera cam;
private void Start()
{
rb = GetComponent<Rigidbody2D>();
}
private void Update()
{
testForClick();
}
private void FixedUpdate()
{
// also do this here
faceMouse();
if (doForce)
{
doForce = false;
rb.AddForce(transform.forward, ForceMode.Impulse);
}
}
private bool doForce;
private void faceMouse()
{
// try to reuse the reference
if(!cam) cam = Camera.main;
var mousePos = cam.ScreenToWorldPoint(Input.mousePosition);
// try to re-use the reference
if (!gunArm) gunArm = GameObject.Find("gunArm");
var difference = gunArm.transform.position - mousePos;
var gunAngle = Mathf.Atan2(difference.y, difference.x) * Mathf.Rad2Deg;
gunArm.rotation = Quaternion.Euler(0, 0, gunAngle);
}
private void testForClick()
{
if (Input.GetMouseButtonDown(0))
{
print("click");
// only set the flag
doForce = true;
}
}
}
The reason your code doesn't do anything is not because it doesn't work, but instead because transform.forward is a vector with magnitude 1. Adding a force of magnitude 1 will not do much to most objects, and friction will likely slow down the object again.
Try adding a force with a higher strength and ForceMode.Impulse instead:
float strength = 50f;
rb.AddForce(transform.forward * strength, ForceMode.Impulse);
Update:
It looks like you want the gun to face your mouse position and that's where your problem might be:
Let's try using Quaternion.LookRotation to get that working instead of doing to math manually.
Maybe:
GameObject gunArm;
void Awake()
{
gunArm = GameObject.Find("gunArm");
}
void faceMouse()
{
Vector3 difference = mousePos - gunArm.transform.position;
difference.z = 0;
gunArm.transform.rotation = Quaternion.LookRotation(difference);
}
I am currently creating my final project for my coding class, and I have run into a snag in figuring out how to jump. I have used a tutorial video series as reference to my project, here is the link to the jumping episode. https://www.youtube.com/watch?v=05TCTrpGB-4&t=861s
I most likely have a logic error somewhere but every thing looks fine.
Heres my code:
private Rigidbody2D myRigidbody;
[SerializeField]
private float speed;
[SerializeField]
private Transform[] GroundPoints;
[SerializeField]
private float groundRadius;
[SerializeField]
private LayerMask WhatIsGround;
private bool isGrounded;
private bool Jump;
[SerializeField]
private float JumpForce;
// Use this for initialization
void Start () {
myRigidbody = GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void FixedUpdate () {
isGrounded = IsGrounded();
if(isGrounded && Jump)
{
isGrounded = false;
myRigidbody.AddForce(new Vector2(0, JumpForce));
}
float horizontal = Input.GetAxis("Horizontal");
HandleMovement(horizontal);
}
private void HandleInput()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Jump = true;
}
}
//Moving left/right
private void HandleMovement(float horizontal)
{
myRigidbody.velocity = new Vector2(horizontal * speed, myRigidbody.velocity.y);
}
private bool IsGrounded()
{
if(myRigidbody.velocity.y <= 0)
{
foreach (Transform point in GroundPoints)
{
Collider2D[] colliders = Physics2D.OverlapCircleAll(point.position, groundRadius, WhatIsGround);
for (int i =0; i < colliders.Length ; i++)
{
if(colliders[i].gameObject != gameObject)
{
return true;
}
}
}
}
return false;
}
The HandleInput was not clarified in FixedUpdate. therefore, the code was not read in the update, making the code "invisible". so adding HandleInput like I added HandleMovement allowed for that code to be read.
I'm programming in C# on Unity. When ever I need to reset a variable in a certain interval, I would tend to declare a lot of variables and use the Update() function to do what I want. For example, here is my code for resetting a skill's cooldown (Shoot() is called whenever player presses shoot key):
using UnityEngine;
using System.Collections;
public class Player : MonoBehavior
{
private bool cooldown = false;
private float shootTimer = 0f;
private const float ShootInterval = 3f;
void Update()
{
if (cooldown && Time.TimeSinceLevelLoad - shootTimer > ShootInterval)
{
cooldown = false;
}
}
void Shoot()
{
if (!cooldown)
{
cooldown = true;
shootTimer = Time.TimeSinceLevelLoad;
//and shoot bullet...
}
}
}
Is there any better ways to do the same thing? I think my current code is extremely messy with bad readability.
Thanks a lot.
Use Invoke this will save you a lot of variables.
public class Player : MonoBehavior
{
private bool cooldown = false;
private const float ShootInterval = 3f;
void Shoot()
{
if (!cooldown)
{
cooldown = true;
//and shoot bullet...
Invoke("CoolDown", ShootInterval);
}
}
void CoolDown()
{
cooldown = false;
}
}
A way without Invoke that is a bit easier to control:
public class Player : MonoBehavior
{
private float cooldown = 0;
private const float ShootInterval = 3f;
void Shoot()
{
if(cooldown > 0)
return;
// shoot bullet
cooldown = ShootInterval;
}
void Update()
{
cooldown -= Time.deltaTime;
}
}