quad/src/libs/AdvancedTween/init.lua

459 lines
14 KiB
Lua

---@class quad_module_tween
local module = {}
------------------------------------
-- 기본함수, 필요 모듈 가져오기
------------------------------------
local type = typeof or type
local clock = os.clock
local tonumber = tonumber
local remove = table.remove
local insert = table.insert
local find = table.find
local floor = math.floor
local script = script
local EasingFunctions = require(script and script.EasingFunctions or "EasingFunctions")
local Stepped = require(script and script.Stepped or "Stepped")
local BindedFunctions = {} -- 애니메이션을 위해 프레임에 연결된 함수들
local FunctionTargetItem = setmetatable({},{__mode="kv"}) -- 함수가 타겟으로 하는 아이템
module.PlayIndex = setmetatable({},{__mode = "k"}) -- 애니메이션 실행 스택 저장하기
-- PlayIndex[Item][Property] = 0 or nil <= 트윈중이지 않은 속성
-- PlayIndex[Item][Property] = 1... <= 트윈중인 속성
------------------------------------
-- Easing 스타일
------------------------------------
module.EasingFunctions = {
Linear = EasingFunctions.Linear; ---직선, Linear. i=x, x=0 ~ 1
Quint = EasingFunctions.Quint; ---5제곱, Quint. (^5) i=1-(1-x)^5, x=0 ~ 1
Quart = EasingFunctions.Quart; ---4제곱, Quart. (^4) i=1-(1-x)^4, x=0 ~ 1
Cubic = EasingFunctions.Cubic; ---3제곱, Cubic. (^3) i=1-(1-x)^3, x=0 ~ 1
Quad = EasingFunctions.Quad; ---2제곱, Quad. (^2) i=1-(1-x)^2, x=0 ~ 1
Sin = EasingFunctions.Sin; ---사인파, Sin. i=sin(x*pi/2), x=0 ~ 1
Circle = EasingFunctions.Circle; ---사분원, Circle. i=sqrt(1-(1-x)^2), x=0 ~ 1
Expo = EasingFunctions.Expo; ---지수함수, Expo. i=1-2^(-10*x), x=0~1
Elastic = EasingFunctions.Elastic; ---튀어오름, Elastic.
Bounce = EasingFunctions.Bounce; ---통통거림, Bounce
-- Old things
Exp2 = EasingFunctions.Exp2; ---덜 가파른 지수. i=math.exp(x), x=-4 ~ 2
Exp4 = EasingFunctions.Exp4; ---더 가파른 지수. i=math.exp(x), x=-4 ~ 4
Exp2Max4 = EasingFunctions.Exp2; ---@deprecated
}
for i,v in pairs(EasingFunctions) do
module.EasingFunctions[i] = v
end
module.EasingDirections = {
Out = "Out"; -- 반전된 방향
In = "In" ; -- 기본방향
InOut = "InOut"; -- 두 방향 모두 가감속
}
---@deprecated
module.EasingDirection = module.EasingDirections
---@deprecated
module.EasingFunction = module.EasingFunctions
------------------------------------
-- Lerp 함수
------------------------------------
-- 이중 선형, Alpha 를 받아서 값을 구해옴
local function Lerp(start,goal,alpha)
if alpha == 1 then return goal end
if alpha == 0 then return start end
return start + ((goal - start) * alpha)
end
-- 기본적으로 로블록스가 Lerp 를 지원하는 Class
local DefaultLerpableItems = {
["Vector2"] = true;
["Vector3"] = true;
["CFrame" ] = true;
}
-- 예전 값,목표 값,알파를 주고 각각 해당하는 속성에 입력해줌
-- 기본적으로 모든 속성값 적용은 여기에서 이루워짐
local function LerpProperties(Item,Old,New,Alpha,Setter)
for Property,OldValue in pairs(Old) do
local NewValue = New[Property]
local Type = type(OldValue)
local Value
if Type ~= type(NewValue) then
error(
("Unable to lerp property '%s' of '%s' due to type invalid. Old value type is '%s'. but New value type is '%s'")
:format(
tostring(Type),
tostring(type(NewValue))
)
)
elseif DefaultLerpableItems[Type] then
Value = OldValue:Lerp(NewValue,Alpha)
elseif Type == "number" then
Value = Lerp(OldValue,NewValue,Alpha)
elseif Type == "UDim2" then
Value = UDim2.new(
Lerp(OldValue.X.Scale ,NewValue.X.Scale ,Alpha),
Lerp(OldValue.X.Offset,NewValue.X.Offset,Alpha),
Lerp(OldValue.Y.Scale ,NewValue.Y.Scale ,Alpha),
Lerp(OldValue.Y.Offset,NewValue.Y.Offset,Alpha)
)
elseif Type == "UDim" then
Value = UDim.new(
Lerp(OldValue.Scale ,NewValue.Scale ,Alpha),
Lerp(OldValue.Offset,NewValue.Offset,Alpha)
)
elseif Type == "Color3" then
Value = Color3.fromRGB(
Lerp(floor(OldValue.r*255),floor(NewValue.r*255) ,Alpha),
Lerp(floor(OldValue.g*255),floor(NewValue.g*255),Alpha),
Lerp(floor(OldValue.b*255),floor(NewValue.b*255) ,Alpha)
)
else
error(
("Unable to lerp property '%s' of '%s'. Old value type is '%s'. but tweenable types are UDim2, UDim, Color3, Vector2, Vector3, CFrame, number only")
:format(
tostring(Property),
tostring(Item),
tostring(Type)
)
)
end
if Setter then
Setter(Item,Property,Value)
else
Item[Property] = Value
end
end
end
module.LerpProperties = LerpProperties
-- Easing 값을 계산함
local function CalcEasing(Easing,Direction,Reverse,Index)
if Index >= 1 then
return 1
elseif Direction == "Out" then
if Reverse then
return 1 - Easing(1 - Index)
else
return Easing(Index)
end
elseif Direction == "In" then
if Reverse then
return Easing(Index)
else
return 1 - Easing(1 - Index)
end
elseif Direction == "InOut" then
if Index<0.5 then
-- In
if Reverse then
return Easing(Index*2)/2
else
return 0.5 - Easing(1 - Index*2)/2
end
else
-- Out
if Reverse then
return 1 - Easing(2-Index*2)/2
else
return Easing(Index*2-1)/2+0.5
end
end
end
end
function module.CalcEasing(Data_Easing,Direction,X)
local Reverse
local Easing do
Data_Easing = Easing or EasingFunctions.Exp2
local Data_EasingType = type(Data_Easing)
if Data_EasingType == "string" then
Data_Easing = EasingFunctions[Data_Easing]
Data_EasingType = "table"
end
Easing = (Data_EasingType == "function" and Data_Easing) or (Data_EasingType == "table" and Data_Easing.Run)
if Data_EasingType == "table" and Data_Easing.Reverse then
Reverse = true
end
end
return CalcEasing(Easing,Direction,Reverse,X)
end
------------------------------------
-- 모듈 함수 지정
------------------------------------
-- 트윈 메서드 지정, 트윈을 만들게 됨
-- Item : 트윈할 인스턴트
-- Data : 트윈 정보들 (태이블)
--Data.Time (in seconds, you can use 0.5 .. etc)
--Data.Easing (function)
--Data.Direction ("Out" , "In")
--Data.CallBack 콜백 함수들(태이블), 예시 :
--Data.CallBack[0.5] = function() end 다음과 같이 쓰면 인덱스가 정확히 0.5 가 되는 순간(시간이 아니라 이징 함수에 의해 나온 값이 같아지는 순간)
--해당 함수가 실행됨
--Properties : 트윈할 속성과 목표값 예시 :
--Data.Properties.Position = UDim2.new(1,0,1,0) 처럼 하면 Position 속성의 목표를 1,0,1,0 으로 지정
function module.RunTween(Item,Data,Properties,Ended,OnStepped,Setter,Getter,_)
OnStepped = OnStepped or Data.OnStepped
Ended = Ended or Data.Ended
Setter = Setter or Data.Setter
Getter = Getter or Data.Getter
-- remove self
if Item == module then warn "AdvancedTween:RunTween() is deprecated, Use AdvancedTween.RunTween() instead"; Item = Data; Data = Properties; Properties = Ended; Ended = OnStepped; OnStepped = Setter; Setter = Getter; Getter = _; end
-- 시간 저장
local Time = Data.Time or 1
local EndTime = clock() + Time
-- 플레이 인덱스 저장
local ItemPlayIndex = module.PlayIndex[Item]
if ItemPlayIndex then
for _,stepFunc in ipairs(BindedFunctions) do
if FunctionTargetItem[stepFunc] == Item then
stepFunc() -- 해당 애니메이션을 미리 계산해서 지금 위치를 저장
end
end
ItemPlayIndex = module.PlayIndex[Item]
end
if not ItemPlayIndex then
ItemPlayIndex = {}
module.PlayIndex[Item] = ItemPlayIndex
end
-- 예전의 트윈을 덮어쓰고 현재 값을 저장함 !이전 트윈은 파기됨
local ThisPlayIndex = {}
local LastProperties = {}
for PropertyName,_ in pairs(Properties) do
if Getter then
LastProperties[PropertyName] = Getter(Item,PropertyName)
else
LastProperties[PropertyName] = Item[PropertyName]
end
local PlayIndex = (ItemPlayIndex[PropertyName] or 0) + 1
ItemPlayIndex[PropertyName] = PlayIndex
ThisPlayIndex[PropertyName] = PlayIndex
end
-- 이징 효과 가져오기
local Direction = Data.Direction or "Out"
local Reverse
local Easing do
local Data_Easing = Data.Easing or EasingFunctions.Exp2
local Data_EasingType = type(Data_Easing)
if Data_EasingType == "string" then
Data_Easing = EasingFunctions[Data_Easing]
Data_EasingType = "table"
end
Easing = (Data_EasingType == "function" and Data_Easing) or (Data_EasingType == "table" and Data_Easing.Run)
if Data_EasingType == "table" and Data_Easing.Reverse then
Reverse = true
end
end
-- 중간중간 실행되는 함수 확인
local CallBack = Data.CallBack
if CallBack then
for FncIndex,Fnc in pairs(CallBack) do
if type(Fnc) ~= "function" or (type(tonumber(FncIndex)) ~= "number" and FncIndex ~= "*") then
warn(
("Unprocessable callback function got, Ignored.\n - key: %s value: %s")
:format(tostring(Fnc),tostring(FncIndex))
)
CallBack[FncIndex] = nil
end
end
end
-- 스탭핑
local Step
Step = function()
-- 아에 멈추게 되는 경우
if module.PlayIndex[Item] == nil then
remove(BindedFunctions,find(BindedFunctions,Step))
return
end
-- 다른 트윈이 속성을 바꾸고 있다면(이후 트윈이) 그 속성을 건들지 않도록 없엠
local StopByOther = true
for PropertyName,PlayIndex in pairs(ThisPlayIndex) do
if PlayIndex ~= ItemPlayIndex[PropertyName] then
LastProperties[PropertyName] = nil
Properties[PropertyName] = nil
ThisPlayIndex[PropertyName] = nil
else
StopByOther = false
end
end
local Now = clock()
local Index = 1 - (EndTime - Now) / Time
local Alpha
if Now >= EndTime then
Index = 1
Alpha = 1
else
Alpha = CalcEasing(Easing,Direction,Reverse,Index)
end
-- 속성 Lerp 수행
LerpProperties(
Item,
LastProperties,
Properties,
Alpha,
Setter
)
-- 만약 다른 트윈이 지금 트윈하고 있는 속성을 모두 먹은경우 현재 트윈을 삭제함
if StopByOther then
remove(BindedFunctions,find(BindedFunctions,Step))
return
end
-- 끝남
if Index == 1 then
for Property,_ in pairs(Properties) do
ItemPlayIndex[Property] = 0
end
if next(ItemPlayIndex) then
module.PlayIndex[Item] = nil
end
remove(BindedFunctions,find(BindedFunctions,Step))
Index = 1
if Ended then
Ended(Item)
end
end
-- 중간 중간 함수 배정된것 실행
if CallBack then
for FncIndex,Fnc in pairs(CallBack) do
if FncIndex == "*" then
Fnc(Item,Index,Alpha)
else
local num = tonumber(FncIndex)
if num and num <= Index then
Fnc(Alpha,Index,Item)
CallBack[FncIndex] = nil
end
end
end
end
if OnStepped then OnStepped(Item,Index,Alpha) end
end
-- 스캐줄에 등록
insert(BindedFunctions,Step)
FunctionTargetItem[Step] = Item
return Step
end
-- 여러개의 개체를 트윈시킴
function module.RunTweens(Items,Data,Properties,Ended,OnStepped,Setter,Getter,_)
-- remove self
if Items == module then warn "AdvancedTween:RunTweens() is deprecated, Use AdvancedTween.RunTweens() instead"; Items = Data; Data = Properties; Properties = Ended; Ended = OnStepped; OnStepped = Setter; Setter = Getter; Getter = _; end
for _,Item in pairs(Items) do
module.RunTween(Item,Data,Properties,Ended,OnStepped,Setter,Getter)
end
end
-- 트윈 멈추기
function module.StopTween(ItemOrStep,_)
if ItemOrStep == module then warn "AdvancedTween:StopTween() is deprecated, Use AdvancedTween.StopTween() instead"; ItemOrStep = _ end
if type(ItemOrStep) == "function" then
local pos = find(BindedFunctions,ItemOrStep)
if not pos then
warn(("tween %s was not found from AdvancedTween BindedFunctions. ignored call StopTween"):format(tostring(ItemOrStep)))
return
end
remove(BindedFunctions,pos)
return
end
module.PlayIndex[ItemOrStep] = nil
end
function module.StopPropertyTween(Item,PropertyName,_)
if Item == module then warn "AdvancedTween:StopTween() is deprecated, Use AdvancedTween.StopTween() instead"; Item = _ end
local ItemPlayIndex = module.PlayIndex[Item]
if not ItemPlayIndex then return end
if type(PropertyName) == "table" then
for _,name in pairs(PropertyName) do
ItemPlayIndex[name] = (ItemPlayIndex[name] or 0) + 1
end
else
ItemPlayIndex[PropertyName] = (ItemPlayIndex[PropertyName] or 0) + 1
end
end
-- 해당 개체가 트윈중인지 반환
function module.IsTweening(ItemOrStep,_)
if Item == module then warn "AdvancedTween:IsTweening() is deprecated, Use AdvancedTween.IsTweening() instead"; Item = _ end
if type(ItemOrStep) == "function" then
local pos = find(BindedFunctions,ItemOrStep)
if pos then
return true
else
return false
end
end
if module.PlayIndex[Item] == nil then
return false
end
for Property,Index in pairs(module.PlayIndex[Item]) do
if Index ~= 0 then
return true
end
end
return false
end
-- 해당 개체의 해당 프로퍼티가 트윈중인지 반환
function module.IsPropertyTweening(Item,PropertyName,_)
if Item == module then warn "AdvancedTween:IsPropertyTweening() is deprecated, Use AdvancedTween.IsPropertyTweening() instead"; Item = _ end
if module.PlayIndex[Item] == nil then
return false
end
if module.PlayIndex[Item][PropertyName] == nil then
return false
end
return module.PlayIndex[Item][PropertyName] ~= 0
end
------------------------------------
-- 프레임 연결
------------------------------------
-- 1 프레임마다 실행되도록 해야되는 함수
-- ./Stepped.lua 에서 연결점 편집 가능
-- roblox 는 이미 연결되어 있음
function module.Stepped()
for _,Function in ipairs(BindedFunctions) do
if not FunctionTargetItem[Function] then
remove(BindedFunctions,find(BindedFunctions,Function))
else
Function()
end
end
end
Stepped.BindStep(module.Stepped)
script.Destroying:Connect(function()
Stepped.UnbindAll()
end)
function module.Destroy()
Stepped.UnbindAll()
end
return module