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.
Related
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.
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 mean that when the character is walking or running and getting to the clicked point he change the state in the animator from walk/run to idle so it looks like he walk then stop there is no animation between the walk/run and the start/stop.
I have 3 states in the animator. HumanoidWalk, HumanoidRun, HumanoidIdle.
I need something like fading.
For example if in the line:
_animator.CrossFade("Walk", 0);
I will change the 0 to 1 so when he start "Walk" it will be a bit slowly to walking speed. But in "Idle" if i will change it to 1 it will be something else not fading until he stop.
In other words i want to add fading effects when character start/stop walking/running and also when i click/double click and he switch between Walk and Run. Make some fade effects so it will not switch between the states so fast.
using UnityEngine;
using System.Collections;
public class ClickToMove : MonoBehaviour
{
public int speed = 5; // Determines how quickly object moves towards position
public float rotationSpeed = 5f;
private Vector3 targetPosition;
private Animator _animator;
private Vector3 destination;
private Quaternion targetRotation;
public float clickDelta = 0.35f; // Max between two click to be considered a double click
private bool click = false;
private float clickTime;
void Start()
{
_animator = GetComponent<Animator>();
_animator.CrossFade("Idle", 0);
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Mouse0))
{
if (click && Time.time <= (clickTime + clickDelta))
{
_animator.CrossFade("Run", 0);
click = false;
}
else
{
click = true;
clickTime = Time.time;
}
_animator.CrossFade("Walk", 0);
Plane playerPlane = new Plane(Vector3.up, transform.position);
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
float hitdist = 0.0f;
if (playerPlane.Raycast(ray, out hitdist))
{
Vector3 targetPoint = ray.GetPoint(hitdist);
targetPosition = ray.GetPoint(hitdist);
targetRotation = Quaternion.LookRotation(targetPoint - transform.position);
destination = targetPosition;
}
}
transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, rotationSpeed);
if ((transform.position - destination).magnitude < 0.7f)
{
_animator.CrossFade("Idle", 0);
}
}
}
If you increase your transform duration value a little (like 0.25), then you can get a smooth transition between states. Also uncheck "fixed duration".
Currently my character works perfectly on the keyboard, but when I convert your movements to touch, through 3 UI Buttons (i tried UI image too, but success) i'm not succeeding
It basically goes to the right, left, and jumps.
How should do to make it follow these instructions:
When the user presses directional, the character does not stop walking until the user user releases the button, and when you press the jump player jump.
This is the script I use to move through the keyboard!
using UnityEngine;
using System.Collections;
public class Player : MonoBehaviour
{
public float velocity;
public Transform player;
private Animator animator;
public bool isGrounded;
public float force;
public float jumpTime = 0.4f;
public float jumpDelay = 0.4f;
public bool jumped = false;
public Transform ground;
private Gerenciador gerenciador;
// Use this for initialization
void Start ()
{
gerenciador = FindObjectOfType (typeof(Gerenciador)) as Gerenciador;
animator = player.GetComponent<Animator> ();
gerenciador.StartGame ();
}
// Update is called once per frame
void Update ()
{
Move ();
}
void Move ()
{
isGrounded = Physics2D.Linecast (this.transform.position, ground.position, 1 << LayerMask.NameToLayer ("Floor"));
animator.SetFloat ("run", Mathf.Abs (Input.GetAxis ("Horizontal")));
if (Input.GetAxisRaw ("Horizontal") > 0) {
transform.Translate (Vector2.right * velocity * Time.deltaTime);
transform.eulerAngles = new Vector2 (0, 0);
}
if (Input.GetAxisRaw ("Horizontal") < 0) {
transform.Translate (Vector2.right * velocity * Time.deltaTime);
transform.eulerAngles = new Vector2 (0, 180);
}
if (Input.GetButtonDown ("Vertical") && isGrounded && !jumped) {
// rigidbody2D.AddForce (transform.up * force);
GetComponent<Rigidbody2D> ().AddForce (transform.up * force);
jumpTime = jumpDelay;
animator.SetTrigger ("jump");
jumped = true;
}
jumpTime -= Time.deltaTime;
if (jumpTime <= 0 && isGrounded && jumped) {
animator.SetTrigger ("ground");
jumped = false;
}
}
}
C# if possible, remember i am using Canvas UI for these buttons =]
For the Jump action, what you need is a Button. GameObject->UI->Button. Replace the Image with your own image and click "Set Native Size". Re-size the button to the size that's good for your game.
Attach the Button to the Jump Button slot script below.
public Button jumpButton;
void jumpButtonCallBack()
{
// rigidbody2D.AddForce (transform.up * force);
GetComponent<Rigidbody2D>().AddForce(transform.up * force);
jumpTime = jumpDelay;
animator.SetTrigger("jump");
jumped = true;
}
void OnEnable(){
//Un-Register Button
jumpButton.onClick.AddListener(() => jumpButtonCallBack());
}
void OnDisable(){
//Un-register Button
jumpButton.onClick.RemoveAllListeners();
}
For the Left and Right Movement buttons,you should not use Button component for them like the jump button. You have to implement Virtual JoyStick to make it look realistic. Unity have Assets called CrossPlatformInputManager that can do that. You have to import it and modify it a little bit in order to use it. Watch this to understand how to import it.
Now, you can replace your Input.GetAxis and Input.GetAxisRaw functions with CrossPlatformInputManager.GetAxis("Horizontal") and CrossPlatformInputManager.GetAxisRaw("Horizontal").
If you get it to work, then can use below to make your code comptible with both mobile and desktop.
#if UNITY_EDITOR || UNITY_STANDALONE || UNITY_WEBGL
//put your Input.GetAxis` and `Input.GetAxisRaw` code here
#elif UNITY_ANDROID || UNITY_IOS
//Put your `CrossPlatformInputManager.GetAxis("Horizontal")` and `CrossPlatformInputManager.GetAxisRaw("Horizontal")`. here
#endif
I think you should use EventTrigger OnPointerDown and OnPointerUp events:
http://docs.unity3d.com/ScriptReference/EventSystems.EventTrigger.html
I hope it helps you
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.