Tycoon animations?

Here’s what it seems to be doing:

When a new building is purchased, this happens:

  • It’s made completely invisible but the original transparency values for each Part is saved.
  • Each part is moved off in a random direction, but the original CFrames relative to the CFrame of the model is saved.
  • One by one, a Tween is played for each Part that smoothly makes the transparency what it originally was, and makes the CFrame relative to the Model what it originally was.

Do you need help implementing any of these concepts? Just let us know in another comment :slight_smile:

1 Like

Yeah that would be great! :)))

You can make the model invisible and store the necessary information by looping through every BasePart inside the model. If the “instance hierarchy” of each building is guaranteed to only be level deep, you can use BuildingModel:GetChildren() to get all the children. If not, you can use :GetDescendants(). You can then loop over each potential BasePart and check if it’s actually one by calling :IsA("BasePart") on the thing.

The easiest way to store the necessary data is to create two BasePart->Data dictionaries, where each key is a BasePart in the model and the corresponding data is the original transparency and the original Position, respectively. Inside the loop, that would look something like

originalTransparencies[basePart] = basePart.Transparency
originalPositions[basePart] = basePart.Position

Once you’ve stored the original values, you can change the BasePart to be invisible and move it in a random direction.

You’ll then want to loop over all the BaseParts again, in the same manner, but this time creating Tweens to make them look like they originally did.

Inside the 2nd for loop, create a new Tween using TweenService:Create(). The 3rd argument to :Create() is a Property->Value dictionary, where each key is the name of a property (in this case Transparency and Position), and each corresponding value is the original value of the property. We get this from the dictionaries we made earlier something like this:

local propertyDict = {
    Transparency = originalTransparencies[basePart],
    Position = originalPositions[basePart]
}

Then call :Play() on the new tween to play it, and wait for the Tween to finish by calling Tween.Completed:Wait(). That way the loop won’t continue to the next part before the previous one is back in it’s original configuration, and it’ll look like the building is being made one part at a time.

If you wrap it all in a function and have the “original property” dictionaries be local variables in that function, they’ll go out of scope and automatically be garbage-collected when the function is done, so you don’t have to worry about cleaning the tables up.

If you need more details, look up the different Services, method names and class names on the wiki. Feel free to ask if anything is confusing or if you need help turning all of this into working code.

1 Like

Thanks! I’ll try this out later :))

1 Like

Hey, so it is a bit confusing on how I can make this into working code, I understand the stuff you said, just not really sure how to put it into code format.

2 Likes

Here’s some old code that I modified to work for this situation. If there’s something you don’t understand let me know and I’ll try to explain. It’s easier than explaining how to do everything :sweat_smile:

local TweenS = game:GetService("TweenService")

local r = Random.new()

local BUILDING_ANIMATION_POSITION_OFFSET_AMOUNT = 2
local BUILDING_ANIMATION_PART_DELAY = 0.03

function hasProperty(instance, property)
  assert(typeof(instance) == "Instance")
  assert(typeof(property) == "string")
    
  local hasProperty = false
  
  pcall(function()
    local v = instance[property] 
    hasProperty = true -- This line only runs if the previous line didn't error
  end)
  
  return hasProperty
end

function instanceListToPropertyDict(instances, propertyList)
  assert(typeof(instances) == "table")
  assert(typeof(propertyList) == "table")
  
  --[[Given a list of instances and a list of properties, construct a dictionary like so:
  dict = {
      [instance1] = {property1 = instance1.property1, property2 = instance1.property2, ...},
      [instance2] = {property1 = instance2.property1, property2 = instance2.property2, ...},
      ...
  }]]
  local dict = {}

  for _, instance in ipairs(instances) do
    local dictEntry = {}

    for _, property in pairs(propertyList) do
      assert(hasProperty(instance, property), string.format(
        [[Instance '%s' (a %s) doesn't have property '%s'.]], 
        tostring(instance), instance.ClassName, property)
      )
      dictEntry[property] = instance[property]
    end

    dict[instance] = dictEntry
  end

  return dict
end

function getDescendantsWhichAre(ancestor, className)
  assert(typeof(ancestor) == "Instance")
  assert(typeof(className) == "string")
  
  --[[Returns all descendants of ancestor which are of class className or a class that inherits from className]]
  local descendants = {}

  for _, descendant in pairs(ancestor:GetDescendants()) do
    if descendant:IsA(className) then
      table.insert(descendants, descendant)
    end
  end

  return descendants
end

function animateBuildingIn(buildingModel, tweenInfo)
  assert(typeof(buildingModel) == "Instance" and buildingModel.ClassName == "Model", string.format(
    "Invalid argument #1 to 'animateBuildingIn' (Model expected, got %s)", 
    typeof(buildingModel) == "Instance" and buildingModel.ClassName or typeof(buildingModel)
  ))
  assert(typeof(tweenInfo) == "TweenInfo", string.format(
    "Invalid argument #1 to 'animateBuildingIn' (TweenInfo expected, got %s)",
    typeof(tweenInfo)
  ))
  
  --Collect BaseParts and original properties
  local parts = getDescendantsWhichAre(buildingModel, "BasePart")
  local originalProperties = instanceListToPropertyDict(parts, {"Transparency", "CFrame", "Color", "Size"})
  local originalBasePartCFrame = buildingModel.PrimaryPart.CFrame

  --Make parts invisible and randomly move them
  for _, part in pairs(parts) do
    part.Transparency = 1
    part.Color = Color3.fromRGB(255, 255, 255)
    part.Size = Vector3.new()
    
    local positionOffset = Vector3.new(r:NextNumber(-1, 1), r:NextNumber(-0.25, 1.75), r:NextNumber(-1, 1)) * BUILDING_ANIMATION_POSITION_OFFSET_AMOUNT
    local rotationOffset = CFrame.Angles(r:NextNumber(-math.pi, math.pi), r:NextNumber(-math.pi, math.pi), r:NextNumber(-math.pi, math.pi))
    part.CFrame *= CFrame.new(positionOffset) * rotationOffset
  end

  --Tween them back to their original state, one at a time
  local lastTween --Return this so the caller can do animateBuilding(...):Wait() to wait for the animation to complete
  
  for _, part in pairs(parts) do
    local tween = TweenS:Create(part, tweenInfo, originalProperties[part])
    lastTween = tween
    
    tween.Completed:Connect(function(playbackState)
      --Sometimes Tweens stop before reaching their goal properly.
      --  Make sure each Part is *exactly* how it was before.
      part.Transparency = originalProperties[part].Transparency
      part.CFrame = originalProperties[part].CFrame
    end)

    tween:Play()

    wait(BUILDING_ANIMATION_PART_DELAY)
  end
  
  return lastTween.Completed
end	

You call the function like so:

wait(1)
print("Animation goes brrrr...")
animateBuildingIn(game.Workspace["Well"], TweenInfo.new(1, Enum.EasingStyle.Elastic, Enum.EasingDirection.Out)):Wait()
print("DING!")

I used the Well from the Roblox free models, just call it with any Model.

6 Likes

That is a lot of code to take in, I am confused on this part though.

--Tween them back to their original state, one at a time
 local lastTween --Return this so the caller can do animateBuilding(...):Wait() to wait for the animation to complete

Are you saying that is what you are doing? or I have to tween them back? If so, I’m not that experienced with tweening. If not, then good haha. The script is a bit confusing, just because that code is pretty advanced, and I’m not advanced, but I can get some of it. Also, does this go into a model? Or would this be in severScriptService or something like that.

I’m also a bit confused of where I would put my model that it would animate. Also, if I wanted the animation to happen once something was bought, would I need to do

button.Touched:Connect(animateBuildingIn(game.Workspace["Well"], TweenInfo.new(1, Enum.EasingStyle.Elastic, Enum.EasingDirection.Out)):Wait())

Sorry for all the messages lol, and it also got an error, not sure if it is because I’m suppose to do something but it says, SeverScriptService.Script:80:attempt to index nil with CFrame.

That part is just handy because it lets you wait for the animation to finish. So you start the animation by calling the function. If you then want to do something, for example enabling scripts inside the building, you can make sure that doesn’t happen before the animation is done, screwing things up. The function returns the Completed event of the last tween that happens, so when that tween is done, the whole animation is done.

The model should just be any model in Workspace.

I don’t know how your “buying building code” works, but if you have a function that you call when a player wants to buy a building, it might look like so:

function buyBuilding(buildingModel)
    local buildingModel = buildingModel:Clone()
    buildingModel.Parent = game.Workspace
    animateBuildingIn(buildingModel, TweenInfo.new(1))
end

Forgot to check if the Model has a PrimaryPart. It absolutely needs to have one, so make sure you set it before calling the function. You can change the function to check for it by changing the first part to this:

function animateBuildingIn(buildingModel, tweenInfo)
  assert(typeof(buildingModel) == "Instance" and buildingModel.ClassName == "Model", string.format(
    "Invalid argument #1 to 'animateBuildingIn' (Model expected, got %s)", 
    typeof(buildingModel) == "Instance" and buildingModel.ClassName or typeof(buildingModel)
  ))
  assert(buildingModel.PrimaryPart,
    "'animateBuildingIn: argument #1 is a Model without a PrimaryPart. Set PrimaryPart before calling.'"
  )
  assert(typeof(tweenInfo) == "TweenInfo", string.format(
    "Invalid argument #1 to 'animateBuildingIn' (TweenInfo expected, got %s)",
    typeof(tweenInfo)
  ))
1 Like

Oh ok makes more sense, so where you had “Well” would I just change that to the model name?

No. game.Workspace["Well"] is just a reference to a model. In this case, a model called “Well” located in game.Workspace. It can be replaced to any reference to a model.

What I usually do is serialize the positional values when they buy the plot for everything and save those for on buy, when it is they just deserialize and spawn in by fading their transparency. If you want them to like bounce down like legos simply make a generic for loop nested to set their y up by like idk, 5 studs? Then smoothly change that offset to 0.

YAY! It worked, thank you sooo much! Also, when it builds itself, some of the parts turn red, then go back to the original color, also, if I wanted to make the animation longer, where would I do that?

1 Like

Oh, sorry I see the time, I didn’t see it before, thanks!

1 Like

Remove the “Color” entry in originalProperties, and remove the part.Color = Color3.fromRGB(255, 255, 255) line.

1 Like

ok, thank you! Have a good day!

1 Like

I’m sorry to bother again but one last thing, if I wanted to make a remote function or remote event, so I can fire the tween in another script, how would I do that?

It’s a bit hard to answer because it depends on the other scripts in your game. If you’ve given it a try I’ll take a look at your code and see where you’re going wrong, but it’s a widely covered topic so read try reading up on it yourself first.

Here are some decent places to start reading:

And of course also try the search bar here on the forums :slight_smile:

Ok! Will do! Thanks man! Have a amazing day :))

1 Like