gmod lua draw texture in 3d space
| Posts: 2,346 Threads: 40 Joined: Sep 2013 |
This tutorial assumes a basic understanding of Lua's syntax. I highly advise learning the basics of the Lua linguistic communication first before attempting to use Starfall.
Yous tin admission Starfall's documentation in-game by clicking the ? button at the superlative-right hand corner of the editor.
Starfall Tutorials
Last updated: 10/04/2016 - Added department for permissions.
Contents:
- Including and requiring
- Creating and using wire inputs and outputs
- Meta-tables and meta-methods (objects)
- Hooks and timers
- Server and client environments
- Starfall emitters and 3D2D cameras
- 3D Models in screens/HUDs
- Render Targets
- VGUI Elements
- Channels Library
- Coroutines
- General Lua Tips
- Permissions
The parts are not in whatever special social club, go through the kickoff two parts if you lot're new to Starfall then await at the parts that interest you after.
one. Including and requiring
The style Starfall handles inclusion of other files is slightly different to that of Expression 2. Instead of just putting #include, y'all need to do two things.
Setting up the file
At the summit of your Starfall file, you will need to add together 1 or more than lines for each of the files you lot wish to include. This tells Starfall to package these files alongside the principal script file.
--@proper name Include some stuff
--@writer Person8880
--@include lib/test.txt
--@include lib/chatcmd.txt This volition add 2 files to the chip, "test.txt" and "chatcmd.txt". Note the full path is required, including the .txt extension.
Require vs dofile
One time you accept added the files in include directives, you need to tell Starfall when to execute them. You have two ways of doing this, using require() or using dofile().
Essentially, require() volition execute your included file and return the event. It will also cache this result, meaning if you were to run it again, the same result as the outset crave is returned. On the other mitt, dofile() will simply return the issue of the execution, it will not cache the result anywhere. Therefore, dofile()'s return value can alter, just require()'s cannot.
In do, you unremarkably don't care nigh the return value resulting from the included file, y'all just desire to ensure its contents are executed. Therefore, dofile() should be how yous execute your included files if you only plan on running them once. Otherwise, use crave() (unless you accept a changing return value in a file for whatever reason).
For example:
--@name Include some stuff
--@author Person8880
--@include lib/test.txt
--@include lib/chatcmd.txt dofile( "lib/test.txt" )
dofile( "lib/chatcmd.txt" )
volition run the two included files immediately, just not enshroud the render value from each file. If the file examination.txt was set to return time.curTime() when executed, then dofile() would update this value, but require() would render only the fourth dimension it was commencement executed.
The behaviour of crave() is based on its use in standard Lua. It is used to load modules, which generally return a table containing all their functions. Rather than have Lua go through and define everything again every time a file requests the module, Lua caches the upshot. In Starfall, its usefulness is diminished as each Starfall bit is in its own instance.
Automatic requiring
Information technology seems tiresome to include and crave files. You have to put them in the --@include sections and so run crave on them too. Enter the --@crave statement. With this, the file is both included AND require()'d before your main script runs so you don't have to put require()southward everywhere.
--@name Car require
--@author Person8880
--@require lib/somesharedlib.txt
--@requiresv lib/someserverlib.txt
--@requirecl lib/someclientlib.txt
--@shared --Out here, lib/somesharedlib.txt has been required already.
if SERVER then
--In hither, lib/someserverlib.txt has been required already.
else
--In here, lib/someclientlib.txt has been required already.
stop
Note the three variations. Using --@require require()southward the script on both the server and the client. Using --@requiresv only require()s on the server, and using --@requirecl only crave()s on the client.
Including a folder
Yous may want to structure your work then that you lot accept folders of useful things. Rather than having to --@include every single file individually, you can apply --@includedir to include the entire directory.
Note, that this only includes the files directly inside the given folder, it won't become into sub-folders.
--@name Include directory
--@author Person8880
--@includedir lib --This requires every included script, you lot could instead come up
--with a per-folder loader using the result of getScripts().
requireAll()
--All the files in the lib/ folder take at present loaded.
Back to contents
2. Creating and using wire inputs and outputs
Starfall has a significantly different method of creating inputs and outputs and using them. If you lot are new to Lua, this may be difficult to empathize at get-go.
Creating inputs and outputs
In order to create inputs and outputs, make sure you are running server-side. Starfall processors are server-side, screens will need the --@shared directive and an if SERVER so block. Then make your inputs and outputs equally follows:
--@name Wire case
--@author Person8880 wire.createInputs( { "A", "B", "C" }, { "normal", "string", "vector" } ) --A is a number, B is a cord and C is a vector.
wire.createOutputs( { "D", "E", "F", "G" }, { "entity", "string", "normal", "vector" } ) --D is an entity, E is a string, F is a number and G is a vector.
Both functions take ii arguments, a tabular array of input/output names and a table of input/output types. You can ascertain them inline, or if you prefer, you tin can create 2 tables before and then insert them into the functions. This will allow you lot to accept variable amounts of wire inputs and outputs gear up.
Accessing inputs and outputs
Once created, the inputs and outputs are bachelor in the wire.ports table. Reading from information technology will get the value of an input, writing to it will set the value of an output. For case, with the above lawmaking:
local AwesomeNumber = wire.ports.A
local AwesomeString = wire.ports.B wire.ports.E = "Stuff!"
this shows how to get the values of inputs and how to prepare the value of an output. Make sure yous match types correctly, if you accept a number output, pass it a number when yous set its value.
Hooking into input changes
Unlike E2, where the entire flake executes on input alter, Starfall works with the concept of hooking. This allows you to ascertain functions to run when certain events occur rather than having to use ugly if(first()), if(clk()) etc. statements. To run lawmaking when an input changes, y'all need to hook into the "input" hook. This is achieved as follows:
hook( "input", "UniqueHookNameHere", role( Input, Value )
if Input == "A" and so
wire.ports.F = Value ^ 2
end
end ) This will ready the output 'F' to the value of the input A, squared. The function you pass this claw needs the ii parameters equally above, as it is passed the input's proper name outset as a cord, then the value that the input has been set to.
Wirelinks
Wirelinks are handled in a nice way in Starfall. You create them like any other input, then you can use them as a table directly from the wire.ports tabular array. For example:
wire.createInputs( { "Stargate" }, { "wirelink" } ) local Active = wire.ports.Stargate.Agile
wire.ports.Stargate.Close = 1
Assuming you've wired the wirelink input to a Stargate, the Active value will be the Active output on the Stargate. Setting values, similar I've done with the Shut value in a higher place, sends that value to the given input on the wirelink. So I would send the Shut input 1 with the higher up lawmaking and close the gate if it'southward open up.
Back to contents
three. Meta-tables and meta-methods (objects)
This section is meant for people with a firm grasp of Lua's basic ideas. If you are merely start with Starfall and Lua, exercise not try to go through this section without first understanding tables and methods.
With that said, Lua'due south principal form of object manipulation is through the use of meta-tables. These are, every bit the proper name implies, tables that describe a table. In Starfall, you are free to create your own new objects and meta-tables but you cannot edit the existing objects.
Creating a new object
Lets start by creating a 'colour' object. In this tutorial we're going to make information technology so we can add colours together, multiply colours past some other color or a number and divide colours by a number.
Nosotros brainstorm with the colour cosmos part.
local Clamp = math.clamp local ColourMeta = {} --We create an empty table, this will be our meta-tabular array.
function Colour( R, M, B, A ) --Make a role that takes R, G, B and A as arguments.
local Col = {
r = Clamp( R or 0, 0, 255 ),
k = Clamp( G or 0, 0, 255 ),
b = Clamp( B or 0, 0, 255 ),
a = Clench( A or 0, 0, 255 )
}
return setmetatable( Col, ColourMeta ) --This is the of import part.
end
Now when you use the 'Colour' role, it will return a table with 4 number values indexed at 'r', 'g', 'b' and 'a'. Dissimilar an ordinary table nonetheless, this table volition accept our 'ColourMeta' tabular array as its meta-tabular array, which lets united states define what happens when we add, multiply and divide colours and other things too.
The function setmetatable( Tabular array, Meta-Table ) is the important part. It returns the modified table so nosotros return it direct.
Setting up our meta-methods
Now, we tin can define what happens when we perform various operations on this colour object. This is washed through meta-methods.
To cord
The first meta-method we're going to look at is the :__tostring() method. This lets united states of america tell Lua what to output when we use tostring() on our object. In this case, we want it to tell u.s.a. we have a colour and what its r, thousand, b and a values are. Note the use of the colon, information technology's syntactic carbohydrate for ColourMeta.__tostring( self ).
function ColourMeta:__tostring() --What happens when we use tostring() or print() on a colour?
render cord.format( "Color. R = %s. G = %s. B = %s. A = %s.", self.r, cocky.g, cocky.b, cocky.a )
terminate If nosotros at present exercise:
local Test = Colour( 255, 255, 255, 255 )
print( Examination ) this will output: "Colour. R = 255. K = 255. B = 255. A = 255.".
Addition
The addition meta-method is the :__add( Value ) function.
part ColourMeta:__add( Col ) --What happens when we add something to the colour?
if type( Col ) ~= "table" or getmetatable( Col ) ~= ColourMeta so --We simply want to add colours to colours.
fault( "Attempted to add a "..type( Col ).." to a colour!" )
end return Colour( self.r + Col.r, self.g + Col.yard, self.b + Col.b, self.a + Col.a )
finish
Now if you effort something like:
local Examination = Color( 255, 0, 0 ) + Color( 0, 255, 255 ) yous should find 'Test' volition be 255, 255, 255.
Multiplication
The multiplication meta-method is the :__mul( Value ) part.
role ColourMeta:__mul( Value ) --What happens when we multiply our colour with a value?
if blazon( Value ) == "tabular array" and so --If it is a table, and so make certain information technology is a colour.
if getmetatable( Value ) ~= ColourMeta then
error( "Attempted to multiply a color with a table!" )
terminate return Colour( cocky.r * Value.r, self.thou * Value.g, self.b * Value.b, cocky.a * Value.a )
elseif type( Value ) == "number" and then --Simply other allowed multiplier is a scalar value.
return Colour( cocky.r * Value, self.g * Value, self.b * Value, cocky.a * Value )
else --All other types are non defined for multiplication.
fault( "Attempted to multiply a colour with a "..blazon( Value ).."!" )
end
end
From this if y'all endeavour something like:
local Exam = Colour( 100, 100, 100 ) * ii will requite you lot the colour 200, 200, 200. Besides,
local Test = Colour( 100, 100, 100 ) * Colour( two, 2, ii ) volition give you the aforementioned issue.
Division
Finally, let's set upward our colour objects with division. The division meta-method is the :__div( Value ) function.
function ColourMeta:__div( Value )
if type( Value ) == "tabular array" then
if getmetatable( Value ) ~= ColourMeta and then
error( "Attempted to carve up a colour by a table!" )
end
render Colour( cocky.r / Value.r, self.g / Value.yard, cocky.b / Value.b, self.a / Value.a )
elseif type( Value ) == "number" and then
render Colour( self.r / Value, self.yard / Value, self.b / Value, self.a / Value )
else
error( "Attempted to divide a colour by a "..type( Value ).."!" )
end
terminate As you can see, it is very similar to the multiplication method. Now, if y'all were to do:
local Test = Colour( 100, 100, 100 ) / 2 yous should find 'Test' will exist 50, 50, 50. Similarly for division by another color.
Hopefully you have seen plenty methods to understand how to add other operators such equally equivalence and subtraction.
It is of import to note that the methods I take given above work only if the color is the first chemical element of the operation. I go out information technology to the reader to brand them mistake handle if the first element of the operation is not a colour. (HINT: Check the value of the 'self' variable.)
Setting upward methods
Now we have our meta-methods ready, we can also define standard methods. We practice this using one last meta-method, the __index meta-method. This allows us to redirect index calls to a different tabular array so nosotros can return things that aren't actually present in the colour object. Note that this won't override the values we accept in our color, then we'll even so become the r, g, b and a values correctly.
ColourMeta.__index = ColourMeta --We redirect all index calls to the meta-table itself. You can make a new methods table if you wish. function ColourMeta:Goose egg() --A method to reset the colour to its default values.
self.r = 0
self.yard = 0
self.b = 0
self.a = 0
finish
At present if we were to practice the post-obit:
local Test = Color( 255, 255, 255, 100 ) Test:Cipher()
print( Test )
yous should notice information technology prints that the color is now 0, 0, 0, 0.
That's all for this section. At that place's plenty of meta-methods bachelor that allow yous create all kinds of classes in Lua, hopefully y'all tin see how much more powerful Starfall is compared to Expression ii.
Back to contents
four. Hooks and timers
The main style to get things done in Starfall is through the use of hooks and timers. They both share like ideas but practise slightly different things.
A claw is something that is chosen on a certain result, for instance, the think hook is chosen when the server processes a new tick. A timer is called in a fixed interval of time and you can set up how many of those intervals after starting should phone call the timer's part.
Both hooks and timers will need a function to call upwards when they're ran. This is where Starfall is significantly different to E2. In E2, timers would execute the unabridged fleck at once, here they only execute the function y'all give them.
Hooks
Listing of all available hooks:
http://teamscm.co.great britain/sfdoc/hooks.html
To set upward a hook, y'all use the hook part.
local Count = 0
hook( "call back", "ServerTickCounter", function()
Count = Count + 1
cease ) This lawmaking will increase the count value past 1 every tick.
claw( "starfall_used", "ScreenUsed", ScreenUsed ) This code will run the role 'ScreenUsed()' (if you have defined a function with that proper noun) with the player that pressed utilize on the screen as the argument.
Removing a hook
To remove a hook, use the hook office again with the same hook name, only pass nil every bit the function. For example:
hook( "think", "ServerTickCounter", nil ) would remove the tick counter hook from above.
Timers
Starfall has two kinds of timers available, the simple timer and the normal timer. The simple timer is called only once and is useful to filibuster an action. The normal timer can exist called as many times as you desire, even indefinitely.
Simple timer
Simple timers are similar a filibuster, you give them a time in seconds to expect, then after that time they'll run the function you've given them.
timer.simple( 5, function()
print( "5 seconds take passed!" )
stop ) This code will print "5 seconds have passed!" afterwards 5 seconds. It will non run the timer again.
Normal timer
Normal timers are more flexible than simple timers and can be repeated automatically at a given charge per unit.
timer.create( "5SecondCount", v, 0, function()
print( "Another 5 seconds accept passed!" )
end ) This code will print "Another 5 seconds have passed!" every 5 seconds for equally long as the chip is spawned and the timer exists.
The first value of the function is a unique name yous give the timer so you can identify it subsequently on if you lot want to remove it. The 2d value is the delay time in seconds, hither nosotros take a 5 second filibuster. The third value is the number of times the timer should echo. A value of 0 like nosotros have means information technology repeats indefinitely until the timer is destroyed or the flake is removed. The last value is the function to run when the timer is chosen.
Removing a timer
You can remove a normal timer by using
timer.remove( TimerName ) where 'TimerName' matches the proper name you gave the timer you want to stop.
Chat listening
For whatsoever reason, the event of chat being said is non a hook. Instead, you must gear up upwards a listen function with chat.mind() in the chat library.
local part ParseChat( Message, Player )
impress( Thespian:proper noun().." said: "..Message )
finish chat.listen( ParseChat )
This will print the player's proper name and the message they entered into your conversation whenever someone says something.
Y'all can add a second parameter to chat.listen() to specify a specific thespian to listen to. For instance:
local Owner = ents:owner() local office ParseChat( Message, Thespian )
print( Player:proper noun().." said: "..Message )
end
chat.listen( ParseChat, Owner )
would but print when yous say something.
Removing a chat listener
To remove a conversation listener, utilise chat.terminate().
chat.stop( ParseChat, Owner ) would remove the in a higher place chat listener.
Back to contents
5. Server and client environments
Starfall screens accept access to run on both the server and the client environments, unlike E2 and Starfall processors which are fixed to run on the server simply.
This presents a problem. How do you tell the customer instance most something like "a Stargate just opened" or "a wire input changed"? This function of the tutorial will cover basic networking principles and what each case of your Starfall screen has access to.
The server environment
The server surround is where almost everything other than rendering a screen is done. This surround can get information about entities and manipulate them. By default, a Starfall screen will not run on the server at all, meaning it is restricted purely to rendering and gathering basic entity information.
You tin can tell the server to run your Starfall screen'due south code by adding the "--@shared" pre-processor directive.
--@name Shared screen exam
--@author Person8880
--@shared if SERVER and so
print( "Hullo, I'm the server!" )
else
print( "Hi, I'one thousand the customer!" )
end
Notice how we tin get code to run on a sure case by the employ of "if SERVER then". Yous tin also utilize "if Customer and so", but "CLIENT" is just "not SERVER" and vice-versa.
Running on the server environment, we tin at present create wire inputs and interact with entities accordingly.
--@proper name Shared screen exam
--@writer Person8880
--@shared if SERVER then
wire.createInputs( { "Text" }, { "string" } )
else
print( "Hi, I'1000 the client!" )
end
If you were to try and use the wire library in the customer block or outside the if argument, you'd end up with an error when you try to use it as the wire library doesn't exist on the customer.
The client environment
The customer environment is where we can render to the screen. It can become basic information almost entities on its ain, but things similar my Stargate library and lots of entity functions are non available to it.
--@name Shared screen test
--@author Person8880
--@shared if SERVER then
wire.createInputs( { "Text" }, { "string" } )
else
local Text = "Bleh."
hook( "render", "RenderHook", function()
render.clear()
return.setColor( 255, 255, 255, 255 )
render.drawText( "Default", 256, 256, Text, render.TEXT_ALIGN_CENTER )
end )
end
The shared surround
Though it may not be immediately obvious, there is a 3rd environment, known as the shared environs. Lawmaking hither is executed on both the server and the customer instances.
The shared environment in our case is anywhere exterior our if SERVER then else terminate block.
--@proper name Shared screen test
--@author Person8880
--@shared --Shared environment is out here.
if SERVER so --In here is the server surroundings.
wire.createInputs( { "Text" }, { "string" } )
else --In here is the client surround.
local Text = "Bleh."
hook( "render", "RenderHook", function()
render.clear()
render.setColor( 255, 255, 255, 255 )
render.drawText( "Default", 256, 256, Text, return.TEXT_ALIGN_CENTER )
cease )
end
Networking information between environments
Ok, maybe you tin come across where this instance is going. We at present desire to change the text that'southward being rendered using our wire input on the server side. The trouble is, how do we tell the client instance that the wire input'southward inverse, and how practice we tell it what information technology inverse to?
This is solved using my internet library.
--@name Shared screen test
--@author Person8880
--@shared if SERVER then
wire.createInputs( { "Text" }, { "string" } )
claw( "input", "TextChanged", part( Input, Value )
if Input == "Text" and then
if internet.start() then
cyberspace.writeString( Value )
net.circulate()
end
terminate
end )
else
local Text = "Bleh."
hook( "render", "RenderHook", function()
render.clear()
render.setColor( 255, 255, 255, 255 )
render.drawText( "Default", 256, 256, Text, render.TEXT_ALIGN_CENTER )
end )
hook( "cyberspace", "ReceiveString", function( Length )
Text = internet.readString()
terminate )
end
This code will update the text in the centre of the screen with whatever the wire input is inverse to. Essentially, this is at present a wire text screen (except you tin customise the font, colours etc. on the fly and more).
Let'southward become over exactly what I did with internet.
if net.start() and then --This starts the net message. If information technology returns fake, we tin't write a internet message and then we bank check to make sure.
net.writeString( Value ) --We write the string the input changed to.
net.circulate() --Now we end the bulletin and transport information technology to anybody then their screen instance is updated.
end claw( "cyberspace", "ReceiveString", office( Length ) --We hook into the net claw, which is ran when a message is received.
Text = net.readString() --We read the cord nosotros were sent past the server instance which volition now get rendered.
end )
As you can see, it's pretty easy to transport cyberspace messages, and the best thing is they work both ways! You can send data from the client to the server in the same way, only end information technology with net.sendToServer() instead of net.broadcast().
Also, the server's cyberspace hook receives an extra statement, which is the player entity that sent the bulletin.
hook( "net", "ReceiveOnServer", function( Length, Ply )
print( Ply:nick() )
cease ) Remember that the client side is running on each client individually. Thus if you set up a network message to send to the server and don't check the local client, you can terminate up with a net message from every single client to the server example. Use the ents.player() entity to determine which client an instance is running for.
That'due south it for this part about the three environments and the net library, hopefully yous can come across how powerful a Starfall screen tin can go with some networking magic.
Dorsum to contents
half dozen. Starfall emitters and 3D2D cameras
A recent improver to Starfall on the server is emitters. These, similar screens and HUDs, are a primarily customer side entity that uses the render library to draw stuff. Emitters however, will not draw anything by simply calling render functions in the return hook. This is because they are used to draw anywhere in the world by projecting a 2D plane into 3D space.
Starting a 3D2D camera context
In order to begin rendering, nosotros need to define a 3D2D photographic camera context. This requires 3 variables.
- Position of the airplane's top left corner.
- World angles of the plane, these define its orientation in the globe.
- Calibration gene. Scales less than 1 will scale down what is rendered inside so yous tin render text nicer, but it volition need a higher font size. A calibration of one is one:1 with units in the game.
With these three variables, we tin commencement a 3D2D context by using:
return.start3D2D( Position, Angles, Scale ) This must be called inside your render claw, and once you have drawn everything you lot want to depict in this plane, you must terminate the camera with:
render.end3D2D() Dealing with angles
When using 3D2D cameras, it is often necessary to rotate the angles effectually an entity'southward local co-ordinate axis, for case, you want the camera to exist in front end of a prop and you want the text to be aligned with information technology in its upright position. In order to get the aeroplane in the right orientation, yous demand to use the entity's angles and rotate them effectually its axis.
To rotate an angle, use:
Angle:RotateAroundAxis( Axis, Corporeality ) where the amount is in degrees.
For example, to go the photographic camera to show aligned to a Stargate:
local Font = render.createFont( "Default", 36, 750, truthful ) hook( "return", "Stargate3D2D", role()
local Angles = Ent:ang()
--[[
The Stargate's globe angles will position the text above it and parallel to the ground.
These rotations ensure it is instead parallel to the Stargate's up direction and the text is not upside downward.
]]
Angles:RotateAroundAxis( Ent:right(), 90 )
Angles:RotateAroundAxis( Ent:forward(), xc )
Angles:RotateAroundAxis( Ent:up(), 180 )
--All rendering for emitters MUST take identify inside a 3D2D camera, otherwise you won't be drawing anything.
return.start3D2D( Ent:pos(), Angles, 1 )
render.drawText( Font, 0, 0, "How-do-you-do there!", render.TEXT_ALIGN_CENTER )
return.end3D2D()
end )
This will show the text "Hello there!" in the centre of the Stargate, assuming 'Ent' is the Stargate's entity. You will need to either notice the entity clientside, or network information technology from a server side case.
Back to contents
7. 3D Models in screens/HUDs
Cartoon 3D models in 2d is a slightly complicated affair, however, information technology sets Starfall significantly above all other forms of wire screen rendering such every bit EGP and GPUs.
This method ONLY WORKS ON SCREENS AND HUDS.
The 3D photographic camera context
The foundation of drawing 3D models on screens and HUDs is the 3D camera context. Much like the 3D2D context, this camera takes numerous input values that determine where it is placed, but dissimilar the 3D2D photographic camera, information technology is cartoon a 3D globe in a 2D rectangle.
The procedure for setting upwards a 3D camera is different depending on whether y'all are using a screen or a HUD. The lawmaking below will work on both.
local Possessor = ents.owner()
local Centre = Vector( 0, 0, 0 ) claw( "render", "DrawStuff", part()
if not render.isHUD() then
return.clear( 0, 0, 0, 255 )
render.setColor( 0, 0, 0, 255 )
render.drawRect( 0, 0, 512, 512 )
end
local AimVec = Possessor:aimVector()
render.start3D( Centre + AimVec * -128, AimVec:Angle(), 70, 0, 0, 512, 512, 1, 2048 )
--Here is the 3D world in the 3D camera context!
return.end3D()
terminate )
The arguments to start3D are:
render.start3D( Origin, Angles, FOV, X, Y, W, H, NearZ, FarZ ) - Origin is the position of the camera relative to the center of the 3D globe in its 3D infinite.
- Angles is the angle of the photographic camera in 3D space.
- FOV is the field of view of the camera.
- Ten and Y are the 2D position of the 3D photographic camera.
- Due west and H are the width and height of the 3D photographic camera on the screen.
- NearZ and FarZ are the altitude to the virtually and far clipping planes.
Clientside Models
Now we move on to the actual procedure of cartoon a 3D model. To do so, you need to create a clientside model. There is a limit on these (not enforced straight, but if you striking information technology your console volition spam that information technology can create no more), so utilise them sparingly.
Notation also: you merely demand to create these once. Retrieve of them like holograms, but each client sees their own version rather than being synced by the server.
local Owner = ents.owner()
local Heart = Vector( 0, 0, 0 ) local CSModel = render.createClientModel( Owner:model(), render.RENDERGROUP_OPAQUE )
hook( "render", "DrawStuff", function()
if not render.isHUD() then
return.articulate( 0, 0, 0, 255 )
render.setColor( 0, 0, 0, 255 )
return.drawRect( 0, 0, 512, 512 )
end
local AimVec = Owner:aimVector()
--Vitally important, without this, zippo will depict!
render.clearDepth()
render.start3D( Centre + AimVec * -128, AimVec:Angle(), 70, 0, 0, 512, 512, 1, 2048 )
--Starting time by suppressing the engine lighting.
return.suppressEngineLighting( true )
--Set the light to come from the centre of the earth.
render.setLightingOrigin( Centre )
--Set the low-cal to exist white (values are 0-1)
render.resetModelLighting( 1, ane, 1 )
--Gear up the model'southward colour to white, and alpha to 0.5 (again, values are 0-1)
render.setColorModulation( i, 1, 1 )
render.setBlend( 0.five )
--Add directional lights.
for i = 0, 5 practice
render.setModelLighting( i, 1, 1, 1 )
end
--Describe the model.
CSModel:drawModel()
--Finish suppressing engine lighting.
return.suppressEngineLighting( false )
render.end3D()
end )
With this code, you should see your playmodel in the centre of the screen, and as you turn your view, it rotates with it.
In that location's a few important things this code does.
- We start by clearing the depth buffer with return.clearDepth(). Without this, aught draws.
- We then suppress the standard lighting and set upward our own lite in the world. In screens, the lite will only work on models with an alpha value less than ane. This is a garry bug. HUDs can draw opaque models with lighting fine.
- After setting up the light, we draw the model itself.
- Later on cartoon, we stop suppressing the engine light.
Now yous know how to describe one model. To depict more, and to alter their position in the world, utilise the standard entity functions. For case:
CSModel:setPos( Vector( 0, 0, 100 ) ) will move the model 100 units above the centre in the 3D camera's globe. You can besides breathing clientside models with CSModel:setAnim() which functions identically to holograms.
You practise not have to fix lighting for every model unless you want to alter how they are lit. Setup the light first, then describe all your models in the scene together.
To remove a clientside model you are no longer using, use:
CSModel:remove() Dorsum to contents
8. Return Targets
Render targets provide a way to draw stuff to a screen without wasting ops. A render target is a texture that you can draw into that remembers what you lot've drawn until you describe over information technology or clear information technology.
Getting a render target
To get a render target for your screen/HUD/emitter, simply telephone call create() or createWithSize() from the render target library.
For a screen:
-- Creates a rendertarget that matches the screen's size.
local RT = rendertarget.createWithSize( render.getConfiguredScreenRes() ) For a HUD/Emitter:
-- Creates a rendertarget with width 1024 and summit 512. Note that width and height must exist powers of 2.
local RT = rendertarget.createWithSize( 1024, 512 ) Drawing to the render target
All drawing to the return target is done asynchronously. You lot laissez passer it a role which is executed inside a drawing context that writes to the render target. Once the office has executed, the render target will retain the pixel information until you clear information technology or depict to it again.
You can telephone call drawToTexture() at any time, including within the function you pass to information technology to queue another return for the adjacent frame, simply remember that information technology does not execute immediately. Thus yous cannot write to local variables and expect to encounter the changes subsequently drawToTexture().
For instance:
local part DrawingFunction()
render.articulate( 0, 0, 0, 255 )
return.setColor( 255, 255, 255 )
render.drawRect( 0, 0, 512, 256 )
end
RT:drawToTexture( DrawingFunction ) Here nosotros simply draw to the texture one time with a white rectangle that takes up the top one-half of the texture.
Drawing the render target texture
Once you have drawn to the texture, you tin can draw the texture itself on a screen, HUD or emitter plane using draw().
hook( "render", "DrawRT", function()
RT:draw( 0, 0, 512, 512 )
end ) This draws the RT at 0, 0 with width and height 512. Increasing/decreasing the size compared to the RT's size when cartoon will stretch the texture.
Note that you can as well pass the return target object to render.setTexture() or render.setMaterial() and use other drawing functions for more advanced rendering use cases.
Back to contents
9. VGUI Elements
VGUI is a simple merely useful set of UI controls that allow you lot create interactive interfaces in Garry'due south Modern.
Starfall has VGUI leap for utilize in HUDs. A few important points regarding the implementation:
- VGUI elements are only created when the local role player is linked to the HUD. When they unlink, all VGUI controls are destroyed.
- There are 2 very useful hooks, "hudlink" and "hudunlink". Both of these are passed the player linking/unlinking. Employ these to decide whether your VGUI elements are showing and can exist created or non.
- If you find elements stuck on your screen from other HUDs that have gone out of view, and then you tin can use the command "starfall_hud_removevgui" to strength them to be removed. This may suspension the scrap that is using them though!
With that said, permit's dive into how they work.
Creating a VGUI control
This is done with the "vgui" library.
--A player has linked to us.
claw( "hudlink", "bleh", function( Ply )
print( Ply, "linked to us" )
--This will make a frame, size 800x600 in the centre of the screen.
local Frame = vgui.create( "DFrame" )
Frame:SetSize( 800, 600 )
Frame:Eye()
Frame:MakePopup()
end ) --A actor has unlinked from united states. We don't need to remove the VGUI stuff, it's washed automatically.
hook( "hudunlink", "bleh", function( Ply )
print( "Goodbye", Ply )
end )
All methods and fields are attainable with the same names as for GLua. Note that you cannot write fields direct to the object, but you lot can override methods.
For example, to make the frame show as a white rectangle instead of the default await:
function Frame:Paint( W, H )
render.setColor( 255, 255, 255 )
render.drawRect( 0, 0, W, H )
end Back to contents
10. Channels Library
The channels library allows for advice between server instances. It is an event driven system, ane chip broadcasts a message on a channel, and subscribed chips will receive the bulletin.
Channel types
There are 2 types of channel.
- Public channels, which accept a string name and circulate to anyone's chip that is listening out for them.
- Individual channels, which only send to chips you own.
Listening to a aqueduct
To listen to a channel, use the channels.listen* functions.
--Listen out for a public channel
channels.mind( Name, ID, Callback ) --Listen out for a private aqueduct
channels.listenPrivate( ID, Callback )
Here the ID value is a unique identifier that lets you remove the callback later if yous want to.
The callback takes first the entity that sent the message, and then the bulletin's values.
channels.listen( "Block", "MyAwesomeListener", function( Scrap, ... )
print( "We received stuff:", ... )
cease ) channels.listenPrivate( "MyAwesomeListener", function( Chip, ... )
print( "Super secret stuff:", ... )
end )
Sending information to a aqueduct
To send data to a channel, call the channel.transport* functions.
--Ship data to a public channel called "Block"
channels.send( "Cake", "Is Bang-up" ) With the listener registered higher up, we'd get a print of:
Lawmaking:
We received stuff: Is Great
as the listener function received the string "Is Cracking".
For private channels,
--Send data to your private channel
channels.sendPrivate( "Super undercover information hither" ) with the listener we added above, we'd become a print of:
Code:
Super secret stuff: Super secret information here
equally the listener part received the cord "Super hush-hush information hither".
Data you ship can exist annihilation, not simply strings. Do note however, if you send a table then the other instance can directly modify that table'south values and it will affect the table in your instance too. Too, yous tin can send functions, but it will run in your chip'south instance and use up your own ops/time quota.
Back to contents
11. Coroutines
Coroutines are a form of co-operative threading available to Lua. They are not carve up threads, so practice not benefit from multiple processor cores, but their idea is similar. Their chief usage is to stagger workloads, processing large amounts of data steadily over time. They can likewise be used for queuing tasks.
Creating a coroutine
In Starfall, creating a coroutine is identical to creating one in Lua.
local office ILikeCake()
impress( "Cake is great!" ) coroutine.yield( "Mmmm." ) --Halt the function here, nosotros'll commencement from here again next.
impress( "But don't forget cookies and milk!" )
return "Thanks for request."
end
local Thread = coroutine.create( ILikeCake ) --Creates the coroutine object.
print( coroutine.resume( Thread ) ) --Runs the coroutine through, volition stop at the yield.
impress( coroutine.resume( Thread ) ) --Runs it again, this time starting from our yield.
Here, nosotros create a coroutine or 'thread' object. We have to laissez passer it the part information technology should run.
Yielding
Now, dissimilar ordinary functions, we can end coroutines office manner through what they are doing. In the above:
coroutine.yield( "Mmmm." ) this ends the function at this point, and returns "Mmmm." to coroutine.resume.
Thus, the in a higher place code will print this in sequence:
Cake is great!
true Mmmm.
Merely don't forget cookies and milk!
truthful Cheers for asking!
Annotation that when we yielded, and when the function returned, it passed these values as the second return value of coroutine.resume(). The get-go value will always exist either true or simulated, and tells you whether the coroutine ran correctly or whether it errored.
A more than involved example
Let's look at a more than useful example that shows us the real usefulness of coroutines. Say we have a table of information, and we want to utilise an action to every entry. However, this action is expensive, and we want to only process part of the table at a time. This is a perfect job for coroutines.
Allow's assume our data is stored in a tabular array called "Information", and we want to process simply 5 entries every tick to limit its functioning impact. Also assume the office we're using to process the elements is called "Process", and it takes the value equally its statement.
--Keep track of how many items we've processed.
local Iterations = 0 --This is our coroutine function, it volition go through the table five elements at a time.
local function IterateTable()
for Key, Value in pairs( Information ) do
Process( Value )
--Nosotros've performed another iteration, add to our count.
Iterations = Iterations + ane
--We need to end the thread hither, equally we've done our 5 iterations for now.
if Iterations == five then
coroutine.yield()
end
end
finish
--This is our thread object we're going to use.
local Worker = coroutine.create( IterateTable )
--We want to process 5 per tick, then we hook into "think".
hook( "think", "Process v at a time!", office()
--Reset the iterations for this tick.
Iterations = 0
local Condition = coroutine.status( Worker )
--If the status is "dead", then nosotros're finished, and so we can remove our hook.
if Status == "expressionless" so
hook( "think", "Process 5 at a time!" )
return
cease
--We're not dead, and so we still have work to do!
coroutine.resume( Worker )
terminate )
Back to contents
12. General Lua Tips
There's quite a few things about Lua that aren't obvious or well documented. I'll go over a few good tips I've picked upwardly and utilise while I code Lua.
The bad table.insert habit
For all intents and purposes, using table.insert to insert something into the terminate of a table is BAD. For starters, information technology's slow, and secondly, there'south a much quicker way to practise it.
When you are building a table once from scratch:
--Proceed track of the table index we need to utilize.
local Table = {}
local Count = 0 --Here Status() is some function used to filter.
for i = one, ten do
if Status( i ) so
Count = Count + i
Tabular array[ Count ] = i
finish
end
--Conveniently, we at present accept the populated table, and its size.
print( "Populated with", Count, "elements" )
When you lot are appending to a table you do non know the size of:
Table[ #Table + ane ] = Value When to use table.insert
The only time you need table.insert is if you desire to add an element to the table at a specific index that isn't the terminal. For example, if yous wanted to add a value to the start (though you lot could just reverse the table's gild and and then add new values to the cease and avoid this).
The bad ipairs habit
Straight off table.insert's heels, is ipairs. This innocent iterator is entirely pointless. Information technology does nothing merely add overhead to what yous're doing.
Its replacement? The simple numeric for loop.
for i = ane, #Tabular array do
local Value = Tabular array[ i ] --To replicate ipairs exactly, we should break if Value is nada.
if not Value and then break stop
--Do something with i and Value here.
terminate
You tin contend that ipairs is easier to write, and nicer to read, only the execution time departure is quite significant if your lawmaking is existence run frequently. Information technology'southward not worth it. Likewise, the numeric for loop is easier to understand for new coders than ipair's cryptic method.
Function declaration styles
Lua has a nice function defining syntax. Despite that, some people seem to similar to declare functions as values instead of, well, functions. This is fine for global values and table values, simply for local functions, you're missing out on a nice shortcut.
Let's look at the 2 ways to ascertain a local function.
local VarFunc = function()
impress( "Variable style announcement" )
end local function FunkyFunc()
impress( "Office style declaration" )
end
No difference correct? Incorrect. Try this:
local VarFunc = function( i )
i = i + 1
if i > x then render i stop
render VarFunc( i )
end local office FunkyFunc( i )
i = i + ane
if i > 10 and then return i end
return FunkyFunc( i )
terminate
print( FunkyFunc( 0 ) )
print( VarFunc( 0 ) )
You'll get a print of 11 from FunkyFunc, just an error from VarFunc. This is because for VarFunc, the local value isn't assigned until later the function is alleged. And then VarFunc is called from the global space rather than equally an upvalue.
With FunkyFunc, the function syntax has the added reward that it declares FunkyFunc every bit a local value earlier the part is defined, and then that FunkyFunc has itself as an upvalue.
Arrays and tables
Many people similar to write Lua every bit if it is C. They will use tables solely every bit arrays and take a helper function that loops through their assortment to remove values each time. This is rarely the best method.
The merely time you need arrays is when gild is important, or you iterate the table far more than often than you add together/remove elements. Otherwise, you should use tables as what they are, hash tables.
An example:
local Names = {
Bob = true,
Beak = truthful,
Joe = true
} if Names[ "Bob" ] and then
--Do something with Bob here.
end
--Goodbye Beak!
Names[ "Bill" ] = nil
--Hi Alex!
Names[ "Alex" ] = truthful
By using the important values every bit keys, nosotros remove the need to loop through the table to notice elements, and information technology makes adding and removing elements extremely like shooting fish in a barrel too.
Only it doesn't stop at strings. Lua tables can store ANY value as a key. You can use tables, functions, strings, numbers, userdata and fifty-fifty booleans as keys. This is far more powerful than a elementary array structure.
Localise commonly used functions
If you've always coded Python or similar languages, you know about the import statement. You tin import classes from other files and name them how you similar, and also restrict what yous import to simply the parts yous apply. The equivalent in Lua is localising the functions y'all want.
This has two advantages:
- Functions called from locals are faster, as it avoids reading the global table (and whatever table they're contained in) every time. Virtually noticeable for lawmaking ran often.
- You can rename functions to fit your manner.
For instance:
local Rad = math.rad
local Sin = math.sin print( Sin( Rad( ninety ) )
You may also see people localise using the same proper name equally a global, for instance:
local Table = Table This may seem odd and pointless at first, merely remember that local variables are faster to access than globals, and that this is a perfectly legal Lua performance.
Back to contents
xiii. Permissions
Some actions in Starfall require explicit permission from the user running the code to be able to perform actions. For instance, on the customer, playing sounds or using halos on holograms crave the relevant permission to be granted to do it. This helps forestall players from having actions performed that potentially annoy them, or lag their client, while still allowing the features to exist used.
Permissions besides come up into play on the server when y'all are using a fleck loaded directly from the shared area (using the "Load to tool" characteristic). This helps prevent lawmaking that you cannot see from running actions on the server without yous knowing, such as removing entities or moving y'all around. If yous are using your own code straight, or lawmaking you have the source for, the sever volition not require permissions. Only code loaded directly from the shared area using "load to tool" requires server permissions.
For developing, you must exist enlightened of permissions whenever yous want to utilise a feature that requires them.
The first pace to your flake being able to perform deportment that crave a permission is to register the requirement of that permission using permissions.register( permissions.PERMISSION_NAME ).
if SERVER and so
-- On the server, register needing the permission to motion the chip owner.
permissions.register( permissions.SET_PLAYER_POSITION )
else
-- On the client, register needing the permission to play client sounds, and draw halos.
permissions.annals( permissions.PLAY_CLIENT_SOUNDS )
permissions.register( permissions.DRAW_HALOS )
end In one case registered, the user of the flake volition be able to grant it access to the features you registered. If you lot want to know if your chip has access to a permission, use permissions.canPerformAction( permissions.PERMISSION_NAME ).
if non permissions.canPerformAction( permissions.DRAW_HALOS ) then
impress( "Unable to draw halos. Yous must first grant the permission in the permissions tab!" )
stop For a full list of available permissions, check the docs in-game nether the permissions library's fields.
Back to contents
Source: https://teamscm.co.uk/thread-7.html
0 Response to "gmod lua draw texture in 3d space"
Post a Comment