- Home /
Drawing with mouse on GUI or world texture
Hello,
I would like to be able to fluidly draw on top of any unity scene. I have tried to do this on the Gui as well as on a texture in a world object.
The code below works, but the mouse drawing is slow and jerky. The problem is that I don't get real time pos from Input.mousePosition. I tried to use Texture2D.SetPixels and Texture2D.Apply as little as possible.
My questions:
How can I draw fluidly on a texture ? GUI or world object. I'm new to Unity so I am not sure the approach I chose is the correct one. Feel free to suggest different methods. I know there are other ways to get mouse input, but Input.mousePosition seemed the most precise.
Is there a way to freeze the Unity3D engine and give the GUI priority ? I don't mind freezing the scene in some cases, while in others I would like the action to continue in the background.
I also tried without the GUI, putting a semi transparent world object in front of the camera and drawing on its texture, thinking that function Update() might update faster than function OnGUI() but Input.mousePosition was just as slow.
If I reduce the game world window, mouse tracking is more fluid. But I need this to run on a high resolution.
I don't necessarily have to draw 1 pixel dots, I just used that as the first attempt. Eventually I would like to use brushes of various sizes. I saw here that textures can be linked to brushes in the inspector.
Also, eventually I would like to draw not with the mouse but with gestures on an iPad. Will it be easy to replace Input.mousePosition with gesture inputs ? For now I don't have Unity Pro or the iOS addon, but plan to buy them later on.
Is it possible to make a game like crayon physics with Unity ? or is it optimized for 3D rendering ? Just wondering if this is the right tool for what I need to do.
Notes:
I'm not making a game, but just used crayon physics as a reference of something that has fluid drawing capabilities.
I'm new to all this so please bare with me. Also, thought this might be useful for other people, so I will edit to add references to other answers.
System info: win7 64bit, decent graphic card.
Here is my code
#pragma strict
private var canvasBackground: Texture2D;
private var locked: boolean = true;
// initialize the canvas
canvasBackground = new Texture2D(Screen.width, Screen.height);
// get the size of the screen
private var pixels = Screen.width * Screen.height;
// create a color array
private var colors = new Color[pixels];
private var pencolor = Color.black;
// buffer is used to avoid setting pixels on each onGUI call
private var buffer = 0;
// last detected mouse position
private var lastpos:Vector3;
private var reset:boolean = true;
// fill color array with a BG color -- optional -- commented out to leave it transparent
/* for (var i=0;i<pixels;i++)
{
colors[i] = Color.black;
} */
function OnGUI() {
// mouse 1 shows the 'canvas'
if (locked && (Input.GetMouseButton(1)) ) {
locked = false;
}
if (locked)
return;
// mouse 2 hides the 'canvas'
if (!locked && (Input.GetMouseButton(2)) ) {
locked = true;
return;
}
// mouse 0 'paints'
if (Input.GetMouseButton(0)){
var currpos:Vector3 = Input.mousePosition;
if (reset){
reset = false;
lastpos = currpos;
}
line(lastpos.x, lastpos.y, currpos.x, currpos.y);
lastpos = currpos;
buffer++;
if (buffer > 9){
buffer = 0;
canvasBackground.SetPixels(colors,0);
canvasBackground.Apply(false);
}
} else {
reset = true;
}
// GUI.Box(Rect(xpos, ypos, 120, 120), "");
GUI.Box(Rect(0, 0, canvasBackground.width, canvasBackground.height), "");
// Override the default texture with the custom texture.
GUI.skin.box.normal.background = canvasBackground;
}
// Bresenham's line algorithm
function line(x0:int, y0:int, x1:int, y1:int){
var dx = Mathf.Abs(x1-x0);
var dy = Mathf.Abs(y1-y0);
var sx = (x0 < x1) ? 1 : -1;
var sy = (y0 < y1) ? 1 : -1;
var err = dx-dy;
while(true){
// Draw the 'pixels' in the color array
colors[x0 + Screen.width * y0] = pencolor;
if ((x0==x1) && (y0==y1)) break;
var e2 = 2*err;
if (e2>-dy){
err -= dy;
x0 += sx;
}
if (e2 < dx){
err += dx;
y0 += sy;
}
}
}
Interesting question ! Take a look at that example from Aron Granberg, and this one from me (it's a trick, I use particles + a camera with don't clear flag, the color buffer accumulates).
Hi, thanks for the comment and the response. These are quite good and help me see that there are ways to do what I want :)
A question, how did you track the mouse movements in your example ?
Can you share some code or the source of the example that you made ?
Did you try out my code ? Why is the mouse input I get so slow and how can I improve it ?
Thanks :)
Thanks for sharing. Took a detailed look. Your code is actually too complex for me to understand everything, but I got the main structure of it.
Ultimately, you are also using Input.mousePosition to get the mouse position. You just transform and do other things with it. And you call it from an update() not from an onGUI() call.
So I still don't get what is slowing me down. Is it the heavy overhead of repeated onGUI() ? is it SetPixels ? is it the fact that I do things in the GUI and not in the game world ? I tested out your code on the same machine and mouse tracking is super smooth. So it's not a performance issue.
I'll keep trying, but if anyone has ideas I'll be happy to hear :)
I suppose it's because of the Apply function, even if you don't call it every frames
Answer by shadi.lahham · Jun 27, 2012 at 11:33 PM
This is not really an answer, just an extended comment.
I rewrote the code to track the mouse in update() instead of onGUI() but that hasn't made any difference.
I think that I should not use SetPixel or SetPixels. Any other way to do this ?
I like Berenger's trick, it's very fluid, but I need it to look more like real pen and brush painting. Also, Berenger's code is quite complex for me to replicate quickly. I would have to study C# and Unity more :)
Here is my code anyway, separated in two files now.
Driver.js
#pragma strict
private var cc : canvas;
private var locked : boolean = true;
// get the size of the screen
private var pixels = Screen.width * Screen.height;
// create a color array
private var colors = new Color[pixels];
private var pencolor = Color.black;
// counter is used to avoid setting pixels on each onGUI call
private var counter = 101;
// last detected mouse position
private var lastpos : Vector3;
private var reset : boolean = true;
function Start() {
cc = GetComponent(canvas);
// fill color array with a BG color
for (var i = 0; i < pixels; i++) {
colors[i] = Color(1.0, 1.0, 1.0, 0.45); // Color.white; // canvasBackground.GetPixel(0, 0);
}
}
function Update() {
// ctrl toggles locking of the 'canvas'
if (Input.GetKeyUp(KeyCode.LeftControl)) {
locked = !locked;
// print(locked);
}
if (locked) {
cc.draw = false;
return;
}
cc.draw = true;
// mouse 0 'paints'
if (Input.GetMouseButton(0)) {
var currpos : Vector3 = Input.mousePosition;
if (reset) {
reset = false;
lastpos = currpos;
}
line(lastpos.x, lastpos.y, currpos.x, currpos.y);
lastpos = currpos;
counter++;
if (counter > 3) {
counter = 0;
cc.setColors(colors);
}
} else {
reset = true;
}
}
// Bresenham's line algorithm
function line(x0 : int, y0 : int, x1 : int, y1 : int) {
var dx = Mathf.Abs(x1 - x0);
var dy = Mathf.Abs(y1 - y0);
var sx = (x0 < x1) ? 1 : -1;
var sy = (y0 < y1) ? 1 : -1;
var err = dx - dy;
while (true) {
// Do what you need to for this
// setPixel(x0,y0);
colors[x0 + Screen.width * y0] = pencolor;
if ((x0 == x1) && (y0 == y1))
break;
var e2 = 2 * err;
if (e2 > -dy) {
err -= dy;
x0 += sx;
}
if (e2 < dx) {
err += dx;
y0 += sy;
}
}
}
and canvas.js
#pragma strict
public var draw : boolean = false;
private var canvasBackground : Texture2D;
// initialize the canvas
canvasBackground = new Texture2D(Screen.width, Screen.height);
function setColors(newColors:Color[]) {
// print("set new colors");
canvasBackground.SetPixels(newColors, 0); // rem 0
canvasBackground.Apply(false);
}
function OnGUI() {
if (!draw)
return;
// GUI.Box(Rect(xpos, ypos, 120, 120), "");
GUI.Box(Rect(0, 0, canvasBackground.width, canvasBackground.height), "");
// Override the default texture with the custom texture.
GUI.skin.box.normal.background = canvasBackground;
}
function Start() {}
function Update() {}