-
Notifications
You must be signed in to change notification settings - Fork 2
In order to have more control over the creative output, Zenith makes use of a custom UI solution in lieu of a standard UI library. This allows us to integrate custom UI components and animations without having to leave the familiar territory of the engine.
The UI subsystem is not unlike many of the subsystems we've already seen. It includes standard, non-parametrized Initialize
and Draw
methods, which we won't bother with since they're handled directly by the engine.
We can add UI elements with the AddElement
and AddElements
methods. The first is much like the ZGame AddGameObject
method, but it takes a shared pointer to a ZUIElement instance as opposed to a ZGameObject. The latter is a convenience method that takes an initializer list of elements:
ZEngine::UI()->AddElement(button);
ZEngine::UI()->AddElements({panel1, panel2, panel3});
We can use the UI system to register external font's with the engine, mostly for use by ZUIText elements:
ZEngine::UI()->RegisterFont("Assets/Fonts/roboto.ttf");
If we have a ZUICursor we'd like to use, it would be best to use the SetCursor
method to ensure that it is internally drawn above other UI elements:
ZEngine::UI()->SetCursor(myCursor);
Certain getters might be of use:
// Get the ZUICursor if one is set
ZEngine::UI()->Cursor();
// Get a map of all top level UI elements
ZEngine::UI()->Elements();
// The shader we use to draw text elements
ZEngine::UI()->TextShader();
// The shader we use to draw most other UI elements
ZEngine::UI()->UIShader();
We can also use the templated FindElement
method to find a UI element of a certain type, passing in the object id as the sole parameter:
auto backgroundImage = ZEngine::UI()->FindElement<ZUIImage>("ZUI_15");
The UI system operates in a hierarchical fashion. All UI elements can have multiple children, all children should only have a single parent, and only the top level root elements are stored within the UI subsystem. The parent child hierarchy is walked down in order to render UI elements and update element states (e.g. If a parent is hidden, all children should be hidden as well).
All element positions and scales are specified in pixel coordinates. The dimensions are internally resolved so that sizes and positions stay consistent across different screen resolutions and sizes.
Several useful methods help us query UI element states, such as the Hidden
, Enabled
and Selected
methods. We can also query UI transform properties using the Position
, Size
and Angle
getters:
if (element->Enabled()) {
glm::vec2 pos = element->Position();
float angle = element->Angle();
// Do something with position and angle...
}
The corresponding setters for the transform properties are SetPosition
, SetSize
and SetRotation
.
An interesting operation to note is the SetTranslationBounds
method, which allows us to specify bounds beyond which a UI element cannot move:
element->SetTranslationBounds(0.f, 300.f, 300.f, 0.f);
The above creates an invisible box 300 pixels wide and tall beyond which the UI element's movement is effectively clamped.
We can set the size, position and rotation of the element relative to it's current transform properties by using Scale
, Translate
and Rotate
instead of the transform property setters above, which transform the object relative to it's starting reference frame.
We can use Hide
, Show
, Enable
, Disable
, Select
and Deselect
to set the corresponding element states.
Adding child elements is trivial, as all we need to do is call AddChild
and pass in a shared pointer to another UI element. Removing a child should be second nature:
std::shared_ptr<ZUIButton> button(new ZUIButton);
element->AddChild(button);
element->RemoveChild(button);
We can use the templated Child
method to query for the direct UI children of an element:
auto label = element->Child<ZUIText>("ZUI_52");
Two other methods of interest are the TrySelect
and Contains
methods. Contains
simply checks whether the provided position lies within the element. TrySelect
attempts to select the element at a given position by using the position to call Contains
, also checking if the element is enabled. If both conditions pass, the element is selected.
glm::vec3 position(155.0f, 10.0f, 0.0f);
if (element->Contains(position)) {
// Do some nice hit detection stuff here
}
anotherElement->TrySelect(position); // Might or might not select the element
Zenith comes with a few ready to use UI elements: