====== UI Animations ======
===== Intro =====
Creating cool UI animations can make your game feel more polished and fun. Scripts are a powerful tool to add dynamic effects to your game interface. In this guide, we’ll show you how to use Lua to bring your UI to life with smooth animations, from basic fades to slick slides. If you’re just starting out or looking to improve your game, we've got you covered. this guide will help you get the hang of scripting UI animations in Lua.
===== Guide =====
In this article, you will learn how to create and implement various types of UI animations using scripts. By the end of this guide, you will have knowledge of:
- Using the CustomUI Class: Learn how to utilize the CustomUI class to manipulate UI elements dynamically and create animations.
- Learn about multithreading
- Learn how to create the following animations with scripts. Opacity, linear movement, rotation, zoom, combined
- Learn how to use linear interpolation
===== Linear interpolation basics =====
Linear interpolation is a method of estimating unknown values that lie within the range of two known values, the formula that we're going to use is for linear step interpolation.:
Step = (TargetValue - StartingValue) / (Time / TimeBetweenChanges)
Where:
* Step: The value by by which the X that we have value is increased at each time interval.
* TargetValue: Its the value that you want to reach through interpolation.
* StartingValue: This is the starting value from which the interpolation begins. It serves as the starting point or initial value.
* Time: Its denotes the total time or time duration over which the interpolation occurs. It represents the total span over which you will compute the steps.
* TimeBetweenChanges: This value represents the time step size or the interval at which each interpolation step is performed.
And this is the formula that we're using to ensure that animations run smoothly.
===== 1. Opacity Animation =====
This animation changes the transparency of a UI element over time, creating, for example, fade-in or fade-out effects. This can be useful for transitions, highlighting elements, or creating dynamic visual effects.
**Effect Present**
{{ :developer_center:developer_editor:script:ui_opacity_animation_present.webm?600&direct |}}
==== Explanation ====
We're going to explain how to create the opacity animation, the explanation can be used to create any other animation, as they follow the same principles.
First, we make a function that takes the playeruid (to update the uid to the player), the id of the ui (uiid), the element that we're going to update. The initial opacity, The final opacity, and the animation time.
We will increment the current opacity every 0.05 seconds, so using the linear step interpolation formula, we get that **AlphaStep = (FinalOpacity-InitialOpacity)/(AnimationTime/0.05)**, this will be the value that we're going to increment the Current opacity by, after that, we create a variable that holds the current opacity and set it to be the initial opacity.
Then we calculate the number of "steps" that we're going to use in our for loop to ensure that the opacity reaches the target value, we do this with (AnimationTime/0.05), after that, we create a for loop with this step variable that we have, where we add the (CurOpacity + AlphaStep) and we use the Customui:setAlpha() to the CurOpacity. we then wait 0.05 seconds, so there aren't any problems in the execution of the code.
==== Script ====
function opacity_animation(playerid,uiid,element,InitialOpacity,FinalOpacity,AnimationTime) --Opacity Animation Function
local AlphaStep = (FinalOpacity-InitialOpacity)/(AnimationTime/0.05) --Set the opacity change of each for loop
local CurOpacity = InitialOpacity --Set the initial opacity
local IterationNum = AnimationTime/0.05
for i = 1, IterationNum do --Based on the time, set the loop value
CurOpacity = CurOpacity + AlphaStep --Set the opacity current value
Customui:setAlpha(playerid,CurUi.Id,CurUi.AnimElem,CurOpacity) --Modify component opacity
threadpool:wait(0.05) --Wait for 0.05 seconds
end
end
function ubc(e)
local p = e.eventobjid
local uuid = e.CustomUI
local el = e.uielement
--opacity_animation(playerid,UIid,element,starting value, ending value, animation duration)
opacity_animation(p,uiid,el,100,0,0.5)--Fade out Animation
opacity_animation(p,uiid,el,0,100,0.5)--Fade in Animation
end
--Register a listener to run when the any button is clicked by any player
ScriptSupportEvent:registerEvent("UI.Button.Click",ubc)
===== 2. Linear movement Animation =====
**Effect Present**
{{ :developer_center:developer_editor:script:ui_linear_animation_present.webm?600&direct |}}
==== Script ====
**Please read the comments through the script below**
function linear_animation(playerid, uiid, element, StartX, StartY, TargetX, TargetY, AnimationTime)
local StepX = (TargetX - StartX) / (AnimationTime / 0.05) -- Calculate the value x will increase every iteration
local StepY = (TargetY - StartY) / (AnimationTime / 0.05) -- Calculate the value y will increase every iteration
local CurX = StartX
local CurY = StartY
local IterationNum = AnimationTime / 0.05 -- Calculate the number of iterations
for i = 1, IterationNum do
CurX = CurX + StepX -- Update the current x-coordinate
CurY = CurY + StepY -- Update the current y-coordinate
Customui:setPosition(playerid, uiid, element, CurX, CurY) -- Set the new position of the component
threadpool:wait(0.05) -- Wait for 0.05 seconds
end
end
function ubc(e)
local p = e.eventobjid
local uuid = e.CustomUI
local el = e.uielement
--opacity_animation(playerid,UIid,element,starting value, ending value, animation duration)
linear_animation(p,uiid,el,150,190,600,300,1)--Animate to new coodinate
linear_animation(p,uiid,el,600,300,150,190,1)--Animate to original coodinate
end
--Register a listener to run when the any button is clicked by any player
ScriptSupportEvent:registerEvent("UI.Button.Click",ubc)
**Please noted that the starting point coodinate(x=150,y=190) should be same as the UI coodinate**
{{ :developer_center:developer_editor:script:ui_linear_animation_explain.png?600&direct |}}
===== 3. Rotate Animation =====
**Effect Present**
{{ :developer_center:developer_editor:script:ui_rotate_animation_present.webm?600&direct |}}\\
==== Script ====
**Please read the comments through the script below**\\
function rotate_animation(playerid, uiid, element, StartAngle, TargetAngle, AnimationTime) --Rotate Animation
local StepAngle = (TargetAngle - StartAngle) / (AnimationTime / 0.05) -- Calculate the increment for angle rotation for every iteration
local CurAngle = StartAngle -- Initialize the current angle
local IterationNum = AnimationTime / 0.05 -- Calculate the number of iterations
for i = 1, IterationNum do -- Loop through each iteration
CurAngle = CurAngle + StepAngle -- Update the current angle by adding the increment
Customui:rotateElement(playerid, uiid, element, CurAngle) -- Set the new rotation angle of the component
threadpool:wait(0.05) -- Wait for 0.05 seconds
end
end
function ubc(e)
local p = e.eventobjid
local uuid = e.CustomUI
local el = e.uielement
--opacity_animation(playerid,UIid,element,starting value, ending value, animation duration)
rotate_animation(p,uiid,el,0,360,1)
end
--Register a listener to run when the any button is clicked by any player
ScriptSupportEvent:registerEvent("UI.Button.Click",ubc)
**As you can see, the element will rotate 360 degrees in 1 second. But for better rotation we expect the element to be centered. How can we present it?**\\
** * Create a new parent element**
{{ :developer_center:developer_editor:script:ui_rotate_image2.png?600&direct |}}\\
{{ :developer_center:developer_editor:script:ui_rotate_image1.png?600&direct |}}\\
**We create a new parent element with the width and height zero, and put the orignal element as subset element.**\\
**Then, we have to replace the "el" variable to new parent element. **\\
local el = [[7258119162206233205_3]]--Replace to your element id that want to animate
**Here is the result:**\\
{{ :developer_center:developer_editor:script:ui_rotate_animation_present_2.webm?600&direct |}}
===== 4. Zoom Animation =====
**Effect Present**
{{ :developer_center:developer_editor:script:zoom_animation_present.webm?600&direct |}}\\
==== Script ====
**Please read the comments through the script below**\\
function zoom_animation(playerid, uiid, elementid, StartWidth, StartHeight, TargetWidth, TargetHeight, AnimationTime)
local StepWidth = (TargetWidth - StartWidth) / (AnimationTime / 0.05)
local StepHeight = (TargetHeight - StartHeight) / (AnimationTime / 0.05)
local CurWidth = StartWidth
local CurHeight = StartHeight
local IterationNum = AnimationTime / 0.05
for i = 1, IterationNum do
CurWidth = CurWidth + StepWidth
CurHeight = CurHeight + StepHeight
Customui:setSize(playerid, uiid, elementid, CurWidth, CurHeight)
threadpool:wait(0.05)
end
end
function ubc(e)
local p = e.eventobjid
local uiid = e.CustomUI
local el = e.uielement
zoom_animation(p, uiid, el, 120, 126, 360, 376, 1)
zoom_animation(p, uiid, el, 360, 376, 120, 126, 1)
end
ScriptSupportEvent:registerEvent("UI.Button.Click", ubc)
===== 5. Combined Animation =====
Before starting with this, we will need to explain something called "multithreading", as it is important when playing more than 2 animations at any given time.
==== What is multithreading ====
The game is run in multiple of this so called "threads of execution". Threads are like different workers in a team. Usually, the scripts run in a single thread, this thread is called the **Main thread** and when we do this, we are running it **Concurrently**. Imagine this, you want to build a house, and you only have 1 worker (thread). This worker would have to do everything secuentially, for example, build the walls, build the roof, add decorations, etc. Multithreading uses multiple workers or threads to achieve a task, so, if we have more workers (threads), One worker would build the wall, another the roof, another would decorate and so on, when we do this, we are running it in **Parallel**.
As the script by default run in only a single thread, we are executing it concurrently. When we try executing the two animations concurrently, They are executed in an interleaved manner on a single thread. Because this is concurrent execution, the tasks make progress in overlapping time periods, but they are not truly running simultaneously.
But if we execute them in different threads, we will explain later how to do this, the two animations are running on separate threads. This is parallel execution because the tasks are running simultaneously, or in parallel in different threads.
Here's an illustration on how this works:
{{ :developer_center:developer_editor:script:miniparallelprogramming.png?nolink&400 |}}
==== How to achieve multithreading ===
We can achieve it using a method that the game provides. This method is **threadpool:work()** and it isn't just useful for animations, it is useful for many things, ranging from executing things in the background, to updating graphics and leaderboards in real time, to scheduling tasks, etc. The way this method works, is that we provide it with a function, and it executes it for us in a different thread.
**Effect Present**
{{ :developer_center:developer_editor:script:combined_animation_present.webm?600&direct |}}\\
==== Script1 ====
local function ubc(e)
local playerid = e.eventobjid -- Set the player
local uiid = "7258119162206233205" -- Set the UI
local el = "7258119162206233205_2" -- Set the element
local o_locat = {x=-60,y=-63,w=120,h=126} -- Original location of the element
-- Translation animation
linear_animation(playerid, uiid, el, o_locat.x, o_locat.y, o_locat.x + 200, o_locat.y + 200, 1)
-- Rotation animation
rotate_animation(playerid, uiid, el, 0, 360, 1)
-- Scaling animation
zoom_animation(playerid, uiid, el, o_locat.x, o_locat.y, o_locat.x + 200, o_locat.y + 200, 1)
-- Opacity animation
opacity_animation(playerid, uiid, el, 100, 0, 1)
end
ScriptSupportEvent:registerEvent("UI.Button.Click", ubc)
**This code snippet defines a function ubc that is registered as an event handler for the "UI.Button.Click" event. Within the function, it sets the player ID, UI ID, and element ID. Then, it applies four different animations to the element: translation, rotation, scaling, and opacity animations. Each animation has its own function (linear_animation, rotate_animation, zoom_animation, opacity_animation) and parameters are passed to these functions to define the specific animation behavior.**\\
**Combining multiple animations simultaneously is not considered a true combined animation. In order to play multiple animations at the same time, we need to utilize the multi-threading function __threadpool:work()__.\\
Before using this function, it is important to understand the concept of threads and multi-threading.**\\
**Effect Present**
{{ :developer_center:developer_editor:script:combined_animation_present1.webm?600&direct |}}\\
==== Script2 ====
threadpool:work(function()
-- Translation animation in a new thread
linear_animation(playerid, uiid, el, o_locat.x, o_locat.y, o_locat.x + 200, o_locat.y + 200, 1)
end)
-- Opacity animation
opacity_animation(playerid, uiid, el, 0, 100, 1)
ScriptSupportEvent:registerEvent("UI.Button.Click", ubc)
This is what everything does:
* threadpool:work(function() ... end): Executes the enclosed function in a new thread, allowing the linear animation to run concurrently with the opacity animation.
* ubc function: Triggered when a button is clicked, applying translation and opacity animations simultaneously to the UI element using multithreading.