How can I change animator variable using c# script in Unity? - c#

In the process of studying Game Development, I decided to do a game of my own. However, while I was putting my animations on my model through the "Animator" tab, I stumbled upon a problem.
I have created a parameter of type "float" in the animator, where if the speed is greater or less than the value x, it plays a certain animation. However, in order to instantiate the walking/running speed, I am using a field which is located in the Inspector tab.
The problem is that since the initialized speed is always different than 0, the animator uses that inserted value and plays the walking animation despite the fact that no key is pressed!
I have already tried various things that I found online such as, using the "Parameter" checkbox on the animations or using different lines of code such as "animator.SetFloat("Speed", (speed));" on my script but none of these have worked out.
// Update is called once per frame
void Update()
{
//animator.SetFloat("Speed", Mathf.Abs(speed));
animator.SetFloat("Speed", (speed));
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
Vector3 moveDirection = new Vector3(horizontal, 0f, vertical) * speed * Time.deltaTime;
transform.Translate(moveDirection);
I expect the output to be as follows:
when no key is pressed, Idle animation to play.
when WASD keys are pressed, walking animation to play.
when Shift + WASD keys are pressed, running animation to play.

In the case you describe you might want to consider not using transitions at all but rather do it all by code using animator.Play or maybe even animator.CrossFade for transitions
public class ExampleEditor : MonoBehaviour
{
private Animator animator;
private void Update()
{
if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift))
{
animator.Play("Running");
return;
}
if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.D))
{
animator.Play("Walking");
return;
}
animator.Play("Idle");
}
}
or if you rather want to use the parameters you could instead use bools like
public class ExampleEditor : MonoBehaviour
{
private Animator animator;
private void Update()
{
if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift))
{
animator.SetBool("Running", true);
return;
}
animator.SetBool("Running", false);
if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.D))
{
animator.SetBool("Walking", true);
return;
}
animator.SetBool("Walking", false);
}
}
and have transition conditions like
IDLE -> Running if Running=true
IDLE -> Walking if Running=false && Walking=true
Running -> Walking if Walking=true && Running=false
Running -> IDLE if Walking=false && Running=false
Walking -> Running if Running=true
Walking -> IDLE if Walking=false

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.

Why does this not stop my character from jumping unlimited times?

My character just jumps continuously even in the air, I'm not sure why the boolean does not stop it and I cannot figure it out. This is what I have so far:
using UnityEngine;
public class PlayerCollision : MonoBehaviour
{
public Rigidbody rb;
bool spacePressed = false;
float upForce = 200f;
void OnCollisionEnter(Collision collisionInfo)
{
if (collisionInfo.collider.tag == "Obstacle")
{
spacePressed = false;
}
}
void Update()
{
if (Input.GetKey("space") && spacePressed == false)
{
spacePressed = true;
rb.AddForce(0, upForce * Time.deltaTime, 0, ForceMode.VelocityChange);
}
}
}
My guess is that 'space pressed' is never or instantly set to false. So a first step on debuging it can be to watch the boolean. You can do that with 'public ...'. Then you should just observe when it's set to true/false.
There is also the possiblilty of this being a totally different kind of issue. For instance that one of the colliders could be of the wrong size (too big/small)
And to complete the holy trinity: There is a free 2D platformer example in the unity hub. I think they use a different approche by using an empty GO as a 'isGrounded' checker. One of the first things I learned as a hobbist is not to be ashamed to copy code of example projects.
Good luck.
It is likely that the collider is hitting Obstacle as it makes its first jump. OnCollisionEnter is very unreliable when checking if an object is beside/on another object. This can be checked if you use Debug.Log and check to see how much times it randomly activates OnCollisionEnter.
To reliably check if an object is beside another object use raycasts or boxcasts instead.
This is some code that should work that uses raycasts:
using UnityEngine;
public class PlayerCollision : MonoBehaviour
{
public Rigidbody rb;
public Collider collider;
public float extraCheckHeight = 0.1f;
float upForce = 200f;
bool isGrounded(){
Physics2D.Raycast(collider.bounds.center, Vector3.down, collider.bounds.extents.y + extraCheckHeight);
}
void Update()
{
if (Input.GetKey("space") && isGrounded)
{
rb.AddForce(0, upForce, 0, ForceMode.VelocityChange);//Time.deltaTime is should not be here because it should be the same force regardless of the time between frames
}
}
}
This should do the trick but I recommend researching about unity raycasts and boxcasts.
Here is a video on Ground Checking that I found useful when I was approached with a similar problem: Ground Check

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.

Unity 2D, Shooting with bow Instantiate problem

Hi everyone I have a little problem with my bow shooting script. I mean i want to shoot an arrow when play the one of last frames from my animation. I try to do it by setting an firePoint GameObject, put it by recording in my Animation Tab in desired frame. It's of course disabled but its enabled when animation plays and then its again disabled. So the problem is:
- When i hit button which match my Shooting input, the animation plays,
- My Instantiate appears and it produces multiple arrows,
- When its disabled it stops to produce arrows.
I want to produce only one arrow. Could anyone help?
CombatScript.cs:
/* private bool shootBow;
* public bool needReload = false;
* public float reloadTime = 1.5f;
* public float realoadCD;
*/
public void RangeAttack()
{
if (needReload == false && gameObject.GetComponent<PlayerControls>().grounded == true && Input.GetButtonDown("Ranged"))
{
animator.SetTrigger("shootBow");
attack1 = false; // Melle attacks sets to false in case of lag or smth.
attack2 = false;
attack3 = false;
needReload = true;
if (needReload == true)
{
reloadCD = reloadTime;
}
}
if (reloadCD > 0 && needReload == true)
{
reloadCD -= Time.deltaTime;
}
if (reloadCD <= 0)
{
reloadCD = 0;
needReload = false;
}
if (firePoint.gameObject.activeSelf == true)
{
Instantiate(Missile, new Vector3(firePoint.position.x + 1, firePoint.position.y), firePoint.rotation);
Debug.Log("It's a bird, a plane, no.. it's arrow.");
}
}
Arrow Controller.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ArrowController : MonoBehaviour {
public float speed;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update ()
{
GetComponent<Rigidbody2D>().velocity = new Vector2(speed, GetComponent<Rigidbody2D>().velocity.y);
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == "Enemy")
{
Destroy(collision.gameObject);
}
Debug.Log("Arrow Broke");
Debug.Log(gameObject.name);
//Destroy(gameObject);
}
public void OnCollisionEnter2D(Collision2D collision)
{
}
}
Example of my situation:
Example of true/false needReload statement:
in right Inspector you have my Player informations, in left (or bottom) Inspector you have Missile (Arrow) inspector
You can use Animation Events and for example a boolean that is your firerate.
And you Code can look something like this:
float LastShoot;
float FireRate = 10;
public void OnShoot()
{
if (LastShoot <= 0)
{
//Shoot your arrow
LastShoot = FireRate;
}
}
void Update()
{
LastShoot -= 0.5f;
}
But i don't know if this is the best solution to calculate a firerate. If someone knows a better one feel free and edit my awnser :)
Okey... couple of hours later (which I wasted...). The answer was ridiculously easy. For future "problems" like that... the thing was to create in my script function called "ProduceArrow()"and (additionaly to what i did) something like Animation Event it's in Animation Tab, when you create your animation timeline you just need to call it clicking right mouse button and then choose it and pick proper function.
Some feedback - gif
The way your code works right now, Instatiate will be called every frame. So one button click, which is probably longer than one frame, will trigger multiple arrows, so you need to set a condition for when you want Instantiate to be called. You already calculate a reload time, which is exactly what you need. You just need to include it in your if-statement like follows:
if (firePoint.gameObject.activeSelf == true && !needReload)
{
Instantiate(Missile, new Vector3(firePoint.position.x + 1, firePoint.position.y), firePoint.rotation);
Debug.Log("It's a bird, a plane, no.. it's arrow.");
}

GameObject not visually displaying in the Scene Tab in the correct location in Unity Networking

I have been trying to add little things on top of the tutorials Unity supplies and I am confused about how to get this certain mechanic to work.
When my player shoots it shoots in the direction of the mouse. When I run a client and host on my computer so that 2 people are connect for testing I get weird results.
I have both of my players shooting in the correct direction but when I am moving my mouse on one of the clients I see that my blue circle (which represents where the bullets will be spawned) is moving when that client is not focused, meaning I am not currently clicked on that client and when I am moving my mouse I see the blue circle moving on the client I am not focused on which I am not sure if my friend down the street was to test this would it cause errors.
Here are 2 screenshots of my Scene/Game view to get a better visual : Pic 1 - Pic 2
I ended up using Network Transform Child on my parent GameObject for one of my children GameObjects that helps spawn the location of the bullets but still the visual look from my Scene tab makes me worry about the accuracy of bullets being spawned.
Here is my shooting code that :
public class PlayerShooting : NetworkBehaviour
{
public GameObject bulletPrefab;
public GameObject fireSpot;
public float bulletSpeed;
public Transform rotater;
public Camera cam;
void Update()
{
// Only run the below code if this is the local player.
if (!isLocalPlayer)
{
return;
}
// Rotate based on the location of the mouse our spot to shoot bullets.
Vector3 dir = cam.ScreenToWorldPoint (Input.mousePosition) - rotater.position;
float angle = Mathf.Atan2 (dir.y, dir.x) * Mathf.Rad2Deg;
rotater.rotation = Quaternion.AngleAxis (angle, Vector3.forward);
// When we hit spacebar
if(Input.GetKeyDown(KeyCode.Space))
{
// Fire some bullets.
CmdFire ();
}
}
// Something the client is wanting to be done and sends this "command" to the server to be processed
[Command]
public void CmdFire ()
{
// Create the Bullet from the Bullet Prefab
var bullet = (GameObject)Instantiate (bulletPrefab, fireSpot.transform.position, Quaternion.identity);
// Add velocity to the bullet
bullet.GetComponent<Rigidbody2D>().velocity = (fireSpot.transform.position - rotater.position).normalized * bulletSpeed;
// Spawn the bullets for the Clients.
NetworkServer.Spawn (bullet);
// Destroy the bullet after 2 seconds
Destroy(bullet, 4.0f);
}
}
My movement script :
public class Movement : NetworkBehaviour {
void Update()
{
if (!isLocalPlayer)
{
return;
}
var x = Input.GetAxis("Horizontal") * Time.deltaTime * 3.0f;
var y = Input.GetAxis("Vertical") * Time.deltaTime * 3.0f;
transform.Translate(x, y, 0f);
}
}
This will continue to be a problem if you'd like to run multiple instances of unity games on the same machine. If you go into Edit > Project Settings > Player, there is a Run in Background option under Resolution and Presentation. If you have this checked, both instances of the game will receive updated mouse positions per frame from Input.mousePosition (Keep in mind Input.GetKeyDown will only register to the focused instance of the game so there's some inconsistency here). If you don't have that check box selected, your game instance will pause when not focused (I'm guessing you don't want this if you're working with a client/host networking paradigm).
There are a few ways you can work around this. The ideal way to test a networked game would be to have a unique PC for each instance of the game. If that's not an option, you can add some checks to ensure the mouse is over the window. As long as the two windows don't overlap you can do that by checking the position against the screen info:
bool IsMouseOverWindow()
{
return !(Input.mousePosition.x < 0 ||
Input.mousePosition.y < 0 ||
Input.mousePosition.x > Screen.width ||
Input.mousePosition.y > Screen.height);
}
You can then use this to decide, in your Update(), whether or not you want to update rotator.rotation.
Another option would be to implement MonoBehaviour.OnApplicationFocus. You can then enable/disable things (like updating rotations based on mousePosition) in response to that event. The cleanest solution would be to have a clear way for your systems to ask "is my window focused". You can make a class like this:
public class FocusListener : MonoBehaviour
{
public static bool isFocused = true;
void OnApplicationFocus (bool hasFocus) {
isFocused = hasFocus;
}
}
Make sure you have one of these somewhere in your game and then anything can check by looking at FocusListener.isFocused.

Categories