overlappng buttons.... possible?

Hey guys,

I’m trying to create a piano-keyboard using GUI buttons.
All is well except that the black keys are giving me trouble since they are overlapping the other white keys. They get drawn all right but the mouse click doesn’t seem to be restricted to the topmost button; also the underlying button gets activated.
Any change of fixing this or am I on the wrong road?

Thanks and regards,

Robert

This has been established as a bug in Unity and is being investigated. A possible workaround is to render the black keys in a separate script and set the GUI depth lower in that script. For example, set the depth to 1 for the white keys and other elements and set it to 0 in the black key script. Not ideal, I know, but it works.

Ooh that great news Andeeee.
So this means that essentially it should be possible to have buttons overlap?
And it could even be fixed in Unity 3 I hope.
I will try what you suggested, thanks a lot for your help.

Kind regards, Robert

Well I tried fixing it with the depth variable.
And although it changes indeed whether a button will be drawn on top or behind the other key,
it still doesn’t block the underlying button.
Maybe I’m missing something?

Thanks, Robert

Odd… I actually tried an example to check this and it worked OK. Can you post the code you are using?

Something strange with this; after applying the white-Keys to an empty object without aplying clips to the vars, I get the usual warning.
But with the second script I don’t get any warning at all, I have no idea if this is related.
Anyhow here are the scripts I’m using.
Thanks again for helping, its very much appreciated.

Regards, Robert

var C1 : AudioClip;
var E1 : AudioClip;
var Gis1 : AudioClip;
var C2 : AudioClip;


// Create a public variable where we can assign the GUISkin
var customSkin : GUISkin;

// Apply the Skin in our OnGUI() function
function OnGUI () {
	GUI.skin = customSkin;
	GUI.depth = 1;


	 //GUI.contentColor = Color.black; 
	  //GUI.backgroundColor= Color.white; 
	  //GUI.color= Color.white; 

	if (GUI.Button (Rect (10,40,30,130),  "C")) {
		print ("Dit is C1");
		audio.pitch = 1.0;
		audio.PlayOneShot (C1);
	}
	if (GUI.Button (Rect (40,40,30,130), "D")) {
		print ("Dit is D1");
		audio.pitch = 1.1;
		audio.PlayOneShot (C1);
	}
	if (GUI.Button (Rect (70,40,30,130), "E")) {
		print ("Dit is E1");
		audio.pitch = 1.0;
		audio.PlayOneShot (E1);
	}
	if (GUI.Button (Rect (100,40,30,130), "F")) {
		print ("Dit is F1");
		audio.pitch = 1.07;
		audio.PlayOneShot (E1);
	}
	if (GUI.Button (Rect (130,40,30,130), "G")) {
		print ("Dit is G1");
		audio.pitch = 0.9675;
		audio.PlayOneShot (Gis1);
	}
	if (GUI.Button (Rect (160,40,30,130), "A")) {
		print ("Dit is A1");
		audio.pitch = 1.09;
		audio.PlayOneShot (Gis1);
	}
	if (GUI.Button (Rect (190,40,30,130), "B")) {
		print ("Dit is B1");
		audio.pitch = 0.955;
		audio.PlayOneShot (C2);
	}
	if (GUI.Button (Rect (220,40,30,130), "C")) {
		print ("Dit is C2");
		audio.pitch = 1.0;
		audio.PlayOneShot (C2);
	}
	
	
}
var C1 : AudioClip;
var E1 : AudioClip;
var Gis1 : AudioClip;
var C2 : AudioClip;


// Create a public variable where we can assign the GUISkin
var customSkin : GUISkin;

// Apply the Skin in our OnGUI() function
function OnGUI () {
	GUI.skin = customSkin;
	GUI.depth = 0;


	 //GUI.contentColor = Color.black; 
	  GUI.backgroundColor= Color.black; 
	  //GUI.color= Color.white; 

	
	
	if (GUI.Button (Rect (30,40,20,80), "Des")) {
		print ("Dit is Des");
	}
	
	if (GUI.Button (Rect (60,40,20,80), "Des")) {
		print ("Dit is Des");
	}
	
	if (GUI.Button (Rect (120,40,20,80), "Des")) {
		print ("Dit is Des");
	}
	
	if (GUI.Button (Rect (150,40,20,80), "Des")) {
		print ("Dit is Des");
	}
	
	if (GUI.Button (Rect (180,40,20,80), "Des")) {
		print ("Dit is Des");
	}
	
	
}

The problem you are experiencing is that all events are handled in the order of your code lines. This means that if your black keys are rendered on top of your white keys, they will also be asked to handle input after your white keys have been asked to do the same.

This is a problem when the buttons are overlapping because your white keys will go “yeah, this mouse click is within my rectangle” before the black keys are even asked.

So you basically need to split up event handling on your black keys - handling their input before the white keys and rendering them after.

You can do this in three ways:

A) Embed every black key into a GUI.Window.
The GUI.Window control renders on top of other controls, but processes input before those same controls. This could lead to really messy code though.

B) Do the actual split of the event handling of the black keys.
As this is not built in to the button control out of the box, it would mean that you have to write your own control ground-up which is probably overkill for this exercise.

C) Do all the Button calls for the black keys before the white keys and then re-draw the styles of them after the white keys.
This means that your black buttons will be rendered twice, but apart from that, this should be the simplest solution of the three.

You can draw styles like this: GUI.skin.GetStyle (“style name”).Draw (rect, false, false, false, false);

In your case “style name” would be “Button” unless you created a custom style in the skin for the key. If you already have a custom style for the key loaded in a variable, just replace GUI.skin.GetStyle (“style name”) with the variable name.

Thanks a lot Emil for helping.

I was not sure weather I should literally use the “false”value as in your example, anyway I tried it and it gave me this error:
The type ‘UnityEngine.Rect’ does not have a visible constructor that matches the argument list ‘(boolean, boolean, boolean, boolean)’.

Then I figured you proposed me to make the “re-draw” of the styles for each black key with the same arguments as in the first block where the black-keys are called.
This gave me the following error:
“No appropriate version of ‘UnityEngine.GUIStyle.Draw’ for the argument list ‘(UnityEngine.Rect)’ was found.”

I started using Unity3Beta, and I checked against the error messages in Unity 2.6.
I’m experiencing the same errors so I guess it does not matter which Unity version I use in this respect?

Do you have any suggestion of what to do next?

Thanks again,
Robert

The above sounds to me what you need.

You need to make sure that the depth you gave the buttons is then made use of to prevent the other events from occuring. Give that code snippet a shot and let me know if it works for you.

Ezzerland:
That piece of code just ensures that after that GUI call, no other OnGUI call ever gets input - regardless of where the input was given (as in - even if you click on a white key, the event will not go through).

Robert G:
It sounds like you’ve got some syntax errors in your code. Could you paste in here an example of how you’re trying to achieve this?

First of all, big thanks to everyone trying to help me; its very much appreciated.

@ Ezzerland, Emil already commented your solution, and indeed it did not seem to work.

@ Emil, I stripped the code to the minimum to have a better overview. I’m pretty confident that I’m missing the point of what you were saying.
I tried different variants of what you where proposing; the last attempt gave me the following error:
Assets/TestScripts/BK 1.js(48,43): BCE0023: No appropriate version of ‘UnityEngine.GUIStyle.Draw’ for the argument list ‘(System.Type, boolean, boolean, boolean, boolean)’ was found.

So here is the code I was trying out.

Thanks again,

Regards, Robert.

function OnGUI () {
	
		
	if (GUI.Button (Rect (30,40,20,80), "")) {
		}
	
	if (GUI.Button (Rect (60,40,20,80), "")) {
		}
	
	if (GUI.Button (Rect (120,40,20,80), "")) {
		}
	
	if (GUI.Button (Rect (150,40,20,80), "")) {
		}
	
	if (GUI.Button (Rect (180,40,20,80), "")) {
		}
		
		
		
		if (GUI.Button (Rect (10,50,30,130),  "")) {
		}
		
	if (GUI.Button (Rect (40,50,30,130), "")) {
		}
		
	if (GUI.Button (Rect (70,50,30,130), "")) {
		}
		
	if (GUI.Button (Rect (100,50,30,130), "")) {
		}
	
	if (GUI.Button (Rect (130,50,30,130), "" )){
		}
	
	if (GUI.Button (Rect (160,50,30,130), "")) {		
	}
	
	if (GUI.Button (Rect (190,50,30,130), "")) {
		}
		
	if (GUI.Button (Rect (220,50,30,130), "")) {
			}
			
	//GUI.skin.GetStyle ("Button").Draw (Rect, false, false, false, false); 
	//GUI.skin.GetStyle ("Button").Draw (Rect( false, false, false, false)); 
	GUI.skin.GetStyle ("Button").Draw (Rect, false, false, false, false); 
		
	
}

Could this not work?

bool pressed = false;

//Black Keys
if(GUI.Button()){pressed = true;}
if(GUI.Button()){pressed = true;}
if(GUI.Button()){pressed = true;}
if(GUI.Button()){pressed = true;}
if(GUI.Button()){pressed = true;}

//White Keys
if(GUI.Button() !pressed){}
if(GUI.Button() !pressed){}
if(GUI.Button() !pressed){}
if(GUI.Button() !pressed){}
if(GUI.Button() !pressed){}
if(GUI.Button() !pressed){}
if(GUI.Button() !pressed){}
if(GUI.Button() !pressed){}

Therefore if any black keys are pressed, then the white keys are not polled

Ntero:
That is only part of what we are doing. What is missing from your solution is the re-rendering of the black keys.

Robert G:
I’m afraid you took my code example a bit too literal :wink: This is what I meant:

    var blackKeys = new Array (
        Rect (30,40,20,80),
        Rect (60,40,20,80),
        Rect (120,40,20,80),
        Rect (150,40,20,80),
        Rect (180,40,20,80)
    );

    var whiteKeys = new Array (
        Rect (10,50,30,130),
        Rect (40,50,30,130),
        Rect (70,50,30,130),
        Rect (100,50,30,130),
        Rect (130,50,30,130),
        Rect (160,50,30,130),
        Rect (190,50,30,130),
        Rect (220,50,30,130)
    );


function OnGUI ()
{ 
    for (i = 0; i < blackKeys.length; i++)
    {
        if (GUI.Button (blackKeys [i], ""))
        {
            Debug.Log ("Black key " + i);
        }
    }

    for (i = 0; i < whiteKeys.length; i++)
    {
        if (GUI.Button (whiteKeys [i], ""))
        {
            Debug.Log ("White key " + i);
        }
    }

    if (Event.current.type != EventType.Repaint)
    {
        return;
    }

    for (i = 0; i < blackKeys.length; i++)
    {
        GUI.skin.GetStyle ("Button").Draw (blackKeys [i], false, false, false, false);
    } 
}

As you see I also took the liberty of putting your rects into arrays as the above code is much more readable and easier to maintain compared to all that duplication you did previously.

Did you get a chance to try it out?

Hello Emil,

I’m very busy with other things at the moment, so I could only give it a fast shot.

For what I can see it gives out the events I’m after, but the underlying keys are still responding to the rollover, so this issue is still not completely solved. Tomorrow or the day after I hope to have a little bit more time to play with it.

Thanks for putting the keys in an array, I was planning to do that after I was certain this was going to work, but now don’t need to anymore.

Thanks a lot, Robert

As far as I know, mouse-over is currently bugged and gets triggered always - while other mouse events are properly “consumed” if you set it up the right way.

One thing you could do which would definitely work, but would be a PiTA is something like this…

Array of key rects like Angry set up

int mouseOver = -1;

if event = repaint {

foreach black rect, if rect contains mouse position {
mouseOver = int associated with this key;
break;
}

if (mouseOver == -1){
foreach white rect, if rect contains mouse position {
mouseover = int associated with this key;
break;
}

foreach black rect {
if I am not the mouseOver, draw a box with my normal graphic;
if I am the mouseover, draw a box with my hover graphic;
}

foreach white rect {
if I am not the mouseOver, draw a box with my normal graphic;
if I am the mouseOver, draw a box with my hover graphic;
}
} else if (event == MouseDown) {

int mouseDown = -1;

foreach black key {
if mouse contains me, trigger my action;
mouseDown = my int;
break;
}

if (mouseDown != -1){
foreach white key {
if mouse contains me, trigger my action;
mouseDown = my int;
break;
}
}

}


It’s certainly a convoluted way to solve the issue, but it definitely works - I’ve done something pretty similar in the past. You could also clean it up a ton by making a single list of rects where keyRect[0-5] = black keys and [6-12] = white keys (whatever count you have of course). You could then have another array of sound files and just have them set up in the same orders as your keys, so you can say if (keyRect[0].contains(mouse position)) trigger sound [0]) - make them a linked list.

If you want mouse over to handled correctly, you can do so via the boolean parameters to GUIStyle.Draw. someRect.Contains (Event.current.mousePosition) is your friend.

Thanks Kyle and Emil.

I will try to make some nice soup with the ingredients you both offered. My brain is in pain already because its a tiny bit :wink: over my head, so I might come back with questions if you don’t mind.
Anyway its good exercising for me.

All the best, Robert