2021
3/5
22
12hrs
Download files are available at the bottom of the page.
Solutions to common issues are listed below:
This is one of the most common complaints I hear in regards to the series, I know it can be very frustrating if this is your first time scripting.
First off, I recommend everyone has already watched my beginner guide before starting this series.
This will save you so much time in the long run! Watching this will ensure you are familiar with many of the basic concepts of coding which this series assumes you already know.
In order for us to know what the problem actually is, we need to open our output window. This displays any errors or typos in big red text that are preventing the code from running properly.
Select the "View" panel from the top of your screen and select "Output"
You should now see this window at the bottom of your screen
It's important to note, that we have 2 different variables, one named waypoints and the other named waypoint singular these should not be mixed up!
If you've got them confused, your code may look something like this with a red underline:
waypoint is just used as a counter, which will have a number value.
waypoints refers to the folder in the workspace that our parts are contained within.
In this example, the problem is because you're trying to get the contents of the waypoint which is just a number, when you need to get the contents of the waypoints folder.
This is the correct code:
local zombie = script.Parent
local waypoints = workspace.Waypoints
for waypoint=1, #waypoints:GetChildren() do
zombie.Humanoid:MoveTo(waypoints[waypoint].Position)
zombie.Humanoid.MoveToFinished:Wait()
end
In the script above, we have stated that there is a folder named "Waypoints", located in the workspace
Therefore, this must be the exact name of the folder.
For example, if you've named your folder waypoints with a lowercase w this is not the same as Waypoints
Everything in coding is case sensitive
Your output window is blank, and there are no error messages but your zombie is still not moving?
Is your model anchored?
Every part inside the mob needs to have the anchored property unticked
Otherwise your monster will be rooted to the spot, regardless of what code you write!
The humanoid MoveTo method automatically times out if the destination has not been reached after 8 seconds, therefore it will skip ahead to the next waypoint if the mob is too slow or the waypoints too far apart.
The easiest way to fix this is to simply place down more waypoints, so that even the slowest mob can always reach each waypoint within 8 seconds. (Remember to re-number the names of your waypoints if doing this)
Alternatively, for a more comprehensive solution you can overwrite this behaviour by re-calling the function. This code sample is from the roblox docs
local function moveTo(humanoid, targetPoint, andThen)
local targetReached = false
-- listen for the humanoid reaching its target
local connection
connection = humanoid.MoveToFinished:Connect(function(reached)
targetReached = true
connection:Disconnect()
connection = nil
if andThen then
andThen(reached)
end
end)
-- start walking
humanoid:MoveTo(targetPoint)
-- execute on a new thread so as to not yield function
task.spawn(function()
while not targetReached do
-- does the humanoid still exist?
if not (humanoid and humanoid.Parent) then
break
end
-- has the target changed?
if humanoid.WalkToPoint ~= targetPoint then
break
end
-- refresh the timeout
humanoid:MoveTo(targetPoint)
task.wait(6)
end
-- disconnect the connection if it is still connected
if connection then
connection:Disconnect()
connection = nil
end
end)
end
local function andThen(reached)
print((reached and "Destination reached!") or "Failed to reach destination!")
end
moveTo(script.Parent:WaitForChild("Humanoid"), Vector3.new(50, 0, 50), andThen)
Many scripters will suggest a quick modification by adding a repeat...until loop.
repeat
humanoid:MoveTo(waypoints[waypoint].Position)
until humanoid.MoveToFinished:Wait()
Personally I'm not a fan of this approach as I think you should avoid adding loops in your code where they are not needed.
Combining loops with events can easily introduce recursive problems if you don't know what you're doing.
Ultimately, the best option is to forego the humanoid entirely. The humanoid datamodel can be expensive on resources if you want to have lots of enemies and it's methods and functionality can often be clunky.
An alternative way to move models is using Lerp, to move the model a small amount each frame.
The basics of this I covered here: https://www.youtube.com/watch?v=R-xurNJtx7M
You can see the principles of this integrated into a more advanced project such as my doors monster: https://www.youtube.com/watch?v=fDIIv-XZbVY
Animations make your games more lively, without them something just doesn't feel right. Here are the most common issues.
In the TD tutorial, GnomeCode implemented the Animations so that it is played on the client via a LocalScript. These scripts run codes which are only visible to the client, not the server. We don't want the server to handle the Animations because they take up performance.
The obvious fix is pressing Play!
You can only use Animations owned by you or on the Marketplace Catalog. You cannot use Animations from others.
As of now, there are no practical methods of sharing Animations between users to users. However, you could upload a model containing the Animation and let the other user publish it to Roblox themselves.
An obvious fix would be to republish the animation with the right AnimationPriority. However, you can set them in the script as well by doing animationTrack.AnimationPriority = Enum.AnimationPriority.PriorityName
Here are the AnimationPriority for every animations in the TD tutorial:
Walk.AnimationPriority = Enum.AnimationPriority.Movement
Idle.AnimationPriority = Enum.AnimationPriority.Idle
Attack.AnimationPriority = Enum.AnimationPriority.Action
An alternative to 3. AnimationPriority, you can add an attribute called RbxLegacyAnimationBlending as a boolean and set it to true.
However, according to this DevForum Reply, you will lose some new animation engine features for it
Ep1-10: For players who have slow loading times. Sometimes, at the start of a wave, the first few mobs won't play animations.
This happens because the animation script only runs when it detects when a child is added in the mobs folder and because the
main script spawns in some mobs before that, the first few aren't animated.
Solution: You can loop through the mobs folder again and call the animate function when the local script is fully loaded in.
for _, mob in ipairs(workspace.Mobs:GetChildren()) do
animateMob(mob)
end
If you're having trouble with your player collisions I'd suggest changing CharacterAdded to CharacterAppearanceLoaded in your OnPlayerAdded script.
This should give the character a bit more time to load in before it tries to assign the collision groups.
CharacterAdded gets called the very instant the character begins loading. The only reason I get away with it in my video is because my character has very few components.
Since the tutorial, Roblox has deprecated
PhysicsService:SetPartCollisionGroup()
To fix this warning replace SetPartCollisionGroup with object.CollisionGroup = "CollisionGroupName"
After you are done with that you can remove PhysicsService from your script because it is no longer being used
Make sure to do this in Mob, Tower, onPlayerAdded and gameController scripts
When using :SetNetworkOwner(), you might come across the error:
Network Ownership API cannot be called on Anchored parts or parts welded to Anchored parts error
To solve this, check if any of the model's BasePart descendants have their Anchored property set to false. You could either select every descendants of the model in the Explorer or run some code in the Command Bar to loop through every descendants and set every BasePart.Anchored to false. I would recommend going for the latter.
Here's the code to run in the Command Bar (formatted to be readable), replace model with whatever way you wanna reference the model:
for _, instance in model:GetDescendants() do
if instance:IsA("BasePart") then
instance.Anchored = false
end
end
For convenience, you can also put this into your Tower script, just put it before you do :SetNetworkOwner(). However, it might slow down the game every time you place a new tower, especially if there are many children inside of the model.
An alternative solution would be to ditch :SetNetworkOwner() entirely and set every BasePart.Anchored to true. If you want to do this, you can no longer use BodyGyro as shown in the tutorial.
(GnomeCode fixed this problem in Ep7 at 14:00 Merging tower scripts, he moved the test script over to a module script and make it generalized)
In this Episode, GnomeCode uses a Union for his test tower, it's a BasePart, not a Model.
Many people use a Model as their test tower (which is not a BasePart!) There is no Model.Position, but you can use a Part as a replacement.
Check if your Model has a HumanoidRootPart as its child. If not, add a HumanoidRootPart, they are usually a Part with Part.Transparency = 1 at the center of the Model. You could set the Model.PrimaryPart to this Part.
After checking, you make a slight change the TowerTesting Script. Instead of tower.Position, change to tower.HumanoidRootPart.Position (similar to in Ep7) or tower.PrimaryPart.Position (I'd prefer this)
The towers I showed in my video were all R15 characters, however if you're using the classic R6 character shape, then hipheight is calculated slightly differently and cannot be used.
Instead you can calculate the height of torso from the floor, using their leg height.
Update your game controller local script with this new value for R6 humanoids only (You will still need the old formula for R15 models)
local y = result.Position.Y + towerToSpawn["Left Leg"].Size.Y + (towerToSpawn.PrimaryPart.Size.Y / 2)
(see below image for difference between the two character types)
This error occurs when you attempt to enter a number with decimal points into IntValue.Value
(e.g 0.3, 1.2, 4367.125812353289)
IntValue.Value only accepts integers, they are numbers without decimal points
(e.g 1, 3, 29, 62)
You can easily fix this by replacing IntValue with a NumberValue. After that, rename it to Cooldown or whatever name you called it and problem solved! Now you can use any numbers with decimal points
An example of what your Cooldown NumberValue would look like:
This one is actually a pretty simple fix
in Health (the module in ReplicatedStorage.Modules)
simply parent the newHealthBar to the model instead
so your old script:
local newHealthBar = script.HealthGui:Clone()
newHealthBar.Adornee = model:WaitForChild("Head")
newHealthBar.Parent = Players.LocalPlayer.PlayerGui:WaitForChild("Billboards")
becomes:
local newHealthBar = script.HealthGui:Clone()
newHealthBar.Adornee = model:WaitForChild("Head")
newHealthBar.Parent = model
That way it will automatically be deleted whenever the mob is destroyed
Sometimes you may find your first and last modes selecting the wrong target.
The targeting has a case where the incorrect target is selected lets imagine two mobs "MobA" and "MobB"
If MobA is 3 studs from the 5th waypoint and MobB is 4 studs away from the 6th waypoint
If MobA is considered first in the loop it will set best distance to 3 meaning that when it considers MobB it updates the bestWaypoint but fails to then update as it is a lower distance than the best distance.
This can be solved by switching the "First" and "Last" targeting modes to:
elseif mode == "First" then
if not bestWaypoint or mob.MovingTo.Value > bestWaypoint then
bestWaypoint = mob.MovingTo.Value
bestDistance = nil
end
if mob.MovingTo.Value == bestWaypoint and (not bestDistance or distanceToWaypoint < bestDistance) then
bestDistance = distanceToWaypoint
bestTarget = mob
end
elseif mode == "Last" then
if not bestWaypoint or mob.MovingTo.Value < bestWaypoint then
bestWaypoint = mob.MovingTo.Value
bestDistance = nil
end
if mob.MovingTo.Value == bestWaypoint and (not bestDistance or distanceToWaypoint > bestDistance) then
bestDistance = distanceToWaypoint
bestTarget = mob
end
that way the best distance is reset whenever a new best waypoint is set.
This one is pretty simple.
First go to your elevators ElevatorServer script and find
elevatorEvent.OnServerEvent:Connect(function(player)
After that add elevator near the player so it becomes
elevatorEvent.OnServerEvent:Connect(function(player, elevator)
(you need to do this for every ElevatorServer script)
After that go to your StarterGui then find Client script inside of Elevator ScreenGui and add
local elevatorTo
On top of your local script
Now go to
elevatorEvent.OnClientEvent:Connect(function(elevator)
and add a new line, on that line type
elevatorTo = elevator
Then add elevatorTo in
elevatorEvent:FireServer()
so it becomes
elevatorEvent:FireServer(elevatorTo)
Small typo made during this video.
All you need to do is replace this line from line 45 in Datastore script
data[player.UserId].Stars = stars
with this
data[player.UserId].Stars += stars
This has already been fixed in the download file for this series.