UI Subsystem - coldrockgames/gml-raptor GitHub Wiki
- All controls are implemented to auto-resolve strings through the LG Localization subsystem and format text through Scribble.
- All controls can be
docked
,aligned
,anchored
orpositioned freely
in their container. - Available containers are
the desktop/game ui layer
(accessible through theUI_ROOT
macro), theWindow
control or the invisiblePanel
control.
The most simple and visual way is to draw your UI directly on a specific layer in the Room Editor. However, this approach has several major disadvantages:
- You lose all ability of a dynamic interface as it's drawn on static coordinates. Screen Resolution changes might become a nightmare.
- You can not put controls in containers and build a control hierarchy.
- You can not "dock" or "align" controls to their neighbors.
- You can not "center" or "spread to 66% of screen size" anything, you have to tinker and calculate it all by hand.
- Many controls change/adapt their sprites at runtime (through the skin for instance), therefore the "preview" you see in the Room Editor will never match what is actually displayed during the running game. This quite eliminates all the "visual" advantages you might see at first glance, when dragging things in the Room Editor.
- on_click callbacks need to be public functions when set directly in the variable definitions of the room editor. Again: No dynamics here.
- ...this list could be continued for quite a while...
Coding your UI through the ControlTree gives you flexibility with callbacks, enables docking, aligning and all other layouting features of the raptor UI subsystem and makes changes easy to implement.
Building your UI with the ControlTree is a matter of using the builder pattern in a simple and intuitive way. This example here shows the entire UI of the Unit Test Room that comes with Raptor Pro:
UI_ROOT
.clear_children()
.add_control(Panel, { min_height: 144 })
.set_dock(dock.top)
.set_padding_all(12)
.add_control(Label, struct_join(label_struct, { text: "[scale,1.5][ci_accent]UTE - raptor Unit Test Engine", }))
.set_position(48, 32)
.add_control(Label, struct_join(label_struct, { text: "[scale,1.5]Discovered Test Suites", }))
.set_position(48, 84)
.add_control(Label, struct_join(label_struct, { text: "[scale,1.5]Detailled Test Results", }))
.set_position(900, 84)
.build()
.add_control(ScrollPanel, struct_join(scrpanel_struct, { min_width:1000, }))
.set_content_autofill(true, 4)
.set_content_object(UnitTestResultsViewer, { detail_mode: true, })
.set_dock(dock.right)
.build()
.add_control(ScrollPanel, scrpanel_struct)
.set_content_autofill(true, 4)
.set_content_object(UnitTestResultsViewer, { detail_mode: false, })
.set_dock(dock.fill)
.build()
.build();
It can be "read like a book", with intuitive function names, a clear hierarchy and when you do your indendation cleanly, you can cleary see, what's going on.
As you can see in the example above, while the structure is clear, there's quite a lot function calls to write. This is not a performance problem, as the UI is built only once during the RoomStart event.
There is a second way to write your interface, with the so-called style_struct
. If you're closer to web development, you might be used to such structs (or xml-attributes) like "padding_left: 25" and things like that. If you prefer this way of communication with the ControlTree, then maybe the approach shown here is more for you:
The key element here is also the .add_control
method of the ControlTree. It offers a third argument, the style_struct
. In this struct you can write all properties supported by the ControlTree without having to write the function calls.
The same control tree for the Unit Test Room using the style-struct would look like this (the code below delivers the same layout as the example above):
UI_ROOT
.clear_children()
.add_control(Panel, { min_height: 144 }, { dock: dock.top, padding: 12 })
.add_control(Label, struct_join(label_struct, { text: "[scale,1.5][ci_accent]UTE - raptor Unit Test Engine", }), { position: [48,32]})
.add_control(Label, struct_join(label_struct, { text: "[scale,1.5]Discovered Test Suites", }), { position: [48,84]})
.add_control(Label, struct_join(label_struct, { text: "[scale,1.5]Detailled Test Results", }), { position: [968, 84]})
.build()
.add_control(ScrollPanel, struct_join(scrpanel_struct, { min_width:1000, }), {
dock: dock.right,
content_autofill: [true, 4],
content_object: [UnitTestResultsViewer, { detail_mode: true, }]
})
.build()
.add_control(ScrollPanel, scrpanel_struct, {
dock: dock.fill,
content_autofill: [true, 4],
content_object: [UnitTestResultsViewer, { detail_mode: false, }]
})
.build()
.build();
The style-array feature is the most sophisticated way to create your UI. You can do this in a json struct or directly in code by creating an array for the control hierarchy, as shown here (again: It doesn't matter how you build this array. Dynamically in your game, through rich json or by simply coding the array).
Again, the code shown here produces the exact same result for the UI of the Unit Test Room as the examples above:
[
[ Panel, { min_height: 144 }, { dock: dock.top, padding: 12 } ,
[ Label, struct_join(label_struct, { text: "[scale,1.5][ci_accent]UTE - raptor Unit Test Engine", }), { position: [48,32]} ],
[ Label, struct_join(label_struct, { text: "[scale,1.5]Discovered Test Suites", }), { position: [48,84], name: "middel"} ],
[ Label, struct_join(label_struct, { text: "[scale,1.5]Detailled Test Results", }), { position: [968, 84]} ],
],
[ ScrollPanel, struct_join(scrpanel_struct, { min_width:1000, }), {
name: "der_rechte",
dock: dock.right,
content_autofill: [true, 4],
content_object: [UnitTestResultsViewer, { detail_mode: true, }]
}
],
[ ScrollPanel, scrpanel_struct, {
name: "new_style",
dock: dock.fill,
content_autofill: [true, 4],
content_object: [UnitTestResultsViewer, { detail_mode: false, }]
}
]
]
With such an array at hand, you just send it to one of the two available global functions for Styled UI:
-
styled_ui_create_room(_style_array)
for creating the room UI. Use this to apply the array to theUI_ROOT
of the room. -
styled_ui_create(_container_or_tree, _layer_or_depth, _init_struct, _style_array)
to apply the array to any control tree. This function can even create a new instance for you. As an example, you could create your window layout as a style-array and send it to this function, which will not only render the layout, it will even create the window instance for you and apply the layout in that window.
Read more details about this feature on the Styled UI page.
Also, there's theming and skins implemented in raptor. Basically, this is a set of predefined colors you want to use throughout your game's ui. All controls are set up to use the app theme through defined constants. Activate your initial Skin and Theme in the Game_Configuration
. They can be changed later on-the-fly during the running game.
UI Themes and UI Skins tell you everything, you need to know about this feature. Skins and Themes are also capable of being dynamically loaded at runtime and fully support the Raptor RichJson Format.
The controls are built object-oriented (as far as GML allows this) and follow this hierarchy:
Control | Description |
---|---|
Base Controls | |
Label | The most basic UI element, just a scribble string (text ) |
Containers | |
Panel | An invisible container that helps you aligning your layout |
ScrollPanel | A Panel with scrollbars where you can assign any Content and it will scroll it, no matter, how big it is. This control uses the new function gpu_set_scissor() provided by GameMaker, makes the clipping area directly on the graphics card and is therefore blazing fast!
|
Window | A very powerful, movable, sizable dynamic window which can be used for every dialog |
WindowXButton | A special ImageButton , used by the Window object |
MessageBoxWindow | A special Window , used by the MessageBox Functions |
MessageBoxWindowXButton | A special WindowXButton , used by the MessageBoxWindow object |
Tooltips | |
Tooltip | Little info-tag when hovering the mouse over a control |
Clickable Controls | |
TextButton | A standard button, containing a scribble-string as caption (text ) |
ImageButton | A standard button, containing a scribble-string as caption (text ) and an image as icon (sprite_to_use ) |
Slider | A highly configurable value slider control |
Checkable Controls | |
CheckBox | A standard checkbox |
RadioButton | A standard radiobutton. You can have multiple radio groups in one container |
Input Controls | |
InputBox | A comfortable text input control with lots of features. This control offers so much functionality, that I spent it its own InputBox sub page |
Mouse Cursor | |
MouseCursor | A sprite-based mouse cursor replacement for the standard mouse cursor. Just drag one into your room in any layer. It works transparently and fully automated |
MouseCursorCompanion | An attachment-object for your MouseCursor
|
I have created some lightweight controls, which help a bit with common tasks. Nothing too special, but still things, you do over and over again, especially when you do game jams and create new games frequently.
They can be found in the _gml_raptor_ui_\helpers
folder of the project template and are ready-to-use, without any configuration needed.
Control | Description |
---|---|
GameVersionLabel | Child of Label which reads the version information from the game's version.json file and sets this as its text . This is the most easy way to display your version/build at the main screen |
CopyrightLabel | Also a child of Label , with text set to =legal/copyright , so it will display copyright information from the localization file |
FPSTracker | A simple control that measures the render time of frames and displays a text like 60 FPS
|
StoryTeller | A child of Label which utilizes scribble's Typist, to slowly write text, as if some character would be talking |
When you start the Example Project Demo, you see these objects in action.
Read on in UI Themes or UI Skins or head directly to Base Controls.