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.
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 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:
And this is the formula that we're using to ensure that animations run smoothly.
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
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.
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)
Effect Present
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
Effect Present
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
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:
Effect Present
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)
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.
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:
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
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
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: