Jump to content

Lua and Gui's



Gui's at the moment seem to be all the rage on the Leadwerks forum. We have the recent release of the noesisGUI, Patrik's showing of his gui's progress, and tjheldna's release of tjui and probaly many others. I also decided to try my hand at gui creation.


Coming from a BASIC background (started with qb 1.0), I don't have the proper skills to use a third party gui without malloc errors or segfaulting everything. That, and my game is a lua game. Taking inspiration from Tjhldna's gui I started off making something similar in pure lua.


After 2 days I managed to create a window manager and the windows themselves.


It's a good start but a lot of things still need to be worked out.






Frontfacing code to make the demo:

function Script:Start()
-- Load Module
lwgui = require "/scripts/lwgui"

--Create Window Manger
self.windowmanager = lwgui.WindowManager()

--Create Windows
self.window1 = lwgui.Window(200,200,300,300,windowcallback) -- window1 callback
self.window2 = lwgui.Window(280,200,300,300)
self.window3 = lwgui.Window(480,180,300,300)

-- make the window see through

--self.window.SetCallback(windowcallback) -- you can manually set a callback

self.window1.SetTitle("Einlanders' Leadwerks Gui Window")
self.window2.SetTitle("Einlanders' Leadwerks Gui WIndow 2")
self.window3.SetTitle("Einlanders' Leadwerks Gui WIndow 2")

-- set window color

--Add Window To Window Manager
self.windowmanager:AddWindow(self.window2)-- type checked 
self.windowmanager:AddWindow(self.window1)-- type checked 
self.windowmanager:AddWindow(self.window3)-- type checked 



function Script:UpdateWorld()
-- update title
self.window1.SetTitle(self.window1.GetX()..":"..self.window1.GetY().." Einlanders' Leadwerks Gui Window:"..self.window1.GetDepth())
self.window2.SetTitle(self.window2.GetX()..":"..self.window2.GetY().." Einlanders' Leadwerks Gui WIndow 2:"..self.window2.GetDepth())
self.window3.SetTitle(self.window3.GetX()..":"..self.window3.GetY().." Einlanders' Leadwerks Gui WIndow 2:"..self.window3.GetDepth())
local window = Window:GetCurrent()
-- remove a window
if window:KeyHit(Key.Q) then
self.windowmanager:RemoveWindow(self.window1)-- type checked 
self.window1 = nil
--This function will be called after the world is rendered, before the screen is refreshed.
--Use this to perform any 2D drawing you want the entity to display.
function Script:PostRender(context)
--process all logic inside the windows and window manager
-- Render all windows
function windowcallback(e) -- window 1 callback


Recommended Comments

Looks like a good start! Eventually I will have to tackle GUIs as well and I only have Lua (so I can't use tjheldnas :( ). I will keep track of your progress (if you post updates on this) and learn from your learning!

Share this comment

Link to comment

I would think the following should be in UpdateWorld() instead of PostRender():





When storing the callback you might want to consider allowing for script level functions instead of global functions. In your example windowcallback() is global to the entire game which means if you use a GUI in another script and have the same function you are overwriting the function based on which script was read in first.


The way to do this is to first allow your SetCallback() to accept 2 parameters. The first one would be the script object and the second the script function. So usage would look like: SetCallback(self, windowcallback).


Then we would define our callback inside these entity scripts like:


function Script:windowcallback()



In your library just store the obj and function in variables. When it's time to call you can call it like:




This calls the script function and passes the script object. In Lua this is what gives you the 'self' variable (it's hidden and done behind the scenes).

Share this comment

Link to comment

Go to http://www.lua.org/cgi-bin/demo and copy paste the below in and you'll see that it calls the Script level function as the callback.


-- this table will store our callback script object and script function
callback = {}
callback.obj = nil
callback.func = nil

-- function like yours to set callback
function SetCallback(obj, func)
  callback.obj = obj
  callback.func = func

-- simulate how the scripts work
Script = {}

function Script:Create()
  local obj = {}

  -- this copies the object Script function to this local table
  local k,v
  for k,v in pairs(Script) do
     obj[k] = v

  return obj

function Script:Start()
  SetCallback(self, self.MyCallback)

-- user defined script level function
function Script:MyCallback()
  print("Inside MyCallback")

-- create an instance of this script and call it's Start(). LE does this behind the scene for us but this is just to show
testScript = Script:Create()

-- this is how you would call the callback from your library.

Share this comment

Link to comment

Thanks Rick, helped alot. Is the callback script is supposed to change to the modules scope right?

Share this comment

Link to comment

Not sure what you are asking. You would put this callback table inside your GUI library and expose the SetCallback() function from your GUI library as well for us to set what script function we want to assign as the callback. Then when you need to call it from your GUI library you would do that last line of code. That's all you'd really need to do. All the other stuff in there was just an example to show that.

Share this comment

Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Blog Entries

    • By Josh in Josh's Dev Blog 1
      I've been doing some work on the sound system in Leadwerks 5 beta, and I added EAX effects in. If you have a dedicated sound card this can be used to add some nice reverb effects that make your sound environment sound a lot more real:
      Here's the simplest usage:
      auto fx = LoadSoundEffect("Sound/FX/sewerpipe.json"); auto listener = CreateListener(world); listener->SetEffect(fx); This will apply the effect to all mono sources. Stereo sources are assumed to be music or GUI noises, and will be unaffected. Eventually, the way I see this being used is a script attached to a CSG brush that changes the listener's EAX effect when the player enters and leaves the volume, but the above shows the API approach.
      I exported all the EAX presets into JSON files like so. You can load one of the existing files, or if you are feeling really creative you can try making your own:
      { "AirAbsorptionGainHF": 0.99426, "DecayHFLimit": 0, "DecayHFRatio": 0.89, "DecayLFRatio": 0.41, "DecayTime": 2.76, "Density": 1.0, "Diffusion": 0.82, "EchoDepth": 0.17, "EchoTime": 0.13, "Gain": 0.316228, "GainHF": 0.281838, "GainLF": 0.0891251, "HFReference": 2854.4, "LateReverbGain": 0.891251, "LateReverbPan": [0.0, 0.0, 0.0], "LFReference": 107.5, "LateReverbDelay": 0.02, "ModulationDepth": 0.0, "ModulationTime": 0.25, "ReflectionsDelay": 0.029, "ReflectionsGain": 0.354813, "ReflectionsPan": [0.0, 0.0, -0.0], "RoomRolloffFactor": 0.0 } Here's the full list of available presets:
      CastleSmallroom CastleMediumroom CastleLongpassage CastleLargeroom CastleHall CastleCupboard CastleCourtyard CastleAlcove FactoryAlcove FactoryShortPassage FactoryMediumRoom FactoryLongPassage FactoryLargeRoom FactoryHall FactoryCupboard FactoryCourtyard FactorySmallRoom IcepalaceAlcove IcepalaceShortPassage IcepalaceMediumRoom IcepalaceLongPassage IcepalaceLargeroom IcepalaceHall IcepalaceCupboard IcepalaceCourtyard IcepalaceSmallRoom SpacestationAlcove SpacestationMediumRoom SpacestationShortpassage SpacestationLongPassage SpacestationLargeRoom SpacestationHall SpacestationCupboard SpacestationSmallRoom WoodenAlcove WoodenShortPassage WoodenMediumRoom WoodenLongPassage WoodenLargeRoom WoodenHall WoodenCupboard WoodenSmallRoom WoodenCourtyard SportEmptyStadium SportSquashCourt SportSmallSwimmingPool SportLargeSwimmingPool SportGymnasium SportFullStadium SportStadiumTannoy Workshop SchoolRoom PractiseRoom Outhouse Caravan Dome Tomb PipeSmall DomeSaintPauls PipeLongThing PipeLarge PipeResonant OutdoorsBackyard OutdoorsRollingPlains OutdoorsDeepCanyon OutdoorsCreek OutdoorsValley MoodHeaven MoodHell MoodMemory DrivingCommentator DrivingPitGarage DrivingInCarRacer DrivingInCarSports DrivingFullGrandstand DrivingEmptyGrandstand DrivingTunnel CityStreets CitySubway CityMuseum CityLibrary CityUnderpass Dustyroom Chapel SmallWaterRoom I might consider implementing Steam Audio in the future (formerly Phonon) but for now OpenAL does everything I want.
    • By reepblue in reepblue's Blog 4
      Loading sounds in Leadwerks has always been straight forward. A sound file is loaded from the disk, and with the Source class emits the sound in 3D space. The sound entity also has a play function, but it's only really good for UI sounds. There is also Entity::EmitSound() which will play the sound at the entity's location. (You can also throw in a Source, but it'll auto release the object when it's done.)
      While this is OK for small games, larger games in which sounds may change might mean you have to open your class, and adjust the sounds accordingly. What if you use the sound in multiple places and you're happy with the volume and pitch settings from an earlier implementation? You could just redefine the source in a different actor, but why should you?
      A solution I came up with comes from SoundScripts from the Source Engine. With that engine, you had to define each sound as a SoundScript entry. This allowed you to define a sound once, and it allowed for other sound settings such as multiple sounds per entry. I thought this over, and with JSON, we can easily create a similar system for Leadwerks 4 and the new engine.
      I first started with a dummy script so I can figure out how I wanted the end result to be.
      { "soundData": { "Error": { "file": "Sound/error.wav", "volume": 1.0, "pitch": 1.0, "range": 0.25 }, "RandomSound": { "files": { "file1": "Sound/Test/tone1.wav", "file2": "Sound/Test/tone2.wav", "file3": "Sound/Test/tone3.wav" }, "volume": 1.0, "pitch": 1.0, "range": 0.25 } } } In this script, we have two sound entries. We have an error sound (Which is suppose to be the fall back sound for an invalid sound entry) and we have a sound entry that holds multiple files. We want a simple, straight forward. entry like "Error" to work, while also supporting something "RandomSound" which can be used for something like footstep sounds.
      The script is streamed and stored into multiple structs in a std::map at the application start. We use the key for the name, and the value is the struct.
      typedef struct { std::string files[128]; char filecount; float volume; float pitch; float range; bool loopmode; } sounddata_t; std::map<std::string, sounddata_t> scriptedsounds; Also notice that we don't store any pointers, just information. To do the next bit, I decided to derive off of the engine's Source class and call it "Speaker". The Speaker class allows us to load sounds via the script entry, and support multiple sounds.
      You create one like this, and you have all the functionalities with the Source as before, but a few differences.
      // Speaker: auto speaker = CreateSpeaker("RandomSound"); When you use Play() with the speaker class and if the sound entry has a "files" table array, it'll pick a sound at random. You can also use PlayIndex() to play the sound entry in the array. I also added a SetSourceEntity() function which will create a pivot, parent to the target entity. From there, the Play function will always play from the pivot's position. This is a good alternative to Entity::EmitSound(), as you don't need to Copy/Instance the Source before calling the function as that function releases the Source as mentioned earlier. Just play the speaker, and you'll be fine! You can also change the sound entry at anytime by calling SetSoundEntry(const std::string pSoundEntryName); The creation of the Speaker class will start the JSON phrasing. If it has already been done, it will not do it again.
      Having sounds being loaded and stored like this opens up a lot of possibles. One thing I plan on implementing is a volume modifier which will adjust the volume based on the games volume setting.Right now, it uses the defined volume setting. It's also a part of another system I have in the works.
    • By Josh in Josh's Dev Blog 7
      An update for Leadwerks 5 is now available.
      The Vulkan data transfer system has been revised and is now simpler but uses more memory. Data is likely to be quadruple-buffered, but it's a fairly small amount of data and this isn't a big concern. 
      I fixed a bad bug where multiple threads were accessing a global variable in the Mat4::GetQuaternion function. This fixes the object flashing glitch that was visible in previous builds.
      The engine is updated to the latest version of Newton Dynamics and kinematic joints work correctly now. The upvector joint is removed and a plane joint has been added for 2D physics, but I don't think it will work yet. Object picking up is implemented in the player controller script.
      I switched out the default scene with a new one using some of @TWahl 's materials.
      Added an FPSWeapon script that loads a gun and makes it sway.
      Entity::AddScript() can now be called in the Start() function of another script with no problems.
      Fullscreen windows are now working.
      The window / display system is changed a bit. New display commands:
      std::vector<shared_ptr<Display> > ListDisplays() std::vector<iVec2> Display::GraphicsModes() You must pass a display object in the window creation command now. Here's how I do it in Lua:
      --Get the primary display local displaylist = ListDisplays() local display = displaylist[1]; --Get the display's highest resolution graphics mode gfxmodes = display:GraphicsModes() gfx = gfxmodes[#gfxmodes] --Create a window local fullscreenmode = false local window if fullscreenmode then window = CreateWindow(display, "My Game", 0, 0, gfx.x, gfx.y, WINDOW_FULLSCREEN) else window = CreateWindow(display, "My Game", 0, 0, 1280 * display.scale.x, 720 * display.scale.y, WINDOW_CENTER + WINDOW_TITLEBAR) end And in C++:
      const bool fullscreenmode = false; //Get the primary display auto displays = ListDisplays(); auto display = displays[0]; //Create a window shared_ptr<Window> window; if (fullscreenmode) { auto gfxmodes = display->GraphicsModes(); auto gfx = gfxmodes[gfxmodes.size() - 1]; window = CreateWindow(display, L"My Game", 0, 0, gfx.x, gfx.y, WINDOW_FULLSCREEN); } else { Vec2 displayscale = display->GetScale(); window = CreateWindow(display, L"My Game", 0, 0, 1280 * displayscale.x, 720 * displayscale.y, WINDOW_TITLEBAR | WINDOW_RESIZABLE | WINDOW_CENTER); } The speed of the point light shadow updating is unbelievably fast. Point light shadows in Leadwerks 4 are very expensive to update because they require six different render passes for each of the six cubemap faces, but in Leadwerks 5 beta with Vulkan they are basically free. I'm sure it will slow down if I add enough points lights and have them all constantly updating, but I don't see any difference at all in the framerate right now when shadows are active. If you are having any trouble with their appearance you can set the global variable MULTIPASS_CUBEMAP to false in C++ at the very beginning of your program.

      This script can be used to display performance statistics. At this time it only shows the framerate but I can expand on this in the future.
      function Script:Start() self.statsEnabled = true self.textcache = {} self.font = LoadFont("Fonts/arial.ttf") self.fontsize = 16 self.textalignment = TEXT_LEFT self.world:EnableStats(self.statsEnabled) self:BindKey(KEY_F11, self.ToggleStats) end function Script:ToggleStats() self.statsEnabled = not self.statsEnabled self.world:EnableStats(self.statsEnabled) end function Script:Update() --Return if disabled or font missing if self.statsEnabled == false or self.font == nil then return end --Hide previously used sprite if self.displayfps ~= nil then self.displayfps:Hide() end --Retrieve the framerate and convert to string --Convert to integer to limit the amount of different string values local fps = tostring(math.ceil(self.world.renderstats.framerate - 0.5)).." FPS" --Check for cached version and create it if it doesn't exist if self.textcache[fps] == nil then self.textcache[fps] = CreateText(self.world, self.font, fps, self.fontsize, self.textalignment, 1) self.textcache[fps]:SetPosition(4,4) self.textcache[fps]:SetColor(0,1,0,0.75) end --Set current sprite and show self.displayfps = self.textcache[fps] self.displayfps:Show() end It may seem like a lot of code just to draw a bit of text onscreen, but the benefit is extreme performance. Instead of drawing one character at a time like the Leadwerks renderer does, this creates persistent text objects and reuses them when needed. That cuts the performance cost of displaying text down to basically zero, making it great for complex GUIs and game interfaces.
  • Create New...