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.
Related
Firstly, the code:
private bool? _hasJumped = false;
private void Update()
{
Debug.Log("Checking the _hasJumped value in Update(): " + _hasJumped);
Debug.Log("Is the player Grounded?: " + IsGrounded());
if (IsGrounded())
{
_extraJumps = _extraJumpCount;
_coyoteTimer = _coyoteTimerVal;
_hasJumped = false; //The Variable that I am having issues with.
Debug.Log("This statement only runs when the player is on ground!");
}
else
{
_coyoteTimer -= Time.deltaTime;
}
}
public bool IsGrounded()
{
return Physics2D.OverlapCircle(_feetpos.position,_feetCheckRadius,_groundLayerMask);
}
public void GetJumpInput(InputAction.CallbackContext context)
{
if (context.started)
{
_jumpBuffer = Time.time;
}
if (_coyoteTimer > 0f && (Time.time - _jumpBuffer >= 0) && context.performed)
{
if(context.interaction is TapInteraction)
{
_myRigidBody.velocity += new Vector2 (0f, _jumpForce);
_whichInteraction = 0;
}
else if (context.interaction is HoldInteraction)
{
_myRigidBody.velocity += new Vector2 (0f, _jumpForce);
_whichInteraction = 1;
}
_jumpBuffer = null;
_hasJumped = true; //_hasJumped set to true when I first jump
Debug.Log("The Player Has Pressed Jump!: " + _hasJumped);
}
else if (_extraJumps > 0 && context.performed && hasJumped) //Double Jump should ONLY work when I have jumped, and not when I fall off a ledge.
{
Debug.Log(_hasJumped);
_extraJumps--;
if(context.interaction is TapInteraction)
{
_myRigidBody.velocity += new Vector2 (0f, _jumpForce*_secondJumpForceMult);
_whichInteraction = 0;
}
}
else
{
_coyoteTimer = 0f;
}
}
Sorry if the the code is confusing, I've tried to add comments to the variable in focus. But the issue that I'm facing is this:
Right now, when a player falls off the edge, the player is able to jump once because of the additional "extra jump" but this should not be happening. The "extra jump" should only come into effect when the player HAS already jumped.
To stop this, I decided to use a bool "_hasJumped" which (in my head works as follows):
As long as the player is touching the ground, it is false.
When the player first jumps, it is set to true.
Only when it is true can player perform the "extra jump".
If it isn't true, the player cannot perform the "extra jump".
However, the issue that I'm facing is that although _hasJumped is set to true when I jump, it is immediately set to false in the next update (even though I'm still in the air!). Video Evidence #1
I've checked that my IsGrounded() function is working completely fine [Video Evidence #2].
The only reason I can deduce is that it may have something to do with Unity's runtime order? I'm a newbie though so I could be wrong.
I am also using the new Input System.
Thank you for taking the time to read it/give advice/help! I really appreciate it!
The problem is how fast the update actually runs in relation to the way you structured your code.
To visualize, this is what happens when the player pressed the jump button:
First frame: Player presses jump button
Second frame: Character is slightly up on the air, but the overlap collision detector is still touching the ground
Solution
Only check for IsGrounded() when the player presses the jump button, not per-update.
This also saves some computing resource as doing Physics calculation per-frame can be demanding.
Use OnCollisionEnter2D or OnTriggerEnter2D to check if the character has just landed to set canExtraJump to false.
Looks something like this:
bool canExtraJump = false;
void Update(){
// Set extrajump when player inputs jump button, and char is grounded
if (Player_Input_Jump) {
if (IsGrounded()){
Jump();
canExtraJump = true;
}
}
}
void TriggerLandedOnGround(){
canExtraJump = false;
}
// ...
/// Ideally this function should be in the character's feet (as another gameObject)
/// As you do not want this to trigger when the side of a character touches the platform.
void OnTriggerEnter2D(Collider2D other){
if (other.CompareTag("platform")){
TriggerLandedOnGround();
}
}
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
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;
}
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.");
}
Currently I'm simply trying to change the sprites candle from unlit to lit when the player has 'picked up' both the candle and the matches and the candle will 'go out' after a certain amount of time. However, when the space bar is pressed the transition from unlit to lit isn't occurring, even though the debug log is returning true when it should. I'm posting here to get some guidance as I have spent most of the day looking online and literally have no idea how to proceed.
Basically the images I am trying to transition between are two different images which are in the sprites folder under assets.
This is what I've got so far.
//the two sprites transition
public Sprite unlitCandle;
public Sprite litCandle;
private SpriteRenderer spriteRenderer;
bool pickUpMatches = false;
bool pickUpCandle = false;
float timeRemaining =5;
bool candleLit = false;
// Use this for initialization
void Start () {
spriteRenderer = GetComponent<SpriteRenderer>();
if (spriteRenderer.sprite == null)
spriteRenderer.sprite = unlitCandle;
}
// Update is called once per frame
private void OnTriggerEnter2D(Collider2D collision)
{
if(collision.gameObject.CompareTag("Matches"))
{
collision.gameObject.SetActive(false);
pickUpMatches = true;
}
if (collision.gameObject.CompareTag("UnlitCandle"))
{
collision.gameObject.SetActive(true);
pickUpCandle = true;
}
}
public void CandleTimer()
{
if (candleLit == true)
{
timeRemaining = 5;
timeRemaining -= Time.deltaTime;
if (timeRemaining <= 0)
{
candleLit = false;
spriteRenderer.sprite = unlitCandle;
}
}
}
public void ChangeSprite()
{
if (spriteRenderer.sprite == unlitCandle)
{
spriteRenderer.sprite = litCandle;
}
}
void Update () {
if (pickUpCandle == true && pickUpMatches == true)
{
//Debug.Log(candleLit);
if (Input.GetKey(KeyCode.Space) && !candleLit)
{
CandleTimer();
ChangeSprite();
Debug.Log(timeRemaining);
candleLit = true;
//Debug.Log(candleLit);
}
}
}
}
Try comparing with a method like equals() instead of == in
spriteRenderer.sprite == unlitCandle
Because right now you are just comparing references and not the objects.
At least I think thats the problem.
There are a few possible issues with your code. First, you are calling changeSprite at the top of Update, which means that it is unconditionally being called every frame. Therefore, after a single frame of your candle being unlit, it will immediately change its sprite to litCandle.
I assume that the reason you are calling changeSprite every frame is in order to process the timer if you have a lit candle already. Really, you should move the code to process the timer (your whole second if statement in changeSprite) to a separate function and name it something like processCandleTimer. Call that at the top of Update and save the changeSprite method to only be called on the keypress.
Lastly, the issue that I suspect is giving you the most trouble is that you aren't resetting your timer, timeRemaining. The first time you light the candle the timer will go down to 0 after the 5 seconds pass. Every time changeSprite is run after that, you will change the sprite to litCandle in the first if statement and then immediately change it back to unlitCandle because the timer is 0 in the second. To remedy this, you need to add a line like timeRemaining = 5.0f; when the key is hit.