[ LUA TUTORIAL ] How to: Create a dynamic GUI

Discussion in 'Utilities and programming' started by GregBlast, Sep 14, 2013.

  1. GregBlast

    GregBlast
    Expand Collapse

    Joined:
    Aug 12, 2013
    Messages:
    224
    Hello everyone,


    Introduction

    In this tutorial I will demonstrate how to create a GUI that updates itself based on user interaction. For this tutorial you must already know how to do what I described in my first tutorial about GUIs: How to create in-game GUI.


    Getting started


    For this tutorial we will need most of the things that were made in the previous tutorial. So we can start a new module with the same contents already. Under [game path]\lua\system create a new file called dynamicGUI.lua and copy the contents of the file testGUI.lua from the previous tutorial (available here How to create in-game GUI).

    As you should remember don't forget to add a require statement for this new module into [game path]\lua\system\main.lua. Below the latest require statement add:
    Code:
    dynamicGUI = require("dynamicGUI")
    Now the module is already functional. One thing to note though is that in the previous tutorial we defined the callback of the controls with...
    Code:
    callback system testGUI.GUICallback
    ...which means that if you run this module as is any user interaction on the GUI will call the callback from the module testGUI. As it's not what we want you can change this to
    Code:
    dynamicGUI.GUICallback
    .

    So now if you reload the System LUA in game (Shift+T by default) and open your console (with mode BNGS selected) and type in dynamicGUI.showGUI() you should have the exact same behaviours as in the previous tutorial.


    Preparing our GUI

    Alright first we need to get rid of the things we won't need. Start by removing the printText function and its exposition in the public interface:
    Code:
    M.printText        = printText
    Also remove the call to it in the GUICallback function.

    We'll be using a text control to change the title of the GUI. So in the showGUI function leave the inputText control of type text but change its name to txtTitle and its description to Title.
    Code:
        
        control
            type = text
            name = txtTitle
            description = Title
        
    The doneButton will serve as a close button. So change its icon to iconCancel.png and its description to Close.
    Code:
        
        control
            type = doneButton
            icon = tools/gui/images/iconCancel.png
            description = Close
        
    We'll need a button to validate the input info without closing the GUI. So add a button above the doneButton with icon iconAccept.png and Ok for description. Give it the name btOk. We'll also allow the user to change the description of this button. So this time add another text control below the txtTitle control with the name txtOk and description Button Ok text.

    At this point your code should look like this (the callback has been cleared):
    Code:
        
        --    This will be the module table
        local M            = {}
        
        
        --    This is the callback that is called once a GUI event is triggered (button click, ...)
        local function GUICallback( mode, str )
            
            --    Extract args
            local args    = unserialize( str )
        end
        
        
        --    This is the function that displays the GUI
        --    It should be included in the public interface so that
        --    any external component can call it (like a key bind in keyboard mappings)
        local function showGUI()
            local g        = [[beamngguiconfig 1
        callback system dynamicGUI.GUICallback
        title Test GUI
        
        
        container
            type = verticalStack
            name = root
            
        control
            type = text
            name = txtTitle
            description = Title
            
        control
            type = text
            name = txtOk
            description = Button Ok text
            
        control
            type = button
            name = btOk
            icon = tools/gui/images/iconAccept.png
            description = Ok
            
        control
            type = doneButton
            icon = tools/gui/images/iconCancel.png
            description = Close
            
        ]]
            gameEngine:showGUI( g )
        end
        
        
        --    Public interface
        --    Defines what is visible to the outside world
        M.showGUI        = showGUI
        M.GUICallback    = GUICallback
        
        
        --    Return the module table
        return M
        
        
    And here's what you should get in-game:
    Tutorial-LUA-DynamicGUI_000.png


    Adding the actions

    Now lets head to the control actions. As I said we'll want to change the title of the GUI and the text on the button Ok. That's already two variables we can define in our module. So at the top of the file below the module declaration add the following variables:
    Code:
        local title = "Dynamic GUI"
        local okText = "Ok"
        
    We can already use them in our GUI definition in the showGUI function by concatenating the value of these variables. For this you can erase the text and close the formatted string there then use .. to concatenate it with some other string and then use the variable name. Then concatenate it with a formatted string opening using .. [[:
    Code:
                local function showGUI()
                local g = [[beamngguiconfig 1
                callback system dynamicGUI.GUICallback
                title ]] .. title .. [[ 
                 
                container
                type = verticalStack
                name = root
                 
                control
                type = text
                name = txtTitle
                description = Title
                 
                control
                type = text
                name = txtOk
                description = Button Ok text
                 
                control
                type = button
                name = btOk
                icon = tools/gui/images/iconAccept.png
                description = ]] .. okText .. [[ 
                 
                control
                type = doneButton
                icon = tools/gui/images/iconCancel.png
                description = Close
                 
                ]]
                gameEngine:showGUI( g )
                end
    
        
    Now lets make it change on user input. Head to the GUICallback function and handle the button click event by checking that mode is equal to button:
    Code:
        -- Clicked a button
        if mode == "button" then
         
        end
        
    Remember than when a button is clicked we have an argument called button with the button name in it. We can thus check which button was pressed. If it's the Ok button we will simply change the value of our title variable and the value of the okText one. We'll also check that the text entered is not empty so we don't assign an empty text to the title or button. Here's what the callback should look like with this done:
    Code:
                local function GUICallback( mode, str )
                 
                -- Extract args
                local args = unserialize( str )
                 
                -- Clicked a button
                if mode == "button" then
                 
                -- Button: Ok
                if args.button == "btOk" then
                 
                -- Update title
                if args.txtTitle and args.txtTitle ~= "" then
                title = args.txtTitle
                end
                 
                -- Update button Ok text
                if args.txtOk and args.txtOk ~= "" then
                okText = args.txtOk
                end
                 
                end
                 
                end
                end
    
        
    Well if you click the ok button now you won't notice any change. You'll have to close the GUI and re-open it to see your changes.


    Dynamically updating the GUI

    To update our GUI and see its changes immediately we can either close it and re-open it by hand... or we can do this by code. For this you can simply call your showGUI function from within your callback function.

    You may be tempted to simply add showGUI() after changing the title and Ok text but you'd be wrong. For the callback here the function showGUI hasn't yet been defined. And as it is not global by the time it is called it won't be available. To avoid your GUI from raising an error on calling showGUI() you can move your showGUI function above the GUICallback function. Or you can simply use the module table as a prefix with M.showGUI():
    Code:
        local function GUICallback( mode, str )
         
        -- Extract args
        local args = unserialize( str )
         
        -- Clicked a button
        if mode == "button" then
         
        -- Button: Ok
        if args.button == "btOk" then
         
        -- Update title
        if args.txtTitle and args.txtTitle ~= "" then
        title = args.txtTitle
        end
         
        -- Update button Ok text
        if args.txtOk and args.txtOk ~= "" then
        okText = args.txtOk
        end
         
        end
         
        -- Reload GUI
        M.showGUI()
         
        end
        end
        
    Now when you press the Ok button your title and Ok text will change according to what you entered. You just may be surprised with some specific input not showing formatted like you wanted. For example if you enter Done you will get done. This must be because those words are pre-formatted and used like that.


    Adding optional controls

    While we're at it I thought it would be even nicer to have an Options button showing or hiding our text controls. So for that we have to make the part of the GUI that defines the text controls optional. Let's put that part in a separate function for clarity. Add a function called addOptionsGUI above showGUI and for now define it to always return the text controls as we have that already.
    Code:
        -- Adds the option controls to the GUI
        local function addOptionsGUI()
        return [[
        control
        type = text
        name = txtTitle
        description = Title
         
        control
        type = text
        name = txtOk
        description = Button Ok text
        ]]
        end
        
    Don't forget to update the showGUI function to use this as the method that will define the options controls. Replace them with a call to the addOptionsGUI function:
    Code:
        local function showGUI()
        local g = [[beamngguiconfig 1
        callback system dynamicGUI.GUICallback
        title ]] .. title .. [[ 
         
        container
        type = verticalStack
        name = root
         
        ]] .. addOptionsGUI() .. [[ 
         
        control
        type = button
        name = btOk
        icon = tools/gui/images/iconAccept.png
        description = ]] .. okText .. [[ 
         
        control
        type = doneButton
        icon = tools/gui/images/iconCancel.png
        description = Close
         
        ]]
        gameEngine:showGUI( g )
        end
        
    Well these controls are supposed to be optional so lets make them optional. First we need a boolean variable to serve as a flag to determine whether or not we want those text controls in the GUI. In the variables declaration at the top of the file add one called showOptions and initialize it to false:
    Code:
        local showOptions = false
        
    Now update the new addOptionsGUI function so that it returns an empty string if showOptions is false:
    Code:
        local function addOptionsGUI()
        if not showOptions then return "" end
        return [[
        control
        type = text
        name = txtTitle
        description = Title
         
        control
        type = text
        name = txtOk
        description = Button Ok text
        ]]
        end
        
    Add an options button as first control of your GUI by updating the showGUI function and make its icon change whether it is supposed to be pressed or not. For this you can concatenate a check on our showOptions flag to decide which icon to use:
    Code:
        control
        type = button
        name = btOptions
        icon = tools/gui/images/]] .. ( showOptions and [[iconDelete.png]] or [[iconAdd.png]] ) .. [[ 
        description = Options
        
    Now the only thing left is to switch our flag when this button is pressed. Update your callback function to check for a click on the btOptions button and toggle the showOptions variable if so. Don't forget we still need to refresh the GUI with that so leave the call to M.showGUI() outside of the button name tests (but inside the mode == "button" test so that other events do not reload the GUI):
    Code:
        local function GUICallback( mode, str )
         
        -- Extract args
        local args = unserialize( str )
         
        -- Clicked a button
        if mode == "button" then
         
        -- Button: Options
        if args.button == "btOptions" then
         
        -- Toggle options
        showOptions = not showOptions
         
        -- Button: Ok
        elseif args.button == "btOk" then
         
        -- Update title
        if args.txtTitle and args.txtTitle ~= "" then
        title = args.txtTitle
        end
         
        -- Update button Ok text
        if args.txtOk and args.txtOk ~= "" then
        okText = args.txtOk
        end
         
        end
         
        -- Reload GUI
        M.showGUI()
         
        end
        end
        
    That's it ! Click the Options button to diplay your text controls and click it again to hide them. One last thing that is optional but pretty user-friendly is to automatically hide the text controls once we press the Ok button. Just set showOptions to false at the end of the args.button == "btOk" test:
    Code:
        -- Hide options
        showOptions = false
        

    Screenshots

    Here are some screenshots of the final GUI in action:
    Tutorial-LUA-DynamicGUI_001.png
    Tutorial-LUA-DynamicGUI_002.png
    Tutorial-LUA-DynamicGUI_003.png



    Thanks for reading

    As usual any comments on mistakes or possible improvements are welcome :). Everything I described here may not be the best way to do things so let me know about it.


    Full code
    The full code can be found below this in the attachments. The zip file contains only the dynamicGUI.lua file (the module file) which should be dropped within [game path]\lua\system.
     

    Attached Files:

    #1 GregBlast, Sep 14, 2013
    Last edited: Sep 14, 2013
  2. bits&bytes

    bits&bytes
    Expand Collapse

    Joined:
    Jun 13, 2013
    Messages:
    40
    I am trying to make a GUI for changing the steering rate parameters, but I get an error:
    “attempt to call global ‘UpdateSteeringParameters’ (a nil value)."

    This is the GUI, the line where I get the error from is marked red ([gamepath]\lua\system\SteeringParametersGUI.lua).
    Code:
    -- Module add by bits&bytes on 19/Sep/2013
    ------------------------------------------
     
    --            This will be the module table
    local M                                = {}
     
    --            A function that prints some text
    --            and prevent raising an error if it is 'nil'
    local function printText( text )
                    print( "Your text was: " .. tostring( text ) )
    end
     
    --            This is the callback that is called once a GUI event is triggered (button click, ...)
    local function GUICallback( mode, str )
                   
                    --            Extract args
                    local args            = unserialize( str )
                   
                    --            Clicked the Ok button
                    if mode == "apply" then
                                                   printText( args.Parameter_kbdInRate )
                                                   printText( tostring(str) )
                                   Par_kbdInRate = tonumber( args.Parameter_kbdInRate )
                                                   print("Par_kbdInRate = " .. tostring(Par_kbdInRate))
                                   [COLOR=#ff0000]UpdateSteeringParameters(Par_kbdInRate)[/COLOR]                    
                    end
    end
     
    --            This is the function that displays the GUI
    --            It should be included in the public interface so that
    --            any external component can call it (like a key bind in keyboard mappings)
    local function showGUI()
                                   print("GUI wordt aangemaakt")
                                   print("Par_kbdInRate = " .. tostring(Par_kbdInRate))
                    local g                  = [[beamngguiconfig 1
    callback system SteeringParametersGUI.GUICallback
    title Steering Parameters
     
    container
                    type = verticalStack
                    name = root
                   
    control
                    type = text
                    name = Parameter_kbdInRate
                    description = Steering In Rate (original was ]] .. tostring(Par_kbdInRate) .. [[)
                   
    control
                    type = doneButton
                    icon = tools/gui/images/iconAccept.png
                    description = Ok
                   
    ]]
                    gameEngine:showGUI( g )
    end
     
    --            Public interface
    --            Defines what is visible to the outside world
    M.printText                       = printText
    M.showGUI                       = showGUI
    M.GUICallback  = GUICallback
     
    --            Return the module table
    return M
    
    The function I am trying to launch is programmed in input.lua (under [gamepath]\lua\vehicle\).
    The function is marked blue.
    Code:
    -- This Source Code Form is subject to the terms of the bCDDL, v. 1.1.
    -- If a copy of the bCDDL was not distributed with this
    -- file, You can obtain one at http://beamng.com/bCDDL-1.1.txt
     
    local M = {}
     
    M.keys = {} -- TODO: REMOVE
    M.rawDevices = {}
    M.raw = {}           
    M.state = {}
     
    M.VALUETYPE_KEYBD  = 0
    M.VALUETYPE_PAD    = 1
    M.VALUETYPE_DIRECT = 2
     
    local rateMult = nil
     
    --set initial rates (derive these from the menu options eventually)
    local kbdInRate = 8.0
    local kbdOutRate = 4.0
    local kbdAutoCenterRate = 3.0
     
    Par_kbdInRate = 1 -- Mod by bits&bytes on 19/sep/2013
    Par_kbdOutRate = 1  -- Mod by bits&bytes on 19/sep/2013
    Par_kbdAutoCenterRate = 1  -- Mod by bits&bytes on 19/sep/2013
     
                    print ("Par_kbdInRate = " .. tostring(Par_kbdInRate)) -- Mod by bits&bytes on 19/sep/2013
     
    [COLOR=#0000ff]function UpdateSteeringParameters(var1)
                    print("Parameters worden geupdate: " .. var1)               
    end
    [/COLOR] 
    local padInRate = 5.0
    local padOutRate = 2.5
     
    local steerSmoothing = newExponentialSmoothing(4)
     
    local function init()
                    --scale rates based on steering wheel degrees
                    if hydros then
                                   for _, h in pairs (hydros.hydros) do
                                                   --check if it's a steering hydro
                                                   if h.inputSource == "steering_input" then
                                                                   --if the value is present, scale the values
                                                                   if h.steeringWheelLock then
                                                                                  rateMult = 450 / math.abs(h.steeringWheelLock)
                                                                                  break
                                                                   end
                                                   end
                                   end
                    end
     
                    if rateMult == nil then
                                   rateMult = 5/8
                    end
     
                    --inRate (towards the center), outRate (away from the center), autoCenterRate, startingValue
                    M.state = { 
                                   axisx0 = { val = 0, inputType = 0, 
                                                   smootherKBD = newTemporalSmoothing(kbdInRate * rateMult, kbdOutRate * rateMult, kbdAutoCenterRate * rateMult, 0), 
                                                   smootherPAD = newTemporalSmoothingNonLinear(padInRate * rateMult, padOutRate * rateMult, nil, 0), 
                                                   minLimit = -1, maxLimit = 1, binding = "steering" },
                                   axisy0 = { val = 0, inputType = 0, 
                                                   smootherKBD = newTemporalSmoothing(3, 3, 5, 0), 
                                                   smootherPAD = newTemporalSmoothing(10, 10, nil, 0), 
                                                   minLimit =  0, maxLimit = 1, binding = "throttle" },
                                   axisy1 = { val = 0, inputType = 0, 
                                                   smootherKBD = newTemporalSmoothing(3, 3, 5, 0), 
                                                   smootherPAD = newTemporalSmoothing(10, 10, nil, 0), 
                                                   minLimit =  0, maxLimit = 1, binding = "brake" },
                                   axisy2 = { val = 0, inputType = 0, 
                                                   smootherKBD = newTemporalSmoothing(3, 3, 5, 0), 
                                                   smootherPAD = newTemporalSmoothing(10, 10, nil, 0), 
                                                   minLimit =  0, maxLimit = 1, binding = "parkingbrake" },
                                   axisy3 = { val = 0, inputType = 0, 
                                                   smootherKBD = newTemporalSmoothing(3, 3, 5, 0), 
                                                   smootherPAD = newTemporalSmoothing(10, 10, nil, 0), 
                                                   minLimit =  0, maxLimit = 1, binding = "clutch" },
                                   }
    end
     
    local function update(dt)
                    -- map the values
     
                    for k, e in pairs(M.state) do
                                   local tmp = 0
                                   if e.inputType == M.VALUETYPE_DIRECT then
                                                   -- steering wheel
                                                   tmp = e.val
                                   else
                                                   if e.inputType == M.VALUETYPE_PAD then
                                                                   -- joystick / game controller - smoothing without autocentering
                                                                   tmp = e.smootherPAD:get(e.val, dt)
                                                   else -- VALUETYPE_KEYBD
                                                                   -- digital - smoothing with autocenter
                                                                   tmp = e.smootherKBD:get(e.val, dt)
                                                   end
     
                                                   if k == "axisx0" then
                                                                   tmp = steerSmoothing:get(tmp)
                                                   end
                                   end
     
                                   M[e.binding] = tmp
                                   tmp = math.min(math.max(tmp, e.minLimit), e.maxLimit)
                                   electrics.values[e.binding..'_input'] = tmp
                    end
    end
     
    -- deviceInst : Device instance: joystick0, joystick1, etc
    -- fValue : Value typically ranges from -1.0 to 1.0, but doesn't have to. - It depends on the context.
    -- fValue2, fValue3, fValue4 : Extended float values (often used for absolute rotation Quat)
    -- iValue : Signed integer value
    -- action : InputActionType
    -- deviceType : InputDeviceTypes
    -- objType : InputEventType
    -- objInst : InputObjectInstances
    -- ascii : ASCII character code if this is a keyboard event.
    -- modifier : Modifiers to action: SI_LSHIFT, SI_LCTRL, etc.
    local function processRawEvent(deviceInst, fValue, fValue2, fValue3, fValue4, iValue, action, deviceType, objType, objInst, ascii, modifier)
                    --print("InputEvent("..deviceInst..", "..fValue..", "..fValue2..", "..fValue3..", "..fValue4..", "..iValue..", "..action..", "..deviceType..", 
    
    "..objType..", "..objInst..", "..ascii..", "..modifier..")")
                   
                    local dev = deviceType..deviceInst
                   
                    if M.raw[dev] == nil then M.raw[dev] = {} end
                    if M.raw[dev][objType] == nil then M.raw[dev][objType] = {} end
                    if M.raw[dev][objType][objInst] == nil then M.raw[dev][objType][objInst] = {} end
                   
                   
                    local d = M.raw[dev][objType][objInst]
                   
                    d.fValue  = fValue
                    d.fValue2 = fValue2
                    d.fValue3 = fValue3
                    d.fValue4 = fValue4
                    d.iValue  = iValue
                    d.action  = action
                   
                    --dump(M.raw)
    end
     
    local function mapsReloaded()
                    --print "input maps were reloaded:"
                    dump(M.rawDevices)
                   
                    --[[
                    if bdebug.mode == 10 then
                                   -- we are currently in beam debug mode, what coincidence ;)
                                   canvas.inputInfoText = "Thanks, but this features is not implemented yet."
                                   -- TODO: WIP
                    end
                    ]]--
    end
     
    local function reset()
                    M.raw = {}
                   
                    steerSmoothing:reset()
                    for k, e in pairs(M.state) do
                                   e.smootherKBD:reset()
                                   e.smootherPAD:reset()
                    end
    end
     
    local function event(itype, ivalue, inputType)
                    --print("input.event("..tostring(itype)..","..tostring(ivalue)..","..tostring(inputType)..")")
                    M.state[itype].val = ivalue
                    M.state[itype].inputType = inputType
    end
     
    local function toggleEvent(itype, ivalue, inputType)
                    --print("input.toggleEvent("..tostring(itype)..","..tostring(ivalue)..","..tostring(inputType)..")")
                    if ivalue == 0 then return end
                    if M.state[itype] ~= nil and M.state[itype].val > 0.5 then
                                   M.state[itype].val = 0
                                   M.state[itype].inputType = 0
                    else
                                   M.state[itype].val = 1
                                   M.state[itype].inputType = 0
                    end
    end
     
     
    local function toggleParkingbrake()
                    if M.parkingbrakeInput < 0.01 then
                                   M.parkingbrakeInput = 1
                    elseif M.parkingbrakeInput > 0.99 then
                                   M.parkingbrakeInput = 0
                    end
    end
     
     
    -- public interface
    M.update = update
    M.init = init
    M.reset = reset
    M.toggleParkingbrake = toggleParkingbrake
    M.processRawEvent = processRawEvent
    M.mapsReloaded = mapsReloaded
    M.toggleDynamicSteering = toggleDynamicSteering
    M.event = event
    M.toggleEvent = toggleEvent
     
    return M
    
    I am planning to call the local function Init() from there with the new steering parameters from the GUI.

    What am I doing wrong?

    By the way, thanks for making this detailed tutarial.
     
  3. Incognito

    Incognito
    Expand Collapse

    Joined:
    Aug 4, 2013
    Messages:
    246
    bits&bytes, system lua and vehicle lua work separately from each other. And as far as I know, currently they cant "talk" to each other.
     
  4. GregBlast

    GregBlast
    Expand Collapse

    Joined:
    Aug 12, 2013
    Messages:
    224
    Yes and I personally didn't yet have a look at how the GUIs interacting with cars (like the configurator) work with the vehicles. But that would be the thing I would dig into.

    But can you describe what you're trying to get as a final result ? Because you can get some values of the current car and trigger some input events from System LUA via 'queueLuaCommand'. If your module has an 'update' function called from the 'graphicsStep' function of 'main.lua' than you can modify your steering from that 'update' function which will be called quite often (having your chosen "rates" chosen from the GUI and stored within some local variables of the module and used within 'update').

    @Incognito
    I'm not yet sure whether this is a viable solution or not as I haven't tried yet. Maybe Incognito can give us a hint on that ?

    - - - Updated - - -

    Double post because both are different.

    Actually I just found out that when you use 'queueLuaCommand' with a 'BeamObject' retrieved with 'BeamEngine:getSlot(car id number, 0 for player)' you can use the vehicle lua. Which means the vehicle modules can be used as they are (which is why you can call 'input.event' from 'queueLuaCommand'). In your case using the following snippet inside your System module should do the work:
    Code:
    local playerCar = BeamEngine:getSlot(0)
    playerCar:queueLuaCommand("input.UpdateSteeringParameters()")
    
    Just include your parameters taking care of the fact it's within a string ;).

    And Incognito seriously you didn't know about that ? Or you just faked it ;) ? Because you're using it...


    PS: btw make your function LOCAL and add it to the public interface. So it's part of the module and not global to everything.


    EDIT:
    Just tested and it worked fine. But you should make a separate module. Rather than using 'input.lua' you can make one that requires it if needed.
     
    #4 GregBlast, Sep 19, 2013
    Last edited: Sep 19, 2013
  5. Incognito

    Incognito
    Expand Collapse

    Joined:
    Aug 4, 2013
    Messages:
    246
    I forgot about it function, sorry. just long ago I didn't write any mods for beamng.

    Btw, player car id does not always 0.
     
  6. bits&bytes

    bits&bytes
    Expand Collapse

    Joined:
    Jun 13, 2013
    Messages:
    40
    That explains a lot.

    I have already tried to move the GUI to the 'vehicle' part, with the require in [gamepath]\lua\vehicle\input.lua
    The GUI was displayed correct but the apply function was giving an error. I should also take a look at the car configurating GUI's. Do not know yet if they are at the vehicle part.

    But the answer from GregBlast looks also interesting to have a look at.
     
  7. Incognito

    Incognito
    Expand Collapse

    Joined:
    Aug 4, 2013
    Messages:
    246
    What error? Make UpdateSteeringParameters public and call it so: input.UpdateSteeringParameters(param)
     
  8. GregBlast

    GregBlast
    Expand Collapse

    Joined:
    Aug 12, 2013
    Messages:
    224
    You may want to have a look at the little example I made here:
    Speed limiter with GUI


    I made this after chatting here to have a concrete example. It forces you to no longer accelerate if you reach a speed that you can define with a GUI. So it checks your speed and inputs and acts according to those facts. I hope it will help.
     
  9. bits&bytes

    bits&bytes
    Expand Collapse

    Joined:
    Jun 13, 2013
    Messages:
    40
    (This part of the post is referenced to the previous tutorial How to create in-game GUI)

    Creating a GUI into the vehicle part of the lua code

    To have a GUI at het vehicle part of the lua code, you can do it the same way as in this (and the previous) tutorial with some minor changes:

    1. Save the GUI script under [game path]\lua\vehicle

    2. Place the ‘require’ statement into [game path]\lua\vehicle\default.lua:
    Code:
    -- This Source Code Form is subject to the terms of the bCDDL, v. 1.1.
    -- If a copy of the bCDDL was not distributed with this
    -- file, You can obtain one at http://beamng.com/bCDDL-1.1.txt
     
    -- change default lookup path
    package.path = "lua/vehicle/?.lua;lua/?.lua;?.lua"
     
    local STP = require "StackTracePlus"
    debug.traceback = STP.stacktrace
     
    require("utils")
    canvas     = require("canvas")
    drivetrain = require("drivetrain")
    sounds     = require("sounds")
    bdebug     = require("bdebug")
    input      = require("input")
    props      = require("props")
    beamng     = require("beamng")
    particlefilter = require("particlefilter")
    particles  = require("particles")
    material   = require("material")
    electrics  = require("electrics")
    json       = require("json")
    beamstate  = require("beamstate")
    sensors    = require("sensors")
    bullettime = require("bullettime")
    thrusters  = require("thrusters")
    hydros     = require("hydros")
    inputwizard = require("inputwizard")
    perf       = require("perf")
    partmgmt   = require("partmgmt") -- do not change its name, the GUI callback will break otherwise
    [COLOR=#0000ff]SteeringParametersGUI = require("SteeringParametersGUI") -- Mod by bits&bytes on 19/sep/2013[/COLOR]
     
    --console    = require("console")
     
    -- globals for this object
    v = beamng.newVehicle()
     
    3. Into the GUI script, replace the keyword ‘system’ by ‘activeVehicle’ (after ‘callback’ into the local function showGUI() ):
    Code:
    --            This will be the module table
    local M                                = {}
     
    --            A function that prints some text
    --            and prevent raising an error if it is 'nil'
    local function printText( text )
                    print( "Your text was: " .. tostring( text ) )
    end
     
    --            This is the callback that is called once a GUI event is triggered (button click, ...)
    local function GUICallback( mode, str )
                   
                    --            Extract args
                    local args            = unserialize( str )
                   
                    --            Clicked the Ok button
                    if mode == "apply" then
                                   printText( args.inputText )
                    end
    end
     
    --            This is the function that displays the GUI
    --            It should be included in the public interface so that
    --            any external component can call it (like a key bind in keyboard mappings)
    local function showGUI()
                    local g                  = [[beamngguiconfig 1
    callback [COLOR=#0000ff]activeVehicle[/COLOR] testGUI.GUICallback
    title Test GUI
     
    container
                    type = verticalStack
                    name = root
                   
    control
                    type = text
                    name = inputText
                    description = Your text here
                   
    control
                    type = doneButton
                    icon = tools/gui/images/iconAccept.png
                    description = Ok
                   
    ]]
                    gameEngine:showGUI( g )
    end
     
    --            Public interface
    --            Defines what is visible to the outside world
    M.printText                       = printText
    M.showGUI                       = showGUI
    M.GUICallback  = GUICallback
     
    --            Return the module table
    return M
    
    ____________________________________________________________________________________________________________________________________

    (This part of the post is about my modification for changing some steering parameters using a GUI)

    Are you guys talking about the same thing? Can you tell me how to do it?
    Btw, that error is soved (see step 3 above).

    Sorry for all the noob questions but I am new to Lua and realy appreciate yours help and advices.
    I reached the point of giving it all up about modding and that’s why I putted these codes full of rubbish into this tread.
    Incognito’s reaction was enough to switch on the light bulb again.

    I did not yet take a look into GregBlast’s reactions about passing variables between ‘vehicle’ and ‘system’, but I am sure I am going to need it someday in the future.
    For now I solved it by placing the GUI on the ‘vehicle’ part.

    Got my mod working now :D.
    As soon as I can make that function local and added to the public, I will start a WIP topic about this mod.
     
    #9 bits&bytes, Sep 20, 2013
    Last edited: Sep 20, 2013
  10. Incognito

    Incognito
    Expand Collapse

    Joined:
    Aug 4, 2013
    Messages:
    246
    Code:
    local function someFunc()
      print("olololo")
    end
    
    -- public interface
    M.someFunc = someFunc
    Maybe the error was gone due to the fact that you no longer calls the function "UpdateSteeringParameters" after press "Apply" button? :)

    You can execute some lua-code (function) for the some car.
     
  11. bits&bytes

    bits&bytes
    Expand Collapse

    Joined:
    Jun 13, 2013
    Messages:
    40
    Thanks, I will check it out soon.

    No :), the error was there because I used the keyword 'system' in stead of 'activeVehicle' into a GUI at the vehicle part.

    The codes in my previous post had nothing to do with my modification, they are the ones from GregBlast's tutorial with some minor changes to show the differences between GUI's on the system and on the vehicle site. It actualy references to the first tutorial about GUIs: How to create in-game GUI

    Sorry for all the confusion, i will do some editing and place some links to make it more clear.
     
  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice