-
Notifications
You must be signed in to change notification settings - Fork 278
How does it work?
Note this is going to take a moderately long time to complete, so I'll probably remove 90% then slowly add more since the majority of it is outdated.
This will detail the 'thinking' for each system and how it operates with reference to either wikipedia or similar articles (for simplicity). This won't cover the modding 'hooks' or how to mod, that'll be in a seperate section. Once offline documentation comes online (most likely through sandcastle), that'll also act as a way to find specific details of functions/variables/properties.
The code based UI system is currently used for the SettingsMenu and PerformanceHUD (and will be pushed outwards to the other UI systems over time). This system supports both LUA and C# (though LUA is done through params support), and wraps everything up in a class to support modularisation (though exception in the case of LUA since its just function support). There is a base class called BaseUIElement
(which is abstract since it should only be used as a base point for all implementations), essentially it holds a bunch of helper functions like CreateSlider(...)
, CreateText(...)
, GetFluidHorizontalBaseElement
, GetHorizontalBaseElement
.
Essentially the functions can be split up into a few sections (which I'll detail):
- abstract required functions;
InitializeElement()
andGetName()
are both required functions (with all implementations also having extra other required functions). In all current instancesGetName()
is implemented through the initial subclass (likeBaseSettingsElement
andBasePeformanceHUDElement
), to simplify the amount of clutter there is. - initial layout elements; These really should be the first object spawned, and act as the basis for your code, the fluid ones will layout children horizontally/vertically and the width/height of each child will depend on how many there are (i.e. if you have a width of 200 and two children each child will be 100 long). The 'non-fluid' ones use grid layout groups and some more complex logic (which is all abstracted away making your life easy as a modder/coder) to position each child to be x width and y height.
- allocation commands; Currently just one, though I do have plans for more types of allocation. Essentially you allocate yourself a certain size, this size becomes a preferred and a minimum size also will exist (which the computer generates), the system will try to allocate you that space but in some cases (which are often due to insanely small computer screens or other cases) it won't be possible. There are defaults for allocation depending on circumstance (i.e. for SettingsMenu its 220x60; WxH), so try to follow these though obviously if you can take up less space go for it. I.e. in SettingsMenu all toggles are instead 220x30 (WxH) since they don't require the extra space.
- create presets; A ton of functions exist for creation of preset options like
CreateText()
,CreateEmptyDropdown()
and so on.
Since the job of this is to make every implementation clear and outlined it is recommended for you to use these functions to create and handle your UI. Though you aren't limited to using it and it was built to be flexible, anyone can add any component to any gameobject as per unity usual rules. However, the hardest thing with this UI system is setting it up for an implementation (so basically its a little bit of a pain to setup but for modders and for anyone to create any elements in that implementation its super easy).
The system is built to avoid using any pixel data or coordinates, you should never (and currently never) have to do stuff like PlaceObjectAt(x, y)
. Never! And it should stay that way it makes everyones life easier, especially ours for scaling. So use layout groups and cleverly build the framework that each element sits in, its not easy to setup and will require fiddling with. For example I've recently just released a big bugfixing patch that fixes all of the issues that I could find with the UI layout for SettingsMenu (performanceHUD was easier but also simpler and more limited in control), but this is fine in the grand scheme of things since it makes 'their' job easier (modders) and forces us to have good UI layouts rather than 'crappy' throw together ones. Have a look at SettingsMenu/PerformanceHUD to see some good standards and tricks/helpers to get nice layouts.
InitializeElement()
is a function that is abstract (a.k.a. you have to implement it) and should return a gameobject representing 'your' UI element. This will be called a varied amount of times and should handle the creation of all gameobjects (later on pooling may be brought in).
Furniture animation states are added in Data/Furniture.xml. The first animation is used as placeholder image, when placing furniture, as well as default animation when placed.
Switching states from Lua, can be done using furniture.SetAnimationState("idle"). If the state is already "idle", the animation isn't interrupted, so this can be called frequently.
NOTE from this point onwards these are mostly mod level stuff and will be moved over and most of them have changed a lot and aren't 'relevant' any more.
The functions:
void SetTemperature(int x, int y, float temp)
void ChangeTemperature(int x, int y, float incr)
float GetThermalDiffusivity(int x, int y)
void SetThermalDiffusivity(int x, int y, float coeff)
void ChangeThermalDiffusivity(int x, int y, float incr)
are exposed to LUA. They should be used to change the temperature and the thermal diffusivity of the objects.
- The "temperature" is in K, ranges from 0 (the empty space) to Infinity.
- The "thermal diffusivity" represents how "heat" "travels" trough the object. The value must be between 0 (no heat exchange) to 1 (maximum flow rate).
In XML, one can set the "thermal diffusivity" of an object. By default it is 1 (air, non blocking object). But it is likely for walls to have a value very close to zero (but not zero: they may leak a bit of heat).
To set the thermal diffusivity you can specify the XML parameter in "Furniture.xml":
<Params>
<Param name="thermal_diffusivity" value="0.0" />
</Params>
TODO:
- register sinks and sources (via LUA or C#)
- an "OnTemperatureUpdate" event for temperature changes (cf. )
Temperature is mainly implemented in the standalone "Temperature" class. It is stored as an array of floats.
An Overlay is a color map assigning to each tile a color, representing a value (oxygen level, temperature, room, etc.) Overlays can be managed via LUA and xml. This is done via a "color map" that assign to each value in an interval (say btw. 0 and 255) to a color. Temperature, for instance, can go from blue (cold, value 0) to red (hot, value 1000). This is a "Jet" map. The coloring of each individual room is best done with a "Random" coloring.
The list of overlays is in "overlay_prototypes.xml". Each overlay, is a line like: oxygenValueAt For now only id, color_map and the Element content work:
- id: unique name for the overlay (TODO: localize)
- color_map: coloring scheme for the overlay (For now only "Jet", rainbow color, or "Random", random coloring) TODO: more colors
- content of element: LUA function called to create overlay
The second file you want to edit is "overlay_functions.lua". This contains the actual functions called by the OverlayMap class. THe function returns, for each tile, a value between 0 to 255, representing a "grade" on the color_map scheme. E.g. for "Jet", 0 will be blue, 128 green and 255 red. Each function has the following signature:
- 1st parameter: "tile", on which to operate (return value at this tile)
- 2nd parameter: "world", a reference to the world instance (may be needed)
- return: value at "tile" between 0 and 255
TODO:
- read min and max from xml to set proper bounds for the color map
- localize the color map names?
- add more colors
You can edit the method:
public static Color32[] ColorMap(OverlayDescriptor.ColorMap colorMap,
int size = 256, byte alpha = 128)
Just add a new enum to OverlayDescriptor.ColorMap, and fill the switch() conditional. THe function returns an array of Colors (Color32) of size "size" with alpha channel "alpha". TODO
Parameters are an object used to store arbitrary data for an object, typically read from xml. Presently used for furniture. It can also be used for data unique to an instance of an object in Lua.
Parameters are defined in XML, wrapped in a <Params>
tag. A Parameter can either be a self-closing <Param />
tag, representing a singular value, or an opening and closing <Param></Param>
tag pair, enclosing other Param tags, creating a nested data structure useful for grouping related data. All Param tags must have a name, and optionally a value. Most commonly you will want a stand-alone tag to have a value, and an enclosing tag pair to have no value.
An example Params structure excerpted from Furniture.xml:
<Params>
<!-- <Param name="gas_name" value="O2" /> -->
<Param name="gas_limit" value="0.2" />
<Param name="gas_per_second" value="0.16" />
<Param name="gas_gen">
<Param name="O2">
<Param name="gas_limit" value="0.2" />
</Param>
<Param name="N2">
<Param name="gas_limit" value="0.8" />
</Param>
</Param>
</Params>
In Lua this will be accessible through your object's Parameters property. So with the previous example structure, if you have the furniture as furn, gas_per_second Parameter would be accessed as furn.Parameters["gas_per_second"], and the gas_limit for O2 would be furn.Parameters["gas_gen"]["O2"]["gas_limit"].
Available functions: ToFloat(); Returns the value of the Parameter as a float. Returns 0 if value is unset or is set as a non-number
ToFloat(float) Returns the value of the Parameter as a float or returns the passed value if the value hasn't been explicitly set through a Set function, XML, or the Change function.
ToString(); Returns the value of the Parameter as a string.
ToString(); Returns the value of the Parameter as a string or returns the passed value if the value hasn't been explicitly set through a Set function, XML, or the Change function.
ChangeFloatValue(float); Increases the value by the given amount. A negative passes will decrease the value appropriately. If the value hasn't been set or can't be treated as a float, it will be set to the passed value.
SetValue(string); SetValue(float); Sets the value to the argument.
ContainsKey(string); Returns true if the Parameter contains the key.
HasContents(); Returns true if any other Parameters have been added to the Parameter.
AddParameter(Parameter); Adds the passed parameter with a key matching the parameter's name;
In addition to the functions exposed to Lua Parameter has a WriteXml function to write the Parameters to a save file, and a ReadXml to read from save file or a data file. The ReadXml function, when passed the reader will return a Parameter object which will contain all Parameters defined for the object. The returned object should be stored and be accessible through a property called Parameters, to ensure all objects parameters can be access the same way. To match the current implementation in furniture, when loading from xml files ReadXml should be called when the Params element is encountered.
A custom dialog box consists in 2 files, each one with the same name (doesn't need to be the same as the Dialog Box's title): your_name.xml and your_name.lua
The XML file should contain:
-
The base object:
<DialogBoxLuaInformation xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
-
A
<Title>
tag, that will represent the Dialog Box's name too:<Title>Test</Title>
-
Multiple
<Control>
tags, which determine the "Controls" (images, text, buttons...) of the Dialog Box Each Content must have:-
A "name" argument that identifies the control.
-
A "type" argument that can vary from "Image","Button","Input","Text" (So, if we wanted to add some text we would add:
<Control name="myText" type="Text">
) -
A
<Position>
tag with , and child nodes, that would indicate the position of "Control" from the top-left corner<Position> <x>150</x> <y>-50</y> <z>0</0> </Position>
-
A
<Size>
tag, with and nodes, that would mean width and height respectively -
A
<Data xsi:type="xsd:string">
element with- Text displayed if it's a
<Content type="Text">
- Image path inside StreamingAssets folder if it's a
<Content type="Image">
- Button name if it's a
<Content type="Button">
(this name will be used in LUA to call OnClick functions) - Nothing if it's a
<Content type="Input">
- Text displayed if it's a
-
-
There must be a list of
<Buttons>
elements, to indicate which buttons should be displayed (Yes, No, OK, Cancel) -
And an
<Actions>
tag, which contains the LUA functions that will be called Available events are "OnShow" and "OnClosed" for the dialog, "OnClicked" for every button. Eg:
<Actions>
<Action event="OnClosed" functionName="Testing_DialogClosed" />
<Action event="OnmyButtonClosed" functionName="MyButton_Closed" />
</Actions>
So, as a template you can use:
<?xml version="1.0" encoding="Windows-1252"?>
<ModDialogBoxInformation xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Title>Testing</Title>
<Control name="Img1" type="Image">
<Position>
<x>100</x>
<y>-50</y>
<z>0</z>
</Position>
<Size>
<x>50</x>
<y>50</y>
</Size>
<Data xsi:type="xsd:string">UI\DialogBoxes\img\test.jpg</Data>
</Control>
<Buttons>Yes</Buttons>
<Buttons>No</Buttons>
<Buttons>Cancel</Buttons>
<Actions>
<Action event="OnClosed" functionName="Testing_DialogClosed" />
</Actions>
</ModDialogBoxInformation>
And add a .lua file that handles "Testing_DialogClosed" function.
Here you'll handle your Actions as functions, the following way:
function Testing_DialogClosed( DialogBoxObject, result, data )
-
DialogBoxObject
is the reference to the actual DialogBox that called the function -
result
(int) The DialogBoxResult enum that represents which button was clicked.
DIALOGRESULT_YES = 0
DIALOGRESULT_NO = 1
DIALOGRESULT_CANCEL = 2
DIALOGRESULT_OKAY = 3
-
data
(table) Extra data that specific Control returns (up to now it's just the Input component)
Eg: If you want to get data from the first Control you'd use:
inputResult = data[1]
Alternatively, you can use the following command:
inputResult = DialogBoxObject.GetControl("controlName").result
Which is far more useful when you're not sure of the order of data.
- Make a empty scene.
- Make your dialog box in here then save the prefab to the Resource Folder.
- Delete the new scene you made.
- Open the DialogBoxManager and copy a box of code that made and input your new info there.
- Put a script on your dialog box the derived from DialogBox.
- Get a reference to DialogBoxManager.
- Call that with get component your reference name for DialogBoxManager.
- Then the reference your script name with "." showdialog.
- Done.
https://github.com/TeamPorcupine/ProjectPorcupine/pull/707 - Is a PR to do the same with menu as we did with dialog boxes.
- Make a prefab of the button
- Save it in the resource folder
- Now in the MenuController script instantiate the button and make the bottom menu the parent.
- Features
-
Roadmap
- Milestone 0.3
- Milestone 0.2 (reached)
- Milestone 0.1 (reached)
- Gameplay
- Frequently Asked Questions