How to manage animations? - c#

I'm using C# and I'm currently building a 3rd person view game. I have a 3D character model with the animations in frames so I have to cut the animations by frame
The problem is I currently have 5 animations (idle, run, walk, punch, jump) and this is my code
void Update () {
if (Input.GetAxis("Horizontal") != 0 || Input.GetAxis("Vertical") != 0){
//play run animation
} else {
if (motion.x != 0 || motion.z != 0){
motion.x = 0;
motion.z = 0;
}
//play idle anim
}
if (Input.GetKeyDown(KeyCode.Space) && controller.isGrounded){
//play jump anim
}
if (Input.GetKeyDown(KeyCode.P)){
//play punch anim
}
if (!controller.isGrounded){
//play jump anim
}
vertVelo -= gravity * Time.deltaTime;
motion.y = vertVelo;
this.controller.Move(motion * Time.deltaTime);
}
The problem occurs when I press P to make the character punch. It seems the idle animation in the update function is being called so that the punch animation doesn't have time to play.
So, what's the solution? are there any animation managing technique or should I use delay?

You may block idle animation while punch is playing (but may be it's not best approach):
bool isPunchPlaying = false;
void Update () {
//...
if (!isPunchPlaying) {
// Play idle anim
}
// ...
if (Input.GetKeyDown(KeyCode.P)){
//play punch anim
isPunchPlaying = true;
StartCoroutine(WaitThenDoPunch(animation["punch"].length));
}
// ...
}
IEnumerator WaitThenDoPunch(float time) {
yield return new WaitForSeconds(time);
isPunchPlaying = false;
}

Try googling for animation layers in Unity and blending between animations.
You can set anim loops on a layer with higher layer numbers overriding lower layers.
This means that you can have the idle loop on say layer 1, and this plays continuously. Then say your jump loop is on layer 2. When the jump logic fires the jump loop, it plays once, then the idle loop continues.
This doc on 'AnimationState' in Unity could also be useful > link <

Related

Unity attack once during animation

Since I am still very beginner question maybe dumb but I am really cant find any solution. I am creating 3rd person adventure game and trying to implement enemy attack. The problem is that I cannot implement it in a way that enemy do damage only once during attack animation. In my code alreadyAttacked bool is changing to false only when the transitions between animations happens. However I want to reset this value everytime when the attack animation starts or finish.
void FixedUpdate()
{
playerInSightRange = Physics.CheckSphere(transform.position, sightRange, playerMask);
playerInAttackRange = Physics.CheckSphere(transform.position, attackRange, playerMask);
if (!playerInSightRange && !playerInAttackRange) Patroling();
if (playerInSightRange && !playerInAttackRange) Chasing();
if (playerInSightRange && playerInAttackRange) Attacking();
}
private void Attacking()
{
animator.SetInteger("Condition", 2);
agent.SetDestination(player.position);
if (animator.GetCurrentAnimatorStateInfo(0).normalizedTime > 0.4f
&& animator.GetCurrentAnimatorStateInfo(0).normalizedTime < 0.6f
&& alreadyAttacked == false)
{
player.GetComponent<Health>().healthValue -= damage / 100f;
alreadyAttacked = true;
}
if (animator.GetCurrentAnimatorStateInfo(0).normalizedTime > 0.7f )
{
alreadyAttacked = false;
}
}
You might rather want to look into Animation Events.
Without having to query states from the Animator you can rather simply invoke certain events directly from your animation state itself!
Simply rather make it
public void CauseDamage()
{
if(player) player.GetComponent<Health>().healthValue -= damage / 100f;
}
and then in your animation itself add an Event
and select the method you want to call in the Event's inspector.
Then everytime your animation passes that key frame the event will be Invoked exactly once.

How to Move and Jump in Unity

I am making a First Person Shooter Game and am currently working on the jumping. I am using animation to jump as I have struggled with gravity before. However my problem is that my player cannot move if my jump script is attached to the player as well as the movement. However I know that it has nothing to do with the scripts as both scripts work but not simultaneously.
I believe it has something to do with the animation. There are 3 parts to the animation, Base, Running and Jump. If the player is moving the running animation is active, if the player has pressed the space bar then the jump animation is active. Jump animation is prioritised.
Is there any reason why the player cannot move and jump?
Here is the codes and animation controller:
Animation and Jump
public class Jump : MonoBehaviour
{
public bool run;
public float speed;
public GameObject player;
public Animator dgbanim;
public bool jump;
public float rejump;
public float rejumper;
// Start is called before the first frame update
void Start()
{
speed = 50;
run = false;
jump = false;
rejumper = 0;
rejump = 0;
}
// Update is called once per frame
void Update()
{
if (jump == false && Input.GetKey("space")) { dgbanim.SetBool("Jump", true); jump = true; rejump = 21; }
if (rejump > 0) { rejump = rejump - 1; }
if(rejump == 0) { jump = false; dgbanim.SetBool("Jump", false); }
if (Input.GetKey("w") || Input.GetKey("s") || Input.GetKey("a") || Input.GetKey("d") || Input.GetKey("up") || Input.GetKey("down") || Input.GetKey("left") || Input.GetKey("right")) { run = true; }
if (run == true) { dgbanim.SetBool("Running", true); run = false; }
else if (run == false) { dgbanim.SetBool("Running", false); }
}
private void OnCollisionEnter(Collision collision)
{
//if (collision.gameObject.tag != "EnemyBullets") { jump = true; rejump = 0; }
if (collision.gameObject.tag != "EnemyBullets") { jump = false; }
}
}
Movement
public class Movement : MonoBehaviour
{
public GameObject player;
public float speed;
// Start is called before the first frame update
void Start()
{
speed = 50;
}
// Update is called once per frame
void Update()
{
player.transform.Translate(Input.GetAxis("Horizontal") * speed * Time.deltaTime, 0, Input.GetAxis("Vertical") * speed * Time.deltaTime);
}
}
It seems like your animator have ‘apply root motion’ checked and its taking movement from animations rather than code, try unchecking it.
Animation can control some properties of a GameObject, such as the position. If you try to edit a GameObject's position in a script but an animation controls the position, the animation will override the script's changes to the position.
To see if this is the problem, you can open the animation window and select your player. If the player's position (in the inspector) is red, then the animation is controlling it.
I know of 2 ways to fix this:
Method 1 (not recommended): Edit the animation to move the root bone instead of the player
Most character models have a GameObject called "Armature" or "root" as a child. The position of your character should not be in the animation. If you need to move the character up in your animation, you can animate the "Armature" or "root" GameObject. In the screenshot below, note that in the animation window you don't see "PlayerPref: position" but instead see "Armature: position". This allows my movement script to move PlayerPref without the animation overriding the position.
Method 2 (recommended): Use the physics engine to jump
In your animation, don't make the player jump. Instead make the player "jump in place" i.e. maybe swing arms upward as if jumping but don't actually move upward. Just like Method 1, your animation should not set the player's position. The easiest way to do this is to click on "player: position" in the animation window and press delete.
Add a Rigidbody to your character. In your script:
private Rigidbody rig;
...
void Start(){
rig = GetComponent<Rigidbody>();
...
}
In your script when you trigger jumping, you can add rig.AddForce(jumpPower * transform.up);. This will make the physics system handle the jumping. If the player is jumping but there is something above, the physics engine will stop the player. With animation, the player will jump through any obstacles.

Attack animations lasts for 1 frame

Hello I am making a 2D top down student game in Unity. I am stuck at the animation part where My attack animations lasts for 1 frame and then goes straight back to Idle. Using C# in visual studio:
void Update () {
Move ();
float lastInputX = CrossPlatformInputManager.GetAxis("Horizontal");
float lastInputY = CrossPlatformInputManager.GetAxis("Vertical");
if (lastInputX != 0 || lastInputY != 0)
{
anim.SetBool("Walking", true);
anim.SetFloat("LastMoveX", lastInputX);
anim.SetFloat("LastMoveY", lastInputY);
anim.Play("Walk");
}
else if (CrossPlatformInputManager.GetButtonDown("Fire1"))
{
anim.SetBool("Attacking", true);
//StartCoroutine(Attack());
anim.Play("Attack");
}
else
{
anim.SetBool("Attacking", false);
anim.SetBool("Walking", false);
anim.Play("Idle");
}
float inputX = CrossPlatformInputManager.GetAxis("Horizontal");
float inputY = CrossPlatformInputManager.GetAxis("Vertical");
anim.SetFloat("SpeedX", inputX);
anim.SetFloat("SpeedY", inputY);
}
I'm not sure about this but if the behaviour of CrossPlatformInputManager.GetButtonDown() is the same as Input.GetButtonDown() it will return true during the frame the user pressed down, but it will not return true until the user has released the key and pressed it again.
This means that on the first frame the user pressed the button it will trigger the Attack animation but next frame will return false and trigger the Idle animation.
Also, I'm not sure why are you calling anim.Play() on each animation, the purpose of Animator parameters is to trigger the animation when a given value is present.
Its because the update loop runs on every frame, and on every frame you are setting the animation. Remove the if else from the update loop. In the animator controller you can make transitions so after the attack it goes back to idle, walking, or whatever you want, you do not need to code what happens at the end of an animation.

How can you check if object is moving/not moving?

i want to make a simple script that when u clicked the screen the ball in the game will move (20000 * Time.deltaTime) to the right, and then if i'll click again, it will move to the left side and then right and so on.
I managed to get the ball to move to the right, but i need it to wait after the animation is finish because i need to check if the player clicked again (if he did i need to check to what direction to move the ball).
I tried many methods i found online like checking if Rigidbody.velocity.magnitude == 0.0f that means the ball is not moving..
public Rigidbody rb;
public Transform PlayerPosition;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
rb.AddForce(20000 * Time.deltaTime, 0, 0); // moving ball to the right
while (rb.velocity.magnitude != 0.0f) // I tried to check until the ball is not moving
{
}
Debug.Log(PlayerPosition.position.x);
}
}
And here is my latest try:
void Update()
{
if (Input.GetMouseButtonDown(0))
{
rb.AddForce(20000 * Time.deltaTime, 0, 0); // moving ball to the right
if(rb.velocity.magnitude < 0.05f) // if i click the ball it just prints it and not wating for the ball to not move
{
Debug.Log(PlayerPosition.position.x);
}
}
}
I expected the output to wait until the animation is finished but instead, its printing the vaule(x) the moment i click the mouse.
Edit
You need to check if your animation is still playing. You are checking only if your velocity is greater than 0.05f, which is correctly printing out the statement.
Use Animation.IsPlaying(string name). One caveat is that this method will return false for the same frame of Update that it was invoked, since the animation hasn't technically started until afterward.
void Update()
{
if (!rb.velocity.magnitude <= 0.01f && !Animation.IsPlaying(nameOfAnimation))
{
Debug.Log("We're not moving and the animation is not playing");
}
}
Original
You should not need to use while in your Update method.
Use an if statement inside of your Update
void Update()
{
if (rb.velocity.magnitude > 0.01f) Debug.Log("We're moving!");
}
First
rb.velocity.magnitude != 0.0f
will almost allways be true due to single precision floatong point : Two float values even if they seem to be equal logical are most likely not.
So you can either use a threshold how you tried already
if(rb.velocity.magnitude <= 0.5f)
or use Mathf.Approximately which uses a very small Epsilon or threshold for the comparing
if(Mathf.Approximately(rb.velocity.magintude, 0))
Than it sounds like you want to wait until the ball has stopped moving and than output the position - like e.g. for a billard game. So actually there seems to be no Animation involved.
In most cases where you think/speek of of an "animation" you actually mean "doing something over time" not to confuse with using an Animator or Animation component with AnimationClips in Unity.
You can/should use a Coroutine for that:
public Rigidbody rb;
public Transform PlayerPosition;
// a flag to make sure there is only one animation at a time
private bool isMoving;
// a flag for altering between left and right movement
private bool isMovingRight;
// Update is called once per frame
void Update()
{
// only allow clicks while not moving already
if (!isMoving && Input.GetMouseButtonDown(0))
{
// stop further input until not moving anymore
isMoving = true;
// add the force
// (you might btw want to skip that Time.deltaTime here it makes no sense)
rb.AddForce(isMovingRight ? 20000 : -20000 * Time.deltaTime, 0, 0);
// alter the direction for the next call
isMovingRight = !isMovingRight;
// if you rather want to be able to interrupt the current animation by clicking again
// remove the isMoving flag and instead use this
//StopCoroutine(WaitForMoveStops());
// Start the routine
StartCoroutine(WaitForMoveStops());
}
}
private IEnumerator WaitForMoveStops()
{
// Inside a Coroutine while is okey now
// as long as you yield somwhere
// check if velocity is below threshold
while (!Mathf.Approximately(rb.velocity.magnitude, 0)
{
// yield in simple words means "leave" this method here, render the frame
// and than continue from here in the next frame
yield return null;
}
// I would now hard reset the velocity just to be sure
rb.velocity = Vector3.zero;
Debug.Log(PlayerPosition.position.x);
// whatever you want to do now
// reset the flag to allow input again
isMoving = false;
}
I think you want to move it if it's stopped, then call AddForce only when it's idle:
var wasMovingLastTime = false;
void Update()
{
var isMoving = rb.velocity.magnitude > 0f;
if (wasMovingLastTime && !isMoving)
{
/// Has just finished moving
Debug.Log(PlayerPosition.position.x);
}
if (Input.GetMouseButtonDown(0))
{
if (!isMoving)
{
rb.AddForce(20000 * Time.deltaTime, 0, 0);
}
}
wasMovingLastTime = isMoving;
}

How to check if a certain animation state from an animator is running?

I created an animator called "m4a4animator". Inside it, the main function is called "idle" (nothing), and other 2 states: "shoot" (mouse0) and "reload" (R). These 2 animation states are transitioned to "idle". Now, everything is working... but the only problem I have is this: if I am in the middle of reloading and and press mouse0 (shoot), the animation running state immediately changes to shoot... but I want to block that.
Now, the question: How can I stop CERTAIN animation changes while an animation is running?
Here is my animator
And here is my script:
using UnityEngine;
using System.Collections;
public class m4a4 : MonoBehaviour {
public Animator m4a4animator;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
if (Input.GetKeyDown (KeyCode.R)) {
m4a4animator.Play("reload");
}
if (Input.GetMouseButton(0)) {
m4a4animator.Play("shoot");
}
}
}
For the legacy Animation system, Animation.IsPlaying("TheAnimatonClipName) is used to check if the animation clip is playing.
For the new Mechanim Animator system, you have to check if both anim.GetCurrentAnimatorStateInfo(animLayer).IsName(stateName) and anim.GetCurrentAnimatorStateInfo(animLayer).normalizedTime < 1.0f) are true. If they are then animation name is currently playing.
This can be simplified like the function like the Animation.IsPlaying function above.
bool isPlaying(Animator anim, string stateName)
{
if (anim.GetCurrentAnimatorStateInfo(animLayer).IsName(stateName) &&
anim.GetCurrentAnimatorStateInfo(animLayer).normalizedTime < 1.0f)
return true;
else
return false;
}
Now, everything is working... but the only problem I have is this: if
I am in the middle of reloading and and press mouse0 (shoot), the
animation running state immediately changes to shoot... but I want to
block that.
When the shoot button is pressed, check if the "reload" animation is playing. If it is, don't shoot.
public Animator m4a4animator;
int animLayer = 0;
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.R))
{
m4a4animator.Play("reload");
}
//Make sure we're not reloading before playing "shoot" animation
if (Input.GetMouseButton(0) && !isPlaying(m4a4animator, "reload"))
{
m4a4animator.Play("shoot");
}
}
bool isPlaying(Animator anim, string stateName)
{
if (anim.GetCurrentAnimatorStateInfo(animLayer).IsName(stateName) &&
anim.GetCurrentAnimatorStateInfo(animLayer).normalizedTime < 1.0f)
return true;
else
return false;
}
If you need to wait for the "reload" animation to finish playing before playing the "shoot" animation then use a coroutine. This post described how to do so.
There are other threads about that: https://answers.unity.com/questions/362629/how-can-i-check-if-an-animation-is-being-played-or.html
if (this.animator.GetCurrentAnimatorStateInfo(0).IsName("YourAnimationName"))
{
//your code here
}
this tells you if you are in a certain state.
Animator.GetCurrentAnimatorStateInfo(0).normalizedTime
this give you the normalized time of the animation: https://docs.unity3d.com/ScriptReference/AnimationState-normalizedTime.html
Try to play with those function, I hope that solve your problem

Categories