koragg Posted June 26, 2020 Share Posted June 26, 2020 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
JeViCo Posted June 26, 2020 Share Posted June 26, 2020 (edited) 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 June 26, 2020 by JeViCo upd 1 Link to comment
koragg Posted June 26, 2020 Author Share Posted June 26, 2020 @JeViCo it's not really my function, just used the resource how it was when I downloaded it. Don't wanna touch it if it works ok as it is Only wanna create the animation I mentioned. Link to comment
Moderators IIYAMA Posted June 26, 2020 Moderators Share Posted June 26, 2020 (edited) @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 June 26, 2020 by IIYAMA Link to comment
koragg Posted June 26, 2020 Author Share Posted June 26, 2020 wow @IIYAMA thanks, I'll need some days to see how this works and if I can use it 1 Link to comment
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now