mirror of
https://github.com/BeamMP/Docs.git
synced 2026-07-21 14:20:54 +00:00
More work!!!
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
::: warning "This site is under construction!"
|
||||
|
||||
This site is being actively worked on.
|
||||
|
||||
Feel you could help? Please do by clicking on the page with a pencil on the right!
|
||||
|
||||
This can be done any page too.
|
||||
# Creating an ImGui Window
|
||||
|
||||
This page covers how to create a basic ImGui window.
|
||||
|
||||
## Setup
|
||||
|
||||
Before using ImGui, some setup is required:
|
||||
|
||||
```lua
|
||||
local im = ui_imgui -- shortcut to prevent lookups all the time. should help with optimization
|
||||
local imguiExampleWindowOpen = im.BoolPtr(true)
|
||||
```
|
||||
|
||||
`imguiExampleWindowOpen` will be used to determine when this example window should be rendered.
|
||||
|
||||
## Window Rendering
|
||||
|
||||
ImGui windows and their contents must be recreated for every frame they should be displayed. This means that some form of onUpdate function is necessary to use ImGui.
|
||||
|
||||
```lua
|
||||
local function onUpdate()
|
||||
if worldReadyState == 2 then
|
||||
if imguiExampleWindowOpen[0] == true then
|
||||
imguiExample()
|
||||
end
|
||||
end
|
||||
end
|
||||
M.onUpdate = onUpdate
|
||||
```
|
||||
|
||||
This will run a function to create this example's window, so long as the level is fully loaded, and that the example window should be displaying.
|
||||
|
||||
## Window Content
|
||||
|
||||
If you're new to writing ImGui, think of it as a distant cousin of HTML:
|
||||
|
||||
* `im.SetNextWindowSize(im.ImVec2(x, y), im.Cond_FirstUseEver)` defines your viewport size if it hasn't already been defined
|
||||
* `im.Begin()` and `im.End()` is your `<body>` and `</body>`
|
||||
* `im.Text()` is your `<p></p>`
|
||||
|
||||
```lua
|
||||
local buttonPresses = 0
|
||||
|
||||
local function imguiExample()
|
||||
im.SetNextWindowSize(im.ImVec2(366, 100), im.Cond_FirstUseEver) -- prepare our window
|
||||
im.Begin("Hello World, I am a window") -- create a window with the title of "Hello World, I am a window"
|
||||
im.Indent() -- a... padding element
|
||||
im.Text("Hello World, I am text.") -- add a line of text, somewhat like a <p> element
|
||||
im.SameLine() -- Not really HTML. This appends the following element to the same line as the previous element.
|
||||
if im.Button("The Hello World Button") then -- Like <button>. This runs Lua when pressed.
|
||||
buttonPresses = buttonPresses + 1
|
||||
end
|
||||
if buttonPresses > 0 then
|
||||
im.Text("The Hello World Button has been pressed " .. buttonPresses .. " times!")
|
||||
else
|
||||
im.Text("The Hello World Button has not been pressed.")
|
||||
end
|
||||
im.Unindent() -- end the "padding element"
|
||||
im.End() -- complete our "canvas" so it can be drawn
|
||||
end
|
||||
```
|
||||
|
||||
You can add the following function to easily toggle visibility of the window:
|
||||
|
||||
```lua
|
||||
local function toggleExampleImgui()
|
||||
imguiExampleWindowOpen[0] = not imguiExampleWindowOpen[0]
|
||||
end
|
||||
```
|
||||
|
||||
## Result
|
||||
|
||||
<figure class="image image_resized" style="width:100%" markdown>
|
||||

|
||||
</figure>
|
||||
|
||||
When the The Hello World Button button is pressed, the counter below it will update to display the amount of times the The Hello World Button button has been pressed.
|
||||
|
||||
## Download
|
||||
|
||||
This tutorial is almost entirely based off of [StanleyDudek](https://github.com/StanleyDudek)'s ImGui example mod. You can download this example mod [here](../../../../assets/content/imguiExample.zip).
|
||||
@@ -0,0 +1,2 @@
|
||||
# lua-mods.md
|
||||
This page needs creating
|
||||
@@ -0,0 +1,266 @@
|
||||
# UI-App Creation
|
||||
|
||||
In order to make a UI-App you will need some knowledge of the AngularJS framework, the main documentation can be found here: [AngularJS docs](https://docs.angularjs.org/guide)
|
||||
|
||||
## File structure
|
||||
|
||||
A UI-App needs four important files to work:
|
||||
- app.js | Contains the main code used by the UI-App [Javascript docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript)
|
||||
- app.html | The code that displays your app [Html docs](https://developer.mozilla.org/fr/docs/Web/HTML)
|
||||
- app.json | Contains the information of the UI-App
|
||||
- app.png | The image file showing in the app selector
|
||||
|
||||
### UI-App style
|
||||
We recommend using the ``<style>`` tag to style your app, a .css file will work, but you will not be able to see the changes in real-time.
|
||||
|
||||
## Example
|
||||
|
||||
This example is from DanielW Thanks to him
|
||||
|
||||
ui\modules\apps\ExampleApp\app.html
|
||||
```html
|
||||
<div style="width: 100%; height: 100%;" class="bngApp">
|
||||
<link type="text/css" rel="stylesheet" href="/ui/modules/apps/ExampleApp/app.css" />
|
||||
|
||||
<div id="exampleAppContainer">
|
||||
<span>Gear: <span>{{ gearName }}</span></span>
|
||||
|
||||
<div layout="row" layout-align="center center">
|
||||
<md-input-container flex>
|
||||
<label>Input</label>
|
||||
<input ng-model="message" ng-keydown="sendMessage($event)">
|
||||
</md-input-container>
|
||||
|
||||
<md-button md-no-ink class="md-warn" ng-disabled="!message" ng-click="sendMessage()">Send</md-button>
|
||||
</div>
|
||||
|
||||
<span style="display: block">Messages:</span>
|
||||
|
||||
<!-- Scroll Area -->
|
||||
<ul bng-nav-scroll style="margin: 0; padding: 0; overflow-y: auto; width: 100%; height: 100%; background-color: #37373740;">
|
||||
|
||||
<!-- Iterate over the messages and display them -->
|
||||
<li ng-repeat="message in messages track by $index" style="display: flex; align-items: center; height: 35px;">
|
||||
<span style="padding: 0 0.2em; width: 100%;">{{ message }}</span>
|
||||
|
||||
<!-- Button to delete the message, this calls the `deleteMessage` function in `app.js` -->
|
||||
<md-button md-no-ink class="md-icon-button md-warn" ng-click="deleteMessage($index)">
|
||||
<md-icon class="material-icons">delete</md-icon>
|
||||
</md-button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
Here, you can see a ``<span>`` tag displaying the gear of your vehicle, an input used to send a message to the ``sendMessage()`` function in the Javascript and a repeated ``<li>`` tag using <b>ng-repeat</b> on the ``messages`` variable located in the Javascript
|
||||
|
||||
ui\modules\apps\ExampleApp\app.js
|
||||
```js
|
||||
angular.module('beamng.apps')
|
||||
.directive('exampleApp', [function() {
|
||||
return {
|
||||
templateUrl: '/ui/modules/apps/ExampleApp/app.html',
|
||||
replace: true,
|
||||
restrict: 'EA',
|
||||
scope: true,
|
||||
|
||||
controller: ['$scope', function($scope) {
|
||||
$scope.gearName = '0'
|
||||
$scope.message = ''
|
||||
$scope.messages = []
|
||||
|
||||
// Setup the streams we want. For now, we only want the engine information. You can add more, you'll just have to look around to find the different streams
|
||||
let steamList = ['engineInfo']
|
||||
StreamsManager.add(steamList)
|
||||
|
||||
$scope.$on('destroy', function() {
|
||||
StreamsManager.remove(steamList)
|
||||
})
|
||||
|
||||
// Do I even need to put this comment here explaining what this function does?
|
||||
// Well, I have done it for a lot of other things when they weren't needed. I'll leave this one be...
|
||||
$scope.$on('streamsUpdate', function(event, streams) {
|
||||
if (!streams.engineInfo) // Early return... You probably noticed that without this useless comment though
|
||||
return;
|
||||
|
||||
// `lua/vehicle/controller/vehicleController.lua:538` (or use console.log)
|
||||
let gear = streams.engineInfo[5]
|
||||
|
||||
// Update the gear name in HTML if needed
|
||||
if ($scope.gearName !== gear)
|
||||
$scope.gearName = gear
|
||||
})
|
||||
|
||||
$scope.sendMessage = function(event) {
|
||||
if (event && event.key !== 'Enter')
|
||||
return
|
||||
|
||||
if ($scope.message == '')
|
||||
return
|
||||
|
||||
// Forward the message to the Lua extension to modify it
|
||||
bngApi.engineLua('extensions.exampleMod.modifyMessage("' + $scope.message + '")')
|
||||
$scope.message = ''
|
||||
}
|
||||
|
||||
$scope.deleteMessage = function(idx) {
|
||||
$scope.messages.splice(idx, 1)
|
||||
}
|
||||
|
||||
// The `modifyMessage` function will call this hook with the modified data
|
||||
$scope.$on('MessageReady', function(_, modifiedMessage) {
|
||||
$scope.messages.push(modifiedMessage)
|
||||
});
|
||||
}]
|
||||
}
|
||||
}])
|
||||
```
|
||||
Note the usage of <b>$scope</b>. This is very important because you will need to define your variables and functions within <b>$scope</b> to be able to access it from the <b>Html</b> inside any <b>ng-*</b> tag.
|
||||
So in this example, after the ``sendMessage()`` function being executed from the <b>Html</b> it will send it to a lua file located in the extensions directory of the mod and execute the ``modifyMessage()`` function inside this lua file.
|
||||
|
||||
An example of how the lua side could look like:
|
||||
```lua
|
||||
local function modifyMessage(message)
|
||||
message = message .. " [Modified!]"
|
||||
guihooks.trigger('MessageReady', message)
|
||||
end
|
||||
```
|
||||
^ This is a simplified version of the lua to just show the function
|
||||
|
||||
The main focus here is the usage of <b>guihooks.trigger</b> which triggers an AngularJS event defined with ``$scope.$on()``. As you can see at the very bottom of the Javascript file the event is named MessageReady and will be executed by the <b>guihooks.trigger</b> function with the message payload and then will be pushed inside the ``$scope.messages`` variable to be displayed by the li tag using <b>ng-repeat</b> in the <b>Html</b> file
|
||||
|
||||
The full lua file is just below
|
||||
|
||||
lua\ge\extensions\exampleMod.lua
|
||||
```lua
|
||||
local M = {}
|
||||
|
||||
--[[
|
||||
This is the entry point of our extension, this is what the game loads from our `modScript.lua`.
|
||||
In the modScript file, you can load more extensions and put them in the same directory as this file.
|
||||
|
||||
In this file, we will communicate with the following:
|
||||
1. Our vehicle extension. That extension tells this extension when to send it data, and we send it. Take a look at `vehicle/extensions/auto/exampleVehicleExtension.lua`
|
||||
2. Input. Take a look at `core/input/actions/myActions.json`. When the bounded key is pressed, it will call `onActionKeyDown` (a function we export below)
|
||||
]]
|
||||
|
||||
-- Game Function Hooks
|
||||
--------------------------------------------
|
||||
local function onExtensionLoaded()
|
||||
log('D', "onExtensionLoaded", "Called")
|
||||
end
|
||||
|
||||
local function onExtensionUnloaded()
|
||||
log('D', "onExtensionUnloaded", "Called")
|
||||
end
|
||||
|
||||
-- Custom Functions
|
||||
--------------------------------------------
|
||||
local function onActionKeyDown()
|
||||
log('D', "onActionKeyDown", "Pressed!")
|
||||
end
|
||||
|
||||
local function onVehicleExtensionLoaded(vehID)
|
||||
log('D', "onVehicleExtensionLoaded", "Sending some data to the vehicle")
|
||||
|
||||
local veh = be:getObjectByID(vehID) -- If you don't have the ID, you can also use `be:getPlayerVehicle(0)` to get the current vehicle.
|
||||
if not veh then return end -- The usual error checking
|
||||
|
||||
local data = {
|
||||
["name"] = "Daniel W"
|
||||
}
|
||||
|
||||
veh:queueLuaCommand("extensions.exampleVehicleExtension.onDataReceived('" .. jsonEncode(data) .. "')")
|
||||
end
|
||||
|
||||
local function modifyMessage(message)
|
||||
message = message .. " [Modified!]"
|
||||
guihooks.trigger('MessageReady', message)
|
||||
end
|
||||
|
||||
-- Export Interface
|
||||
--------------------------------------------
|
||||
M.onExtensionLoaded = onExtensionLoaded
|
||||
M.onExtensionUnloaded = onExtensionUnloaded
|
||||
|
||||
M.onActionKeyDown = onActionKeyDown
|
||||
M.onVehicleExtensionLoaded = onVehicleExtensionLoaded
|
||||
M.modifyMessage = modifyMessage
|
||||
|
||||
--[[ Other functions could include:
|
||||
- onPreRender(dtReal, dtSim, dtRaw)
|
||||
- onUpdate(dtReal, dtSim, dtRaw)
|
||||
- onClientPreStartMission(levelPath)
|
||||
- onClientPostStartMission(levelPath)
|
||||
|
||||
To find all of these, search the following in `BeamNG.Drive/lua`: `extensions.hook(`
|
||||
--]]
|
||||
|
||||
return M
|
||||
```
|
||||
|
||||
Note that its very important to return the M (module) variable with the needed functions inside!
|
||||
For example, without the ``M.modifyMessage = modifyMessage`` line, the ``bngApi.engineLua('extensions.exampleMod.modifyMessage("' + $scope.message + '")')`` function will not be able to find the modifyMessage() function
|
||||
|
||||
|
||||
ui\modules\apps\ExampleApp\app.css
|
||||
```css
|
||||
#exampleAppContainer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
#exampleAppContainer > * {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
```
|
||||
|
||||
ui\modules\apps\ExampleApp\app.json
|
||||
```json
|
||||
{
|
||||
"domElement": "<example-app></example-app>",
|
||||
"name": "Example App",
|
||||
"types": [
|
||||
"ui.apps.categories.debug"
|
||||
],
|
||||
"description": "example-app",
|
||||
"css": {
|
||||
"left": "0px",
|
||||
"height": "auto",
|
||||
"width": "270px",
|
||||
"min-width": "200px",
|
||||
"min-height": "90px",
|
||||
"top": "0px"
|
||||
},
|
||||
"author": "Daniel W",
|
||||
"version": "0.1",
|
||||
"directive": "exampleApp"
|
||||
}
|
||||
```
|
||||
The directive needs to be the same as in the <b>Javascript</b> file
|
||||
|
||||
|
||||
|
||||
|
||||
# Javascript functions provided by BeamNG for UI-Apps
|
||||
|
||||
```js
|
||||
bngApi.engineLua("lua_path.function()")
|
||||
```
|
||||
Useful to run a lua function with or without arguments
|
||||
|
||||
# Lua functions provided by BeamNG for UI-Apps
|
||||
|
||||
```lua
|
||||
guihooks.trigger("EventName", Payload)
|
||||
```
|
||||
The payload can be any type but its better to keep it as an Array / Object or a String to not be lost.
|
||||
|
||||
<b>IMPORTANT</b> : Sometime it can happen that the event name you use is already used internally by something else and cause problems, so for example if your app is named Nickel, it can be a good practice to name every of your Angular event like NKEventName instead of EventName
|
||||
|
||||
Reference in New Issue
Block a user