I'm trying to make a "player can't move while this animation is playing" check in my method for movement.
I have a 3x8 grid of panels I'm using for this application, with the player moving from panel to panel. I have 2 animations for this: MovingOut which plays when the player is moving away from a panel, and "MovingIn" which plays when the player is moving into a panel. So the flow I want is:
Player presses a movement key → movement is disabled → "MovingOut" plays → player's transform.position is moved to the target position → "MovingIn" plays → movement is re-enabled.
Each animation is only 4 frames. I currently have an animation event at the beginning of "MovingOut" which sets an int CanMove to 0, and another animation event at the end of "MovingIn" which sets CanMove to 1.
Here's what my code looks like so far:
public void Move(int CanMove)
{
//this lets me use panelManager to access methods in the PanelManager script.
panelManager = GameObject.FindObjectOfType(typeof(PanelManager)) as PanelManager;
animator = GetComponent<Animator>();
if (Input.GetAxisRaw("Horizontal") == 1 && CanMove == 1) //go right
{
movingToPanel += 1;
if (IsValidPanel(movingToPanel))
{
//play animation MovingOut
animator.Play("MovingOut");
transform.position = panelManager.GetPanelPos(onPanel + 1);
onPanel += 1;
}
else
{
movingToPanel -= 1;
}
}
//else if( ...the rest of the inputs for up/down/left are below.
}
I have MovingIn set up in the animator so that it plays at the end of the MovingOut animation, which is why I don't call it in the script:
I can't for the life of me figure out how to get CanMove passed into the method without being forced to define it upon calling the method. Currently I have Move(1); being called in my Update() method just to check if movement is working (other than this issue I'm having) and it works in the sense that I can move panel to panel, but since CanMove is being defined as 1 in the Update Function, the animation's events don't prevent the movement.
Any insight would be greatly appreciated!
You should change your design slightly. Remove canMove as a parameter being passed to Move and make it a field of the player's class. Then have one function to set canMove. Then have a separate function that allows you to move if canMove is true. Something like this:
private bool canMove = true;
public void SetMove(int setCanMove) // called with animation events
{
canMove = setCanMove == 1 ? true : false;
}
public void Move()
{
if (Input.GetAxisRaw("Horizontal") == 1 && canMove == true) //go right
{
//movement code and animation call...
}
// Other directions...
}
Then you can call the setMove funciton using your animation events and stop the player moving for the duration of them. i.e call setMove(false) at the start of the MovingOut animation and setMove(true) at the end of the MovingIn animation. This will stop canMove being set in your Update loop.
you can get the integer value from animator's GetInteger function which will return the integer value set to current animation.
if(this.GetComponent<Animator>().GetInteger(canmove)==0)
//move
else
//can't move
Related
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class StatReader : MonoBehaviour
{
public float modifier;
bool isIncrease = false;
void Update()
{
CheckInput();
Reading();
}
void CheckInput()
{
if (Input.GetKeyDown(KeyCode.Mouse0))
{
Debug.Log("Input received.");
if (isIncrease == false)
{
Slider slider = gameObject.GetComponent<Slider>();
slider.value += modifier * Time.deltaTime;
isIncrease = true;
}
else
{
Slider slider = gameObject.GetComponent<Slider>();
slider.value -= modifier * Time.deltaTime;
isIncrease = false;
}
}
}
void Reading()
{
Slider slider = gameObject.GetComponent<Slider>();
float readingOutputs = slider.value;
if (readingOutputs <= 0f)
{
readingOutputs = 0f;
}
if (readingOutputs >= 100f)
{
readingOutputs = 100f;
}
Debug.Log(Mathf.RoundToInt(readingOutputs));
}
}
As you can see from my script above, I'm trying to achieve the following goals:
Once press left mouse button, the slider starts filling up until I click the button again.
Once click it again, the slider starts depleting until I click the button again.
The problem is:
When I click the left mouse button, the slider will fill up but only for a moment notice. So the value increases to 0.000438 or something. Then it stops automatically. When I click again, it goes back to zero. I've been debugging it by myself for quite some time now. It seems like Update() is interrupting itself. Every frame it stops to check the input, which is why the increase/decrease was such a small value.
I have another problem actually:
Initially, I used FixedUpdate() instead of Update(). Weirdly, when I hit play , the program cannot pick up every input when I'm spamming the left mouse button. The problem went away once I changed FixedUpdate() to Update(). The problem is solved but I want to know why it happened.
Please help! Thanks a lot in advance!
Update is called every frame. Your Update is not interrupted but rather only doing something when GetKeyDown returns true.
Well GetKeyDown is true only in one single frame when you started pressing the key.
And then you also immediately toggle between increasing and decreasing after adding the value once.
Anyway you should also avoid calling GetComponent in Update and rather store and re-use the reference
I would rather simply do something like
// already reference via the Inspector if possible
[SerializeField] private Slider _slider;
// a little helper method you can invoke via the context menu of your component
// allows you to a) already store the reference in the serialized field
// and b) you ca already see whether the reference is actually found as expected
[ContextMenu(nameof(FetchComponents))]
private void FetchComponents()
{
// as a FALLBACK get the reference ONCE
if(!_slider) _slider = GetComponent<Slider>();
}
private void Awake()
{
FetchComponents();
}
void CheckInput()
{
if (Input.GetKeyDown(KeyCode.Mouse0))
{
// simply invert the direction ONCE on key press
isIncrease = !isIncrease;
}
// increase or decrease depending on the current flag state EVERY FRAME
// Clamp your result value between 0 and 100
var newValue = Mathf.Clamp(_slider.value + modifier * Time.deltaTime * (isIncrease ? 1 : -1), 0f, 100f);
// apply the new value EVERY FRAME
// but only if it is actually different (-> hasn reached 0 or 100)
// to avoid unnecessary calls of onValueChanged
if(!Mathf.Approximately(_slider.value, newValue))
{
slider.value = newValue;
}
}
Your other issue about FixedUpdate: This is the physics routine (and should only be used for physics related things usually). It is called on a fixed time base (by default usually every 0.02 seconds) => It is clear that some of your single-time input like GetKeyDown is missed by this method since there might be multiple Update frames in between two FixedUpdate calls => The GetKeyDown was true in a frame where there was no FixedUpdate call.
=> Thumbrule: Get (single-event) User input in Update. Then if you are dealing with physics apply this input in FixedUpdate (store it in fields meanwhile).
I am following the 2D Roguelike tutorial from unity learn.
When testing my game I enter the play mode and everything is fine, when using the arrows to move the player, unity stops and I can't click at any of the buttons in the editor, and the animation stops.
Here is my player script :
//Delay time in seconds to restart level.
public float restartLevelDelay = 1f;
//Number of points to add to player food points when picking up a food object.
public int pointsPerFood = 10;
//Number of points to add to player food points whne picking up a soda object.
public int pointsPerSoda = 20;
//How much damage a player does to a wall whne chopping it.
public int wallDamage = 1;
//Used to store a refrence to the Player's animator component
private Animator animator;
//Used to store player food points total during level.
private int food;
//Start overrides the Start function of MovingObject
protected override void Start()
{
//Get a component reference to the Player's animator component
animator = GetComponent<Animator>();
//Get the current food point total stored in GameManager.instance between levels.
food = GameplayManager.instance.playerFoodPoints;
//Call the Start function of the MovingObject base class.
base.Start();
}
//This function is called when the behaviour becomes disabled or inactive.
private void OnDisable()
{
//When Player object is disabled, store the current local food total in the GameManager so it can be re-loaded in next level.
GameplayManager.instance.playerFoodPoints = food;
}
private void Update()
{
//If it's not the player's turn, exit the function.
if (!GameplayManager.instance.playersTurn) return;
int horizontal = 0; //Used to store the horizontal move direction.
int vertical = 0; //Used to store the vertical move direction.
//Get input from the input manager, round it to an integer and store in horizontal to set x axis move direction
int v = (int) (Input.GetAxisRaw("Horizontal"));
horizontal = v;
//Get input from the input manager, round it to an integer and store in vertical to set y axis move direction
vertical = (int) Input.GetAxisRaw ("Vertical");
//Check if moving horizontally, if so set vertical to zero.
if (horizontal != 0)
{
vertical = 0;
}
//Check if we have a non-zero value for horizontal or vertical
if (horizontal != 0 || vertical != 0)
{
//Call AttemptMove passing in the generic parameter Wall, since that is what Player may interact with if they encounter one (by attacking it)
//Pass in horizontal and vertical as parameters to specify the direction to move Player in.
AttemptMove<Wall>(horizontal, vertical);
}
}
//AttemptMove overrides the AttemptMove function in the base class MovingObject
//AttemptMove takes a generic parameter T which for Player will be of the type Wall, it also takes integers for x and y direction to move in.
protected override void AttemptMove<T>(int xDir, int yDir)
{
//Every time player moves, subtract from food points total.
food--;
//Call the AttemptMove method of the base class, passing in the component T (in this case Wall) and x and y direction to move.
base.AttemptMove<T>(xDir, yDir);
//Hit allows us to reference the result of the Linecast done in Move.
RaycastHit2D hit;
//If Move returns true, meaning Player was able to move into an empty space.
if (Move(xDir, yDir, out hit))
{
//Call RandomizeSfx of SoundManager to play the move sound, passing in two audio clips to choose from.
}
//Since the player has moved and lost food points, check if the game has ended.
CheckIfGameOver();
//Set the playersTurn boolean of GameManager to false now that players turn is over.
GameplayManager.instance.playersTurn = false;
}
//OnCantMove overrides the abstract function OnCantMove in MovingObject.
//It takes a generic parameter T which in the case of Player is a Wall which the player can attack and destroy.
protected override void OnCantMove<T>(T component)
{
//Set hitWall to equal the component passed in as a parameter.
Wall hitWall = component as Wall;
//Call the DamageWall function of the Wall we are hitting.
hitWall.DamageWall(wallDamage);
//Set the attack trigger of the player's animation controller in order to play the player's attack animation.
animator.SetTrigger("playerChop");
}
//OnTriggerEnter2D is sent when another object enters a trigger collider attached to this object (2D physics only).
private void OnTriggerEnter2D(Collider2D other)
{
//Check if the tag of the trigger collided with is Exit.
if (other.tag == "Exit")
{
//Invoke the Restart function to start the next level with a delay of restartLevelDelay (default 1 second).
Invoke("Restart", restartLevelDelay);
//Disable the player object since level is over.
enabled = false;
}
//Check if the tag of the trigger collided with is Food.
else if (other.tag == "Food")
{
//Add pointsPerFood to the players current food total.
food += pointsPerFood;
//Disable the food object the player collided with.
other.gameObject.SetActive(false);
}
//Check if the tag of the trigger collided with is Soda.
else if (other.tag == "Soda")
{
//Add pointsPerSoda to players food points total
food += pointsPerSoda;
//Disable the soda object the player collided with.
other.gameObject.SetActive(false);
}
}
//Restart reloads the scene when called.
private void Restart()
{
//Load the last scene loaded, in this case Main, the only scene in the game.
SceneManager.LoadScene(0);
}
//LoseFood is called when an enemy attacks the player.
//It takes a parameter loss which specifies how many points to lose.
public void LoseFood(int loss)
{
//Set the trigger for the player animator to transition to the playerHit animation.
animator.SetTrigger("playerHit");
//Subtract lost food points from the players total.
food -= loss;
//Check to see if game has ended.
CheckIfGameOver();
}
//CheckIfGameOver checks if the player is out of food points and if so, ends the game.
private void CheckIfGameOver()
{
//Check if food point total is less than or equal to zero.
if (food <= 0)
{
//Call the GameOver function of GameManager.
GameplayManager.instance.GameOver();
}
}
How can I fix this?
I probably tried everything.
Regards.
This feels like an infinite loop to me, which would probably lock up the game and editor.
The code you posted calls out to other methods not listed here... I would look for a place where you might be recursively calling a method, or maybe where you're meaning to call base.theMethod but instead called this.theMethod.
This is probably a stack overflow where a function is calling himself infinitely.
It can also be a Coroutine from the base class with a infinite loop.
However, we can't say exactly where in the code, because is missing some information like what's inside the parent class (from the comments I suppose is called MovingObject)
Whenever posting a code be sure to send only the necessary piece of code (maybe removing some useless comments) and giving precious information like the inheritance and if is an override function would be useful to give also his base implementation.
I'm creating a Character Selection Menu for my mobile game. I have a GlobalManager Gamobject which is a singelton persisting through scenes. Then I have a GameSession Gameobject which controls stuff inside the Gameplay Scene.
In MenuScene inside character selection window when I click on the character toggle I call function, which sets sprite and runtimeAnimatorController into a field of GlobalManager. Then When I load the Gameplay inside Start I call the function below to set the Player sprite and Animator.
Problem is that the Animator is set but the sprite not. Also when I start moving the player it looks like the Animator is calling all Animations at once for 3-4 sec until it stabilizes and starts playing the right one.
#region Set Character
public void SetCharacter(GameObject player)
{
if (CharacterSprite && CharacterRuntimeAnimatorController && player)
{
player.GetComponent<SpriteRenderer>().sprite = CharacterSprite;
if (player.GetComponent<PlayerBehavior>() && player.GetComponent<Animator>())
{
player.GetComponent<Animator>().runtimeAnimatorController = CharacterRuntimeAnimatorController;
}
}
}
#endregion
Expected behavior is that the sprite will be set and the Animator won't be glitching
EDIT:
When I deactivate Animator on Player GameObject the sprite is set. Still not sure what is causing this problem
EDIT 2:
Following script enables the sprite to be set but the Animation glitch remains
public void SetCharacter(GameObject player)
{
if (CharacterSprite && CharacterRuntimeAnimatorController && player)
{
player.GetComponent<SpriteRenderer>().sprite = CharacterSprite;
if (player.GetComponent<PlayerBehavior>() && !player.GetComponent<Animator>())
{
player.AddComponent(typeof(Animator));
}
if (player.GetComponent<PlayerBehavior>() && player.GetComponent<Animator>())
{
player.GetComponent<Animator>().runtimeAnimatorController = CharacterRuntimeAnimatorController;
}
}
}
Setting: Creating my first multiplayer game and running into an odd issue.
it's a tank game where players can shoot bullets and kill each other.
You can charge the bullets to shoot it faster/further away.
Problem:
When the client player charges fully and releases, the bullet continue to be spawned repeatedly and never stops. This issue doesn't occur if the client player does Not charges fully.
I believe the issue is the update function within the if (m_CurrentLaunchForce >= m_MaxLaunchForce && !m_Fired)
Note:
The host player does not have this problem, therefore it's somehow related to networking.
private void Update()
{
if (!isLocalPlayer)
return;
// Track the current state of the fire button and make decisions based on the current launch force.
m_AimSlider.value = m_MinLaunchForce;
if (m_CurrentLaunchForce >= m_MaxLaunchForce && !m_Fired) {
m_CurrentLaunchForce = m_MaxLaunchForce;
CmdFire ();
} else if (Input.GetButtonDown (m_FireButton) && !m_Fired) {
m_Fired = false;
m_CurrentLaunchForce = m_MinLaunchForce;
m_ShootingAudio.clip = m_ChargingClip;
m_ShootingAudio.Play();
} else if (Input.GetButton (m_FireButton)) {
m_CurrentLaunchForce += m_ChargeSpeed * Time.deltaTime;
m_AimSlider.value = m_CurrentLaunchForce;
} else if (Input.GetButtonUp(m_FireButton)) {
CmdFire ();
}
}
[Command]
private void CmdFire()
{
// Set the fired flag so only Fire is only called once.
m_Fired = true;
// Create an instance of the shell and store a reference to it's rigidbody.
GameObject shellInstance = (GameObject)
Instantiate (m_Shell, m_FireTransform.position, m_FireTransform.rotation) ;
// Set the shell's velocity to the launch force in the fire position's forward direction.
shellInstance.GetComponent<Rigidbody>().velocity = m_CurrentLaunchForce * m_FireTransform.forward;
// Change the clip to the firing clip and play it.
m_ShootingAudio.clip = m_FireClip;
m_ShootingAudio.Play ();
NetworkServer.Spawn (shellInstance);
// Reset the launch force. This is a precaution in case of missing button events.
m_CurrentLaunchForce = m_MinLaunchForce;
}
If you look trough the documentation you will find this -> [Command] functions are invoked on the player object associated with a connection. This is setup in response to the "ready" message, by passing the player objec to the NetworkServer.PlayerIsReady() function. The arguments to the command call are seriialized across the network, so that the server function is invoked with the same values as the function on the client.
I think the reason why it wasnt working for you is because you have to pass an argument to the function like
[Command]
private void CmdFire(bool m_Fired)
{
// Set the fired flag so only Fire is only called once.
m_Fired = true;
// Create an instance of the shell and store a reference to it's rigidbody.
GameObject shellInstance = (GameObject)
Instantiate (m_Shell, m_FireTransform.position, m_FireTransform.rotation) ;
// Set the shell's velocity to the launch force in the fire position's forward direction.
shellInstance.GetComponent<Rigidbody>().velocity = m_CurrentLaunchForce * m_FireTransform.forward;
// Change the clip to the firing clip and play it.
m_ShootingAudio.clip = m_FireClip;
m_ShootingAudio.Play ();
NetworkServer.Spawn (shellInstance);
// Reset the launch force. This is a precaution in case of missing button events.
m_CurrentLaunchForce = m_MinLaunchForce;
}
And then call it like this:
CmdFire(m_Fired) where m_Fired has to point to the players own variable.
I am looking for a code or script in C# or Java to make my cube tagged as a Player jump, in below script.
I have written some code and attached it to a button on canvas, but the problem is when I press and hold the button, it keeps jumping and makes an infinitly high jump.
Here is my script written in C#
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class DownstateButton : Button
{
public void Update()
{
//A public function in the selectable class which button inherits from.
if(IsPressed())
{
WhilePressed();
}
}
public void WhilePressed()
{
var player =GameObject.FindWithTag("Player");
player.transform.Translate(0, 1, 0);
//It was for jump
}
}
Hook the PointerDown (called when the button is pressed down) and PointerUp (button has been let go again) events from the UIButton and weight the translation of the position with Time.deltaTime, and you should be good to go. (player.transform.Translate(0,1 * Time.deltaTime, 0), optionally multiply it with another factor for speed modulation.) References: http://unity3d.com/learn/tutorials/modules/beginner/ui/ui-events-and-event-triggers
EDIT: Yeah, some example code. First, I have an EventTrigger component on the button. I use this sothat I can hook the PointerDown and PointerUp events as described before. It looks like this in the inspector:
(Use the "Add New Event Type" button to redirect the event calls.)
Then, I have this script on the button. The code speaks for itself.
using UnityEngine;
using UnityEngine.EventSystems;
public class JumpButton : MonoBehaviour {
private bool shouldJump = false;
// Update is called once per frame
void Update () {
//Find the player
var player = GameObject.FindGameObjectWithTag("Player");
//No player? exit out.
if (player == null)
return;
//Is the jump button currently being pressed?
if (shouldJump)
{
//Translate it upwards with time.
player.transform.Translate(new Vector3(0, Time.deltaTime * 5, 0));
//Make sure the Rigidbody is kinematic, or gravity will pull us down again
if (player.GetComponent<Rigidbody>().isKinematic == false)
player.GetComponent<Rigidbody>().isKinematic = true;
}
//Not jumping anymore? reset the Rigidbody.
else
player.GetComponent<Rigidbody>().isKinematic = false;
}
//When the button is being pressed down, this function is called.
public void ButtonPressedDown(BaseEventData e)
{
shouldJump = true;
}
//When the button is released again, this function is called.
public void ButtonPressedUp(BaseEventData e)
{
shouldJump = false;
}
}
The thing with switching to a kinematic rigidbody is optional, though. Also the speed can be adjusted with the multiplicative constant in the Translate() call. I tested this code with a standard cube, that has the Player tag and a Rigidbody on it, and it works fine.
Happy coding.
You could use a unity coroutine.
At the start of the routine, you'd set (for example) "isJumping" (a bool), and then before you start your loop bit, you'd check if you are jumping by checking 'isJumping'.
If not "isJumping", set isJumping to true, do your jump, and then on the completion of the routine, set isJumping to false.
//untested (uncompiled) code written on the fly
bool isJumping = false;
IEnumerator doJump()
{
if (!isJumping) {
isJumping = true;
// do jump (probably a loop)
while (jumpCondition) {
// jump instructions
yield return
}
//unset isJumping, inside 'if' but after yield return
isJumping = false
}
}
Note : code after yield return in a coroutine will only (probably) be run once, and only run as the coroutine exists (because no more yielding means the coroutine is at an end)