Lesson 3 recap

In lesson 3, we learned a bit about data structures, and how to use physics forces inside Roblox to move models around.

The big thing we did was to create a "pet" model, and make it follow around the character wherever he goes. This involved creating a model, copying the model, and using a script to ensure the pet was moved to wherever the character went.

The script used a BodyPosition object to use physics forces to move the item around. One thing to remember is that physics forces are not something that is done with scripting, but done instead with builtin Roblox mechanisms. This is because a script is much much slower and less efficient than native compiled code. The interface to the BodyPosition for example is just instructing how it should behave, but the real BodyPosition object is working at the native code level.

Homework assignment - Enhance the pet

In this homework, we are going to enhance our bet a bit. First, you may notice that the pet stays on the screen when the character dies or when the user leaves the game. Also, we have the model pet still in your world (the one that we copy), which is not ideal.

We will also explore other ways to make this pet follow a character, and possibly make it possible to "adopt" a pet.

Pets need an owner!

When your character dies, what happens to your pet? If your code is like what we wrote in class, it just stays in the world, soulfully mourning at the spot your character last existed. But when the new character comes along, you create a new pet, leaving the old pet behind. It would be nice to clean that old pet up too!

To fix this, we need to determine when your character has left the world. In many programming languages, lua included, a technique is used called garbage collection which frees resources back to your computer. How this works is that if any object does not have a reference to it from some other object or function, then it is freed from the system (this process is straightforward, but somewhat complex, do a search on garbage collection if you want to learn how it works). When the character is removed from the world (i.e. the workspace) there is still a reference to it -- your script! Remember, we are using the character's head to determine the position for our pet. But because it has been removed from the world, we can detect this condition.

A part or object that is not in the workspace has no Parent. So we can actually check this every time through our loop that is modifying the BodyPosition's Position property to move the pet around. To check to see if an object is no longer set, we check to see if it's set to nil. The special value nil means "references nothing". So your code to check (remember we need a boolean expression) looks like this:

if char.Parent == nil then
    break
end

The break statement exits out of any loop that is running, so then the body position is no longer being altered. However, this does not remove the pet from the workspace. In order to remove the pet (or any other object), you need to stop referring to the pet, AND make sure the workspace no longer refers to the pet. Then the garbage collection will come along and remove the resources the pet is using. To do this, make sure the pet's Parent property is set to nil. See if you can figure this line out on your own.

You do not have to stop referring to the pet in your function, because when the function exits, its data is collected too.

Adopt-a-pet!

What if instead of a pet dying or being removed from the world, it decide to go to another player? You could even make a game out of this, attaching pets to the closest player when their owner dies (he who has the most pets wins!)

To do this, when we detect the owner is dead, we can search the game for any other owner and if one exists, attach to that owner instead (simply reassign char to a new character you find).

In order for this to make sense, we want to avoid creating new pets when the player dies, and only create them when they join the game. To do this, instead of attaching to the CharacterAdded event, we will simply wait for the first time the character is created, and then clone the pet. On the next time the character is added, this code will not be run again. Here is how we will write our handler function:

function addAPet(player)
    -- player.CharacterAdded:Connect(function(char) -- instead of using this, just wait for the character to arrive
    local char = player.Character or player.CharacterAdded:Wait()
    local pet = petModel:Clone()
    ...

Now, we need to avoid removing the pet from the world, and try to find a new character to attach to. Here is some code that will search for new characters to claim as owners, and find the nearest one. Put this code in the right place in our script to make this work:

				char = nil
				while char == nil do
					for i, p in pairs(game.Players:GetChildren()) do
						local potentialchar = p.Character
						if potentialchar then
							if char == nil then
								char = potentialchar
							else
								local potentialdistance = (potentialchar.HumanoidRootPart.Position - pet.Position).magnitude
								if potentialdistance < (char.HumanoidRootPart.Position - pet.Position).magnitude then
									char = potentialchar
								end
							end
						end
					end
					wait(0.5)
				end
				print(char.name, " Adopted me!")

If you put that code in the right place, any time a character in your world dies, any of his pets will be adopted by someone else (or your new character once they show up. Try to read through the code and understand what I'm doing. One place I will say that is a bit complex is determining who is closest. When you subtract two Vector3 items, you get a new Vector3 that is the difference between the two points. Then we calculate the magnitude of the vector to get the distance. You can ask questions about this in the discord if you want more info, but this math is very essential in video games and physics.

I don't want that model around

Remember how we have a pet model that we clone to attach to each character? Well, that pet is sitting in the world somewhere, but it doesn't have to. Instead of storing it in the workspace, we can store it somewhere else -- in ServerStorage. ServerStorage is another service (like the player service or the ServerScriptService) which can hold objects we want to keep track of but we DON'T want to have them show in the world. Moving our pet there is simple, just drag the model into the service.

However, that means we can't edit it on the screen any more. If you need to change your pet after this, you will need to drag it back to the workspace to make editing easier, then put it back into ServerStorage when you are done editing.

Now, if you remember, we used the following code to access the pet:

            local petModel = game.workspace.PetModel
            

But now, we need to access it via the ServerStorage service:

            local petModel = game.ServerStorage.PetModel
            

At this point, everything works just as it did before, but our data structure is stored off-screen so it doesn't confuse the players!

©2020 Steven Schveighoffer