How would I animate this gate opening/closing?

I am working on a game that will feature an opening and closing gate for each player’s base.

I would like for this gate to have a cool “animation” where it will recede into the floor before it is not visible anymore, and then, when the player decides to close the gate, it retracts up to its original position, like seen here.

There would be a part in the model that would not move whatsoever; it would be an invisible, non-collidable part that would contain the ProximityPrompt that would be used for opening and closing this gate.

I have an idea of how I would do this, but I am not sure if TweenService or using an Animation Editor would be appropiate, especially since no parts of the gate are allowed to be unanchored - it is imperative all parts of this gate remain anchored.

1 Like

Unfortunately the CFrame of a Model can’t be set by using its CFrame property, you have to use PivotTo and TweenService only works on properties. Pretty annoying API for both Model and TweenService but oh well.

Here’s a workaround that lets you use TweenService to tween any function:

function tweenFunction(tweenInfo, callback, autoPlay: boolean) --Can't remember if it's bool or boolean xD
    local stepped = RunService.IsServer and RunService.Stepped or RunService.RenderStepped
    local progress = Instance.new("NumberValue")
    progress.Value = 0
    local tween = TweenService:Create(progress, tweenInfo, {Value = 1.0})
    
    local steppedC = stepped:Connect(function()
        callback(progress.Value)
    end)
    
    tween.Completed:Once(function(playbackState)
        if playbackState == PlaybackState.Completed then
            if numberValue.Value < 1.0 then
                callback(1.0) --Probably not necessary but I don't know for sure.
            end
            steppedC:Disconnect()
        elseif playbackState == PlaybackState.Cancelled then
            steppedC:Disconnect()
        end
    end)
    
    if play then tween:Play() end
    
    return tween --So user can play, pause or cancel it
end

Then you can tween the model CFrame by using the progress value to lerp the CFrame:

function tweenModelCFrame(model, tweenInfo, targetCFrame, autoPlay: boolean)
    local initialCFrame = model:GetPivot()
    local function updateCFrame(progress: number)
        model:PivotTo(initialCFrame:Lerp(targetCFrame, progress))
    end
    return tweenFunction(tweenInfo, updateCFrame, play)
end

The advantage is that you can still use all the convenient TweenInfo properties. I can’t test the code but the idea should work. Let me know if there are bugs, and I hope this helps!

2 Likes

What would “tweenInfo” be in your function? Also, what should “targetCFrame” be if I intend to move the gate down it’s y-axis by 13 studs? Move it up 13 studs etc.?

2 Likes

tweenInfo would be any TweenInfo object. If you’re not sure what that is, look into how to use TweenService there are decent tutorials on the wiki.

Here’s how I’d use the function to control gates:

--GateManager, server script.
local EasingStyle, EasingDirection = Enum.EasingStyle, Enum.EasingDirection
local GATE_HEIGHT = 13
local GATE_ANIM_TWEENINFO = TweenInfo.new(1.5, EasingStyle.Bounce, EasingDirection.Out)

local GateState = { --Custom Enum, to avoid using strings. Less prone to typos. Could be even better but this is nice and simple.
    Open = {},
    Closed = {},    
    Opening = {},
    Closing = {},
}

local gateInfos = {}

function setupGate(gateModel)
    local gateInfo = {
        State = GateState.Closed,
        InitialCFrame = gateModel:GetPivot(),
    }
    gateInfos[gateModel] = gateInfo
    
    --Maybe if you need to set up ClickDetectors and stuff also do that here
end

function openGate(gateModel)
    local gateInfo = gateInfos[gateModel]
    local state = gateInfo.State
    
    if state == GateState.Closed or state == GateState.Closing then
        if state == GateState.Closing then
            gateInfo.CurrentTween:Cancel()
        end
        
        gateInfo.State = GateState.Opening
        local targetCFrame = gateInfo.InitialCFrame - (Vector3.yAxis * GATE_HEIGHT)
        gateInfo.CurrentTween  = tweenModelCFrame(gateModel, GATE_ANIM_TWEENINFO, targetCFrame, true)
    elseif state == GateState.Open or state == GateState.Opening then
        --Do nothing if already open or opening
    else
        error("Invalid GateState!")
    end
end

function closeGate(gateModel)
    local gateInfo = gateInfos[gateModel]
    local state = gateInfo.State
    
    if state == GateState.Open or state == GateState.Opening then
        if state == GateState.Opening then
            gateInfo.CurrentTween:Cancel()
        end
        
        gateInfo.State = GateState.Closing
        local targetCFrame = gateInfo.InitialCFrame
        gateInfo.CurrentTween  = tweenModelCFrame(gateModel, GATE_ANIM_TWEENINFO, targetCFrame, true)
    elseif state == GateState.Closed or state == GateState.Closing then
        --Do nothing if already closed or closing
    else
        error("Invalid GateState!")
    end
end

for _, tagged in ipairs(TagService:GetTagged("Gate")) do
    setupGate(tagged)
end

I can’t test the code so let me know if it’s broken