I am working on solo project that is horizontal 2D infinite runner. I have a problem in jumping mechanics where player can hold the jump button and jump as soon as he touches the ground. I want to force player to release the button to be able to jump again. I want to make same mechanics when he is floating(at the end of the jump when player starts falling down its y velocity is being reduced for few bits of second). I am following single responsibility principle so jumping and floating are 2 seperate scripts.
I have tried to implement a timer that will count while player is touching the ground and after some time the player would be able to jump again, but didn't manage to get asked result because you can hold the jump button all the time and after determined time spent on ground player would just jump again without releasing the button.
public class Jumping : MonoBehaviour
{
public bool isJumping;
public bool isGrounded;
public float speedUp;
public float verticalAxis;
public float timePassed;
public float jumpLimit;
private Rigidbody2D rb;
// Start is called before the first frame update
void Start()
{
rb = gameObject.GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void Update()
{
verticalAxis = Input.GetAxisRaw("Vertical");
if (verticalAxis > 0 && timePassed == 0)
{
isJumping = true;
}
}
private void FixedUpdate()
{
if (isJumping)
{
Jump();
}
}
private void OnCollisionStay2D(Collision2D collision)
{
if (collision.collider.tag == "Ground")
{
isGrounded = true;
timePassed = 0f;
}
}
private void Jump()
{
isGrounded = false;
//Modifying y component of players rigidbody(jumping)
Vector2 velocity = Vector2.up * speedUp * verticalAxis;
rb.velocity = velocity;
//Counting time when jumping
timePassed += Time.deltaTime;
if (timePassed >= jumpLimit)
{
isJumping = false;
}
}
}
P.S. I have tried to shrink the code in this question as stackoverflow suggests but I didn't know what to cut out because according to me everything is relevant and crucial to solving the problem. Thanks!
without knowing the rest of your code I would use an additional flag and obviously something for getting the cool down delay like
public float jumpCoolDownTime;
private float coolDownTimer;
private bool canJump;
void Update()
{
// if no can jump you can't jump again
if(!canJump)
{
verticalAxis = Input.GetAxisRaw("Vertical");
if (verticalAxis > 0)
{
canJump = false;
isJumping = true;
coolDownTimer = 0;
}
}
else
{
// run timer to enable jump again
if(isGrounded) coolDownTimer += Time.deltaTime;
if(coolDownTimer >= jumpCoolDownTime)
{
canJump = true;
}
}
}
You could however simplify the code a bit using Coroutines
public bool isJumping;
public float speedUp;
public float verticalAxis;
public float jumpLimit;
public float coolDownTime;
private Rigidbody2D rb;
// Start is called before the first frame update
void Start()
{
rb = gameObject.GetComponent<Rigidbody2D>();
}
void Update()
{
// do nothing is already jumping
if(isJumping) return;
verticalAxis = Input.GetAxisRaw("Vertical");
if (verticalAxis > 0)
{
// start jumping
StartCoroutine(Jump());
}
}
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.collider.tag != "Ground") return;
isGrounded = true;
StartCoroutine(CoolDown());
}
private IEnumerator Jump()
{
isJumping = true;
isGrounded = false;
coolDownTimer = 0;
var timePassed = 0f;
while(timePassed < jumpLimit)
{
// wait for the FixedUpdate call
yield return new WaitForFixedUpdate();
Vector2 velocity = Vector2.up * speedUp * verticalAxis;
rb.velocity = velocity;
}
}
private IEnumerator CoolDown()
{
yield return new WaitForSeoncds(coolDownTime);
isJumping = false;
}
If you are using the axis to get the jump, you can see if the player have released the button by:
if (GetAxis("Vertical") == 0.0) { //Button up
// do stuff
}
if(GetAxis("Vertical") > 0)
{
//Button down, is jumping
}
In Unity axis aren't booleans, they are coefficients, if you play with a controller you can set different behaviors for different intensities.
Related
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Rolling : MonoBehaviour
{
public NaviManager naviManager;
public float speed;
public float timeToStopRolling;
public string onCollisionState;
private Rigidbody rb;
private bool isTouching = false;
private bool stopRollingCoroutine = false;
private bool stopRolling = false;
// Start is called before the first frame update
void Start()
{
rb = GetComponent<Rigidbody>();
}
// Update is called once per frame
void FixedUpdate()
{
if (naviManager.crateOpenOnce)
{
if (isTouching)
{
if(stopRollingCoroutine == false)
{
StartCoroutine(WaitBeforeStopRolling());
stopRollingCoroutine = true;
}
if (stopRolling == false)
{
rb.AddForceAtPosition(Vector3.right * speed * Time.deltaTime, transform.position);
}
}
else
{
rb.velocity = rb.velocity * 0.95f * Time.deltaTime;
}
}
}
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.name == "Crate_0_0")
{
onCollisionState = "Touching !!!";
isTouching = true;
}
if(collision.gameObject.name == "Stair")
{
speed = 100;
}
}
IEnumerator WaitBeforeStopRolling()
{
yield return new WaitForSeconds(timeToStopRolling);
stopRolling = true;
}
}
I want to stop the transform from rolling at once or slowly but the goal is to make it stop rolling after X seconds.
After for example 3 seconds I'm setting the flag stopRolling to true and I used a breakpoint and it is true but the object is kept rolling.
Should I remove force somehow to make it stop?
The object that is rolling has a Rigidbody Use Gravity set to true. and a Sphere Collider.
I didn't do anything to make it start rolling. The object starts rolling once he touches the "Crate_0_0" once the flag isTouching is true.
I also tried to set both velocity and angularVelocity to Vector3.zero but it didn't stop it either.
IEnumerator WaitBeforeStopRolling()
{
yield return new WaitForSeconds(timeToStopRolling);
rb.velocity = Vector3.zero;
rb.angularVelocity = Vector3.zero;
stopRolling = true;
}
Setting the Rigidbody isKinematic to true stop the object but that stop it at once. What if I want to stop the object slowly smooth instead of using isKinematic?
I'm new at Unity and C#. I started this 2D game few weeks ago.
So basically what I want to do is when the player is hit by "FrostBullet", it should deal the damage and slow that player over time. In my script the player is hit by FrostBullet, it deals the damage and the slow is constant, not over time.
I want it to be like, when the player is hit by FrostBullet, the player should be slowed for 2 sec and than his movement speed is back to normal.
So this is how my code look like:
public class FrostHit : MonoBehaviour
{
float maxS = 40f;
float slowSpeed = 20f;
public Rigidbody2D rb;
int damage = -5;
int timeOfReducedSpeed = 2;
bool isHit = false;
void Start()
{
rb.velocity = transform.right * slowSpeed;
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.tag == "Player_Njinja")
{
HealthNjinja healthNjinja = other.gameObject.GetComponent<HealthNjinja>();
healthNjinja.ModifyHealth(damage);
PlayerMovementNjinja reduceMoveSpeed = other.gameObject.GetComponent<PlayerMovementNjinja>();
reduceMoveSpeed.runSpeed = slowSpeed;
Destroy(gameObject);
}
PlayerMovementKnight maxSpeed = other.gameObject.GetComponent<PlayerMovementKnight>();
maxSpeed.runSpeed = maxS;
HealthKnight healthKnight = other.gameObject.GetComponent<HealthKnight>();
if (other.gameObject.tag == "Player_Knight")
{
isHit = true;
healthKnight.ModifyHealth(damage);
StartCoroutine(speedTime());
Destroy(gameObject);
}
IEnumerator speedTime()
{
while (isHit == true)
{
slowPlayer();
yield return new WaitForSeconds(timeOfReducedSpeed);
revertSpeed();
}
}
void slowPlayer()
{
maxSpeed.runSpeed = slowSpeed;
}
void revertSpeed()
{
maxSpeed.runSpeed = maxS;
}
}
}
Also my PlayerMovement code:
public class PlayerMovementKnight : MonoBehaviour
{
public CharacterController2D controller;
public Animator animator;
public float runSpeed = 40f;
float horizontalMove = 0f;
bool jump = false;
bool crouch = false;
public WeaponKnight Bullet;
public bool grounded;
void Update()
{
horizontalMove = Input.GetAxisRaw("Horizontal") * runSpeed;
animator.SetFloat("Speed", Mathf.Abs(horizontalMove));
if (Input.GetButtonDown("Jump"))
{
jump = true;
animator.SetBool("IsJumping", true);
}
if (Input.GetButtonDown("Crouch"))
{
crouch = true;
}
else if (Input.GetButtonUp("Crouch"))
{
crouch = false;
}
if (grounded && GetComponent<Bullet>().knockBack == false)
{
GetComponent<Rigidbody2D>().velocity = new Vector2(0, 0);
}
}
public void OnLanding()
{
animator.SetBool("IsJumping", false);
}
public void OnCrouching(bool isCrouching)
{
animator.SetBool("IsCrouching", isCrouching);
}
void FixedUpdate()
{
controller.Move(horizontalMove * Time.fixedDeltaTime, crouch, jump);
jump = false;
}
}
The issue seems to be that your bool isHit, that is set to true before calling the Coroutine, is never reset to false.
So the loop:
while (isHit == true)
{
slowPlayer();
yield return new WaitForSeconds(timeOfReducedSpeed);
revertSpeed();
}
is really an infinite loop.
You might want to reset the bool to false somewhere.
Edit:
Following to comments, I see now that your script is attached to the bullet GameObject. After starting the Coroutine, and slowing down the player, you Destroy(gameObject);
After that, the script gets destroyed too, and the Coroutine is therefore stopped...and nothing remains to restore player’s speed back to normal.
You should move the Coroutine to the player’s script, and start it when needed. This way, it would still exist after you destroy the bullet.
Your bool isHit is never set to false. Resulting in the Player being slowed and then being reset forever after he get's hit once.
As well as creating isHit in the first place isn't needed. Why would you need to check that the Player has been hit when you enter the IEnumerator only when the Player_Knight has been hit anyway.
Code Example:
public class FrostHit : MonoBehaviour
{
float maxS = 40f;
float slowSpeed = 20f;
public Rigidbody2D rb;
int damage = -5;
int timeOfReducedSpeed = 2;
void Start()
{
rb.velocity = transform.right * slowSpeed;
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.tag == "Player_Njinja")
{
HealthNjinja healthNjinja = other.gameObject.GetComponent<HealthNjinja>();
healthNjinja.ModifyHealth(damage);
PlayerMovementNjinja mSpeedNinja = other.gameObject.GetComponent<PlayerMovementNjinja>();
StartCoroutine(speedTimeNinja());
}
else if (other.gameObject.tag == "Player_Knight")
{
HealthKnight healthKnight = other.gameObject.GetComponent<HealthKnight>();
healthKnight.ModifyHealth(damage);
PlayerMovementKnight mSpeedKnight = other.gameObject.GetComponent<PlayerMovementKnight>();
StartCoroutine(speedTimeKnight());
}
// Destroy Bullet
Destroy(gameObject);
}
IEnumerator speedTimeKnight()
{
mSpeedKnight.runSpeed = slowSpeed;
yield return new WaitForSeconds(timeOfReducedSpeed);
mSpeedKnight.runSpeed = maxS;
}
IEnumerator speedTimeNinja()
{
mSpeedNinja.runSpeed = slowSpeed;
yield return new WaitForSeconds(timeOfReducedSpeed);
mSpeedNinja.runSpeed = maxS;
}
}
Things I've changed and the reason:
Removed bool as well as while loop, didn't solve any real purpose
Seperated IEnumerator and 2 Functions from inside OnTriggerEnter2D()
Added Destroy(gameObject); to the end, because the bullets always gets destroyed anyway
Removed maxSpeed.runSpeed = maxS;, because the speed get's set/reset already anyway. There is no reason to reset the speed after we collided with the bullet.
Put GetComponent for Player_Knight into the else if, there is no reason to execute the GetComponent even if the Player_Njinja gets hit.
Removed reduceMoveSpeed.runSpeed = slowSpeed; the Player_Njinja only gets slowed and never regains his speed. Instead use the IEnumerator again
Added another IEnumerator so that both Player_Njinja and Player_Knight get slowed and unslowed correctly (it would now be even possible to create different slow times depending on which player gets hit)
Removed functions, it's not really worth it to write a function for one line of code. Especially when you can just set it in IEnumerator without needing more lines of code
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;
}
I'm making a 2D platformer. Here's my code so far. The character jumps only when touching the ground as it should - but the code for double jumping doesn't work. Any help is appreciated. I'm new at scripting and I don't understand what I did wrong?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour {
public float speed = 12f, jumpHeight = 30f;
Rigidbody2D playerBody;
Transform playerTrans, tagGround;
bool isGrounded = false;
public LayerMask playerMask;
public float maxJumps = 2;
public float jumpsLeft = 2;
// Use this for initialization
void Start ()
{
playerBody = this.GetComponent<Rigidbody2D>();
playerTrans = this.transform;
tagGround = GameObject.Find(this.name + "/tag_Ground").transform;
}
// Update is called once per frame
public void FixedUpdate ()
{
isGrounded = Physics2D.Linecast(playerTrans.position, tagGround.position, playerMask);
Move();
Jump();
DoubleJump();
}
private void Move()
{
float move = Input.GetAxisRaw("Horizontal") * speed;
playerBody.velocity = new Vector2(move, playerBody.velocity.y);
}
private void Jump()
{
if (isGrounded)
{
if (Input.GetButtonDown("Jump"))
{
playerBody.velocity = new Vector2(playerBody.velocity.x, jumpHeight);
}
}
}
private void DoubleJump()
{
if (Input.GetButtonDown("Jump") && jumpsLeft > 0)
{
Jump();
jumpsLeft--;
}
if (isGrounded)
{
jumpsLeft = maxJumps;
}
}
}
Your code doesn't make much sense. You should handle your jumping in one method and handle it something like this:
private void HandleJump()
{
if(isGrounded) {
jumpsLeft = maxJumps;
}
if(Input.GetButtonDown("Jump") && jumpsLeft > 0) {
playerBody.velocity = new Vector2(playerBody.velocity.x, jumpHeight);
jumpsLeft--;
}
}
This way you can make triple jumps or however many jumps you want.
Try replacing your Jump method code with the DoubleJump method's code and remove the check for IsGrounded before applying the jump. Otherwise your character has to be on the ground every time. Then remove the DoubleJump method as it is no longer needed. If you're utilizing the DoubleJump as an added skill later in your game then just increase maxJumps as your player earns the skill. Set it to 1 initially so that they have to hit the ground every time.
private void Jump() {
if (isGrounded) {
jumpsLeft = maxJumps;
}
if (Input.GetButtonDown("Jump") && jumpsLeft > 0) {
playerBody.velocity = new Vector2(playerBody.velocity.x, jumpHeight);
jumpsLeft--;
}
}
I'm trying to find a way to pause my game in Unity without using "Time.timeScale = 0;". The reason I want to change this is that I want to make the character play an animation while the game is paused. Is there a way to change this script so that the gravity and forward speed only "sets in" after the player have clicked space? Or is there a better way to solve the problem?
This is the script:
public class PlayerMove : MonoBehaviour {
Vector3 velocity = Vector3.zero;
public Vector3 gravity;
public Vector3 FlyVelocity;
public float maxSpeed = 5f;
public float forwardSpeed = 1f;
bool didFly = false;
bool dead = false;
float deathCooldown;
Animator animator;
// Use this for initialization
void Start () {
animator = GetComponentInChildren<Animator>();
}
// Do Graphic & Input updates here
void Update(){
if (dead) {
deathCooldown -= Time.deltaTime;
if (deathCooldown <= 0) {
if (Input.GetKeyDown(KeyCode.Space) || Input.GetMouseButtonDown(0) ) {
Application.LoadLevel( Application.loadedLevel );
}
}
}
if (Input.GetKeyDown(KeyCode.Space) || Input.GetMouseButtonDown(0) ) {
didFly = true;
animator.SetTrigger ("DoFly");
}
}
void OnCollisionEnter2D(Collision2D collision) {
animator.SetTrigger ("Death");
dead = true;
}
// Do physics engine updates here
void FixedUpdate () {
if (dead)
return;
velocity += gravity * Time.deltaTime;
velocity.x = forwardSpeed;
if(didFly == true) {
didFly = false;
velocity += FlyVelocity;
}
velocity = Vector3.ClampMagnitude(velocity, maxSpeed);
transform.position += velocity * Time.deltaTime;
deathCooldown = 0.5f;
}
}
If you are using Mechanim you could update animation state from script using Animator.Update even while actual time scale is zero.
Not sure if the trick is possible with legacy animation.