- Home /
How to tell when a RepeatButton is done being Pressed
I want to make movement using on-screen buttons that you can hold down, unfortunately while the keyboard inputs work as intended, the on-screen inputs just won't. They move the controlled object way too fast for some reason when I try to use those "else" statements in "On GUI" and if I try to use booleans to control it, the same thing happens. If I remove or comment out the else statements in OnGUI I get normal movement, albeit not what I intend (I want it to snap to integers after the controls are let go of). So... any ideas as to why this is happening and how to fix it?
#pragma strict
var speed : float = 3;
var int_position : int;
var negative_int_position : int;
var move_left : boolean;
var move_right : boolean;
var on_screen_only : boolean;
var keyboard : boolean;
var keys_or_screen : String;
function Start () {
}
function Update () {
if(keyboard == true)
{
if(Input.GetKey(KeyCode.D))
{
transform.position.x += speed * Time.deltaTime;
}
else if(Input.GetKeyUp(KeyCode.D))
{
int_position = Mathf.CeilToInt(transform.position.x);
if(transform.position.x != int_position)// && transform.position.x > 0)
{
transform.position.x = int_position;
}
}
if(Input.GetKey(KeyCode.A))
{
transform.position.x -= speed * Time.deltaTime;
}
else if(Input.GetKeyUp(KeyCode.A))
{
negative_int_position = Mathf.FloorToInt(transform.position.x);
if(transform.position.x != negative_int_position)
{
transform.position.x = negative_int_position;
}
}
}
}
function OnGUI () {
if(GUI.Button(new Rect(0,50,100,50),"On-Screen"))
{
on_screen_only = true;
keyboard = false;
keys_or_screen = "On-Screen";
}
if(GUI.Button(new Rect(100,50,100,50),"Keyboard"))
{
on_screen_only = false;
keyboard = true;
keys_or_screen = "Keyboard";
}
GUI.Box(new Rect(200,50,100,50),keys_or_screen);
if(on_screen_only == true)
{
if(GUI.RepeatButton(new Rect(0,Screen.height - 50,100,50),"<-"))
{
//move_left = true;
transform.position.x -= speed * Time.deltaTime;
}
else
{
//move_left = false;
negative_int_position = Mathf.FloorToInt(transform.position.x);
if(transform.position.x != negative_int_position)
{
transform.position.x = negative_int_position;
}
}
if(GUI.RepeatButton(new Rect(100,Screen.height - 50,100,50),"->"))
{
transform.position.x += speed * Time.deltaTime;
}
/*else
{
int_position = Mathf.CeilToInt(transform.position.x);
if(transform.position.x != int_position)// && transform.position.x > 0)
{
transform.position.x = int_position;
}
}*/
}
}
Answer by Jeric-Miana · Sep 13, 2014 at 08:54 AM
Try OnMouseDown & OnMouseUp function
function OnMouseDown()
{
transform.position.x += speed * Time.deltaTime;
}
function OnMouseUp()
{
int_position = Mathf.CeilToInt(transform.position.x);
if(transform.position.x != int_position)// && transform.position.x > 0)
{
transform.position.x = int_position;
}
well I could, but the intention is to have physical buttons, wouldn't this make it so no matter where you click the movement would happen? and only in one direction?
@jeffreywarf: No, On$$anonymous$$ouseDown only runs when you click on the object that the script is attached to.
So to complete this answer you would need a GameObject on screen with a collider to allow clicking. Would work well with a sprite.
Answer by JoshKingsbury · Sep 13, 2014 at 01:41 PM
I think I can point out some of the causes for trouble in your code.
First, I believe the reason why commenting out one of the else statements allows the object to move is because both else statements are called every OnGUI update. The negative_int position is applied first, then the regular int_position is applied right after, canceling out any movement.
Another trouble I noticed was that if a Debug.Log is placed inside the "else" of the GUI.RepeatButton, it is displayed even when the button is being pressed. I found a link where somebody posted a solution for the issue of making sure the GUI.RepeatButton is actually released.
http://answers.unity3d.com/questions/251760/check-if-gui-button-if-released.html
I also think that part of the trouble for the speed discrepancy between using the keyboard and the on-screen buttons is because OnGUI() is called more often than Update(). More info in this link:
http://answers.unity3d.com/questions/159938/ongui-updates-several-times-a-frame-why.html
My suggested solution to this is to keep with the idea of the booleans in the GUI.RepeatButton and move the actual movement code into the Update() function.
Something inside the Update() function along the lines of:
if( move_left ){
transform.position.x -= speed * Time.deltaTime;
} else if (left_button_released) {
negative_int_position = Mathf.FloorToInt(transform.position.x);
if(transform.position.x != negative_int_position){
transform.position.x = negative_int_position;
}
left_button_released = false;
}
if( move_right ){
transform.position.x += speed * Time.deltaTime;
} else if (right_button_released) {
int_position = Mathf.CeilToInt(transform.position.x);
if(transform.position.x != int_position){
transform.position.x = int_position;
}
right_button_released = false;
}
This code is untested, and the button_released booleans would want to be set true in the case of an actual release, referring to the first link I posted above about detecting the true release of a GUI.RepeatButton.
EDIT:
Here is a complete edit of your script, including corrections for the troubles I mentioned above.
#pragma strict
var speed : float = 3;
var int_position : int;
var negative_int_position : int;
var move_left : boolean;
var move_right : boolean;
var left_button_released : boolean;
var right_button_released : boolean;
var on_screen_only : boolean;
var keyboard : boolean;
var keys_or_screen : String;
function Start () {
}
function FixedUpdate () {
if(keyboard == true){
if(Input.GetKey(KeyCode.D)){
transform.position.x += speed * Time.deltaTime;
}
else if(Input.GetKeyUp(KeyCode.D)){
int_position = Mathf.CeilToInt(transform.position.x);
if(transform.position.x != int_position){
transform.position.x = int_position;
}
}
if(Input.GetKey(KeyCode.A)){
transform.position.x -= speed * Time.deltaTime;
} else if(Input.GetKeyUp(KeyCode.A)){
negative_int_position = Mathf.FloorToInt(transform.position.x);
if(transform.position.x != negative_int_position){
transform.position.x = negative_int_position;
}
}
}
if( move_left && !left_button_released ){
transform.position.x -= speed * Time.deltaTime;
} else if( move_left && left_button_released ) {
negative_int_position = Mathf.FloorToInt(transform.position.x);
if(transform.position.x != negative_int_position){
transform.position.x = negative_int_position;
}
move_left = false;
}
if( move_right && !right_button_released ){
transform.position.x += speed * Time.deltaTime;
} else if (move_right && right_button_released){
int_position = Mathf.CeilToInt(transform.position.x);
if(transform.position.x != int_position){
transform.position.x = int_position;
}
move_right = false;
}
left_button_released = true;
right_button_released = true;
}
//function OnMouseUp(){
//
// left_button_released = true;
// right_button_released = true;
//
//}
function OnGUI () {
if(GUI.Button(new Rect(0,50,100,50),"On-Screen"))
{
on_screen_only = true;
keyboard = false;
keys_or_screen = "On-Screen";
}
if(GUI.Button(new Rect(100,50,100,50),"Keyboard"))
{
on_screen_only = false;
keyboard = true;
keys_or_screen = "Keyboard";
}
GUI.Box(new Rect(200,50,100,50),keys_or_screen);
if(on_screen_only == true){
if(GUI.RepeatButton(new Rect(0,Screen.height - 50,100,50),"<-")){
move_left = true;
left_button_released = false;
}
if(GUI.RepeatButton(new Rect(100,Screen.height - 50,100,50),"->")){
move_right = true;
right_button_released = false;
}
}
}
This fixes the multiple update issue of having the movement code inside of the OnGUI() function. The movement code is now only called once per frame.
This also corrects for the conflicting logic of your original code.
Yeah that doesn't work at all. In fact, it just makes the buttons unresponsive. What DID seem to work was when I changed the code for the button to move right to this:
if(GUI.RepeatButton(new Rect(0,Screen.height - 50,100,50),"->"))
{
move_right = true;
}
move_right &= Input.Get$$anonymous$$ouseButton(0);
and the code in Update() looked like this:
if(move_right)
{
transform.position.x += speed * Time.deltaTime;
}
else if(move_right == false)
{
int_position = $$anonymous$$athf.CeilToInt(transform.position.x);
if(transform.position.x != int_position)
{
transform.position.x = int_position;
}
}
then the button worked as intended. However, when I copied it all over to the button for moving left, then it broke and now it won't move either direction.
If I comment out all code for moving to the right, then the button for moving left works as intended. It seems to not like having the code for both directions :/ Here's how it looks when nothing is commented out (this is in the Update() function)
if(move_left)
{
transform.position.x -= speed * Time.deltaTime;
}
else if(move_left == false)
{
negative_int_position = $$anonymous$$athf.FloorToInt(transform.position.x);
if(transform.position.x != negative_int_position)
{
transform.position.x = negative_int_position;
}
}
if(move_right)
{
transform.position.x += speed * Time.deltaTime;
}
else if(move_right == false)
{
int_position = $$anonymous$$athf.CeilToInt(transform.position.x);
if(transform.position.x != int_position)
{
transform.position.x = int_position;
}
}
In the code you posted directly above this comment:
else if(move_right == false)
{
int_position = $$anonymous$$athf.CeilToInt(transform.position.x);
if(transform.position.x != int_position)
{
transform.position.x = int_position;
}
}
And:
else if(move_left == false)
{
negative_int_position = $$anonymous$$athf.FloorToInt(transform.position.x);
if(transform.position.x != negative_int_position)
{
transform.position.x = negative_int_position;
}
}
Are being called at the same time every frame because move_left and move_right are false every single frame.
The trouble here is the logic you are using. Inside of the else, you need a second boolean variable to deter$$anonymous$$e when the correct button is released.
Ins$$anonymous$$d of: else if(move_left == false) { negative_int_position = $$anonymous$$athf.FloorToInt(transform.position.x);
if(transform.position.x != negative_int_position)
{
transform.position.x = negative_int_position;
}
}
I was suggesting:
else if (left_button_released)
{
negative_int_position = $$anonymous$$athf.FloorToInt(transform.position.x);
if(transform.position.x != negative_int_position)
{
transform.position.x = negative_int_position;
}
left_button_released = false;
}
The intended logic is as follows:
If the button is pressed, move_left is true and the transform is applied
When the button is released, move_left becomes false and the else statement is triggered
Since move_left already must be false and stays false, it checks to see if the left_button_released is true
If so, it runs the else code only a single time, and then no longer runs because left_button_released has been set to false
Does that make a bit more sense?
@jeffreywarf I've updated my answer to include an entire working script. Please let me know if it works for you.
Answer by Kiwasi · Sep 14, 2014 at 05:05 AM
First suggestion: Abandon OnGUI and use the 4.6 beta
Second suggestion: Watch this video from Unite 2013 to get the hang of how the intermediate mode GUI works
Third suggestion: Read the rest of my answer, I'll try explain it here.
First thing to understand is that OnGUI gets called multiple times per frame. The code will run once per GUI event. So it runs once for a mouse button down, once for a mouse button up, once for a key down and so forth. On top of that it runs a layout pass before calling each event, this is where it gets all of the rects and positions everything on the screen. There are also redraw events.
There are a couple side effects of this multiple call scenario. First is OnGUI is slow. Every GUI element is processed multiple times. Even if it just sitting there. That's why other solutions are popular in published games
Second up is that code that is not part of an if loop runs multiple times. This means anything on its own, or in an else clause will run at least once. Maybe more times. GUI buttons get around this by internally checking which event is being run through. GUIButton will only return true if the event is MouseDown, and the mouse is inside the bounds of the button rect. Everything else, including used events and layout events are ignored. You need to programme this behaviour into your else clause.
Now for some actual pseudo code. Be aware I haven't touched OnGUI since the 4.6 was released, it might not compile first time around.
private bool isButtonDown;
void OnGUI (){
if (GUI.RepeatButton(....)){
isButtonDown = true;
} else if (isButtonDown && Event.current == EventType.MouseUp){
isButtonDown = false;
}
}
That should be enough to get you started. Let me know if you need more assistance.
Your answer
Follow this Question
Related Questions
How to Convert each 2D Array to GameObject 2 Answers
Input trick question 3 Answers
RepeatButton Problem 1 Answer
how do i make this move slowly over time 1 Answer
GUI.repeatbutton crazy? 1 Answer