I am new to Unity. I am playing around with a 2D sidescroller. Here are the features of the script I am trying to write:
Move left and right
Flip character when changing direction
Increase speed to a cap while continuously moving
Currently, the only thing working is Animation transitions. I am not sure what is wrong. Here is the code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Avatar_Manager : MonoBehaviour {
private Animator anim;
private Rigidbody2D rigidbody2D;
private Transform trans;
private int direction;
private bool moving;
public const float acceleration = 1.0f / 180;
public float horizontal_speed;
public float vertical_speed;
// Use this for initialization
void Start () {
anim = GetComponent<Animator>();
rigidbody2D = GetComponent<Rigidbody2D>();
trans = GetComponent<Transform>();
//Default facing right
direction = 1;
moving = false;
vertical_speed = 0;
}
// Update is called once per frame
void Update () {
//Check direction
if (rigidbody2D.velocity.x < 0)
{
direction = -1;
}
else if (rigidbody2D.velocity.x > 0)
{
direction = 1;
}
// Move right
if (Input.GetKeyDown(KeyCode.D))
{
//Flip Avatar if facing left
if (direction == -1)
{
direction = 1;
trans.rotation.Set(trans.rotation.x, trans.rotation.y + 180, trans.rotation.z, trans.rotation.w);
}
//Start moving
if (!moving)
{
moving = true;
horizontal_speed = 10;
rigidbody2D.velocity = new Vector2(horizontal_speed, vertical_speed);
anim.SetInteger("State", 1);
}
//Update speed
else
{
if(horizontal_speed < 20)
{
horizontal_speed += acceleration;
rigidbody2D.velocity.Set(horizontal_speed, vertical_speed);
}
}
}
//Stop moving right
if (Input.GetKeyUp(KeyCode.D))
{
moving = false;
horizontal_speed = 0;
rigidbody2D.velocity.Set(horizontal_speed, vertical_speed);
anim.SetInteger("State", 0);
}
// Move left
if (Input.GetKeyDown(KeyCode.A))
{
//Flip Avatar if facing right
if (direction == 1)
{
direction = -1;
trans.rotation.Set(trans.rotation.x, trans.rotation.y - 180, trans.rotation.z, trans.rotation.w);
}
//Start moving
if (!moving)
{
moving = true;
horizontal_speed = -10;
rigidbody2D.velocity = new Vector2(horizontal_speed, vertical_speed);
anim.SetInteger("State", 1);
}
//Update speed
else
{
if (horizontal_speed > -20)
{
horizontal_speed -= acceleration;
rigidbody2D.velocity.Set(horizontal_speed, vertical_speed);
}
}
}
//Stop moving right
if (Input.GetKeyUp(KeyCode.A))
{
moving = false;
horizontal_speed = 0;
rigidbody2D.velocity.Set(horizontal_speed, vertical_speed);
anim.SetInteger("State", 0);
}
}
}
change this
if (rigidbody2D.velocity.x < 0)
{
direction = -1;
}
else if (rigidbody2D.velocity.x > 0)
{
direction = 1;
}
to this
if (rigidbody2D.velocity.x < 0)
{
transform.localscale = new Vector3 (-1,1,1);
}
else if (rigidbody2D.velocity.x > 0)
{
transform.localscale = new Vector3 (1,1,1);
}
Related
So I am trying to create a game where a player can swing with a rope/grappling hook, similar to a game like Teeworlds. The grappling hook feels great when I am hooked onto things and I am using my movement keys, the problem comes when I let go of my left or right movement keys the character slowly floats down. Feels like the gravity gets put to like 0.00001 on the character until i let go of the grappling hook button.
Will post a gif of how it looks and the code I am using for the grappling hook.
Thanks,
The Pepsidi.
Game Play of Issue
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
public class GrappleSystem : MonoBehaviour
{
public GameObject grappleAnchor;
public DistanceJoint2D grappleJoint;
public Transform crosshair;
public PlayerMovement playerMovement;
private bool grappleAttached;
private Vector2 playerPosition;
private Rigidbody2D grappleAnchorRb;
private SpriteRenderer grappleAnchorSprite;
public LineRenderer grappleRenderer;
public LayerMask grappleLayerMask;
private float grappleMaxCastDistance = 20f;
private List<Vector2> grapplePositions = new List<Vector2>();
private bool distanceSet;
void Awake()
{
grappleJoint.enabled = false;
playerPosition = transform.position;
grappleAnchorRb = grappleAnchor.GetComponent<Rigidbody2D>();
grappleAnchorSprite = grappleAnchor.GetComponent<SpriteRenderer>();
}
void Update()
{
var worldMousePosition = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, 0f));
var facingDirection = worldMousePosition - transform.position;
var aimAngle = Mathf.Atan2(facingDirection.y, facingDirection.x);
if (aimAngle < 0f)
{
aimAngle = Mathf.PI * 2 + aimAngle;
}
var aimDirection = Quaternion.Euler(0, 0, aimAngle * Mathf.Rad2Deg) * Vector2.right;
playerPosition = transform.position;
if (!grappleAttached)
{
SetCrosshairPosition(aimAngle);
}
HandleInput(aimDirection);
UpdateGrapplePositions();
}
private void SetCrosshairPosition(float aimAngle)
{
var x = transform.position.x + 5f * Mathf.Cos(aimAngle);
var y = transform.position.y + 5f * Mathf.Sin(aimAngle);
var crossHairPosition = new Vector3(x, y, 0);
crosshair.transform.position = crossHairPosition;
}
private void HandleInput(Vector2 aimDirection)
{
if (Input.GetMouseButtonDown(1))
{
if (grappleAttached) return;
grappleRenderer.enabled = true;
var hit = Physics2D.Raycast(playerPosition, aimDirection, grappleMaxCastDistance, grappleLayerMask);
if (hit.collider != null)
{
grappleAttached = true;
if (!grapplePositions.Contains(hit.point))
{
transform.GetComponent<Rigidbody2D>().AddForce(new Vector2(0f, 5f), ForceMode2D.Force);
grapplePositions.Add(hit.point);
grappleJoint.distance = Vector2.Distance(playerPosition, hit.point);
grappleJoint.enabled = true;
grappleAnchorSprite.enabled = true;
}
}
else
{
grappleRenderer.enabled = false;
grappleAttached = false;
grappleJoint.enabled = false;
}
}
if (Input.GetMouseButtonUp(1))
{
ResetGrapple();
}
}
private void ResetGrapple()
{
grappleJoint.enabled = false;
grappleAttached = false;
//playerMovement.isSwinging = false;
grappleRenderer.positionCount = 2;
grappleRenderer.SetPosition(0, transform.position);
grappleRenderer.SetPosition(1, transform.position);
grapplePositions.Clear();
grappleAnchorSprite.enabled = false;
}
private void UpdateGrapplePositions()
{
if (!grappleAttached)
{
return;
}
grappleRenderer.positionCount = grapplePositions.Count + 1;
for (var i = grappleRenderer.positionCount - 1; i >= 0; i--)
{
if (i != grappleRenderer.positionCount - 1)
{
grappleRenderer.SetPosition(i, grapplePositions[i]);
if (i == grapplePositions.Count - 1 || grapplePositions.Count == 1)
{
var grapplePosition = grapplePositions[grapplePositions.Count - 1];
if (grapplePositions.Count == 1)
{
grappleAnchorRb.transform.position = grapplePosition;
if (!distanceSet)
{
grappleJoint.distance = Vector2.Distance(transform.position, grapplePosition);
distanceSet = true;
}
}
else
{
grappleAnchorRb.transform.position = grapplePosition;
if (!distanceSet)
{
grappleJoint.distance = Vector2.Distance(transform.position, grapplePosition);
distanceSet = true;
}
}
}
else if (i - 1 == grapplePositions.IndexOf(grapplePositions.Last()))
{
var grapplePosition = grapplePositions.Last();
grappleAnchorRb.transform.position = grapplePosition;
if (!distanceSet)
{
grappleJoint.distance = Vector2.Distance(transform.position, grapplePosition);
distanceSet = true;
}
}
}
else
{
grappleRenderer.SetPosition(i, transform.position);
}
}
}
}
I am trying to move a GameObject along a path by stages. Conceptually a first script, connected to the controller, defines the path via a List of Vector3 and a second one connected to the object moves it. To move it first rotates to face the destination and then it simply moves by incrementing transform.position by small steps.
The problem is that once the first part of the path has been completed the rest seems to cancel out (the Count became 0) and the robot stops.
This only happens by giving the list from a second script, trying it with a pre-defined list in the same script works fine.
This is the motion code:
public List<Vector3> pathRover;
[SerializeField] private float roverSpeed;
[SerializeField] private float roverRotateSpeed;
private float dotto;
private bool isRotating = false;
private bool isLerping = false;
private Vector3 startPosition;
private Vector3 targetPosition;
private void FixedUpdate()
{
if (isRotating)
{
//To determine whether the robot faces the direction of movement
dotto = Vector3.Dot(transform.TransformDirection(Vector3.forward), (new Vector3(targetPosition.x, 0, targetPosition.z) - new Vector3(startPosition.x, 0, startPosition.z)).normalized);
if (dotto < 0.98f) // rotate until reached then move
{
transform.Rotate(0, Time.deltaTime * roverRotateSpeed, 0);
Debug.Log(dotto);
}
else
{
isLerping = true;
isRotating = false;
}
}
else if (isLerping)
{
var step = roverSpeed * Time.deltaTime;
transform.position = Vector3.MoveTowards(transform.position, targetPosition, step);
if (Vector3.Distance(transform.position, targetPosition) <= 1f)
{
NextStep();
}
}
}
public void StartMoving(List<Vector3> pathReceived)
{
if (pathReceived.Count > 1)
{
pathRover.Clear();
for (int i = 1; i < pathReceived.Count; i++)
{
pathRover.Add(pathReceived[i]);
}
startPosition = transform.position;
targetPosition = new Vector3(pathRover[0].x, pathRover[0].y + 0.7f, pathRover[0].z); //0.7f for the height of the rover from the ground
isRotating = true;
}
else
{
StopMoving();
}
}
public void StopMoving()
{
isRotating = false;
isLerping = false;
pathRover.Clear();
}
public void NextStep()
{
isLerping = false;
StartMoving(pathRover);
}
From the other script:
roverMover.StartMoving(percorsoList);
No error appears in the debug console, I don't know where I am wrong.
I guess the problem is you are starting the move right at the Start of the script but you call StartMoving after a while. You have to move only when StartMove is called.
public List<Vector3> pathRover;
[SerializeField] private float roverSpeed;
[SerializeField] private float roverRotateSpeed;
private float dotto;
private bool isRotating = false;
private bool isLerping = false;
private Vector3 startPosition;
private Vector3 targetPosition;
boolean startMove = false;
private void FixedUpdate()
{
if(startMove)
Move();
}
public void Move(){
if (isRotating)
{
//To determine whether the robot faces the direction of movement
dotto = Vector3.Dot(transform.TransformDirection(Vector3.forward), (new Vector3(targetPosition.x, 0, targetPosition.z) - new Vector3(startPosition.x, 0, startPosition.z)).normalized);
if (dotto < 0.98f) // rotate until reached then move
{
transform.Rotate(0, Time.deltaTime * roverRotateSpeed, 0);
Debug.Log(dotto);
}
else
{
isLerping = true;
isRotating = false;
}
}
else if (isLerping)
{
var step = roverSpeed * Time.deltaTime;
transform.position = Vector3.MoveTowards(transform.position, targetPosition, step);
if (Vector3.Distance(transform.position, targetPosition) <= 1f)
{
NextStep();
}
}
}
public void StartMoving(List<Vector3> pathReceived)
{
startMove= true;
if (pathReceived.Count > 1)
{
pathRover.Clear();
for (int i = 1; i < pathReceived.Count; i++)
{
pathRover.Add(pathReceived[i]);
}
startPosition = transform.position;
targetPosition = new Vector3(pathRover[0].x, pathRover[0].y + 0.7f, pathRover[0].z); //0.7f for the height of the rover from the ground
isRotating = true;
}
else
{
StopMoving();
}
}
public void StopMoving()
{
isRotating = false;
isLerping = false;
pathRover.Clear();
}
public void NextStep()
{
isLerping = false;
StartMoving(pathRover);
}
So decided to give game dev a try, picked up unity,Now I decided to create a simple ping pong game.
My game has Bat.cs class, ball.cs, and GameHandler.cs.
The GameHandler.cs initializes the batRight, batLeft, ball using the Bat.cs, Ball.cs class and prefabs, it also keeps track of if someone scores then starts a serve by stopping the rally:
public class GameHandler : MonoBehaviour
{
public Bat bat;
public Ball ball;
public Score score;
public static Vector2 bottomLeft;
public static Vector2 topRight;
public static bool rallyOn = false;
public static bool isServingLeft = true;
public static bool isServingRight = false;
public static bool isTwoPlayers = true;
static Bat batRight;
static Bat batLeft;
public static Ball ball1;
static Score scoreLeft;
static Score scoreRight;
// Start is called before the first frame update
void Start()
{
//Convert screens pixels coordinate into games coordinate
bottomLeft = Camera.main.ScreenToWorldPoint(new Vector2(0, 0));
topRight = Camera.main.ScreenToWorldPoint(new Vector2(Screen.width, Screen.height));
ball1 = Instantiate(ball) as Ball;
//Debug.Log("GameHandler.Start");
batRight = Instantiate(bat) as Bat;
batRight.Init(true);
batLeft = Instantiate(bat) as Bat;
batLeft.Init(false);
ball1.transform.position = new Vector3(batLeft.transform.position.x + ball1.getRadius() * 2, batLeft.transform.position.y);
//Instatiate scores
scoreLeft = Instantiate(score, new Vector2(-2, -4), Quaternion.identity) as Score;
scoreRight = Instantiate(score, new Vector2(2, -4), Quaternion.identity) as Score;
}
private void Update()
{
if (isServingLeft)
{
ball1.transform.position = new Vector3(batLeft.transform.position.x + ball1.getRadius() * 2, batLeft.transform.position.y);
if (Input.GetKey(KeyCode.LeftControl))
{
rallyOn = true;
isServingLeft = false;
}
}
if (isServingRight)
{
ball1.transform.position = new Vector3(batRight.transform.position.x - ball1.getRadius() * 2, batRight.transform.position.y);
if (isTwoPlayers && Input.GetKey(KeyCode.RightControl))
{
rallyOn = true;
isServingRight = false;
}
else
{
StartCoroutine(batRight.serveAi());
if (GameHandler.rallyOn)
{
StopCoroutine(batRight.serveAi());
}
}
}
}
public static void increaseScoreByOne(bool isRight)
{
rallyOn = false;
if (isRight)
{
scoreRight.increaseScore();
isServingRight = true;
}
else
{
scoreLeft.increaseScore();
isServingLeft = true;
}
}
}
The ball.cs listens to the static GameHandler.rallyOn and starts moving the ball accordingly, it also changes direction of the ball if it hits a vertical wall or the bat:
public class Ball : MonoBehaviour
{
[SerializeField]
float speed;
float radius;
public Vector2 direction;
public Vector3 ballPosition;
// Start is called before the first frame update
void Start()
{
direction = Vector2.one.normalized; // direction is (1, 1) normalized
//Debug.Log("direction * speed * Time.deltaTime:" + direction * speed * Time.deltaTime);
radius = transform.localScale.x / 2;
}
// Update is called once per frame
void Update()
{
ballPosition = transform.position;
if (GameHandler.rallyOn)
{
startRally();
}
}
void startRally()
{
transform.Translate(direction * speed * Time.deltaTime);
Debug.Log("direction * speed * Time.deltaTime:" + direction * speed * Time.deltaTime);
if ((transform.position.y + radius) > GameHandler.topRight.y && direction.y > 0)
{
direction.y = -direction.y;
}
if ((transform.position.y + radius) < GameHandler.bottomLeft.y && direction.y < 0)
{
direction.y = -direction.y;
}
if ((transform.position.x + radius) > GameHandler.topRight.x && direction.x > 0)
{
// Left player scores
GameHandler.increaseScoreByOne(false);
//For no, just freeza the script
// Time.timeScale = 0;
//enabled = false;
}
if ((transform.position.x - radius) < GameHandler.bottomLeft.x && direction.x < 0)
{
// right player scores
GameHandler.increaseScoreByOne(true);
}
}
void OnTriggerEnter2D(Collider2D collision)
{
if(collision.tag == "Bat")
{
if (collision.GetComponent<Bat>().isRight && direction.x > 0)
{
direction.x = -direction.x;
}
if (!collision.GetComponent<Bat>().isRight && direction.x < 0)
{
direction.x = -direction.x;
}
}
}
public float getRadius()
{
return radius;
}
}
The bat.cs initiazes the two paddles, and either listens to user input if its 2 players or listens to player and use AI if it is player vs CPU.:
public class Bat : MonoBehaviour
{
float height;
[SerializeField] // this make this appear on editor without making this field public
float speed;
string input;
public bool isRight;
string PLAYER1_INPUT = "PaddleLeft";
string PLAYER2_INPUT = "PaddleRight";
// Start is called before the first frame update
void Start()
{
height = transform.localScale.y;
}
// Update is called once per frame
void Update()
{
if (GameHandler.isTwoPlayers)
{
if (isRight)
{
movePaddleonUserInput(PLAYER2_INPUT);
}
else
{
movePaddleonUserInput(PLAYER1_INPUT);
}
}
else
{
if (isRight)
{
movePaddleAi();
}
else
{
movePaddleonUserInput(PLAYER1_INPUT);
}
}
}
void movePaddleAi()
{
if (isRight && GameHandler.ball1.direction.x > 0 && GameHandler.ball1.ballPosition.x > 0)
{
//transform.Translate(direction * speed * Time.deltaTime);
if ((transform.position.y) > GameHandler.ball1.ballPosition.y && GameHandler.ball1.direction.y < 0)
{
transform.Translate(GameHandler.ball1.direction * speed * Time.deltaTime * Vector2.up);
}
if ((transform.position.y) < GameHandler.ball1.ballPosition.y && GameHandler.ball1.direction.y > 0)
{
transform.Translate(GameHandler.ball1.direction * speed * Time.deltaTime * Vector2.up);
}
}
}
void movePaddleonUserInput(string input)
{
// move = (-1 -> 1) * speed * timeDelta(this keep move framerate independent)
float move = Input.GetAxis(input) * speed * Time.deltaTime;
//Debug.Log((transform.position.y + height / 2) + " > "+ GameHandler.topRight.y+ "&&" + move +" > 0");
//Restrict paddle movement to to the screen
// (top edge of paddle > Screen top and we are moving up)
if ((transform.position.y + height / 2) > GameHandler.topRight.y && move > 0)
{
move = 0;
}
// (bottom edge of paddle < Screen top and we are moving down)
if ((transform.position.y - height / 2) < GameHandler.bottomLeft.y && move < 0)
{
move = 0;
}
transform.Translate(move * Vector2.up);
}
public void Init(bool isRightPaddle)
{
isRight = isRightPaddle;
Vector2 pos;
if (isRightPaddle)
{
isRight = isRightPaddle;
pos = new Vector2(GameHandler.topRight.x, 0);
// offset since center is the anchor
pos -= Vector2.right * transform.localScale.x;
input = "PaddleRight";
}
else
{
pos = new Vector2(GameHandler.bottomLeft.x, 0);
// offset since center is the anchor
pos += Vector2.right * transform.localScale.x;
input = "PaddleLeft";
}
transform.name = input;
transform.position = pos;
}
public IEnumerator serveAi()
{
yield return new WaitForSecondsRealtime (2f);
GameHandler.rallyOn = true;
GameHandler.isServingRight = false;
}
}
Now I also have score.cs and mainMenu scene , but no need to include that for this question, What I am struggling with now is the AI not stopping after it scores a point, I want the AI paddle to stop for a few seconds before it serves.
As you can see in the code, I added a yield return new WaitForSecondsRealtime(2f);
public IEnumerator serveAi()
{
yield return new WaitForSecondsRealtime (2f);
GameHandler.rallyOn = true;
GameHandler.isServingRight = false;
}
But this only cause the paddle to wait for the first time it scores , and then if it scores again in quick interval, it doesn't wait at all.
Not sure what I doing wrong here, thanks for your time.
The more plausible explanation to me would be that GameHandler.rallyOn and isServing* aren't all being reset to the correct values to prevent the serve from occurring before the coroutine even begins, or another check is needed somewhere to prevent a serve from occurring if all of the correct booleans aren't set. Try placing appropriate breakpoints and stepping through in a debugger.
You'd probably be better off using WaitForSeconds rather than WaitForSecondsRealtime, by the way, at least if there's a chance you might want to allow the game to be paused in the future.
Realized that the coroutine is called multiple times even before its finished due do it being called from update function in GameHandler.cs. Needed a way to check if the co-routine is already running and not start a new one from update if so, used the Game.isServing variable to do so:
public IEnumerator serveAi()
{
GameHandler.isServingRight = false; // coroutine started , no need to start new one
yield return new WaitForSecondsRealtime (2f);
GameHandler.rallyOn = true;
}
And my update in GameHandler is already checking for that variable before starting the coroutine:
private void Update()
{
if (isServingLeft)
{
ball1.transform.position = new Vector3(batLeft.transform.position.x + ball1.getRadius() * 2, batLeft.transform.position.y);
if (Input.GetKey(KeyCode.LeftControl))
{
rallyOn = true;
isServingLeft = false;
}
}
if (isServingRight)
{
ball1.transform.position = new Vector3(batRight.transform.position.x - ball1.getRadius() * 2, batRight.transform.position.y);
if (isTwoPlayers && Input.GetKey(KeyCode.RightControl))
{
rallyOn = true;
isServingRight = false;
}
else
{
StartCoroutine(batRight.serveAi());
}
}
}
I am making a basketball game where you shoot then the ball is positioned back into your hands after a timer runs out and you are sent back to your default position to shoot again. I can't get the player to rotate back to 0,0,0 though. any advice would be greatly appreciated.(This is my first game and i'm kinda winging it so i'm sure the rest of my code that works is still horrible, its a learning experience so any constructive criticism is welcome.)
My code...
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour {
public Ball ball;
public GameObject playerCamera;
public float ballDistance = 2f;
public float ballThrowingForce = 5f;
public float powerTimer = 0f;
public bool holdingBall = true;
public Player player;
public float resetTimer = 5f;
// Use this for initialization
void Start () {
ball.GetComponent<Rigidbody> ().useGravity = false;
}
// Update is called once per frame
void Update ()
{
if (holdingBall) {
{
ball.transform.position = playerCamera.transform.position + playerCamera.transform.forward * ballDistance;
}
if (Input.GetMouseButton (0)) {
if (powerTimer == 10) {
powerTimer = 10;
} else if (powerTimer < 10) {
powerTimer = powerTimer + Time.deltaTime;
ballThrowingForce = ballThrowingForce + 2 * powerTimer;
}
}
}
if (holdingBall) {
if (Input.GetMouseButtonUp (0)) {
holdingBall = false;
ball.GetComponent<Rigidbody> ().useGravity = true;
ball.GetComponent<Rigidbody> ().AddForce (playerCamera.transform.position + playerCamera.transform.forward * ballThrowingForce);
}
}
if (player.holdingBall == false) {
resetTimer -= Time.deltaTime;
if (resetTimer <= 0) {
player.transform.position = new Vector3 (-0.2427547f, 2.6f, -2.357f);
// player roation to 0,0,0 here
holdingBall = true;
ball.GetComponent<Rigidbody> ().useGravity = false;
powerTimer = 0;
ballThrowingForce = 400;
resetTimer = 5;
}
if (Input.GetKeyDown (KeyCode.Return)) {
resetTimer = 0;
}
}
}
}
I think player.transform.rotation = Quaternion.identity should work.
Let's take a look at that :
if (Input.GetMouseButton (0)) {
if (powerTimer == 10) {
powerTimer = 10;
} else if (powerTimer < 10) {
powerTimer = powerTimer + Time.deltaTime;
ballThrowingForce = ballThrowingForce + 2 * powerTimer;
}
}
This part is absurd :
if (powerTimer == 10) {
powerTimer = 10;
}
Should be powerTimer >= 10.
And after, you execute powerTimer += Time.deltaTime which seems good, but ballThrowingForce += 2 * powerTimer is not what you want i think. Here ballThrowingForce is growing exponentially with powerTimer. Should perhaps be ballThrowingForce += 2 * Time.deltaTime.
I'm trying to create a 2D platform game in unity, when I try and make the character double jump, it won't work. I was wondering if i could get any help.
using UnityEngine;
using System.Collections;
public class Player : MonoBehaviour {
public float maxSpeed = 3;
public float speed = 50f;
public float jumpPower = 150f;
public bool grounded;
public bool canDoubleJump;
private Rigidbody2D rb2d;
private Animator anim;
void Start ()
{
rb2d = gameObject.GetComponent<Rigidbody2D>();
anim = gameObject.GetComponent<Animator>();
}
void Update ()
{
anim.SetBool("Grounded", grounded);
anim.SetFloat("Speed", Mathf.Abs(rb2d.velocity.x));
if(Input.GetAxis("Horizontal") < -0.1f)
{
transform.localScale = new Vector3(-1, 1, 1);
}
if(Input.GetAxis("Horizontal") > 0.1f)
{
transform.localScale = new Vector3(1, 1, 1);
}
if(Input.GetButton("Jump"))
{
if(grounded)
{
rb2d.AddForce(Vector2.up * jumpPower);
canDoubleJump = true;
}
else
{
if (canDoubleJump)
{
canDoubleJump = false;
rb2d.velocity = new Vector2(rb2d.velocity.x, 0);
rb2d.AddForce(Vector2.up * jumpPower);
}
}
}
}
void FixedUpdate()
{
Vector3 easeVelocity = rb2d.velocity;
easeVelocity.y = rb2d.velocity.y;
easeVelocity.z = 0.0f;
easeVelocity.x *= 0.75f;
float h = Input.GetAxis("Horizontal");
//fake friction / easing x speed
if(grounded)
{
rb2d.velocity = easeVelocity;
}
//moving player
rb2d.AddForce((Vector2.right * speed) * h);
//limiting speed
if(rb2d.velocity.x > maxSpeed)
{
rb2d.velocity = new Vector2(maxSpeed, rb2d.velocity.y);
}
if(rb2d.velocity.x < -maxSpeed)
{
rb2d.velocity = new Vector2(-maxSpeed, rb2d.velocity.y);
}
}
}
The problem is that you're checking if the Jump button is currently in the down state. Pressing and releasing the button often takes place over multiple frames (i.e. Update() is called many times in the duration of button press).
There are two ways you can fix this problem.
The easiest (and probably best) is to make this alteration:
if(Input.GetButtonDown("Jump"))
GetButtonDown only returns true for the frame in which the button was first pressed, and false until it is released and pressed again.
The other is to include a second variable that prevents the activation of the second block until after the button is released. This is less ideal, but shows what's going on behind the scenes of GetButtonDown.
var isButtonDown = false;
Update() {
if(Input.GetButton("Jump"))
{
if(grounded)
{
rb2d.AddForce(Vector2.up * jumpPower);
canDoubleJump = true;
isButtonDown = true;
}
else if(!isButtonDown)
{
if (canDoubleJump)
{
canDoubleJump = false;
rb2d.velocity = new Vector2(rb2d.velocity.x, 0);
rb2d.AddForce(Vector2.up * jumpPower);
}
}
}
else {
isButtonDown = false;
}
}
Note that this does not address the "fall off a platform and jump once" that the double-jump ability typically includes. I'll leave that as an exercise to the reader.