Adding advanced tween module for tweening objects and fixed some bukket error

This commit is contained in:
세젤귀 고양이들 2021-12-24 18:11:11 +09:00
parent f8bde6f4e8
commit 921af23d66
10 changed files with 529 additions and 91 deletions

View file

@ -7,6 +7,15 @@
"Quad": {
"$path": "src"
}
},
"StarterGui": {
"$className": "StarterGui",
"QuadTesting": {
"$className": "ScreenGui",
"testScript": {
"$path": "testScript.client.lua"
}
}
}
}
}

67
readme.lua Normal file
View file

@ -0,0 +1,67 @@
-- 이렇게 모듈을 부를 수 있다, 뒤에 if 가 붇은것은 자동완성이 뜨도록 하기 위해서 사용된다
---@module "src/init.lua"
local quad = require(game.ReplicatedStorage:WaitForChild "Quad");
local render = quad.init();
-- init 함수를 이용해 여러 스크립트가 이 모듈을 호출해도 완전히 별계의
-- 환경에서 실행될 수 있도록 만든다, init 를 호출하면 완전히 새로운 store 와 style
-- 등을 가진 모듈을 새로 생성한다
-- 하위 모듈을 이렇게 부른다
local class = render.class;
local mount = render.mount;
local store = render.store;
-- 오브젝트를 이렇게 불러온다
local frame = class "Frame"; -- 이렇게 원하는 오브젝트를 불러올 수 있다
local text = class "TextLabel";
text.TextSize = 17; -- 이렇게 오브젝트의 기본값도 정할 수 있다
text.Size = UDim2.new(1,0,0,30);
local titleText = class "TextLabel";
-- 당연 이렇게 똑같은 className 을 가진 오브젝트를 여러번 import 도 된다
-- 다른 임포트에서 설정한 기본값은 여기에 적용되지 않는다
local gui = script.Parent;
local app = mount(gui,frame "testFrame" { -- mount 로 트리를 마운트한다, 이후 app 에서 unmount 호출가능
Size = UDim2.fromOffset(120,60);
Position = UDim2.fromScale(0.5,0.5);
AnchorPoint = Vector2.new(0.5,0.5);
text "texts" { -- 이렇게 단순히 child 개체를 만들 수 있다
Text = "We can make ui like this";
};
text "texts" {
Position = UDim2.fromOffset(0,30);
Text = "qweiufnwlqef";
};
titleText { -- 아이디를 명명하지 않아도 오브젝트를 바로 만들 수 있다
Text = "test";
};
});
print(store.getObject("texts"));
-- getObject 를 호출하면 해당 id 로 명명된 오브젝트중
-- 가장 처음으로 생성된 객체를 반환한다
store.getObjects("texts"):each(function(index,this)
this.Text = "이렇게 모든 택스트를 한꺼번에 바꿀 수도 있습니다";
end);
-- getObjects 를 이용하면 해당 id 로 명명된 오브젝트가 담긴 array 를 가져온다
-- for 을 돌릴 수 있지만 each 를 이용해 async 된 작업을 수행할 수 있다
store.getObjects("texts"):eachSync(function(index,this)
this.Text = "또한, 기본적으로 Aync 를 이용하기 때문에 순차적인 실행이 필요한 경우 Sync 를 붇여야합니다";
end);
-- sync 를 하면 일반 스크립트를 실행하는것 처럼 순차적으로 실행되도록 만들 수도 있다
-- 이 때 순서는 먼저 생생된 순서이다, 항상 같음을 유지한다는 보장이 없으므로 순서가 중요한경우
-- 직접 for 을 이용해 순서 검증을 한 뒤 task 를 수행해야한다
-- 이렇게 어떤 오브젝트를 가지고 task 수행하는 thread 를 만들 수 있다
do local this = store.getObject("testFrame");
spawn(function ()
local flip = false;
while wait(0.5) do
flip = not flip;
this.BackgroundTransparency = flip and 1 or 0;
end
end);
end
app.unmount(); -- 트리의 객체들을 모두 파기한다, 플러그인에서 unload 같은곳에 쓰는 함수
return app;

View file

@ -13,11 +13,14 @@ local module = {};
function module.init(shared)
local new = {};
local bind = shared.event.bind;
local addObject = shared.store.addObject;
local storeNew = shared.store.new;
local mount = shared.mount;
local getHolder = shared.mount.getHolder;
local event = shared.event; ---@module "src.event"
local bind = event.bind;
local store = shared.store; ---@module "src.store"
local addObject = store.addObject;
local storeNew = store.new;
local mount = shared.mount; ---@module "src.mount"
local getHolder = mount.getHolder;
local advancedTween = shared.advancedTween; ---@module "src.libs.AdvancedTween"
-- make object that from instance, class and more
function new.make(ClassName,...) -- render object
@ -89,15 +92,26 @@ function module.init(shared)
end
end
local tween = value.tvalue;
local from = value.fvalue;
-- adding event function
local function regFn(newValue,store)
if from then
newValue = from[newValue];
end
if with then
newValue = with(newValue,item,store);
end
if tween then
if not advancedTween then
return wran "module 'AdvancedTween' needs to be loaded for tween properties but it is not founded on 'src.libs'. you should adding that to src.libs directory";
end
advancedTween.RunTween(item,tween,{[index] = newValue});
else
item[index] = newValue;
end
value.register(regFn);
end
value:register(regFn);
-- this is using hacky of roblox instance
-- this is will keep reference from week table until
@ -208,7 +222,7 @@ function module.init(shared)
if child then
for _,v in pairs(child) do
if v then
((type(v) == table and rawget(v,"__object") or v).Parent = object;
((type(v) == "table") and rawget(v,"__object") or v).Parent = object;
end
end
end

View file

@ -24,15 +24,17 @@ frame{
]]
local require = require(script.require);
local event = require("event"); ---@module "src.event"
local store = require("store"); ---@module "src.store"
local event = require "event"; ---@module "src.event"
local store = require "store"; ---@module "src.store"
--local style = require("style");
local class = require("class"); ---@module "src.class"
local mount = require("mount"); ---@module "src.mount"
local class = require "class"; ---@module "src.class"
local mount = require "mount"; ---@module "src.mount"
local _,advancedTween = pcall(require,"src.libs.AdvancedTween"); ---@module "src.libs.AdvancedTween"
function module.init()
local this = {items = {}};
this.advancedTween = type(advancedTween) == "table" and advancedTween;
this.event = event.init(this);
this.store = store.init(this);
--this.style = style.init(this);

View file

@ -0,0 +1,23 @@
local module = {}
function Lerp(Alpha,Point1,Point2)
return Point1 + ( (Point2 - Point1) * Alpha )
end
function module.Bezier(Alpha,...)
local New = {}
local Len = #{...}
for i = 1,Len do
if i ~= 1 then
New[#New+1] = Lerp(Alpha,select(...,i-1),select(...,i))
end
end
if Len <= 2 then
return New[1]
end
return module.Bezier(Alpha,unpack(New))
end
return module

View file

@ -0,0 +1,75 @@
local EasingFunctions = {}
local function reverse(Index)
return 1 - Index
end
local Linear = {} do
Linear.Run = function(Index)
return Index
end
end
EasingFunctions["Linear"] = Linear
local Circle = {} do
Circle.Run = function(Index)
return 1-((1-Index)^2)
end
end
EasingFunctions["Circle"] = Circle
local Exp2 = {} do
local MinIndex = -4
local MaxIndex = 2
local GetIndex = function(Index)
return Index * (MaxIndex - MinIndex) + MinIndex
end
local Min = math.exp(MinIndex)
local Max = math.exp(MaxIndex) - Min
Exp2.Reverse = true
Exp2.Run = function(Index)
-- Index ; max = 1 min = 0
return (math.exp(GetIndex(Index)) - Min) / Max
end
end
EasingFunctions["Exp2"] = Exp2
local Exp4 = {} do
local MinIndex = -4
local MaxIndex = 4
local GetIndex = function(Index)
return Index * (MaxIndex - MinIndex) + MinIndex
end
local Min = math.exp(MinIndex)
local Max = math.exp(MaxIndex) - Min
Exp4.Reverse = true
Exp4.Run = function(Index)
-- Index ; max = 1 min = 0
return (math.exp(GetIndex(Index)) - Min) / Max
end
end
EasingFunctions["Exp4"] = Exp4
local Exp2Max4 = {} do
local MinIndex = -2
local MaxIndex = 4
local GetIndex = function(Index)
return Index * (MaxIndex - MinIndex) + MinIndex
end
local Min = math.exp(MinIndex)
local Max = math.exp(MaxIndex) - Min
Exp2Max4.Reverse = true
Exp2Max4.Run = function(Index)
-- Index ; max = 1 min = 0
return (math.exp(GetIndex(Index)) - Min) / Max
end
end
EasingFunctions["Exp2Max4"] = Exp2Max4
return EasingFunctions

View file

@ -0,0 +1,23 @@
local IsRoblox = version and version() or nil
-- 로블록스 이외의 다른 lua 플렛폼을 위한 바인드 스탭,
-- 여기에다 프레임 앞에 실행되는 이벤트를 핸들링해서 StepFunction 을
-- 연동해주면 됨,
-- 이 함수는 한 프레임마다 트윈들을 가져와서 실행시켜줌, (만약 트윈 효과가 하나도 없으면 그냥 건너뜀)
local module = {}
function module.BindStep(StepFunction)
if IsRoblox then
local RunService = game:GetService("RunService")
if RunService:IsStudio() and (not RunService:IsRunning()) and RunService:IsEdit() then
RunService.Heartbeat:Connect(StepFunction)
else
RunService.Stepped:Connect(StepFunction)
end
else
-- do something here
end
end
return module

View file

@ -0,0 +1,280 @@
local module = {}
------------------------------------
-- 기본함수, 필요 모듈 가져오기
------------------------------------
local type = typeof or type
local clock = os.clock
local tonumber = tonumber
local script = script
local EasingFunctions = require(script and script.EasingFunctions or "EasingFunctions")
local Stepped = require(script and script.Stepped or "Stepped")
local BindedFunctions = {} -- 애니메이션을 위해 프레임에 연결된 함수들
module.PlayIndex = setmetatable({},{__mode = "k"}) -- 애니메이션 실행 스택 저장하기
-- PlayIndex[Item][Property] = 0 or nil <= 트윈중이지 않은 속성
-- PlayIndex[Item][Property] = 1... <= 트윈중인 속성
------------------------------------
-- Easing 스타일
------------------------------------
module.EasingFunctions = {
--// for Autocomplete
Linear = EasingFunctions.Linear; --// 직선
Circle = EasingFunctions.Circle; --// 사분원
Exp2 = EasingFunctions.Exp2; --// 덜 가파른 지수 그래프
Exp4 = EasingFunctions.Exp4; --// 더 가파른 지수 그래프
Exp2Max4 = EasingFunctions.Exp2Max4; --// 적당히 가파른 지수 그래프
}
for i,v in pairs(EasingFunctions) do
module.EasingFunctions[i] = v
end
module.EasingDirection = {
Out = "Out"; -- 반전된 방향
In = "In" ; -- 기본방향
}
------------------------------------
-- Lerp 함수
------------------------------------
-- 이중 선형, Alpha 를 받아서 값을 구해옴
function Lerp(start,goal,alpha)
return start + ((goal - start) * alpha)
end
-- 기본적으로 로블록스에 있는 클래스중, + - * / 과 같은 연산자 처리 메타 인덱스가 있는것들
local DefaultItems = {
["Vector2"] = true;
["Vector3"] = true;
["CFrame" ] = true;
["number" ] = true;
}
-- 예전 값,목표 값,알파를 주고 각각 해당하는 속성에 입력해줌
-- 기본적으로 모든 속성값 적용은 여기에서 이루워짐
function LerpProperties(Item,Old,New,Alpha)
for Property,OldValue in pairs(Old) do
local NewValue = New[Property]
if NewValue ~= nil then
local Type = type(OldValue)
if DefaultItems[Type] then
Item[Property] = Lerp(OldValue,NewValue,Alpha)
elseif Type == "UDim2" then
Item[Property] = 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
Item[Property] = UDim.new(
Lerp(OldValue.Scale ,NewValue.Scale ),
Lerp(OldValue.Offset,NewValue.Offset)
)
elseif Type == "Color3" then
Item[Property] = Color3.fromRGB(
Lerp(OldValue.r*255,NewValue.r*255),
Lerp(OldValue.g*255,NewValue.g*255),
Lerp(OldValue.b*255,NewValue.b*255)
)
end
end
end
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)
-- 시간 저장
local Time = Data.Time or 1
local EndTime = clock() + Time
-- 플레이 인덱스 저장
local ThisPlayIndex = module.PlayIndex[Item] or {}
module.PlayIndex[Item] = ThisPlayIndex
-- 예전의 트윈을 덮어쓰고 현재 값을 저장함
local NowAnimationIndex = {}
local LastProperties = {}
for Property,_ in pairs(Properties) do
LastProperties[Property] = Item[Property]
ThisPlayIndex[Property] = ThisPlayIndex[Property] ~= nil and ThisPlayIndex[Property] + 1 or 1
NowAnimationIndex[Property] = ThisPlayIndex[Property]
end
-- 이징 효과 가져오기
local Direction = Data.Direction or "Out"
local Easing do
local Data_Easing = Data.Easing or EasingFunctions.Exp2
local Data_EasingType = type(Data_Easing)
Easing = (Data_EasingType == "function" and Data_Easing) or (Data_EasingType == "table" and Data_Easing.Run)
if Data_EasingType == "table" and Data_Easing.Reverse then
Direction = Direction == "Out" and "In" or "Out"
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" then
CallBack[FncIndex] = nil
end
end
end
-- 스탭핑
local Step
Step = function()
-- 아에 멈추게 되는 경우
if module.PlayIndex[Item] == nil then
table.remove(BindedFunctions,table.find(BindedFunctions,Step))
return
end
local Now = clock()
local Index = 1 - (EndTime - Now) / Time
-- 속성 Lerp 수행
if Direction == "Out" then
LerpProperties(
Item,
LastProperties,
Properties,
Easing(Index)
)
else
LerpProperties(
Item,
LastProperties,
Properties,
1 - Easing(1 - Index)
)
end
-- 다른 트윈이 속성을 바꾸고 있다면(이후 트윈이) 그 속성을 건들지 않도록 없엠
local StopByOther = true
for Property,Index in pairs(NowAnimationIndex) do
if Index ~= ThisPlayIndex[Property] then
LastProperties[Property] = nil
Properties[Property] = nil
NowAnimationIndex[Property] = nil
else
StopByOther = false
end
end
-- 만약 다른 트윈이 지금 트윈하고 있는 속성을 모두 먹은경우 현재 트윈을 삭제함
if StopByOther then
table.remove(BindedFunctions,table.find(BindedFunctions,Step))
return
end
-- 끝남
if Now >= EndTime then
for Property,_ in pairs(Properties) do
ThisPlayIndex[Property] = 0
end
local PlayIndexLen = 0
for _,_ in pairs(ThisPlayIndex) do
PlayIndexLen = PlayIndexLen + 1
end
if PlayIndexLen == 0 then
ThisPlayIndex = nil
end
table.remove(BindedFunctions,table.find(BindedFunctions,Step))
Index = 1
if Ended then
Ended()
end
end
-- 중간 중간 함수 배정된것 실행
if CallBack then
for FncIndex,Fnc in pairs(CallBack) do
if tonumber(FncIndex) <= Index then
Fnc()
CallBack[FncIndex] = nil
end
end
end
end
-- 스캐줄에 등록
table.insert(BindedFunctions,Step)
end
-- 여러개의 개체를 트윈시킴
function module.RunTweens(Items,Data,Properties,Ended)
local First = true
for _,Item in pairs(Items) do
module.RunTween(Item,Data,Properties,First and Ended)
First = false
end
end
-- 트윈 멈추기
function module.StopTween(Item)
module.PlayIndex[Item] = nil
end
-- 해당 개체가 트윈중인지 반환
function module.IsTweening(Item)
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 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()
if not BindedFunctions[1] then
return;
end
for _,Function in ipairs(BindedFunctions) do
Function()
end
end
Stepped.BindStep(module.Stepped)
return module

View file

@ -84,6 +84,25 @@ function module.init(shared)
end
end
local registerMt = {
register = function (s,efunc)
insert(s.event,efunc);
end;
with = function (s,efunc)
return setmetatable({wfunc = efunc},{__index = s});
end;
default = function (s,value)
return setmetatable({dvalue = value},{__index = s});
end;
tween = function (s,value)
return setmetatable({tvalue = value},{__index = s});
end;
from = function (s,value)
return setmetatable({fvalue = value},{__index = s});
end;
};
registerMt.__index = registerMt;
-- bindable store object
local store = {};
function store:__index(key)
@ -103,23 +122,12 @@ function module.init(shared)
if not register then
local event = setmetatable({},week);
self.__evt[key] = event;
register = {
register = function (efunc)
insert(event,efunc);
end;
with = function (s,efunc)
return setmetatable({wfunc = efunc},{__index = s});
end;
default = function (s,value)
return setmetatable({dvalue = value},{__index = s});
end;
tween = function (s,value)
return setmetatable({tvalue = value},{__index = s});
end;
register = setmetatable({
event = event;
key = key;
store = self;
t = "reg";
};
},registerMt);
self.__reg[key] = register;
end

View file

@ -1,67 +1,4 @@
-- 이렇게 모듈을 부를 수 있다, 뒤에 if 가 붇은것은 자동완성이 뜨도록 하기 위해서 사용된다
---@module "src/init.lua"
local quad = require(game.ReplicatedStorage:WaitForChild "Quad");
local render = quad.init();
-- init 함수를 이용해 여러 스크립트가 이 모듈을 호출해도 완전히 별계의
-- 환경에서 실행될 수 있도록 만든다, init 를 호출하면 완전히 새로운 store 와 style
-- 등을 가진 모듈을 새로 생성한다
local Quad = require(game.ReplicatedStorage:WaitForChild "Quad"); ---@module "src"
local _ = Quad.init(); local class,mount,store,event,advancedTween = _.class,_.mount,_.store,_.event,_.advancedTween;
-- 하위 모듈을 이렇게 부른다
local class = render.class;
local mount = render.mount;
local store = render.store;
-- 오브젝트를 이렇게 불러온다
local frame = class "Frame"; -- 이렇게 원하는 오브젝트를 불러올 수 있다
local text = class "TextLabel";
text.TextSize = 17; -- 이렇게 오브젝트의 기본값도 정할 수 있다
text.Size = UDim2.new(1,0,0,30);
local titleText = class "TextLabel";
-- 당연 이렇게 똑같은 className 을 가진 오브젝트를 여러번 import 도 된다
-- 다른 임포트에서 설정한 기본값은 여기에 적용되지 않는다
local gui = script.Parent;
local app = mount(gui,frame "testFrame" { -- mount 로 트리를 마운트한다, 이후 app 에서 unmount 호출가능
Size = UDim2.fromOffset(120,60);
Position = UDim2.fromScale(0.5,0.5);
AnchorPoint = Vector2.new(0.5,0.5);
text "texts" { -- 이렇게 단순히 child 개체를 만들 수 있다
Text = "We can make ui like this";
};
text "texts" {
Position = UDim2.fromOffset(0,30);
Text = "qweiufnwlqef";
};
titleText { -- 아이디를 명명하지 않아도 오브젝트를 바로 만들 수 있다
Text = "test";
};
});
print(store.getObject("texts"));
-- getObject 를 호출하면 해당 id 로 명명된 오브젝트중
-- 가장 처음으로 생성된 객체를 반환한다
store.getObjects("texts"):each(function(index,this)
this.Text = "이렇게 모든 택스트를 한꺼번에 바꿀 수도 있습니다";
end);
-- getObjects 를 이용하면 해당 id 로 명명된 오브젝트가 담긴 array 를 가져온다
-- for 을 돌릴 수 있지만 each 를 이용해 async 된 작업을 수행할 수 있다
store.getObjects("texts"):eachSync(function(index,this)
this.Text = "또한, 기본적으로 Aync 를 이용하기 때문에 순차적인 실행이 필요한 경우 Sync 를 붇여야합니다";
end);
-- sync 를 하면 일반 스크립트를 실행하는것 처럼 순차적으로 실행되도록 만들 수도 있다
-- 이 때 순서는 먼저 생생된 순서이다, 항상 같음을 유지한다는 보장이 없으므로 순서가 중요한경우
-- 직접 for 을 이용해 순서 검증을 한 뒤 task 를 수행해야한다
-- 이렇게 어떤 오브젝트를 가지고 task 수행하는 thread 를 만들 수 있다
do local this = store.getObject("testFrame");
spawn(function ()
local flip = false;
while wait(0.5) do
flip = not flip;
this.BackgroundTransparency = flip and 1 or 0;
end
end);
end
app.unmount(); -- 트리의 객체들을 모두 파기한다, 플러그인에서 unload 같은곳에 쓰는 함수
return app;