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).
Related
I wrote a code for an object that instantiates by pressing the space bar. Now, I also wanted it to appear only every ten seconds. So, I wrote this code-
public class Player : MonoBehaviour
{
[SerializeField]
private GameObject _laserPrefab;
[SerializeField]
private float _fireRate=10.0f;
private float nextFire=0f;
// Start is called before the first frame update
void Start()
{
transform.position=new Vector3(0,0,0);
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Space) && (Time.time>nextFire)){
nextFire=Time.time+_fireRate;
Instantiate(_laserPrefab,transform.position+new Vector3(0,1f,0),Quaternion.identity);
}
}
This code is instantiating the object every time space bar is pressed, but it is not giving a gap of 10 seconds, which I want.
Input.GetKeyDown() only returns true during the frame where the key is detected as down.
It will not return true until the user has released the key and pressed it again.
I think you can achieve what you want using Input.GetKey() instead.
You cannot you Time.time in this case as you are not holding the key down for the entire time. You would want to use Time.deltaTime in this case to calculate the elapsed time since the instantiation of the object.
So, in Update() function, keep updating nextFire and when the object is instantiated, reset it to 0.
void Update()
{
if (Input.GetKeyDown(KeyCode.Space) && (nextFire > _fireRate)){
nextFire = 0;
Instantiate(_laserPrefab,transform.position+new Vector3(0,1f,0),Quaternion.identity);
}
nextFire += time.DeltaTime;
}
What happens is when the object is instantiated, nextFire is reset and increments itself every frame. And once the value reaches 10 seconds (which is equal to _fireRate), another object can be instantiated.
I'm just starting out please excuse vast ignorance.
I'm writing a c# script in unity as part of the essentials training. I'm doing the 3d audio module and I thought I'd try and get a little bit fancier than the scope of this particular lesson which is supposed to be having an object fly through a window in a pre-built scene and make a 3d sound as it moves.
I wanted to make the movement of the object conditional upon a player moving close to it in 3d space. I figured out how to trigger the movement of an object in a script with an if statement that changes the transform parameters of the object the script is attached to when a 'distanceFromObject' variable is < 2. It works, however the script runs in the update section of the script which runs once every frame. This means that the object's transform parameters are changed every frame as expected but of course stops doing so when the distance between the object that's moving and the player exceeds 2.
I see the mistake I've made because if the object moves away when the player gets close then it will inevitably eventually move far enough away that the distanceFromObject variable will grow bigger than 2 whereupon it stops and just hovers in place. I don't know how to fix it though.
I need the script to check the distance between the object and the player every frame so that it will trigger the instance the player gets close enough, and when they get close enough, I need the object to move away, however once it has been triggered to move, I need the object to continue moving, but the script to stop checking what the distance is anymore.
The script looks like this
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FlyOff : MonoBehaviour
{
public Vector3 rotateChange;
public Vector3 positionChange;
public float distanceFromObject;
public GameObject character;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
distanceFromObject = Vector3.Distance(character.transform.position, this.gameObject.transform.position);
print (distanceFromObject);
if (distanceFromObject < 2)
{
transform.Rotate (rotateChange);
transform.position += positionChange;
}
}
}
Use flags instead of writing your logic in the if statement :
public class FlyOff : MonoBehaviour
{
// fields removed for more readability
// use a flag that's set to true/false
private bool isCloseEnough = false;
void Update()
{
distanceFromObject = Vector3.Distance(character.transform.position, this.gameObject.transform.position);
print (distanceFromObject);
// set the flag to true when player is close enough
if (distanceFromObject < 2)
{
isCloseEnough = true;
}
// even if the player gets far, the flag will remain true
if (isCloseEnough)
{
transform.Rotate (rotateChange);
transform.position += positionChange;
}
}
}
You can even apply the opposite logic to stop the object to move away when it has reach a certain distance :
if (distanceFromObject < 2)
{
isCloseEnough = true;
}
else if (distanceFromObject > SomeValue)
{
isCloseEnough = false;
}
If I understand correctly you could just add a bool flag and set it once you are close enough. Then you can start moving and skip further distance checks but keep moving forever.
private bool flyAway;
void Update()
{
if(!flyAway)
{
distanceFromObject = Vector3.Distance(character.transform.position, transform.position);
print (distanceFromObject);
if (distanceFromObject < 2)
{
flyAway = true;
}
}
else
{
transform.Rotate (rotateChange);
transform.position += positionChange;
}
}
In general: Avoid using print every frame! Even if you user doesn't see the log in a built app it is still causing overhead!
What breaks is the scrubbing slider, it recognises a click, but it does not move. I have interactable enable.
I'm having to reimport all assets to get it to work again but even now this isn't a sure bet it'll fix it.
This code was working perfectly fine about a week ago, I've made little changes to it since.
A lot of it is commented so I can learn that different parts of the code. Feel free to delete it if it bothers you.
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class NewAnimController : MonoBehaviour {
public Slider scrubSlider; //This is the slider that controls the current position of the animation. Values have to be 0 to 1 for normalized time of the animation, called later in the script
public Slider speedSlider; //This is the slider that controls the playback speed of the animation
public Animator animator; //Animator that is attached to the model that we are looking to control the animation of
public float playbackSpeedAdjustment = 0.5f; //This is a variable that can be easily adjusted to change the total playback speed of the animation. If it's too fast make smaller and vice versa
public Text currentDateText;
public int monthsInProject;
private float rememberTheSpeedBecauseWeMightNeedIt; //Float needed to keep the speed of the animation between pausing and playing
public void Update()
{
float animationTime = animator.GetCurrentAnimatorStateInfo(0).normalizedTime; //float a new value animationTime that is equal to the animators animation state then converted to normalized time
//Debug.Log("animationTime (normalized) is " + animationTime); //logging the normalized time to be able to store it for the scrubbing slider. Doesn't need to be logged for this to work, good to log it to make sure that it's working
scrubSlider.value = animationTime; //making the slider value equal to the now logged normalized time of the animation state
}
public void ScrubSliderChanged(float ScrubSliderchangedValue) // this value has to be floated so that the scrubbing slider can be attached to in the inspector to be able to change the current frame of the animation
{
animator.Play("Take 001", -1, scrubSlider.normalizedValue);
}
public void SpeedSliderChanged(float SpeedSliderchangedValue) //value is floated to be able to change the speed of the animation playback
{
animator.speed = speedSlider.normalizedValue * playbackSpeedAdjustment; // here the speed is multiplied by the adjustment value set in the editor just in case the playback speed needs to be changed outside of normalized values
}
public void UserClickedPauseButton()
{
if (animator.speed > 0f)
{
// we need to pause animator.speed
rememberTheSpeedBecauseWeMightNeedIt = animator.speed;
animator.speed = 0f;
}
else
{
// we need to "unpause"
animator.speed = rememberTheSpeedBecauseWeMightNeedIt;
}
}
public void UserClickedBackButton()
{
scrubSlider.value = scrubSlider.value - (1f / monthsInProject);
}
public void UserClickedForwardButton()
{
scrubSlider.value = scrubSlider.value + (1f / monthsInProject);
}
public void UserClickedStartButton()
{
scrubSlider.value = 0;
}
public void UserClickedEndButton()
{
scrubSlider.value = 1;
}
}
Many thanks for all your help.
I'm pretty sure the problem is this. In Update you are doing something like this:
scrubSlider.value = animationTime
that means that every frame, "NO MATTER WHAT", YOU ARE SETTING THE SLIDER POSITION, to, where the animation is. If the user is trying to move the slider - you are moving it right back, that same frame!
It's not so easy to solve this problem. Unity did not include a trivial built-in function for this. You need a separate script which sits on the slider which determines whether or not the handle is being held down. You have to use the OnPointerDown and OnPointerUp handlers.
How to use pointer handlers in modern Unity:
Step one, make this small script called Teste.cs
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections;
public class Teste:MonoBehaviour, IPointerDownHandler, IPointerUpHandler
{
public Slider theSlider;
public bool sliderIsBeingHeldDown;
public void OnPointerDown(PointerEventData data)
{
sliderIsBeingHeldDown = true;
Debug.Log("holding down!");
}
public void OnPointerUp(PointerEventData data)
{
sliderIsBeingHeldDown = false;
Debug.Log("holding up!");
}
}
Don't forget the declaration...
Teste:MonoBehaviour, IPointerDownHandler, IPointerUpHandler
rather than the usual MonoBehavior.
Drag that script on to your UI.Slider.
Note that in general these only work when they are "on" the thing in question.
Run, and try clicking up and down on the slider button - check the console. It works right? Now you have to use that variable inside NewAnimController
(1) Add a public variable to NewAnimController,
public Teste teste;
(2) Be sure to drag to connect that variable in Inspector
(3) Now you can refer to teste.sliderIsBeingHeldDown to see if that slider is being held down. So instead of doing this every frame...
scrubSlider.value = animationTime;
you will have to do this every frame...
if (teste.sliderIsBeingHeldDown)
Debug.Log("don't try to work against the user!");
else
scrubSlider.value = animationTime;
I hope that works! I think that's the simplest way to know when the slider is being held-down.
You really chose a difficult project for your first project! Hell!
It's worth noting that you could also put the slider-adjusting code (I mean to say your actual code for the scrubber) "inside" the script that is actually on the slider -- but I wouldn't worry about that for now. Your solution as stands is good.
Note depending on how you implement it, you may need to make it pause more elegantly when you hold down the slider handle, but before moving the slider handle. To achieve this, one approach is arrange to call UserClickedPauseButton() or a similar concept, when, the Down/Up routines shown here in "Teste", are called. Conversely you could just not bother with ScrubSliderChanged, and instead do the whole job inside code that runs whenever the handle is "down".
(In general, you'd almost certainly use UnityEvent here to make a solid reusable solution for a slider such as this for use with something like a scrubber.)
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)
I've been working on this script for the past day. For some reason my character will not jump as long as it's animator is active. I've got into the animation (there is only one) and removed all references to the animation placing a position anywhere and still the issue presides.
I have discovered that I can make my player jump if I use Co-routine which I'm using. However, I'm still new to using them and I can't work out why my player won't fall to the ground once a force has been added to it. And my player only moves up when the button is clicked. Could someone please take a look at my script and tell me what I'm doing wrong?
public float jumpSpeed = 100.0f;
public float jumpHeight = 2.0f;
public AudioClip jumpSound;
private GameObject pos;
private bool moving;
private bool isJumping;
void Start()
{
}
// Update is called once per frame
void Update ()
{
if(Input.GetMouseButtonDown(0))// && !moving)
{
isJumping = true;
StartCoroutine(JumpPlayer(gameObject.transform.localPosition));
}
else
{
isJumping = false;
}
}
IEnumerator JumpPlayer(Vector3 startPos)
{
Vector3 jump = new Vector3(transform.localPosition.x, jumpHeight, transform.localPosition.z);
float t = 0f;
t += Time.deltaTime / jumpSpeed;
rigidbody.AddForce(Vector3.up * jumpSpeed);
//gameObject.transform.localPosition = Vector3.Lerp(startPos, jump, 0.5f);
//isJumping = false;
yield return null;
}
Firstly, your use of coroutine isn't doing anything in particular - because it only does yield return null at the end, it'll run in a single frame and then exit. You could make it a regular void function and you shouldn't see any change in behaviour.
Removing other redundant code and you have just this:
if(Input.GetMouseButtonDown(0))
{
rigidbody.AddForce(Vector3.up * jumpSpeed);
}
This force is added for only a single frame: the frame where the mouse button is pressed down (if you used Input.GetMouseButton instead, you'd see the force applied for multiple frames).
You say "my player only moves up when the button is clicked" but I'm not clear why that's a problem - perhaps you mean that the player should continue to move up for as long as the button is held, in which case you should refer to my previous paragraph.
The most obvious reasons for the player not falling again are related to the RigidBody component: do you have weight & drag set to suitable values? An easy way to test this would be to position your player some distance from the ground at the start of the scene, and ensure that they fall to the ground when you start the scene.
Another reason might be that you're using the default override of .AddForce in an Update cycle. The default behaviour of this method applies force during the FixedUpdate calls, and you might find that using ForceMode.Impulse or ForceMode.VelocityChange gives you the result you're looking for.