Cleaning Up an Old Script

May

17, 2007

by John Shedletsky


Archive

One of my favorite tasks at work is to clean up old code. You know what I mean: It’s like the pleasure you get from throwing those smelly socks in the clothes hamper, putting your books back on the shelf and trashing the old apple core that’s been moldering under your bed. Well, maybe you don’t know what I mean.

Anyway, today I looked back at one of the first scripted models I created with Roblox and I realized it stunk worse than an old pair of gym socks. It’s the old Fountain model that sprays little water droplets around.

In this article I’m going to refactor the script. That means I’m going to re-write it in a better way without making substantive changes to the model and how it works. Along the way we’ll explore some new features in Roblox that can make scripts faster and more powerful. We’ll also adopt some better coding styles.

Sound like fun?  If not then stop reading. We’re going to get technical now.

First let’s look at the old code:

local r = game:service(“RunService”)

function rv()
     return 5*math.random()-2.5
end

function spawn(spawnTime)
     local droplet = Instance.new(“Part”)

     local headPos = script.Parent:findFirstChild(“Head”).Position

     droplet.Position = Vector3.new(headPos.x, headPos.y+3, headPos.z)
     droplet.Size = Vector3.new(1,1,1)
     droplet.Velocity = Vector3.new(rv(), 50, rv())
     droplet.BrickColor = BrickColor.new(102)
     droplet.Shape = 0
     droplet.BottomSurface = 0
     droplet.TopSurface = 0
     droplet.Name = “Droplet ” .. spawnTime
     droplet.Parent = script.Parent.Parent

     local delete
     function(time)
          if time > spawnTime + 5 then
               droplet.Parent = nil
               r.Stepped:disconnect(delete)
          end
     end

     delete = r.Stepped:connect(delete)
end

local nextTime = 0

while true do

     time = r.Stepped:wait()

   if time > nextTime then
          spawn(time)
          nextTime = time + 0.3
     end
end

Down near the bottom you’ll see the “while true do” loop.  This is the main loop of the script. Let’s tackle it first. Here’s a much nicer rewrite of the code:

— The main loop
while true do
     time = wait(0.3)
     spawn(time)
end

Roblox now defines a global function called “wait”.  You specify how long you want to wait for (0.3 seconds) and it does the rest. This is much nicer than listening to the RunService.Stepped event, which fires 30 times a second. Our code has gotten shorter and it is much more efficient, since it doesn’t have to check 30 times a second for elapsed time.

Now let’s turn our attention to the meat of the script. The “spawn” function. In here the script connects an anonymous function to the RunService.Stepped event and after 5 seconds it deletes the water droplet. It would be tempting to rewrite this with the global wait() function, but that won’t actually work. You see, since spawn is called directly from our main loop, calling wait() inside of spawn() would actually put our main loop asleep for 5 seconds. As it turns out, there’s a completely different and much better way to do this. A few weeks ago we quietly released a Debris service. Its job is to do exactly what we want: Delete random stuff after a set period of time. Here’s the new code:

local debris = game:service(“Debris”)
     debris:AddItem(droplet, 5)

The Debris service will delete the droplet after 5 seconds. We can “create-and-forget” the droplet. The Debris service has another nice feature – it will delete items earlier if too much debris piles up in a map. So, if you create a map with 500 fountains, the Debris service will make sure there aren’t 10,000 water droplets lagging your map down to a crawl. As a map author you can configure the maximum number of debris items. The default is 300.

Now our script is shorter, easier to read, and more efficient. From here on we’ll do a few more things to make the script stylistically more pleasing. Here is the final script:

— This script sprays water droplets for the fountain

local debris = game:service(“Debris”)
local spout = script.Parent:findFirstChild(“Head”)

— Utility function
function randomVelocity()
     local x = 5*(math.random()-0.5)
     local z = 5*(math.random()-0.5)
     return Vector3.new(x, 50, z)
end

function createDroplet()
     local droplet = Instance.new(“Part”)
     droplet.Position = spout.CFrame * Vector3.new(0,3,0)
     droplet.Size = Vector3.new(1,1,1)
     droplet.Velocity = randomVelocity()
     droplet.Elasticity = 0.2
     droplet.BrickColor = BrickColor.new(102)
     droplet.Shape = 0
     droplet.BottomSurface = 0
     droplet.TopSurface = 0
     droplet.Name = “Droplet”
     droplet.Parent = script.Parent

     — the Debris service will delete the droplet
     debris:AddItem(droplet, 5)
end

— The main loop
while true do
     wait(0.4)
     createDroplet()
end

Here’s what I did:

1)     Renamed spawn() to createDroplet() for clarity

2)     Added a few comments for clarity

3)     Assigned the simple string “Droplet” to droplet.Name rather than giving each droplet a unique name.

Re-using the same name for each droplet improves network performance.

4)     Set the droplet’s parent so that it resides inside the model, not outside of it.

This makes the explorer view look better. Also, if you delete the model, the droplets go away to)

5)     Moved the definition of the spout object outside of the createDroplet() function.

It is slightly more efficient to do this.

6)     Replaced the crypting rv() function with a randomVelocity() function

7)     Did some clever vector math to set the initial position of the droplet

The droplet’s position is now spout.CFrame * Vector3.new(0,3,0) This is nice in case the fountain tips over. The old script would put the droplet 3 units above the middle of the fountain’s shaft, even if it was resting on its side. The new approach puts the droplet at the end of the shaft. See http://wiki.roblox.com/index.php?title=Scripting#CFrame for details of CFrame operators.  I realize now that I should also rotate the initial velocity of the droplet. Ah well, maybe another day!

That’s it! We’ve refactored an old script. It is now easier to read, more efficient and works better in busy maps. As a final bit up tuning I set the sleep interval from 0.3 seconds to 0.4 seconds. It still looks pretty nice and it means the fountain requires a little less physics – which is always a good thing.

– Erik