Okay so here is the code:
// Variable to store character animator component
Animator animator;
// Variables to store optimized setter/getter parameter IDs
int isWalkingHash;
int isRunningHash;
// Variable to store the instance of the PlayerInput
PlayerInput input;
// Variables to store player input values
Vector2 currentMovement;
bool movementPressed;
bool runPressed;
// Awake is called when the script instance is being loaded
void Awake()
{
input = new PlayerInput();
// Set the player input values using listeners
input.CharacterControls.Movement.performed += ctx => {
currentMovement = ctx.ReadValue<Vector2>();
movementPressed = currentMovement.x != 0 || currentMovement.y != 0;
};
input.CharacterControls.Run.performed += ctx => runPressed = ctx.ReadValueAsButton();
}
// Start is called before the first frame update
void Start()
{
// Set the animator reference
animator = GetComponent<Animator>();
// Set ID references
isWalkingHash = Animator.StringToHash("isWalking");
isRunningHash = Animator.StringToHash("isRunning");
}
// Update is called once per frame
void Update()
{
handleMovement();
}
void handleMovement()
{
// Get parameter values from animator
bool isRunning = animator.GetBool(isRunningHash);
bool isWalking = animator.GetBool(isWalkingHash);
// Start walking if movement pressed is true and not already walking
if (movementPressed && !isWalking) {
animator.SetBool(isWalkingHash, true);
}
// Stop walking if movementPressed is false and not already walking
if (!movementPressed && isWalking) {
animator.SetBool(isWalkingHash, false);
}
// Start running if movement pressed and run pressed is true and not already running
if ((movementPressed && runPressed) && isRunning) {
animator.SetBool(isRunningHash, true);
}
// Stop running if movement pressed or run pressed is false and not currently running
if ((!movementPressed || !runPressed) && isRunning) {
animator.SetBool(isRunningHash, false);
}
}
void OnEnable()
{
//Enable the character control action map
input.CharacterControls.Enable();
}
void OnDisable()
{
//Disable the character control action map
input.CharacterControls.Disable();
}
I am using UnityEngine.InputSystem at the start. But movement works fine, just when I let go WASD it is still moving in the last direction pressed. I really have no idea what is the problem here. I am just starting out so I don't know answers to the most simple questions. Sorry for bothering you. Thanks in advance!
in the following block
// Stop walking if movementPressed is false and not already walking
if (!movementPressed && isWalking) {
animator.SetBool(isWalkingHash, false);
}
you said you are checking 'not already walking'.
won't it be '!isWalking' as the if condition?
Related
I'm making a Balloon Fight style game and I'm having trouble with object collision. Each character has two balloons on top of his head and each balloon has an on trigger Box Collider. I want to make it so only one balloon can be hit at a time so you can't destroy both balloons at the same time. In order to do this I added a boolean called isAttacking to prevent it from destroying more than one balloon at the same time.
Hello, I'm making a Balloon Fight style game and I'm having trouble with object collision. Each character has two balloons on top of his head and each balloon has an on trigger Box Collider. I want to make it so only one balloon can be hit at a time so you can't destroy both balloons at the same time. In order to do this I added a boolean called isAttacking to prevent it from destroying more than one balloon at the same time.
public bool isAttacking = false;
private void OnTriggerEnter(Collider collision)
{
if (collision.GetComponent<Collider>().gameObject.layer == 7 && collision.GetComponent<Collider>().gameObject.tag != this.gameObject.tag)
{
if (!isAttacking)
{
Destroy(collision.GetComponent<Collider>().transform.parent.gameObject);
transform.parent.gameObject.GetComponent<Jump>().jump = true;
isAttacking = true;
}
}
}
void LateUpdate()
{
if (isAttacking)
{
isAttacking = false;
}
}
While it does prevent two collisions from registering I still found this solution to be insufficient, since the balloon that is destroyed is not necessarily the one closest to the character destroying it. How could I improve the collision code in order for it to only register the collision happening closer to the character?
Within one frame afaik there is no reliable order of OnTriggerEnter calls (it is somewhat based on the instanceID of objects but that won't really help you).
What you could do instead would be comparing distances, somewhat like e.g.
private readonly HashSet<GameObject> hittingObjects = new();
[SerializeField] private Jump jump;
private void Awake()
{
if(!jump) jump = GetComponentInParent<Jump>(true);
}
private void OnTriggerEnter(Collider collision)
{
if (collision.layer == 7 && !collision.CompareTag(gameObject.tag))
{
hittingObjects.Add(collision.transform.parent.gameObject);
}
}
private void LateUpdate()
{
if (hittingObjects.Count > 0)
{
var closestHit = hittingObjects.OrderBy(hit => (transform.posiion - hit.transform.position).sqrMagnitude).First();
Destroy(closestHit);
jump.jump = true;
hittingObjects.Clear();
}
}
Note: This still doesn't prevent this object from colliding with the other balloon in the very next physics update. If you wanted to track this as well you could make it slightly more complex and only allow collisions if you are newly entering the trigger => You have to exit the object again before you can hit it again.
Somewhat like maybe
private readonly HashSet<GameObject> hastoExitFirstObjects = new();
private readonly HashSet<GameObject> newHittingObjects = new();
[SerializeField] private Jump jump;
private void Awake()
{
if(!jump) jump = GetComponentInParent<Jump>(true);
}
private void OnTriggerEnter(Collider collision)
{
if (collision.layer == 7 && !collision.CompareTag(gameObject.tag))
{
var hit = collision.transform.parent.gameObject;
if(!hastoExitFirstObjects.Contains(hit))
{
newHittingObjects.Add();
}
}
}
private void OnTriggerExit(Collider collision)
{
if (collision.layer == 7 && !collision.CompareTag(gameObject.tag))
{
var hit = collision.transform.parent.gameObject;
hastoExitFirstObjects.Remove(hit);
}
}
private void LateUpdate()
{
if (newHittingObjects.Count > 0)
{
var closestHit = newHittingObjects.OrderBy(hit => (transform.posiion - hit.transform.position).sqrMagnitude).First();
newHittingObjects.Remove(closestHit);
Destroy(closestHit);
jump.jump = true;
foreach(var hit in newHittingObjects)
{
hastoExitFirstObjects.Add(hit);
}
newHittingObjects.Clear();
}
}
First, check if your OnTriggerEnter is executing for both balloons. It probably is?
Then in the moment OnTriggerEnter executes, compare the position of collision with the position of your balloons and see which one is the closest, then destroy the closest balloon and maybe set a variable isInvulnerable as true, so that if it is true, no balloon can be destroyed inside OnTriggerEnter.
I'd just enable only one balloons box collider at once, and when it gets popped enable the other balloon after 1 second delay.
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();
}
}
so here is the scenario,
i want the player to be able to perform a trick, and then be able to hold the trick for a longer duration if they want to, but if they don't hold the input then they instead just continue the animation until completion as i haven't actually tried implementing too much and its not giving me the desired result i figured i'd just ask if people have already done it so i don't spend the next 2 hours down a rabbit hole, any and all help is appreciated thanks! :D
(Unity Script)
{
[Header("Trick Attributes")]
public string GroundTagName; // Tag used for all the platforms
public KeyCode stuntKey;
public float AnimationFreezeTime = 0.75f;
public SpriteAnimator anim; // Put the Sprite animator here
public Animator spriteAnimator; // Put the Animator here
private bool isGrounded;
public bool stunting = false;
private void Start()
{
}
private void Update()
{
if (Input.GetKeyDown(stuntKey) && !isGrounded)
{
anim.StartAnimation("FlyingSquirrel");
stunting = true;
Invoke("FreezeAnimation", AnimationFreezeTime);
}
if (Input.GetKeyUp(stuntKey))
{
stunting = false;
spriteAnimator.speed = 1;
}
}
void FreezeAnimation() {
spriteAnimator.speed = 0;
}
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.tag == GroundTagName)
{
isGrounded = true;
}
else
{
isGrounded = false;
}
if (stunting && collision.gameObject.tag == GroundTagName)
{
Destroy(gameObject);
}
}
private void OnCollisionExit(Collision collision)
{
isGrounded = false;
}
}
FlyingSquirrel needs to be broken up into 3 different animations. This can be done in Maya, Blender, uMotion, or even Unity's animation importer:
You need 3 Animator states:
Start_FlyingSquirrel // Player moves into position
Idle_FlyingSquirrel // Player is "holding" the trick
End_FlyingSquirrel // Player let go, return to normal
When the player presses button to start the trick, play the Start animation. For the transition, use "has exit time" to proceed to the Idle State. The Idle state will be true (use a boolean Animator parameter) while the trick is "held". Uncheck "has exit time" for the transition to the End state- you want this to happen instantly.
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.