I'm learning Unity, and I'm writing a player script. Based on the script I've written, I expect to see my player to be able to jump while standing still, while moving left, and while moving right. The player cant jump if it is moving right at the same time. I did a bunch of refactoring and reorganizing. I think it might have something to do with Input.GetButton("Jump").
Also, I changed rb2d.AddForce(new Vector2(0.0f, jumpHeight)) to rb2d.velocity = new Vector2(0.0f, jumpHeight), but the player just disappears.
Here's my script so far:
private Rigidbody2D rb2d;
[SerializeField]
private LayerMask whatIsGround;
private bool isTouchingGround;
private bool facingRight = true;
[SerializeField]
private float speed;
[SerializeField]
private float jumpHeight;
[SerializeField]
private Transform[] groundPoints;
[SerializeField]
private float groundRadius;
// Use this for initialization
void Start () {
rb2d = GetComponent<Rigidbody2D>();
}
private void FixedUpdate() {
float moveHorizontal = Input.GetAxis("Horizontal");
Flip(moveHorizontal);
if (Input.GetButton("Jump") && IsGrounded()) {
rb2d.AddForce(new Vector2(0.0f, jumpHeight));
}
rb2d.velocity = new Vector2(moveHorizontal * speed, rb2d.velocity.y);
}
private void Flip(float horizontal) {
if ((horizontal > 0 && !facingRight) || (horizontal < 0 && facingRight)) {
facingRight = !facingRight;
Vector3 theScale = transform.localScale;
theScale.x *= -1;
transform.localScale = theScale;
}
}
private bool IsGrounded() {
if (rb2d.velocity.y <= 0) {
foreach (Transform point in groundPoints) {
Collider2D[] colliders = Physics2D.OverlapCircleAll(point.position, groundRadius, whatIsGround);
foreach (Collider2D collider in colliders) {
if (collider.gameObject != gameObject)
return true;
}
}
}
return false;
}
I found a solution, but I'm not sure why it fixed the problem. If you have an idea why, please comment below!
In the private bool IsGrounded() method, I removed the if statement checking if rb2d.velocity.y <= 0. I thought that would be a helpful check to see if the player is moving down or stationary. For some reason, moving the player right was making this if statement fail. This seems like a bug.
Related
A little bit of context, this is a 2D game I got it so I can move left right and jump, I can't jump while moving left and right and if I move while in midair then the player's decent dramatically slows down
Here's the code:
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
[SerializeField] private float horSpeed;
[SerializeField] private float vertSpeed;
private Rigidbody2D rb;
private bool isJumping;
private void Start()
{
rb = GetComponent<Rigidbody2D>();
}
private void Update()
{
if (Input.GetKey(KeyCode.Space) && !isJumping)
rb.velocity = new Vector2(0, horSpeed);
isJumping = true;
if (Input.GetKey(KeyCode.A))
rb.velocity = new Vector2(-vertSpeed, 0);
if(Input.GetKey(KeyCode.D))
rb.velocity = new Vector2(vertSpeed, 0);
if (rb.velocity.y==0f)
{
isJumping = false;
}
}
}
I don't see what I've done wrong here, it might be that I'm registering the inputs on different lines of code but I'm not sure how to fix it if that is the problem
The code presented is dealing with the velocity of an object. As such, you want to assign the calculated velocity as one value once all factors have been taken into account. I’ve corrected a few points and come up with this:
private void Update()
{
// we store the initial velocity, which is a struct.
var v = rb.velocity;
if (v.y==0f)
isJumping = false;
if (Input.GetKey(KeyCode.Space) && !isJumping) {
v.y = vertSpeed;
isJumping = true;
}
if (Input.GetKey(KeyCode.A))
v.x = -horSpeed;
if(Input.GetKey(KeyCode.D))
v.x = horSpeed;
rb.velocity = v;
}
The reason the code stores the velocity first, is because any changes you make to the temporary velocity struct needs to be applied back as a single value assignment to the rigid body’s velocity field (this is due to the difference between value and reference type objects). Here’s the Microsoft discussion about the difference.
The reason that you can't jump while moving left or right is because you overwrite your "horizontal" speed when you move. Look at your code for moving left or right. Notice how you set your y velocity to 0 when a or d is pressed.
Try this:
private void Update() {
if (Input.GetKey(KeyCode.Space) && !isJumping) {
rb.velocity.y = horSpeed;
isJumping = true;
}
if (Input.GetKey(KeyCode.A))
rb.velocity.x = -vertSpeed;
if(Input.GetKey(KeyCode.D))
rb.velocity.x = vertSpeed;
if (rb.velocity.y==0f)
{
isJumping = false;
}
}
I think this should work but I don't have access to Unity right now so I'll have to wait til I get home to check it. Also, I didnt change it in this code but it seems like you have horSpeed and vertSpeed flipped. Up and down movement (a.k.a. jumping) should be vertSpeed.
I'm trying to learn unity 2D and now iI need to do animations. I did walking animation, falling animation, jumping animation and the idle animation. However, when I jump, the falling animation doesn't play as intended. The falling animation isn't working properly, so it looks like this:
https://www.awesomescreenshot.com/video/8054610?key=208b095723f0bd4d8dfb936c88485e76
So when I'm falling, it doesn't play the animation right.
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
private Rigidbody2D rb;
private Animator anim;
private SpriteRenderer sprite;
private enum MovementState { idle, running, jumping, falling }
private float dirX = 0f;
[SerializeField] private float moveSpeed = 7f;
[SerializeField] private float jumpForce = 5f;
private void Start()
{
Debug.Log("program started...");
rb = GetComponent<Rigidbody2D>();
sprite = GetComponent<SpriteRenderer>();
anim = GetComponent<Animator>();
}
private void Update()
{
dirX = Input.GetAxisRaw("Horizontal");
rb.velocity = new Vector2(dirX * moveSpeed, rb.velocity.y);
if (Input.GetButtonDown("Jump"))
{
rb.velocity = new Vector2(rb.velocity.x, jumpForce);
}
Animation();
}
private void Animation()
{
MovementState state;
if (dirX > 0f)
{
state = MovementState.running;
sprite.flipX = false;
}
else if (dirX < 0f)
{
state = MovementState.running;
sprite.flipX = true;
}
else
{
state = MovementState.idle;
}
if (rb.velocity.y > .1f)
{
state = MovementState.jumping;
}
else if (rb.velocity.y < -.1f)
{
state = MovementState.falling;
}
anim.SetInteger("state", (int)state);
}
}
i don't know how to fix it, i've searched for about a hour how to fix it.
animations setting (my settings)
the settings that i want
There's a couple of things you can try to do to narrow down the issue. See if commenting out this else statement has an effect on the issue you are experiencing between jump and fall.
else
{
state = MovementState.idle;
}
I think this else could possibly to interrupt your Y velocity animation transition when the object reaches the max height of the jump.
Try to comment out different part of your if else statements until you find the culprit.
Also try to get rid of the overlap on the transition between jump and fall. I think you want to instantly move from jump animation to fall animation.
I suggest you to use two different floats for your animations, one for Movement and one for Jumping.
Then something like following to handle them.
private void Animation() {
sprite.flipX = dirX > 0f;
//Do some sort of ground check
if (isGrounded) {
anim.SetFloat("Move", dirX);
}
else {
anim.SetFloat("Jump", rb.velocity.y);
}
}
And of course you need to change your condition that you have setup in your Animator as well from your Animator window to make it work.
As it has been suggested, I would add what is called a ground check to your player, which is exactly what it sounds like. An easy way to do this would be to Raycast downward from your player's position offset by the sprite's extents and only detect casts against a specific ground LayerMask.
Once you have a ground check done, you can set the state of your animation based on 4 criteria.
Is the player grounded?
What is your relative velocity? (Positive / Negative)
With your four states, the logic would look as follows
•Idle - Grounded and X Velocity equals 0 (Or default)
•Running - Grounded and X Velocity not equal to 0
•Jumping - Not Grounded and Y Velocity greater than or equal to 0
•Falling - Not Grounded and Y Velocity less than 0
With this in mind, here is how I would edit your existing code to work as intended
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
private Rigidbody2D rb;
private Animator anim;
private SpriteRenderer sprite;
private enum MovementState { idle, running, jumping, falling }
private float dirX = 0f;
[SerializeField] private float moveSpeed = 7f;
[SerializeField] private float jumpForce = 5f;
[SerializeField] private LayerMask groundMask;
[SerializeField] private float groundLengthCheck = 0.03f;
private Vector3 spriteGroundOffset = Vector3.zero;
private bool isGrounded = false;
private void Start()
{
Debug.Log("program started...");
rb = GetComponent<Rigidbody2D>();
sprite = GetComponent<SpriteRenderer>();
anim = GetComponent<Animator>();
// offset the raycast check to be half our sprite bounds in the Y (the bottom of your sprite)
spriteGroundOffset = new Vector3(0f, sprite.bounds.extents.y, 0f);
}
private void Update()
{
dirX = Input.GetAxisRaw("Horizontal");
rb.velocity = new Vector2(dirX * moveSpeed, rb.velocity.y);
// determine if our player is grounded by casting down from their feet with our slight offset
isGrounded = Physics2D.Raycast(transform.position - spriteGroundOffset, Vector2.down, groundLengthCheck, groundMask);
if (Input.GetKeyDown(KeyCode.Space) && isGrounded)
{
rb.velocity = new Vector2(rb.velocity.x, jumpForce);
}
Animation();
}
private void Animation()
{
// default our state to idle - as if we are just standing
MovementState state = MovementState.idle;
// change
if (isGrounded)
{
if (dirX > 0f)
{
state = MovementState.running;
sprite.flipX = false;
}
else if (dirX < 0f)
{
state = MovementState.running;
sprite.flipX = true;
}
}
else
{
if (rb.velocity.y > 0)
{
state = MovementState.jumping;
}
else if (rb.velocity.y < 0f)
{
state = MovementState.falling;
}
}
anim.SetInteger("state", (int)state);
}
}
Keep in mind
Add a new layer, assign it to your ground objects, and set the serialized field groundMask to check for this new layer.
There are a few other issues that might come about with the current code. The current snippet I have provided only fixes the issue related to jumping/falling.
Here is a gif of the current code in action - I am using a public domain sprite as I do not currently have any art for a 2D player.
no matter what I do with my code, the Player doesn't seem to want to jump. Everything is referenced correctly in Unity and the code is hopefully error free. I've also checked to see if the Input System is corresponding to what I've got pressed. The only thing I can think of is the force isn't strong enough for the player to lift off of the ground, due to the high gravity (7f). But I lowered the gravity and nothing changed. Thanks for the help in advance.
using System.Collections;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
private float horizontal;
private float speed = 8f;
private float jumpingPower = 16f;
private bool isFacingRight = true;
private bool isJumping;
private float coyoteTime = 0.2f;
private float coyoteTimeCounter;
private float jumpBufferTime = 0.2f;
private float jumpBufferCounter;
[SerializeField] private Rigidbody2D rb;
[SerializeField] private Transform groundCheck;
[SerializeField] private LayerMask groundLayer;
private void Update()
{
horizontal = Input.GetAxisRaw("Horizontal");
if (IsGrounded())
{
coyoteTimeCounter = coyoteTime;
}
else
{
coyoteTimeCounter -= Time.deltaTime;
}
if (Input.GetButtonDown("Jump"))
{
jumpBufferCounter = jumpBufferTime;
}
else
{
jumpBufferCounter -= Time.deltaTime;
}
if (coyoteTimeCounter > 0f && jumpBufferCounter > 0f && !isJumping)
{
rb.velocity = new Vector2(rb.velocity.x, jumpingPower);
jumpBufferCounter = 0f;
StartCoroutine(JumpCooldown());
}
if (Input.GetButtonUp("Jump") && rb.velocity.y > 0f)
{
rb.velocity = new Vector2(rb.velocity.x, rb.velocity.y * 0.5f);
coyoteTimeCounter = 0f;
}
Flip();
}
private void FixedUpdate()
{
rb.velocity = new Vector2(horizontal * speed, rb.velocity.y);
}
private bool IsGrounded()
{
return Physics2D.OverlapBox(groundCheck.position, groundCheck.GetComponent<BoxCollider2D>().size, 0f, groundLayer);
//return Physics2D.OverlapCircle(groundCheck.position, 0.2f, groundLayer);
}
private void Flip()
{
if (isFacingRight && horizontal < 0f || !isFacingRight && horizontal > 0f)
{
Vector3 localScale = transform.localScale;
isFacingRight = !isFacingRight;
localScale.x *= -1f;
transform.localScale = localScale;
}
}
private IEnumerator JumpCooldown()
{
isJumping = true;
yield return new WaitForSeconds(0.4f);
isJumping = false;
}
}
The code works fine and the player jumps and moves without problems
Make sure to perform these steps inside Unity Engine
Inside the player you must add
Rigidbody2D
Box Collider2D
the code
Inside the code you must specify
Ground Layer : "Ignore Raycast"
Ground Check : "The ground the player is walking on"
And inside the ground must be added
Layer : "Ignore Raycast"
Component: "Box Collider2D"
As in these pictures
Player
enter image description here
Ground
enter image description here
The code worked. The ground check had a collider on it, when it wasn't necessary. The ground check in reality was inside the player.
Im throwing a bomb using physics made by code. For some reason it doesnt detect collision unless my physics stop applying force to the object. in order to bypass it I cancelled the applied force and applied gravity to it on collision enter and put the movement of the bomb to LateUpdate so it will trigger after the OnCollisionEnter but the bomb collides only most of the time with the floor (mesh collision, the floor made with ProBuilder) and not all of the time. the bomb collision detection is set to continuous
Will appreciate all the help, Thanks!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BombBehavior : MonoBehaviour
{
[SerializeField] float ExplosionForce = 300;
[SerializeField] float ExplosionRadius;
[SerializeField] float LaunchForceX;
[SerializeField] float LaunchForceY;
[SerializeField] float Delay;
float countdown;
Rigidbody rigidbodyy;
float gravity = 1;
Player player;
bool HasExploded = false;
// Start is called before the first frame update
void Start()
{
rigidbodyy = GetComponent<Rigidbody>();
player = Player.p;
GetLaunchForceX();
countdown = Delay;
}
private void GetLaunchForceX()
{
if (transform.position.x > player.transform.position.x)
{
LaunchForceX *= 1;
}
else if (transform.position.x < player.transform.position.x)
{
LaunchForceX *= -1;
}
}
private void LateUpdate()
{
ThrowBomb();
}
private void Update()
{
countdown -= Time.deltaTime;
if (countdown <= 0 && !HasExploded)
{
ExplodeNearEnemy();
}
}
private void ThrowBomb()
{
if (rigidbodyy.useGravity == false)
{
Vector3 ThrowDirection = new Vector3(LaunchForceX, LaunchForceY, 0);
LaunchForceY -= gravity;
ThrowDirection.y = LaunchForceY;
transform.Translate(ThrowDirection * Time.deltaTime);
}
}
private void ExplodeNearEnemy()
{
Collider[] colliders = Physics.OverlapSphere(transform.position, ExplosionRadius);
foreach (Collider collider in colliders)
{
if (collider.gameObject.CompareTag("Enemy"))
{
Rigidbody enemyRB = collider.GetComponent<Rigidbody>();
if (enemyRB != null)
{
enemyRB.useGravity = true;
enemyRB.AddExplosionForce(ExplosionForce, transform.position, ExplosionRadius);
Destroy(enemyRB.gameObject,1);
}
}
}
//Destroy(gameObject);
}
private void OnCollisionEnter(Collision collision)
{
LaunchForceY = 0;
LaunchForceX = 0;
gravity = 0;
rigidbodyy.useGravity = true;
}
}
I don't exactly understand the problem but it looks like you want to make a Bomb/Granate so why dont you just write a function and use AddForce()?
void Thow(Vector3 direction, float strength)
{
rigidbodyy.AddForce(direction * strength, ForceMode.Impulse);
}
something like this should help as you only need to get the throwing direction and apply a strength then Unity will handle the rest
Or maybe if your bomb doesn't collide with the ground give the ground a rigidbody and set kinematic to true
Use the MovePosition() to move a rigid body if you want colliders etc to work. Teleporting a rigid body by altering the transform directly messes up the physics.
I was actually able to do this with an enemy but for some reason I can't get it to work if it's the player.
See, the player is in a default, idle animation. When I press the arrow key from the opposite direction its facing at (default is the right when game starts -->), I want it to play a turning animation before the sprite flips over its x scale.
However what it's doing right now when I press the arrow key to turn him, is that firstly, it quickly flips the sprite over, then performs the animation as if it hadn't been flipped yet, then flips over to the other direction again.
In my animator, the idle has no exit time to the flip node and the flip node does have an exit time back to idle, just in case you would inquire. I've tried invoking a timer here and there as well but so far no luck. Can anyone help please?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class tulMoveMount : MonoBehaviour {
private Animator anim;
private Rigidbody2D rb;
public Image manaBar;
public LayerMask whatIsGround;
private bool grounded = false;
public Transform groundCheck;
public float groundCheckRadius;
private bool goRight = true;
private bool jump;
private bool turn = false;
private bool idle = true;
private bool mountOff;
private bool turnComplete = false;
public float runSpeed;
public float walkSpeed;
private float move;
public float turnDelay = 2.25f;
public float timer3 = 2.26f;
void Start ()
{
anim = GetComponent<Animator>();
rb = GetComponent<Rigidbody2D>();
}
void Update ()
{
grounded = Physics2D.OverlapCircle (groundCheck.position, groundCheckRadius, whatIsGround);
timer -= Time.deltaTime;
turnDelay -= Time.deltaTime;
HandleMovement ();
}
void HandleMovement()
{
float move = Input.GetAxis ("Horizontal");
float moveV = Input.GetAxis ("Vertical");
{
rb.velocity = new Vector2 (move * walkSpeed, rb.velocity.y);
anim.SetFloat ("walkSpeed", Mathf.Abs (move));
}
if (!goRight && move > 0) {
FlipConditions ();
Invoke ("ResetValues",timer3 );
Flip ();
turnComplete = false;
}
if (goRight && move < 0) {
FlipConditions ();
Invoke ("ResetValues",timer3 );
Flip ();
}
}
void FlipConditions()//
{
idle = false;
turn = true;
anim.SetTrigger ("turn");
idle = true;
anim.SetTrigger ("idle");
}
void Flip()
{
goRight = !goRight;
Vector3 theScale = transform.localScale;
theScale.x *= -1;
transform.localScale = theScale;
turnComplete = false;
}
void ResetValues()
{
idle = true;
anim.SetTrigger ("idle");
}
}
You can try to flip the sprite in LateUpdate() after you perform perform any animation in Update(). Try something like that, placing the animation in Update:
// store references to components on the gameObject
Transform _transform;
Rigidbody2D _rigidbody;
// hold player motion in this timestep
float vx;
float vy;
float MoveSpeed;
void Awake () {
// get a reference to the components we are going to be changing and store a reference for efficiency purposes
transform = GetComponent<Transform> ();
rigidbody = GetComponent<Rigidbody2D> ();
}
// this is where most of the player controller magic happens each game event loop
void Update()
{
// determine horizontal velocity change based on the horizontal input
vx = Input.GetAxisRaw ("Horizontal");
// get the current vertical velocity from the rigidbody component
vy = rigidbody.velocity.y;
// Change the actual velocity on the rigidbody
rigidbody.velocity = new Vector2(vx * MoveSpeed, vy);
}
// Checking to see if the sprite should be flipped
// this is done in LateUpdate since the Animator may override the localScale
// this code will flip the player even if the animator is controlling scale
void LateUpdate()
{
// get the current scale
Vector3 localScale = transform.localScale;
if (vx > 0) // moving right so face right
{
facingRight = true;
} else if (vx < 0) { // moving left so face left
facingRight = false;
}
// check to see if scale x is right for the player
// if not, multiple by -1 which is an easy way to flip a sprite
if (((facingRight) && (localScale.x<0)) || ((!facingRight) && (localScale.x>0))) {
localScale.x *= -1;
}
// update the scale
transform.localScale = localScale;
}