Jump to content

[HELP] interpolateBetween animation


koragg

Recommended Posts

Hello guys,

I have the below script which creates a small notification at the top center of the screen. I added a /showmsg command for testing purposes. If I type /showmsg once and then after 1 second type it again the new notification would go below the previous one with a little animation. But the problem is that when the first notification disappears, the second just switches places with it with no animation at all. I'd like to have a smooth animation transition for the second notification when the first notification disappears and the second takes its place. If you test it out yourself you'll see what I mean.

--triggerClientEvent(source, "addNotification", root, "text", 2)
--1=success, 2=warning, 3=error

local x, y = guiGetScreenSize()
local sx, sy = x/1440, y/900
local notifications = {}
-------------------------------------------------------------------------------------------------------------------------
addEvent("addNotification", true)
function outputNotification(text, level)
	if type(text) == "string" and text ~= "" and type(level) == "number" then
		local image = "images/succes.png"
		if level == 1 then
			image = "images/succes.png"
		elseif level == 2 then
			image = "images/warn.png"
		elseif level == 3 then
			image = "images/error.png"
		end
		table.insert(notifications, {text = text, level = image, tick = getTickCount(), animPos = {0,0}, state = "up"})
		return true
	else
		outputDebugString("outputNotification: Bad arg",3,255,0,0)
		return false
	end
end
addEventHandler("addNotification", root, outputNotification)
-------------------------------------------------------------------------------------------------------------------------
addEventHandler("onClientRender", root,
function()
	for id, notif in ipairs(notifications) do
		if id == 1 and #notifications >= 7 then
			notif.tick = getTickCount()
			notif.state = "down"
		end
		if notif.state == "up" then
			notif.animPos[1], notif.animPos[2] = interpolateBetween(0,0,0,1,1,0,getProgress(750,notif.tick),"InOutQuad")
			if notif.animPos[1] == 1 then
				notif.tick = getTickCount()
				notif.state = "idle"
			end
		end
		if notif.state == "idle" then
			if notif.tick + (5*1000) <= getTickCount() then
				notif.tick = getTickCount()
				notif.state = "down"
			end
		end
		if notif.state == "down" then
			notif.animPos[2] = interpolateBetween(1,0,0,0,0,0,getProgress(750,notif.tick),"InOutQuad")
			if notif.animPos[2] == 0 then
				table.remove(notifications, id)
				return
			end
		end
		dxDrawRectangle(x*0.439583, y*0.064815+((y*0.04167*(id-1))*notif.animPos[1]), x*0.11979167, y*0.037, tocolor(20,20,20,255*notif.animPos[2]), true)
		dxDrawImage(x*0.4421875, y*0.06852+((y*0.04167*(id-1))*notif.animPos[1]), x*0.0167, y*0.03, notif.level, 0, 0, 0, tocolor(255,255,255,255*notif.animPos[2]), true)
		dxDrawShadowText(notif.text, x*0.4640625, y*0.064815+((y*0.04167*(id-1))*notif.animPos[1]), x*0.090104167, y*0.037, tocolor(255,255,255,255*notif.animPos[2]), 0.93*sy, "default-bold", "left", "center", true, true, true, true, false)
	end
end, true, "low-9")
-------------------------------------------------------------------------------------------------------------------------
function getProgress(addtick, tick)
	local now = getTickCount()
	local elapsedTime = now - tick
	local duration = tick+addtick - tick
	local progress = elapsedTime / duration
	return progress
end
-------------------------------------------------------------------------------------------------------------------------
function dxDrawShadowText(text, x, y, width, height, color, scale, font, alignX, alignY, clip, wordBreak, postGUI, colorCoded, subPixelPositioning)
	width = x+width
	height = y+height
	local alpha = string.format("%08X", color):sub(1,2)
	dxDrawText(text:gsub("#%x%x%x%x%x%x", ""), x, y+scale, width, height, tocolor(getColorFromString("#000000"..alpha)), scale, font, alignX, alignY, clip, wordBreak, postGUI, colorCoded, subPixelPositioning)
	dxDrawText(text, x, y, width, height, color, scale, font, alignX, alignY, clip, wordBreak, postGUI, colorCoded, subPixelPositioning)
end
-------------------------------------------------------------------------------------------------------------------------
function testing()
	triggerEvent("addNotification", localPlayer, "text", 2)
end
addCommandHandler("showmsg", testing)

And since the notification has images too here's the whole resource: https://mega.nz/file/1tgACYBa#Z-etw39cdYNrp-gk7lTasSlfUlrM-rL0IweYbLbHhbA

I'm really bad with interpolateBetween and would be very thankful if someone can make it show a transition when a previous notification vanishes and the next one after it takes its place. :)

Link to comment

You can simplify your getProgress function by using start tick:

function getProgress(startTick, time)
	return (getTickCount() - startTick) / time
end
-- startTick specifies the starting point
-- time - amount of time to use to finish the progress

I would recommend you to use metatables to create simultaneous animations

Edited by JeViCo
upd 1
Link to comment
  • Moderators

@koragg

Unfortunately I will be away for a long while, so I can't test it out. Please ignore the following if this is not helpful.

The code below was one of my experiments to create an animator. While it maybe working very well, it also was not worthed to add something as big inside of my resources, because of the size and feature count.

But what it does is setting up parameters/arguments and adding animations to them.

opacityStart = 0

opacityStart = 255

opacityStart = 0

 

So you might consider use it as inspiration to creating your own animator, smaller/bigger. (or copy this...)

 

Spoiler

-- this is a sample as well as the syntax
setTimer(
    function()
        local sample = {
            id = "sample",
            duration = 5000,
            --or "infinity" / "inf"
            delay = 1000,
            func = function(param)
                local rectangleSize = 100 * param.scaleFactorY
                dxDrawRectangle(
                    param.screenCenterX - rectangleSize / 2,
                    param.screenCenterY - rectangleSize / 2,
                    rectangleSize,
                    rectangleSize,
                    tocolor(255, 0, 0, param.opacityStart)
                )
            end,
            -- optional 1
            parameters = {opacityStart = 0},
            -- optional 2 (requires optional 1)
            animations = {
                {
                    parameter = "opacityStart",
                    from = 0,
                    to = 255,
                    duration = 2000,
                    delay = 0,
                    -- optional 3
                    easingType = "OutInBounce",
                    easingAmplitude = 1.0
                    --
                },
                {parameter = "opacityStart", from = 255, to = 0, duration = 2000, delay = 2000}
            }
        }
        createAnimatedContent(sample)
    end,
    100,
    1
)

 


 

 

Spoiler

local screenWidth, screenHeight = guiGetScreenSize()
local activeAnimatedContent = {}
local idCounter = 0
local renderAnimations
function createAnimatedContent(content)
    local duration
    if content.duration == "infinity" or content.duration == "inf" then
        duration = "inf"
    else
        duration = tonumber(content.duration)
    end
    if duration and (type(content.func) == "function" or type(content.func) == "string") then
        local delay = tonumber(content.delay) or 0
        local id = content.id
        if id then
            removeAnimatedContentById(id)
        end
        local startTime = getTickCount() + delay
        local animations = content.animations
        if animations then
            for i = #animations, 1, -1 do
                local animation = animations[i]
                local removeAnimation = true
                -- check is animation is OK
                do
                    if not animation.parameter then
                        break
                    end
                    animation.from = tonumber(animation.from)
                    if not animation.from then
                        break
                    end
                    animation.to = tonumber(animation.to)
                    if not animation.to then
                        break
                    end
                    animation.difference = animation.to - animation.from
                    animation.duration = tonumber(animation.duration)
                    if not animation.duration then
                        break
                    end
                    removeAnimation = false
                end
                -- prepare the time data.
                animation.delay = tonumber(animation.delay) or 0
                animation.startTime = startTime + animation.delay
                animation.endTime = startTime + animation.delay + animation.duration
                if removeAnimation then
                    table.remove(animations, i)
                end
                --
            end
        end
        -- give it a new id if it hasn't one.
        if not id then
            idCounter = idCounter + 1
            id = "anim:" .. idCounter .. ":" .. getRealTime().timestamp
        end
        local parameters = content.parameters or {}
        -- pre defined parameters --
        parameters.screenWidth = screenWidth
        parameters.screenHeight = screenHeight
        parameters.screenCenterX = screenWidth / 2
        parameters.screenCenterY = screenHeight / 2
        parameters.scaleFactorX = 1 / 1920 * screenWidth
        parameters.scaleFactorY = 1 / 1080 * screenHeight
        if content.isLoadStringFunction then
            content.func = loadstring("return function(param) " .. content.func .. " end")
        end
        -- Move the content to a new table.
        activeAnimatedContent[#activeAnimatedContent + 1] = {
            id = id,
            startTime = startTime,
            endTime = type(duration) == "string" and duration or startTime + duration,
            duration = duration,
            delay = delay,
            parameters = parameters,
            animations = animations,
            func = content.func,
            isLoadStringFunction = content.isLoadStringFunction
        }
        addRenderEvent(renderAnimations)
        return id
    end
    return false
end
function removeAnimatedContentById(id)
    for i = 1, #activeAnimatedContent do
        if activeAnimatedContent[i].id == id then
            table.remove(activeAnimatedContent, i)
            return true
        end
    end
    return false
end
function renderAnimations()
    local timeNow = getTickCount()
    for i = #activeAnimatedContent, 1, -1 do
        local animatedContent = activeAnimatedContent[i]
        local animations = animatedContent.animations
        local parameters = animatedContent.parameters
        -- loop through all animations
        if animations then
            for j = 1, #animations do
                local animation = animations[j]
                -- can we start the animation? or has it already ended?
                if not animation.finished and timeNow > animation.startTime then
                    local parameter = animation.parameter
                    if timeNow < animation.endTime then
                        local progress = ((timeNow - animation.startTime) / animation.duration)
                        -- edit a parameter
                        local value
                        if animation.easingType then
                            value =
                                animation.difference *
                                getEasingValue(
                                    progress,
                                    animation.easingType,
                                    animation.easingPeriod,
                                    animation.easingAmplitude,
                                    animation.easingOvershoot
                                )
                        else
                            value = (progress * animation.difference)
                        end
                        parameters[parameter] = value + animation.from
                    else
                        -- animation has finished
                        local value = animation.to
                        parameters[parameter] = value
                        animation.finished = true
                    end
                end
            end
        end
        if animatedContent.endTime == "inf" or timeNow <= animatedContent.endTime then
            -- call the attached function
            if not animatedContent.isLoadStringFunction then
                animatedContent.func(parameters)
            else
                animatedContent.func()(parameters)
            end
        else
            table.remove(activeAnimatedContent, i)
        end
    end
    if #activeAnimatedContent == 0 then
        removeRenderEvent(renderAnimations)
    end
end

 

Uses https://forum.multitheftauto.com/topic/103866-enchantment-client-render-events-enchanting/

 

 

 

Edited by IIYAMA
Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...