So, I got bored and decided to make a MS Paint replica for Garry's Mod. It works by the canvas being a massive button drawn with draw.roundedbox as it's paint function, and when this button is held down, the cursor position is sampled and a DSprite is drawn in the cursor location continuously until the button is released. The problem with this is (apart from what may be massive CPU usage and lag, which I strangely haven't seen yet) that if you quickly swipe across the screen with the button held down, the button can't think fast enough, resulting in a lot of sprite gaps, as shown below.
[IMG]https://dl-web.dropbox.com/get/mspaintreplica.jpg?_subject_uid=380102358&w=AACJm15jSrMHS56z_gKbkE7YgU0sI2BXLeHO-uMK6XP1ZQ[/IMG]
Also, as shown above (in the ridiculously over sized picture), the sprites can be drawn OUTSIDE the button and don't get clipped (not sure if this is fixable). This is the code for making the button (the canvas) draw sprites:
[CODE]
function button:Think()
if !button:IsVisible() then return false end //If the button isn't shown, don't do it
x, y = button:CursorPos() //x, y is set as a local nil earlier in the script
DetailMousePos:SetText(x.." , "..y.."px" ) //Some text displaying the cursor position in the bottom-left gets updated here
if button:IsDown() then //If it's selected
if !erasetool then //The erasetool currently doesn't work as to make it work, I'd probably have to pack all the sprites into a table and then do some sort of trace to delete a value from the table and then update it, which would be insanely laggy
sprite = vgui.Create( "DSprite", button ) //The actual sprite (also set as a local nil earlier)
sprite:SetMaterial(new_material) //A material gets created earlier for the sprite based on the chosen decal to paint
if colorbuttonselection == 1 then //Depending on whether color1 or color2 is selected
sprite:SetColor(HomeOptionColor1:GetColor()) //HomeOptionColor (1 and 2) are the color options (for quick color changing) shown in the picture.
else
sprite:SetColor(HomeOptionColor2:GetColor())
end
sprite:SetPos( x, y ) //Sets the position where the cursor would be
sprite:SetSize(HomeOptionSize:GetValue(),HomeOptionSize:GetValue()) //HomeOptionSize is the option called 'size' in the home tab visible in the picture.
else
//If the eraser is active, it should do things here
if IsValid(tempsprite) then tempsprite:Remove() end //This only removes the one previous sprite on clicked, for no reason really.
end
end
creatematerial(materialselection) //This is where the material based on the decal choice is made, and creatematerial is a local function not needed to be shown here.
if !erasetool then //If the eraser is off, then draw a temporary sprite just to show where the decal will be painted and at what size
if !IsValid(tempsprite) then
tempsprite = vgui.Create( "DSprite", button ) //It's parented to the fake 'button' canvas
creatematerial(materialselection)
tempsprite:SetMaterial(new_material)
tempsprite:SetColor(Color(0,0,0,150))
tempsprite:SetPos( x, y )
tempsprite:SetSize(HomeOptionSize:GetValue(),HomeOptionSize:GetValue())
else //If the sprite already exists
tempsprite:SetMaterial(new_material)
tempsprite:SetPos( x, y )
tempsprite:SetSize(HomeOptionSize:GetValue(),HomeOptionSize:GetValue())
end
else
//If the eraser is on, then remove the sprite as the eraser doesn't draw sprites, it erases them.
if IsValid(tempsprite) then tempsprite:Remove() end
end
end
[/CODE]
And this is the simple code for drawing the button and its fake canvas (which is probably not possible to clip the sprites to):
[CODE]
local button = vgui.Create( "DButton", PaintPanel ) //The actual thing that you can draw on
button:Dock(FILL)
button:DockMargin(10, 10, 500, 100)
button:SetText("")
button.Paint = function( self, w, h )
draw.RoundedBox( 8, 0, 0, w, h, Color( 255, 255, 255, 255 ))
end
[/CODE]
I had an idea about using a timer to repeat an external decal-drawing function, but I think a timer would be too slow/laggy and wouldn't actually change anything. There's probably a better alternative to draw the decals anyway, and if anyone would help, I'd really appreciate it!
Uh, throwing out random ideas here. Perhaps a callback function at the end which calls your original function? You will probably get a stack overflow though :/. While true do crashes the game as well. I dont really know of any hooks "faster" than paint
The hook isn't paint, it's 'Think' (you can see at the top, button:Think() ), maybe I should change the hook to be 'Paint' instead? (I'll try that).
Clientside Think calls as often as HUDPaint. Serverside Think calls as often as Tick.
It's clientside 'Think' (that I'm currently using). Would 'Paint' (for the button) work faster? I can't tell on my horribly slow laptop.
This might work, fill in and replace with your functions.
[lua]
local FRAMES = 30
local FRAME_RATE_MILLIS = 200
local function quickRender()
local next = os.clock()+FRAME_RATE_MILLIS
for i = 1, FRAMES, 1 do
while (os.clock()<next) do
end
render()
end
end
[/lua]
What is render() meant to call? Is it just my function? If so, why is there a blank space in between os.clock()>next? Is this just meant to make the computer calculate nothing then actually do something?
Your main problem is you're calling vgui.Create twice EVERY FRAME.
You should be creating elements ONCE (typically on init), and then updating them with think/paint functions running off of hooks of each element that you want to modify.
Vgui elements, last I researched, were fairly slow to create, which isn't that noticeable when you create them once, but doing it every frame is what's taking a shit on your FPS.
*Disclaimer: I haven't worked with VGUI in some time so I could be incorrect about this, but my thought process still remains valid - you shouldn't create elements every frame.
You pretty much want to:
1) Create all elements on init
2) Hook into the paint function of the panel
2.1) If the player is holding down m1, draw a square/rect/point/whatever at the mousepos
2.2) Delimit a distance the mouse has to move to draw again (so you don't just draw stuff on top of each other, no point in that) of a few pixels
2.2.1) Use surface.DrawLine or something here on the panels Paint function, don't draw a new sprite or a copy of a spite for every segment. The former is much more efficient and you're not creating a new entity for every dot
3) Do not recreate VGUI elements on Think!
Instead of drawing at mouse-pos each time the hook is called, you need to use the info provided by the mouse functions ( namely the ▲X and ▲Y [ Delta / Change-in ] positions )... Then, a simple tool to draw where the mouse is would be to know the change in x and y and to record a new draw-line from the old x/y to new x/y. For sprites, depending on how you want it to be drawn, you can use a UV Textured Rect Rotated so you can draw the w and h of the sprite and have it repeat ( or simply stretch it ) for the distance drawn.
As others are saying to draw at mouse-pos, I say ignore that and draw the difference. Additionally, instead of creating new DSprites for each new draw-point, simply record the data and render it. This data could be in the form of an extended stack ( for easy undo, just pop ) data-type with the ability to transverse through it ( for rendering ). If you decide on adding layers, each layer would have its own stack with the bottom-most layer being rendered first ( or if you want to calculate out view-able areas you can draw the top-layer first and ass you continue downward in layers you can prevent anything from drawing if the layer above is solid, in terms of opacity, or if the entire panel is filled at some point you can ignore needing anything below to be drawn which would improve performance for complex drawings ).
Like acecool says, don't draw a point at the mouse position every tick; draw a line from the last mouse position to the current one every tick.
vgui.Create("DSprite") does actually create a TexturedRectRotated, which I shouldn't be doing in a vgui...
[editline]3rd April 2015[/editline]
Quick question, how would I make a TexturedRectRotated actually STAY drawn when it gets called? How do I keep calling it?
[editline]3rd April 2015[/editline]
Oh god, I'm going to have to use tables, aren't I? Damn, I'm horrible at them....
Do you use variables? Do you use functions? If so, you're using tables... Everything in Lua is stored in a table, Lua is a language of tables. Using . will allow you to reference something while using : calls. If you use : on a function, self is inferred as an invisible first argument where-as if you call the same function using ., and self is coded into the function then you need to add self as the first arg...
I have some examples which may help:
[url]https://dl.dropboxusercontent.com/u/26074909/tutoring/functions/function_naming.lua.html[/url]
[url]https://dl.dropboxusercontent.com/u/26074909/tutoring/tables/quick_intro_to_tables.lua.html[/url]
Also, take a look at this.. It is an entity spawner to spawn entities when the map loads ( InitPostEntity ) by generating the table for you... Note that you can use , as a delimiter between elements in a table but you can also use ;..... I tend to use ; on multi-line tables and , on 1 line tables: [url]https://dl.dropboxusercontent.com/u/26074909/tutoring/entities/spawn_entities_on_map_start.lua.html[/url]
And another example of an enemy spawner system: [url]https://dl.dropboxusercontent.com/u/26074909/tutoring/entities/enemy_spawner.lua.html[/url]
Tables aren't difficult to understand in Lua. They're quite easy once you understand the differences between referencing something vs calling something and how things are defined, etc...
Hopefully these help!
Sorry, I forgot to comment yesterday. I already thought of a system.
[CODE]
local drawtable = {}
local mousex, mousey = nil
button.Paint = function( self, w, h )
draw.RoundedBox( 8, 0, 0, w, h, Color( 255, 255, 255, 255 ))
mousex, mousey = button:CursorPos()
for k, v in pairs(drawtable) do
surface.SetMaterial( new_material ) //I'll think of something for this later
surface.SetDrawColor( v.color )
if v.drawtype == "default" then
surface.DrawTexturedRectRotated( v.x, v.y, v.size, v.size, v.rotation )
elseif v.drawtype == "line" then
//Do stuff
end
end
if button:IsDown() then
local tablecolorvar = nil
if colorbuttonselection == 1 then
tablecolorvar = HomeOptionColor1:GetColor()
else
tablecolorvar = HomeOptionColor2:GetColor()
end
table.insert( drawtable, { drawtype = "default", x = mousex, y = mousey, size = HomeOptionSize:GetValue(), rotation = 0, color = tablecolorvar } ) //I'll add more options eventually
end
end
end )
[/CODE]
The only problem is... I have NO CLUE how I'd make an eraser tool setting for this.
[QUOTE=MPan1;47452480]Sorry, I forgot to comment yesterday. I already thought of a system.
[CODE]
local drawtable = {}
local mousex, mousey = nil
button.Paint = function( self, w, h )
draw.RoundedBox( 8, 0, 0, w, h, Color( 255, 255, 255, 255 ))
mousex, mousey = button:CursorPos()
for k, v in pairs(drawtable) do
surface.SetMaterial( new_material ) //TODO- fix this
surface.SetDrawColor( v.color )
if v.drawtype == "default" then
surface.DrawTexturedRectRotated( v.x, v.y, v.size, v.size, v.rotation )
elseif v.drawtype == "line" then
//Do stuff
end
end
if button:IsDown() then
local tablecolorvar = nil
if colorbuttonselection == 1 then
tablecolorvar = HomeOptionColor1:GetColor()
else
tablecolorvar = HomeOptionColor2:GetColor()
end
table.insert( drawtable, { drawtype = "default", x = mousex, y = mousey, size = HomeOptionSize:GetValue(), rotation = 0, color = tablecolorvar } ) //I'll add more options eventually
end
end
end )
[/CODE]
The only problem is... I have NO CLUE how I'd make an eraser tool setting for this.[/QUOTE]
Get the points in proximity of the mouse when erasing (as in, not every tick, only when the erase function is called).
You have to take the size into the calculation as well as whatever material it has, though...
[editline]3rd April 2015[/editline]
Also, for the proximity method, how would you determine which line to remove from the shape table (to stop it getting drawn)?
[QUOTE=MPan1;47452521]You have to take the size into the calculation as well as whatever material it has, though...
[editline]3rd April 2015[/editline]
Also, for the proximity method, how would you determine which line to remove from the shape table (to stop it getting drawn)?[/QUOTE]
[url]http://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line#Line_defined_by_two_points[/url]
I don't need to know the distance, I need to know some sort of method or system that can get surface shapes from a UI.
[editline]3rd April 2015[/editline]
I'm probably going to have to rescan the table to check whether any shapes are close to the eraser *groan*
[editline]3rd April 2015[/editline]
I'm trying to check whether the location you click at has already been clicked on one step back, and if so, don't draw the shape again. How would I write 'Get last key from the table 'drawtable', and from this key (which would be a table), get the keys x and y and compare them to the cursor position' in code?
All I though of is something like
[CODE]
if ( drawtable[x, y] ) == ( self:CursorPos() ) then return false end
[/CODE]
But I don't know how to get the last key, or x, y from that key.
-snip-
There HAS to be some way to get a table's value from within a table, probably even in default Lua?
[QUOTE=MPan1;47455078]There HAS to be some way to get a table's value from within a table, probably even in default Lua?[/QUOTE]
From what I understand, you're trying to get the last value of a table, and that value is a table, and you're trying to get a value from that table?
If so, you could just do this, provided the keys are sequential numbers.
[lua]local lastX, lastY = drawtable[#drawtable].x, drawtable[#drawtable].y[/lua]
And from the last table, I need the keys x and y from that table (yes, those are the key names), and then combine them to compare them with another key.
[QUOTE=MPan1;47455134]And from the last table, I need the keys x and y from that table (yes, those are the key names), and then combine them to compare them with another key.[/QUOTE]
I've updated my post.
')' expected near ','
Wait, just realized, it's my code that's the problem-
[CODE]
if ( lastX, lastY ) == ( self:CursorPos() ) then return false end
[/CODE]
How should I correctly combine two values?
-snip-
[CODE]
local lastdrawX, lastdrawY = drawtable[#drawtable].x, drawtable[#drawtable].y
if ( lastdrawX, lastdrawY ) == ( self:CursorPos() ) then return false end
[/CODE]
The second part doesn't work...
[editline]4th April 2015[/editline]
I made a more elaborate thing...
[CODE]
local mousex, mousey = self:CursorPos()
if !IsValid(drawtable[#drawtable]) then return false end
local lastdrawX, lastdrawY = drawtable[#drawtable].x, drawtable[#drawtable].y
if ( lastdrawX ) == ( mousex ) and ( lastdrawY ) == ( mousey ) then return false end
[/CODE]
The problem is, with that I can't draw ANYTHING.
-snip I'm wrong-
[QUOTE=MPan1;47455149][CODE]
local lastdrawX, lastdrawY = drawtable[#drawtable].x, drawtable[#drawtable].y
if ( lastdrawX, lastdrawY ) == ( self:CursorPos() ) then return false end
[/CODE]
The second part doesn't work...[/QUOTE]
[lua]local lastdrawX, lastdrawY = drawtable[#drawtable].x, drawtable[#drawtable].y
local x, y = self:CursorPos()
if (x == lastdrawX and y == lastdrawY) then
return false
end[/lua]
I came up with this, and it works:
[CODE]
if not (drawtable[#drawtable] == nil) then
local lastdrawX, lastdrawY = drawtable[#drawtable].x, drawtable[#drawtable].y
if (mousex == lastdrawX and mousey == lastdrawY) then return end
end
[/CODE]
Am I doing it right? I feel like checking for nil isn't a good way. By the way, mousex and mousey are the names of the mouse coordinates, not just x and y or something.
Do
[code]
if type(drawtable[#drawtable]) == "table" then
[/code]
Instead of your first line of code
Sorry, you need to Log In to post a reply to this thread.