Veract is a state-driven UI library for the Verse programming language in Fortnite UEFN. It is inspired by the popular React library and aims to provide a similar (as possible) development experience for creating dynamic and interactive UIs in Fortnite.
- State-driven UI components
- Animation support
- Event handling
- Dependency management
To use Veract in your project, include the veract.verse
file with the "veract" folder in your project directory.
Then, import the library in your script:
using { veract }
✅ You're set! You can now start using Veract in your project.
States in Veract are used to manage the dynamic data of your UI components. They allow for real-time updates and can be used to create dynamic UIs and animations.
Veract supports various state types, including:
Primitives:
va_int_state
: Represents anint
state.va_float_state
: Represents afloat
state.va_string_state
: Represents astring
state.va_logic_state
: Represents alogic
state.
Verse/UE General Types:
va_color_state
: Represents acolor
state.va_vector2_state
: Represents avector2
state.va_player_state
: Represents aplayer
state.va_message_state
: Represents amessage
state. *va_texture_state
: Represents atexture
state. *
Widget Types:
va_margin_state
: Represents amargin
state.va_anchor_state
: Represents ananchors
state.va_image_tiling_state
: Represents animage_tiling
state.va_text_justification_state
: Represents atext_justification
state.va_text_overflow_policy_state
: Represents atext_overflow_policy
state.va_horizontal_alignment_state
: Represents ahorizontal_alignment
state.va_vertical_alignment_state
: Represents avertical_alignment
state.va_widget_visibility_state
: Represents awidget_visibility
state.va_ui_input_mode_state
: Represents aui_input_mode
state of a UI.
* - These state types represent non-comparable types, hence the base value has to be converted into a unique
special container before being used. This can be easily done by using the (Value).ToUnique
extension
Additionally, an array of states can be represented using the va_array
type. Specific callback types are also supported, this will be discussed in the following sections.
You can quickly convert basic types to states using the ToState
extension. For example:
var IntState : va_int_state = 10.ToState()
var FloatState : va_float_state = 3.14.ToState()
var StringState : va_string_state = "Hello".ToState()
var TextureState : va_texture_state = MyTexture.ToUnique().ToState()
var LogicState : va_logic_state = true.ToLogicState()
var ArrayState : va_array = array{IntState, FloatState, TextureState}.ToArrayState()
⚠️ For non-comparable types, use theToUnique
extension first.⚠️ logic
types exclusively use theToLogicState
extension.
Using the ToState extension won't be possible in some contexts, for instance in class data-members. In such cases, you can use the va_empty_state
type to create an empty state and set its value later.
The recommended approach is creating container classes for your states, so they can be managed easily from any class. For example:
my_state_container := class:
var MyIntState : va_state = va_empty_state{}
block:
set MyIntState = 10.ToState()
va_state
types can be directly assigned to any va_widget property, as they are implicitly converted to the required state type later. You can still easily access the proper state type for updating/reading it as explained here.
Another way to declare an state is to explicitly define the state type in your class constructor. For example:
my_state_container := class:
var MyIntState : va_int_state = va_int_state{ Value := 10 }
You can use callbacks to return any state type with effects. You must specify the state dependencies for the callback to trigger updates. For example:
...class...
Driving : va_logic_state = va_logic_state{Value:=true}
# or true.ToLogicState() outside data-member definitions
CanUsePhone<private>()<transacts> : va_state =
var CanUsePhone : logic = true
if (Driving.AsLogicState[].Get()):
set CanUsePhone = false
CanUsePhone.ToLogicState()
...ui construction...
Widget := va_button_loud:
Text := "Call".ToState()
Enabled := CanUsePhone.UseEffect(array{Driving})
The UseEffect extension is explained in the Generators section.
You can convert states back to basic types using the As<Type>State
extension in case you only have a va_state
generic reference. For example:
var IntValue : int = IntState.AsIntState[].Get()
var FloatValue : float = FloatState.AsFloatState[].Get()
var StringValue : string = StringState.AsStringState[].Get()
var LogicValue : logic = LogicState.AsLogicState[].Get()
You can update states by calling the Set
method on the state object. For example:
FloatState.Set(1.0) # explicit state update
if (State := IntState.AsIntState[]): # implicit state update
State.Set(3)
Once set, the state will automatically update all the components and dependencies it is linked to.
Generators in Veract are used to dynamically generate UI components based on state changes. They are useful for creating dynamic lists and other components that need to update based on state.
To use a generator, you need to define a callback state and set it as the generator for a widget. For example:
DynamicListGenerator<private>()<transacts> : va_state =
var Slots : []va_stack_box_slot = array{}
for (Entry : DynamicListEntries.Get(), EntryId := Entry.AsStringState[].Get()):
set Slots += array:
va_stack_box_slot:
HorizontalAlignment := horizontal_alignment.Fill.ToState()
VerticalAlignment := vertical_alignment.Top.ToState()
Padding := margin{ Left := 0.0, Top := 10.0 }.ToState()
Widget := va_stack_box:
Orientation := orientation.Horizontal
Slots := array:
va_stack_box_slot:
Padding := margin{ Left := 10.0 }.ToState()
Widget := va_button_loud:
Key := "Del:{EntryId}"
Text := "-".ToState()
OnClick := option{OnRemoveItemClick}
va_stack_box_slot:
Widget := va_button_loud:
Text := Entry
va_array:
Value := Slots + array:
va_stack_box_slot:
HorizontalAlignment := horizontal_alignment.Fill.ToState()
VerticalAlignment := vertical_alignment.Top.ToState()
Padding := margin{ Left := 0.0, Top := 10.0 }.ToState()
Widget := va_button_loud:
Text := "Add Item +".ToState()
OnClick := option{OnAddItemClick}
Then, in a compatible va_widget, set the Generator
property to the callback state, as explained in the section below.
The UseEffect
extension is used to create callbacks with state dependencies. It allows you to specify a list of dependencies, and the callback will be triggered whenever any of the dependencies change.
To use UseEffect
, you need to define a callback state and set its dependencies. For example:
DynamicListGenerator<private>()<transacts> : va_state =
... (Full code from the previous section) ...
Setup<override>()<suspends> : void =
set Canvas = va_canvas:
RootInputMode := ui_input_mode.All.ToState()
Slots := array:
va_canvas_slot:
Anchors := va_anchor.Center.ToAnchor().ToState()
Alignment := va_anchor.Center.ToAlignment().ToState()
Offsets := margin{ Left := 0.0, Top := 0.0, Right := 500.0, Bottom := 500.0 }.ToState()
Widget := va_stack_box:
Generator := DynamicListGenerator.UseEffect(array{DynamicListEntries})
Orientation := orientation.Vertical
Canvas.AddToPlayer(Player)
📌 More on generators: If you don't want to use a generator, you can still set the
Slots
property directly with an array of widgets.
Veract provides it's own wrapper classes for all the available widgets in Fortnite, prefixed with va_
. These classes are used to create UI components and manage their properties. States do not support native widget types, so you must use the Veract wrapper classes to create widgets in all instances.
To create a widget, you can use the Veract wrapper classes. For example:
MyButton := va_button_loud:
Text := "Click Me!".ToState()
OnClick := option{OnButtonClick}
All widgets share the same properties as their native counterparts, but with the added benefit of state support. You can set the properties of a widget using states, as shown in the example above. The only difference you may find is that properties with names such as DefaultText
or DefaultColor
are replaced with Text
or Color
respectively.
If you ever need to access the native widget, you can use the Native
propety on the Veract widget.
Slot classes such as canvas_slot
or stack_box_slot
are also wrapped with va_
classes. These classes also have the same properties as their native counterparts, but with state support.
Additionally, Veract widgets have a Key
property that can be used to uniquely identify the widget. This can be useful when dealing with event handlers without having to create custom "lambda containers". The Key
property is optional and can be omitted if not needed.
To render a widget, you need to have a root widget such as a canvas and then add the canvas to a player. For example:
set Canvas = va_canvas:
Slots := array:
va_canvas_slot:
Offsets := margin{ Left := 0.0, Top := 0.0 }.ToState()
Widget := va_button_loud:
Text := "My Button".ToState()
Then, you can add the canvas to a player:
Canvas.AddToPlayer(Player)
Currently, root widgets only support up to one player. If you need to render UI for multiple players, you will need to create separate root widgets for each player. This may or may not change in the future.
Veract widgets support event handling such as the OnClick
and OnValueChanged
properties. You can set the property to a callback state that will be triggered when the widget is interacted with. For example:
MyButton := va_button_loud:
Text := "Click Me!".ToState()
OnClick := option{OnButtonClick}
The callback handler should take a widget_message
argument and a second argument that is the base class of the widget itself. For example:
OnButtonClick<private>(Message:widget_message, Widget:va_text_button) : void =
print("Button clicked!")
OnValueChanged<private>(Message:widget_message, Widget:va_slider_regular) : void =
print("Value changed!")
You can use the widget argument to access the properties of the widget that triggered the event such as Key
.
Veract Animation Controllers are used to create animations for UI components. They allow you to define animations for any state type using keyframe deltas.
To create an animation controller you can use the va_animation_controller
abstract class. For example:
my_anim_animation_controller := class(va_animation_controller):
StateContainer : my_state_container
block:
set Mode = va_animation_mode.PingPong
set Keyframes = array:
va_keyframe_delta:
Values := array:
va_delta_value:
State := StateContainer.MyScale
Value := vector2{Y := 30.0 }.ToState()
va_delta_value:
State := StateContainer.MyOffset
Value := margin{Top := -50.0 }.ToState()
my_ui_class := class:
StateContainer : my_state_container
MyAnimController : my_anim_animation_controller
...
Setup() : void =
set Canvas = va_canvas:
Slots := array:
va_canvas_slot:
Offsets := StateContainer.MyOffset
Widget := va_button_loud:
Text := "Animated!".ToState()
MyAnimController.Play()
Here's the breakdown of the animation controller class:
- *
Mode
: The animation mode, which can beva_animation_mode.PingPong
,va_animation_mode.Loop
, orva_animation_mode.OneShot
. Keyframes
: An array of keyframe deltas (va_keyframe_delta
) that define the animation.- *
Values
: An array of delta values (va_delta_value
) that define the state changes for each keyframe.- *
State
: The state to animate. - *
Value
: The value to animate to as a state of the same type. Interpolation
: The interpolation method used for the transition between the last keyframe and the current one, which can beva_interpolation_types.Linear
,va_interpolation_types.EaseIn
, orva_interpolation_types.EaseOut
, orva_interpolation_types.EaseInOut
. You can also create your own interpolation using ava_cubic_bezier_parameters
struct.
- *
- *
To use an animation controller, you need to call the Play
method on the controller. This will start the animation based on the keyframes and mode you defined.
When using multiple animation controllers for the same states, ensure you stop any other currently active controllers with the Stop
method before starting a new one. Additionally, ensure the active controller is fully stopped by awaiting the WaitUntilAnimationStops
method. If you don't follow these steps, the animations WILL conflict with each other and cause horrendous lag and bugs.
Please check the examples in the demo
directory for more practical usage.
- Always manage the lifecycle of your animation controllers to avoid unintended behavior.
- Use
Stop
before starting new animations on the same states to keep performance optimal. - Utilize
WaitUntilAnimationStops
to ensure smooth transitions between animations. - Avoid using animations in a root widget with player input enabled, as this can block input. Use animations in child widgets instead.
Examples can be found in the demo
directory.
Veract_Animations_DynList.mp4
Contributions are welcome! This library is still in development and was made to help the Fortnite UEFN community. If you have any suggestions, bug reports, or feature requests, feel free to open an issue or submit a pull request. Also, it was made by a single person, so there may be some bugs or issues that need to be fixed.
Veract is licensed under the MIT License. See the LICENSE file for more information.