I'm trying to control a set of character animations but I'm having trouble with the jump animations. My horizontal walk animations are working fine but when I hit the jump button I want one of two jump animations to play depending on whether the player is in an idle or walking state. However when I press the jump button currently it only plays the first frame of the respective jump animation and then goes right back to either idle or walk. How do I get the entire jump animation to play when the jump button is pressed?
using UnityEngine;
using System.Collections;
public class AsherBlackMover : MonoBehaviour {
public float moveSpeed = 3;
public float rotateSpeed = 10;
public Transform graphics;
public SkeletonAnimation skeletonAnimation;
public Vector2 jumpVector;
public bool isGrounded;
// isGrounded Variables
public Transform grounderPosition;
public float grounderRadius;
public LayerMask grounderLayerMask;
private Rigidbody2D asherBlackRB;
private Animator asherBlackAnim;
Quaternion goalRotation = Quaternion.identity;
float xDir;
float yDir;
string currentAnimation = "";
void Start ()
{
// Create a reference to Asher Black Rigidbody2D
asherBlackRB = GetComponent<Rigidbody2D>();
}
void Update ()
{
xDir = Input.GetAxis ("Horizontal") * moveSpeed;
if (xDir > 0) // ------ Walk Right
{
goalRotation = Quaternion.Euler (0, 0, 0);
SetAnimation ("Walk", true);
}
else if (xDir < 0) // ------ Walk Left
{
goalRotation = Quaternion.Euler (0, 180, 0);
SetAnimation ("Walk", true);
}
else if (isGrounded == true) // ------ Idle
{
SetAnimation ("Idle", true);
}
// Jump Button
if (Input.GetKeyDown("space") && isGrounded == true)
{
isGrounded = false;
if (xDir > 0)
{
Jump ("Walk-Jump");
}
else if (xDir < 0)
{
Jump ("Walk-Jump");
}
else
{
Jump ("Idle-Jump");
}
}
// Circle on character that determines when grounded or not
isGrounded = Physics2D.OverlapCircle (grounderPosition.transform.position, grounderRadius, grounderLayerMask);
// Flip character smothly to emulate paper mario effect
graphics.localRotation = Quaternion.Lerp (graphics.localRotation, goalRotation, rotateSpeed * Time.deltaTime);
}
void OnDrawGizmos ()
{
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere (grounderPosition.transform.position, grounderRadius);
}
void SetAnimation (string name, bool loop)
{
if (name == currentAnimation)
{
return;
}
skeletonAnimation.state.SetAnimation (0, name, loop);
currentAnimation = name;
}
void Jump (string animationName)
{
asherBlackRB.AddForce (jumpVector, ForceMode2D.Force);
SetAnimation (animationName, true);
}
// Physics Updates
void FixedUpdate ()
{
asherBlackRB.velocity = new Vector2 (xDir, asherBlackRB.velocity.y);
}
}
Your jumping animations don't display correctly because you're always changing it back to another animation the very next frame. For instance, with idling:
else
{
SetAnimation ("Idle", true);
if (Input.GetKeyDown("space"))
{
SetAnimation ("Idle-Jump", true);
}
}
Every frame the character is idle, the idle animation will be set regardless of whether the character is jumping or not. But, as long as the space bar is held down, it will be swapped back to the first frame of the jumping animation.
You should probably have a Boolean that indicates when the character is jumping. You set it to true any time you set the character to the jumping animation. And then you can adjust your if statements so that the first thing it handles is the character in a jumping state, so that your jumping logic can complete without anything interfering. When the character is done jumping, you set the Boolean back to false. So, something like:
bool jumping = false;
// User Input
void Update ()
{
xDir = Input.GetAxis ("Horizontal") * moveSpeed;
if (jumping)
{
// Process jumping here
if (/* Check for jumping finished here */)
{
// Finished jumping
jumping = false;
}
}
else if (xDir > 0)
{
goalRotation = Quaternion.Euler (0, 0, 0);
SetAnimation ("Walk", true);
if (Input.GetKeyDown("space"))
{
SetAnimation ("Walk-Jump", true);
jumping = true;
}
}
else if (xDir < 0)
{
goalRotation = Quaternion.Euler (0, 180, 0);
SetAnimation ("Walk", true);
if (Input.GetKeyDown("space"))
{
SetAnimation ("Walk-Jump", true);
jumping = true;
}
}
else
{
SetAnimation ("Idle", true);
if (Input.GetKeyDown("space"))
{
SetAnimation ("Idle-Jump", true);
jumping = true;
}
}
graphics.localRotation = Quaternion.Lerp (graphics.localRotation, goalRotation, rotateSpeed * Time.deltaTime);
}
Change GetKeyDown to just GetKey.
As stated in the Unity input documentation here ,
GetKeyDown fires once during the frame that the key is pressed. GetKey will continue to fire as long as the button is held down.
Related
Good day! With grief in half, I wrote a code that works, but with crutches.
Question: What is the correct way to prevent the player from moving when attacking?
Now I take the attack bool in the animator, when I press the mouse button, I switch the bool to the true, make a delay of 1.5 seconds and return it to false. But when you press quickly, all the animations are mixed up and you get a mess.
Also one of the controversial ways to translate the player's speed to 0 when attacking. What is the correct way to prevent the player from moving during the attack animation?
Animator
void Update () {
// WASD
float Horizontal = Input.GetAxis("Horizontal");
float Vertical = Input.GetAxis("Vertical");
Vector3 move = Quaternion.Euler (0, playerCamera.transform.eulerAngles.y, 0) * new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
controller.Move(move * Time.deltaTime * playerSpeed);
if (move != Vector3.zero && !animator.GetCurrentAnimatorStateInfo(0).IsName("Attack"))
{
gameObject.transform.forward = move;
animator.SetBool("Walk", true);
}
else
{
animator.SetBool("Walk", false);
}
if(Input.GetMouseButtonDown(0))
{
Attack();
}
}
private void Attack() {
animator.SetBool("Attack", true);
playerSpeed = 0f;
Debug.Log("Attack");
Invoke("AttackEnd", 1.5f);
}
private void AttackEnd()
{
animator.SetBool("Attack", false);
playerSpeed = 1f;
}
I have a scene that contains a square sprite in the middle at 0,0,0 position as player and two more square sprites on top and bottom of it with some space between all.
I want the player start to move when I press the up arrow ker or the down arrow key. When the player hits one of those squares on top or the bottom the player must return back to it's first position.
{
Rigidbody2D playerRb;
float axisY = 0f;
bool canMove = true;
public float playerSpeed = 0f;
public float returnSpeed = 0f;
public float speed = 1f;
void Start()
{
playerRb = GetComponent<Rigidbody2D>();
}
//decides to which axis player gonna move to
private void Update()
{
if (Input.anyKey)
{
axisY = Input.GetAxisRaw("Vertical");
}
}
private void FixedUpdate()
{
PlayerMovement(axisY, playerSpeed);
}
//give velocity to player depending on axis
void PlayerMovement(float axis, float speed)
{
switch (axisY)
{
case 1:
if (canMove)
{
canMove = false;
playerRb.velocity = new Vector2(0, axis * speed);
}
break;
case -1:
if (canMove)
{
canMove = false;
playerRb.velocity = new Vector2(0, axis * speed);
}
break;
default:
//do nothing
break;
}
}
//return back after hit
private void OnCollisionEnter2D(Collision2D collision)
{
axisY = 0;
playerRb.velocity = Vector2.zero;
//the code below is my way to get back before i decide to lerp
//playerRb.MovePosition(Vector2.MoveTowards(playerRb.position,Vector2.zero,returnSpeed));
StopAllCoroutines();
StartCoroutine(BackToZero());
}
IEnumerator BackToZero()
{
float t = 0;
while (t < 1)
{
t += Time.deltaTime;
playerRb.MovePosition(Vector2.Lerp(playerRb.position, Vector2.zero, returnSpeed));
yield return null;
}
canMove = true;
}
}
I managed to achieve this with my code but the problem is when the lerp gets closer to 0 it is taking more time to reach there. I have to wait for one second to reach to the position and my canMove bool value gets back to true.
How can I skip this and when my object position is close to 0 point snap it to there?
The problem come from the fact you use playerRb.position as the start position of your Lerp in your coroutine.
To fix this, I recommend you to buffer the playerRb.position in a member variable at the OnCollisionEnter() method right before starting your coroutine and use this buffered position in your Lerp as start position.
This way you should have a nice linear movement at a sweet linear speed without the "damping"
Hope that helped ;)
Here is the solution. When player object's position is too close to the starting position you will detect it then snap it to the position immediately by
playerRb.MovePosition().
Then enable player to move right after by setting canMove bool to true.
private void OnCollisionEnter2D(Collision2D collision)
{
axisY = 0;
playerRb.velocity = Vector2.zero;
Vector2 playerCurrentPosition = playerRb.position;
//playerRb.MovePosition(Vector2.MoveTowards(playerRb.position,Vector2.zero,returnSpeed));
StopAllCoroutines();
StartCoroutine(BackToZero(playerCurrentPosition));
}
IEnumerator BackToZero(Vector2 playerStartPos)
{
float t = 0;
while (t < 1)
{
t += Time.deltaTime;
if (Mathf.Round(Mathf.Abs(playerRb.position.y*2))==0)
{
playerRb.MovePosition(Vector2.zero);
canMove = true;
StopAllCoroutines();
}
else
playerRb.MovePosition(Vector2.Lerp(playerRb.position, Vector2.zero, returnSpeed));
yield return null;
}
canMove = true;
}
}
Try Vector3.MoveTowards(); in a while loop until you have reached your target destination
I`m trying to make a wallrun in unity like the wallrun of the Prince of Persia.
The idea is that when you are touching the wall and you press "left shift" the player would run some seconds without falling.
I would like to add that if you press "enter" the character would jump to the side using the Wall as support for impulse.
You can watch this in the first minutes of the video:
https://www.youtube.com/watch?v=zsnB7HEiLr0
I have made this script for the character controller:
public Transform playerPosition;
//controls the x movement. (right/left)
public float horizontalmove;
//controls the y movement. (forward/back)
public float verticalmove;
//controls the movement direction.
private Vector3 playerInput;
//Here I store my character controller.
public CharacterController player;
//controls the player speed.
public float playerSpeed;
//controls de movement direction according to camera
public Vector3 movePlayer;
//controls the last movement
public Vector3 lastMovePlayer;
public float gravity = 9.8f;
public float fallVelocity;
public float jumpForce = 5.0f;
public float verticalSpeed;
private RaycastHit HitR;
private RaycastHit HitL;
//Here I store the main camera
public Camera mainCamera;
//It stores the camera direction when the player is looking forward.
private Vector3 camForward;
//It stores the camera direction when the player is looking right.
private Vector3 camRight;
//Checks
//The meaning of Caida is fall.
public bool Caida;
//The meaning of salto is jump.
public bool Salto;
public bool Wallrun;
public bool WallrunCount;
// Start is called befoe the first frame update
void Start()
{
//i store the character controler.
player = GetComponent<CharacterController>();
Caida = true;
}
// Update is called once per frame
void Update()
{
if (Wallrun == true)
{
Caida = false;
}
if (Salto == true)
{
fallVelocity -= gravity * Time.deltaTime;
movePlayer = lastMovePlayer;
}
if (Caida == true)
{
fallVelocity -= gravity * Time.deltaTime;
}
if (player.isGrounded && Wallrun == false)
{
Caida = false;
Salto = false;
WallrunCount = false;
//I assign the horizontal move to the w and s keys.
horizontalmove = Input.GetAxis("Horizontal");
//I assign the vertical move to the a and d keys.)
verticalmove = Input.GetAxis("Vertical");
//controls the movement direction
playerInput = new Vector3(horizontalmove, 0, verticalmove);
//limits the player speed. With this method if teh player waalks in diagonal doesn´t
//exceed the speed limit.
playerInput = Vector3.ClampMagnitude(playerInput, 1);
// It calls the function that give the camera look direction.
camDirection();
//Here, It`s calculates the player movement considering the camera point and the movement
//we have assing to teh player earlier
//With this method the player always moves looking to the camera
movePlayer = playerInput.x * camRight + playerInput.z * camForward;
//The movement * the speed we want.
movePlayer = movePlayer * playerSpeed;
//we are going to say to the player where is looking at.
player.transform.LookAt(player.transform.position + movePlayer);
//It gives the gravity to the player.
fallVelocity = -gravity * Time.deltaTime;
if (Input.GetKeyDown(KeyCode.Space))
{
Salto = true;
fallVelocity = jumpForce;
}
}
else if (!player.isGrounded && Salto == false && Wallrun == false)
{
Caida = true;
}
movePlayer.y = fallVelocity;
//we give the movement to th eplayer.
player.Move(movePlayer * Time.deltaTime);
lastMovePlayer = movePlayer;
}
private void OnTriggerStay(Collider other)
{
if (Input.GetKeyDown(KeyCode.LeftShift) && Wallrun == false && WallrunCount == false)
{
if (Input.GetKey("w"))
{
Wallrun = true;
WallrunCount = true;
fallVelocity = 5f;
movePlayer.y = fallVelocity;
movePlayer.z = movePlayer.z * 1.6f;
if (Physics.Raycast(transform.position, transform.right, out HitR, 1))
{
movePlayer.x = 1.6f;
}
else if (Physics.Raycast(transform.position, -transform.right, out HitL, 1))
{
movePlayer.x = -1.6f;
}
StartCoroutine(wallrunTime());
}
}
}
void camDirection()
{
//we store the forward and right directions here.
camForward = mainCamera.transform.forward;
camRight = mainCamera.transform.right;
//we block the direction and the camera direction because we are not going to use it.
camForward.y = 0;
camRight.y = 0;
//It gives as the normalized vectors.
camForward = camForward.normalized;
camRight = camRight.normalized;
}
private void OnControllerColliderHit(ControllerColliderHit hit)
{
if (!player.isGrounded && hit.normal.y < 0.1f)
{
if (Input.GetKeyDown(KeyCode.Space))
{
fallVelocity = jumpForce;
movePlayer = hit.normal * 7;
player.transform.LookAt(player.transform.position + movePlayer);
}
}
}
IEnumerator wallrunTime()
{
yield return new WaitForSeconds(1);
Wallrun = false;
}
As you can see, when the player enters the leftshift it checks to what direction is moving the character, if this is front (w) the script make the z movement * 1.6 (to make the character run a bit in the wall) and the character go a bit up bit the y axis.Then, the script checks if the Wall it i son the right or on the left and, depending on where the wall is, sticks the character to that wall.
private void OnTriggerStay(Collider other)
{
if (Input.GetKeyDown(KeyCode.LeftShift) && Wallrun == false && WallrunCount == false)
{
if (Input.GetKey("w"))
{
Wallrun = true;
WallrunCount = true;
fallVelocity = 5f;
movePlayer.y = fallVelocity;
movePlayer.z = movePlayer.z * 1.6f;
if (Physics.Raycast(transform.position, transform.right, out HitR, 1))
{
movePlayer.x = 1.6f;
}
else if (Physics.Raycast(transform.position, -transform.right, out HitL, 1))
{
movePlayer.x = -1.6f;
}
StartCoroutine(wallrunTime());
}
}
}
With this method, I can make the character jump when the player enters space because the character is hitting the wall (a condition required to bounce in the wall).
And after some seconds, the character falls simulating a wallrun.
IEnumerator wallrunTime()
{
yield return new WaitForSeconds(1);
Wallrun = false;
}
The problem is that this works perfectly if the character makes the wallrun when is looking in the same direction as the axes of the environment.
When the character z axis is looking at the same direction of the environment z axes it works perfectly. But when the axes are not looking at the same direction is a disaster. I show you a video I have recorded.
https://youtu.be/KH7rE9kh5d0
The problem, I suppose, is that with the code I have written, I'm moving the character according to the axes of the environment, and not its axes, so I have to tell him to move according to his own.
My teacher says I might have to change the way I make the character move. I would like to know if you can tell me a way to fix this without changing the movement method or if it is not possible, how can I change that movement.
Thank you in advance.
Below is a copy of my player movement script which contains functions to animate my character moving left and right, jumping and shooting his bow. I have also been able to get my character to transition from shooting his bow to being in an alert animation, but I am wondering how to write the code so that for 5 seconds after I shoot my bow I will be alert before going back to my default "idle" animation.
I would also like my character to be able to walk around (i.e. transition the alert animation to the walk animation) while being alert, but if he stops moving he goes back to alert. Currently my character will go back to idle if he walks during his alert stance. The script below, specifically lines 98-102 (my "playerAlert" function) present another problem in that my character cannot perform his "shoot" animation after this function has occurred, but I do not know/can't wrap my head around how to code what I mentioned above.
I am using Unity 5.6.
I appreciate any help you guys may be able to give me.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class player_1_controller : MonoBehaviour {
Rigidbody2D myRB;
Animator myAnim;
bool facingRight;
//movement variables
public float maxSpeed;
//jumping variables
bool grounded = false;
float groundCheckRadius = 0.2f;
public LayerMask groundLayer;
public Transform groundCheck;
public float jumpHeight;
//arrow shooting variables
bool firing = true;
public float arrowShoot;
public Transform arrowTip;
public GameObject arrow;
public float fireRate = 0.5f;
public float nextFire = 0f;
// Use this for initialization
void Start () {
myRB = GetComponent<Rigidbody2D> ();
myAnim = GetComponent<Animator> ();
facingRight = true;
}
// Update is called once per frame
void Update () {
//player jump
if (grounded && Input.GetButtonDown ("Jump")) {
grounded = false;
myAnim.SetBool ("isGrounded", grounded);
myRB.AddForce (new Vector2 (0, jumpHeight));
}
//player shooting
if (grounded && Input.GetButtonDown ("Fire1")) {
myAnim.SetBool ("arrowShoot", firing);
Invoke ("fireArrow", 1);
}
}
void FixedUpdate() {
//player movement
float move = Input.GetAxis ("Horizontal");
myAnim.SetFloat ("speed", Mathf.Abs (move));
myRB.velocity = new Vector2 (move * maxSpeed, myRB.velocity.y);
if (move > 0 && !facingRight) {
flip ();
} else if (move < 0 && facingRight) {
flip ();
}
//player jump; check if we are grounded - if not, then we are falling
grounded = Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, groundLayer);
myAnim.SetBool ("isGrounded", grounded);
myAnim.SetFloat ("verticalSpeed", myRB.velocity.y);
}
void flip () {
facingRight = !facingRight;
Vector3 theScale = transform.localScale;
theScale.x *= -1;
transform.localScale = theScale;
}
void fireArrow() {
if (Time.time > nextFire) {
nextFire = Time.time + fireRate;
if (facingRight) {
Instantiate (arrow, arrowTip.position, Quaternion.Euler (new Vector3 (0, 0, 0)));
} else if (!facingRight) {
Instantiate (arrow, arrowTip.position, Quaternion.Euler (new Vector3 (0, 0, 180)));
}
}
playerAlert ();
}
void playerAlert () {
firing = false;
myAnim.SetBool ("arrowShoot", firing);
}
}
Invoke can help you here. excerpt:
void Start()
{
Invoke("LaunchProjectile", 2);
}
void LaunchProjectile()
{
Rigidbody instance = Instantiate(projectile);
instance.velocity = Random.insideUnitSphere * 5;
}
You can use that to call(invoke) the "back to idle" function after 5 seconds.
Have you considered implementing it inside the animator?
You could create a transition from your Alert animation to your Idle animation, with a fixed exit time of 5 seconds.
This way, after 5 seconds of being in the Alert animation, it will transition to the Idle animation.
I'm trying to control a set of character animations but I'm having trouble with the jump animations. My horizontal walk animations are working fine but when I hit the jump button I want one of two jump animations to play depending on whether the player was in an idle or walking state.
I've set up a bool that updates based on whether my character collider is contacting the ground and I check against to determine when to play the jump animation. However when I press the jump button currently it only plays the first frame of the respective jump animation and then goes right back to either idle or walk. How do I get the entire jump animation to play when the jump button is pressed and the character is airborne?
using UnityEngine;
using System.Collections;
public class AsherBlackMover : MonoBehaviour {
public float moveSpeed = 3;
public float rotateSpeed = 10;
public Transform graphics;
public SkeletonAnimation skeletonAnimation;
public Vector2 jumpVector;
public bool isGrounded;
// isGrounded Variables
public Transform grounderPosition;
public float grounderRadius;
public LayerMask grounderLayerMask;
private Rigidbody2D asherBlackRB;
private Animator asherBlackAnim;
Quaternion goalRotation = Quaternion.identity;
float xDir;
string currentAnimation = "";
void Start ()
{
// Create a reference to Asher Black Rigidbody2D
asherBlackRB = GetComponent<Rigidbody2D>();
}
void Update ()
{
xDir = Input.GetAxis ("Horizontal") * moveSpeed;
if (xDir > 0 && isGrounded == true) // ------ Walk Right
{
goalRotation = Quaternion.Euler (0, 0, 0);
SetAnimation ("Walk", true);
}
else if (xDir < 0 && isGrounded == true) // ------ Walk Left
{
goalRotation = Quaternion.Euler (0, 180, 0);
SetAnimation ("Walk", true);
}
else if (isGrounded == true) // ------ Idle
{
SetAnimation ("Idle", true);
}
// Jump Button
if (Input.GetKeyDown("space") && isGrounded == true)
{
isGrounded = false;
skeletonAnimation.state.SetAnimation (0, "Idle-Jump", true);
if (xDir > 0 && isGrounded == false)
{
Jump ("Walk-Jump");
}
else if (xDir < 0 && isGrounded == false)
{
Jump ("Walk-Jump");
}
else if (isGrounded == false)
{
Jump ("Idle-Jump");
}
}
// Circle on character that determines when grounded or not
isGrounded = Physics2D.OverlapCircle (grounderPosition.transform.position, grounderRadius, grounderLayerMask);
// Flip character smothly to emulate paper mario effect
graphics.localRotation = Quaternion.Lerp (graphics.localRotation, goalRotation, rotateSpeed * Time.deltaTime);
}
void OnDrawGizmos ()
{
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere (grounderPosition.transform.position, grounderRadius);
}
void SetAnimation (string name, bool loop)
{
if (name == currentAnimation)
{
return;
}
skeletonAnimation.state.SetAnimation (0, name, loop);
currentAnimation = name;
}
void Jump (string animName)
{
asherBlackRB.AddForce (jumpVector, ForceMode2D.Force);
SetAnimation (animName, true);
print ("Doing a jump using the " + animName + " animation");
}
// Physics Updates
void FixedUpdate ()
{
asherBlackRB.velocity = new Vector2 (xDir, asherBlackRB.velocity.y);
}
}
From what I can tell, in the // Jump Button if block you set isGrounded to false, but right after that you set isGrounded based on collision detection:
isGrounded = Physics2D.OverlapCircle (grounderPosition.transform.position, grounderRadius, grounderLayerMask);
When you get to this statement you are still processing the same frame and the character still didn't have time to move isGrounded will be true by the time you get to the end of the update function. On the next update the animation will be reverted to the walk/idle one by the first set of if/else blocks. You might want to put the collision detection statement in an else block for the jump if. This might not be enough though as the time between frames might not be long enough for the character to move far enough away from the ground. In that case you might hold a counter and count, say, 5 frames until you go back to check the collision with the ground.