RoboCatz.com
About FLL | Navigation | Playbook | Strategy | Models | Techniques | RobotC | Programming | Robot News |

Roblox Drone Simulator

This lesson is a continuation of previous lessons in Roblox programming. If you are not familiar with how to create the genericShapes module, please review the other Roblox lessons first. For this lesson, you should start a new project. You will need to create two (2) module scripts in the ReplicatedStorage area. First, create a genericShapes module in your ReplicatedStorage area. Then copy-and-paste the code from the website into the genericShapes module. The source code for the genericShapes module is linked below. Then create a "drones" module script also in the ReplicatedStorage area. Rename the module script: "drones". The link to the code for that module is shown below.

2 Module Scripts

genericShapes Module (genericShapes-ModuleScript.htm)
Copy the code from this page into the genericShapes module in your program.

drones Module (drones-ModuleScript.htm)
Copy the code from this page into the drones module in your program.

The next step is to go to the Workspace in Roblox and add a Script. Rename that script: startingScript and paste the code in the example below.

Example:
--startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage")
local s = require(ReplicatedStorage.genericShapes)
local d = require(ReplicatedStorage.drones)

local Players = game:GetService("Players")
local TweenService = game:GetService("TweenService") 

local humanoidRootPart = nil
local compassParts = {}
compassParts = d.newCompass()
d.showingCompass(true)

Players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function(character)
		humanoidRootPart = character:WaitForChild("HumanoidRootPart")
		--local Character = player.Character
		--local description = humanoidRootPart:GetAppliedDescription()
		--description.Face = 128992838 -- 128992838 for Beast Mode; replace with "Face" or id-only
		--humanoidRootPart:ApplyDescriptionReset(description)
		--Character.Head.Decal.Texture = "http://www.roblox.com/asset/?id=7699174"
		--Character.Head.Color = Color3.new(0, 50, 0)

		local iteration = 1
		while humanoidRootPart do
			if d.showingCompass() and compassParts then				-- Draw compass
				d.moveTo2(compassParts, Vector3.new(humanoidRootPart.Position.X, 0.1, humanoidRootPart.Position.Z))
			end
			d.moveDrones(d.collection)
			--print("X: " .. tostring(math.round(humanoidRootPart.Position.X)) .. " Z: " .. tostring(math.round(-1*humanoidRootPart.Position.Z)))
			wait(0.3)
			iteration = iteration + 0.1
		end
	end)
end)
We will be working with this starting script.

Vectors

The direction of a vector indicates where it points in space. This direction can be represented by an arrow or a line segment. For example, if you imagine an arrow pointing from the center of a circle to some point along the edge of the circle, the direction of the vector corresponds to the degree on the circle that the arrow points towards.

The Compass

The drone module includes a compass to help visualize different vectors. This compass will appear below the Avatar and will move with the Avatar as it moves in the 3D world. Two lines of code (below) are needed to turn the compass on.

Example:
compassParts = d.newCompass()
d.showingCompass(true)

The Drone

In this drone module, the drones will be portrayed using a design similar to a quadcopter. A quadcopter drone has 4 small helicopter blades at each corner of the drone. In this example, the helicopter blades will be represented by 4 discs. I tried to get the discs to be transparent. But the transparency was lost when they were joined together as a single model.

A Drone Collection

Most of the examples in today's lesson only use 1 drone. However, it is possible for you to add multiple drones to your Roblox world. The drones are stored in an array identified as: d.collection[]. The code below shows how a for loop is used to store the new drones in the array known as d.collection. The variable i is being used as the array index. So, as the for loop loops through the numDrones, a new drone is added to the .collection using the coordinates of a circle around the origin (spawn) point.

If you decide to create multiple drones, please be advised that you will need to create one or more algorithms to control the drones. You should probably just try to master controlling one drone before you embark on a mission to control a dozen drones.

The example code below shows how to add a drone to the collection. Even if you are using just one drone, you still need to add it to the collection. This is because the "collection" of drones is moved at the same time (though not necessarily in the same directions or speed).

Example:
-- This draws the drone(s)
local numDrones = 1
local radiusOfCircle = 10
for i=1,numDrones do
	local xCoordinate = radiusOfCircle * math.cos(360*(i/numDrones) * math.pi/180)
	local zCoordinate = radiusOfCircle * math.sin(360*(i/numDrones) * math.pi/180)
	d.collection[i] = d.new( { x=xCoordinate, y=5, z=zCoordinate, color="gray", material="metal" } )
end
If you recall from the earlier lesson that drew the Colosseum, the math.sin() and math.cos() functions require input as radians. In this example, we will convert the degrees into radians by multiplying by math.pi/180. That's why the above example includes math.pi/180 in the two trig function arguments above.

Combining the Code Examples

To create a single drone in your Roblox world, use the following as your startingScript.

Example:
--startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage")
local s = require(ReplicatedStorage.genericShapes)
local d = require(ReplicatedStorage.drones)

local Players = game:GetService("Players")
local TweenService = game:GetService("TweenService") 

local blockSize = 64
local halfBlock = 32

local humanoidRootPart = nil
local compassParts = {}
compassParts = d.newCompass()
d.showingCompass(true)

Players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function(character)
		humanoidRootPart = character:WaitForChild("HumanoidRootPart")
		--local Character = player.Character
		--local description = humanoidRootPart:GetAppliedDescription()
		--description.Face = 128992838 -- 128992838 for Beast Mode; replace with "Face" or id-only
		--humanoidRootPart:ApplyDescriptionReset(description)
		--Character.Head.Decal.Texture = "http://www.roblox.com/asset/?id=7699174"
		--Character.Head.Color = Color3.new(0, 50, 0)

		local iteration = 1
		while humanoidRootPart do
			if d.showingCompass() and compassParts then				-- Draw compass
				d.moveTo2(compassParts, Vector3.new(humanoidRootPart.Position.X, 0.1, humanoidRootPart.Position.Z))
			end
			d.moveDrones(d.collection)
			--print("X: " .. tostring(math.round(humanoidRootPart.Position.X)) .. " Z: " .. tostring(math.round(-1*humanoidRootPart.Position.Z)))
			wait(0.3)
			iteration = iteration + 0.1
		end
	end)
end)

-- This draws the drone(s)
local numDrones = 1
local radiusOfCircle = 10
for i=1,numDrones do
	local xCoordinate = radiusOfCircle * math.cos(360*(i/numDrones) * math.pi/180)
	local zCoordinate = radiusOfCircle * math.sin(360*(i/numDrones) * math.pi/180)
	d.collection[i] = d.new( { x=xCoordinate, y=5, z=zCoordinate, color="gray", material="metal" } )
end

Driving Straight

This drone will only drive straight or turn. It does not have a "backward" function. You can create that one on your own if you want. To get it to move forward, you simply have to tell it the speed you want it to travel.

Example:
d.moveForward(30)  -- The number entered is the speed of the drone
If you want the drone to travel for a specific distance, you will need to use an additional function: d.encoderValue() to check how far the drone has moved. The combination of setting the speed and monitoring the distance is shown in the example below.

Example:
d.resetEncoder()                -- Sets the encoder to zero
d.moveForward(40)               -- Sets the speed to 40
while d.encoderValue() < 20 do  -- Checks the distance traveled
	task.wait(0.1)          -- Allows the drone to move between checks
end                             -- Loop back
d.stopMoving()                  -- Criteria has been met, stop moving
Try getting the drone to move various distances. Check the distances moved. The squares on the baseplate grid can be used to measure distance. Try getting the drone to fly at different speeds as well as distances.

In the above example, the d.resetEncoder() function will reset the encoder of the drone. Since we are only working with one drone at this time, you do not need to specify the drone index for this function. But, if you have created 10 drones and you want to reset the encoder for just drone #6, you would use the following: d.resetEncoder(6) to specify that you want the encoder for drone #6 to be reset. If you don't specify a drone index, the computer will use the default index value (which is 1 for drone #1).

If you examine this function in the drones module, you will see the following:

Example:
function drones.resetEncoder(...)
	local arguments = table.pack(...)
	local targetDroneIndex = 1  -- Default value
	if #arguments > 0 then targetDroneIndex = arguments[1] end
	drones.collection[targetDroneIndex]:SetAttribute("encoderValue", 0)
end
As you can see from this code, the targetDroneIndex will be the default value of 1 unless an argument was passed. If an argument was passed, then the targetDroneIndex will be whatever value was passed.

The d.encoderValue() is a function that returns the current value of the encoder. Each drone has an "encoder" which keeps track of how far the drone has traveled since the last "reset".

The d.stopMoving() function will stop the drone. If you have multiple drones, you can pass the drone index as an argument for this function. If no argument is specified, then the default drone will be stopped. The default drone is drone #1.

Accuracy in Distance Traveled

Did you notice that the drone travels farther than expected when traveling faster. This is because there is a lag in processing. It takes a few hundred milliseconds to process each action. Therefore, if you are traveling very fast, the command to stop the drone will not be completed exactly where you think it should be completed. The best way to deal with this is to slow the drone down as it gets nearer to the target value.

Write an algorithm that will slow the drone down when getting near to the criteria you are using to exit the while loop. For example:

Example:
d.resetEncoder()                -- Sets the encoder to zero
d.moveForward(40)               -- Sets the speed to 40
while d.encoderValue() < 20 do  -- Checks the distance traveled
	if d.encoderValue() > 15 then
		d.moveForward(10) -- Resets the speed to 10
	end
	task.wait(0.1)          -- Allows the drone to move between checks
end                             -- Loop back
d.stopMoving()                  -- Criteria has been met, stop moving
Now add a brick wall to the startingScript and see how close you can get the drone to the wall without touching it. See example below.

Example:
s.wall({x1=0, y1=0, z1=23, x2=20, y2=10, z2=23, thickness=1, material=Enum.Material.Brick, color="red"})
Currently the drone will pass through the wall if you want. I have not been able to get the drone to crash into the wall. Nor have I been able to get the .Touched:Connect(function()) to work with the interaction between drone and wall. Sorry.

I'm still trying to figure this out.


Turning

One function will make the drone turn: d.rotateBy(). This function sets the speed of the turning. The speed of the turning is passed as a number (or numeric value) into the function. For example, a larger number will make the robot turn faster than a smaller number. The basic usage of the function is shown below. Please note that "n" in the example below will need to be replaced by a number.

Example:
d.rotateBy( n )

Positive and Negative Values for Turning

Try entering positive values and negative values. What difference does the sign of the number make?

Example:
d.rotateBy( 10 )
Example:
d.rotateBy( -30 )

Asynchronous

Both the d.moveForward() and the d.rotateBy() are asynchronous functions which means that the program flows immediately to the next statement without waiting for the movement of the robot to commence or cease.

Example:
d.rotateBy( 30 )
d.rotateBy( -30 )
Because they are processed asynchronously if you have two d.rotateBy() functions next to each other in the program, only the second one will be executed because the value used for the second statement will overwrite the value of the first statement.

As with the previous example of driving straight, you can use the d.stopMoving() function to get the drone to stop moving.

What do you think will happen in the example code below? Remember, the d.moveForward() and the d.rotateBy() function are processed asynchronously.

Example:
d.rotateBy( 30 )
d.stopMoving()
Try this example code above. What does the drone do? Why?

What should we place after the d.rotateBy() to get the drone to rotate?

Example:
d.rotateBy( 30 )
s.sleepMilliseconds(5000)
d.stopMoving()
Sleeping for a specified amount of time will get the drone to turn. But how can we get it to turn a specific number of degrees?

There are several ways to do this. For example, you could reset the gyroSensor using d.resetGyroSensor() and then enter a while loop checking if the d.gyroSensorValue() has exceeded some criteria.

Today, we will be exploring a method of turning that uses vectors. Our goal will be to tell the drone to turn itself toward a vector that we specify. The drone will decide for itself whether it needs to turn one direction or the other to make the shortest turn necessary to achieve its goal.

For this exercise, we will create a function called: turnTowardVector(). The purpose of this function will be to give the drone a goal and allow the drone to determine the direction and the speed that it needs to turn in order to move toward the goal.

Creating this function will take a few steps which will be outlined in the next examples.

Example:
--[[
turnTowardVector()
Function returns the speed value for making the turn.
]]
function turnTowardVector(v) 
	local g = d.vectorValue()

end
This is the beginning of the steps to create the function. It doesn't do much at this point. We have created a comment to describe the function and created the function definition called: turnTowardVector() which receives a single number passed as the "target" vector (labeled: v) in the example.

Notice that a local variable g is assigned the value of d.vectorValue(). This is the vector that the drone is currently pointed in.

If you remember from earlier in this lesson, the Avatar has a compass which can be displayed using: d.showingCompass(true). The compass shows the vector values for different directions. As the Avatar turns around, you will see the different vector values which range from 0 to 360. The drone has a similar "built-in" compass that returns the same values. The drone's vector value (i.e., the vector that the drone is point towards) is returned with the function d.vectorValue().

So now we know the vector that we want the drone to turn toward (v) and the vector that the drone is currently pointing toward (g). Eventually we will be calculating the difference between these two vectors. However, before we do that, we need to get the drone to figure out which direction is the shortest. Consider the example below:

If the drone is currently pointing to the vector 90-degrees and we want to turn it toward the vector 60-degrees, we will need to turn the drone clockwise just 30 degrees. Or, we could turn the drone counter-clockwise 330 degrees. Obviously it would be better to just turn the 30 degrees.

What if the drone was pointed toward the 60-degree vector and we wanted to turn it toward the 90-degree vector? What direction should the drone turn and how much?

Both of these vectors are in the same quadrant of the compass.

But what if the vectors appear in different quadrants? For example, what if the drone is pointing toward the 10-degree vector and we want the drone to rotate toward the 350-degree vector?

The drone needs to figure out whether turning toward the right or turning toward the left is the shortest distance.

To determine the shortest distance we will calculate the absolute value of the difference between the two vectors. This will be done using: math.abs(v - g)

See example below:

Example:
--[[
turnTowardVector()
Function returns the speed value for making the turn.
The value returned is the difference between the targetVector and the observed gyro vector.
]]
function turnTowardVector(v) 
	local g = d.vectorValue()
	local distanceNotIncludingZero = math.abs(v-g)
	local distanceIncludingZeroPoint = 360 - distanceNotIncludingZero

end
If you look at the example here, you will see two vectors: 20-degrees and 160-degrees. The absolute value of their difference will not include the zero point (shaded in orange). The other area is 360 - the first area. This second area does include the zero point (shaded in blue).

At this point, the drone knows the size of the two areas. In this example, one of the areas is 140 (which is abs(20-160)) and the other area is 220 (which is 360-140). The drone now knows which of these is the shorter distance between the vectors.

The function will test this in an if statement:

As an exercise in calculating these values.

Use the compass to determine which distance is shorter (with or without the zero point).

Target = 125 Goal = 255
Target = 180 Goal = 315
Target = 103 Goal = 45
Target = 10 Goal = 219

Example:
--[[
turnTowardVector()
Function returns the speed value for making the turn.
The value returned is the difference between the targetVector and the observed gyro vector.
]]
function turnTowardVector(v) 
	local g = d.vectorValue()
	local distanceNotIncludingZero = math.abs(v-g)
	local distanceIncludingZeroPoint = 360 - distanceNotIncludingZero
	-- Determine which is the smaller value
	if distanceNotIncludingZero < distanceIncludingZeroPoint then

	else

	end

end

Direction to Turn

Now add a few variables to keep track of the direction and amount to turn.

If the distance "Not" including zero is the smallest of the two, then either subtract g from v or subtract v from g (whichever is the smallest).

Example:
--[[
turnTowardVector()
Function returns the speed value for making the turn.
The value returned is the difference between the targetVector and the observed gyro vector.
]]
function turnTowardVector(v) 
	local g = d.vectorValue()
	local turnDirection = 1
	local degreesToTurn = 0
	local distanceNotIncludingZero = math.abs(v-g)
	local distanceIncludingZeroPoint = 360 - distanceNotIncludingZero
	-- Determine which is the smaller value
	if distanceNotIncludingZero < distanceIncludingZeroPoint then
		if g < v then -- Turn counter-clockwise
			turnDirection = 1
			degreesToTurn = v - g
		else          -- Turn clockwise
			turnDirection = -1
			degreesToTurn = g - v
		end
	else

	end
end
Now if the distance "Including Zero" is the smallest of the two, then you will need to add the distance each vector is from the zero point.

Example:
--[[
turnTowardVector()
Function returns the speed value for making the turn.
The value returned is the difference between the targetVector and the observed gyro vector.
]]
function turnTowardVector(v) 
	local g = d.vectorValue()
	local turnDirection = 1
	local degreesToTurn = 0
	local distanceNotIncludingZero = math.abs(v-g)
	local distanceIncludingZeroPoint = 360 - distanceNotIncludingZero
	-- Determine which is the smaller value
	if distanceNotIncludingZero < distanceIncludingZeroPoint then
		if g < v then -- Turn counter-clockwise
			turnDirection = 1
			degreesToTurn = v - g
		else          -- Turn clockwise
			turnDirection = -1
			degreesToTurn = g - v
		end
	else
		if v < g then -- Turn counter-clockwise
			turnDirection = 1
			degreesToTurn = v + (360 - g)
		else          -- Turn clockwise
			turnDirection = -1
			degreesToTurn = g + (360 - v)
		end
	end
end

The Return Value

At this point, the drone knows the distance and the direction for all types of turns from any vector to any vector.

Example:
--[[
turnTowardVector()
Function returns the speed value for making the turn.
The value returned is the difference between the targetVector and the observed gyro vector.
The value to be returned is 'capped' at a maximum value using the math.min() function.  
As the observed vector gets closer to the target vector the speed of the turn will decrease.
To avoid having the function return zero for the speed, the math.max() function is used to give a minimum value to be returned.
]]
function turnTowardVector(v) 
	local g = d.vectorValue()
	local turnDirection = 1
	local degreesToTurn = 0
	local distanceNotIncludingZero = math.abs(v-g)
	local distanceIncludingZeroPoint = 360 - distanceNotIncludingZero
	-- Determine which is the smaller value
	if distanceNotIncludingZero < distanceIncludingZeroPoint then
		if g < v then -- Turn counter-clockwise
			turnDirection = 1
			degreesToTurn = v - g
		else          -- Turn clockwise
			turnDirection = -1
			degreesToTurn = g - v
		end
	else
		if v < g then -- Turn counter-clockwise
			turnDirection = 1
			degreesToTurn = v + (360 - g)
		else          -- Turn clockwise
			turnDirection = -1
			degreesToTurn = g + (360 - v)
		end
	end
	return math.min(40, math.max(degreesToTurn,2)) * turnDirection  -- calculate a range of 40 to 2 (positive or negative)
end

Using This Function

Since this function returns the speed and direction of the turn, just pass it to the d.rotateBy() function as in the following example.

Example:
d.rotateBy(turnTowardVector(newVector))
Again, remember that the d.rotateBy() function is asynchronous and therefore, you will need a while loop to check the amount the drone has rotated.

Example of Turning Around

Example:
local newVector = (d.vectorValue() + (135 + math.random()*90)) % 360
print("The new vector is: " .. tostring(newVector))
d.rotateBy(turnTowardVector(newVector))
while math.abs(newVector - (d.vectorValue() % 360) ) > 10 do
	d.rotateBy(turnTowardVector(newVector))
	task.wait(0.01)
end

Moving in a Square Pattern

Example:
for j=2,40 do
	local targetDegree = (j % 4) * 90
	d.stopMoving()
	local oldDistanceFromTarget = 999999
	local distanceFromTarget = 0
	print("Loop Counter: "..tostring(j).."; target: "..tostring(targetDegree).."; current: "..tostring(d.vectorValue()).."; distance from target = "..tostring(distanceFromTarget))
	task.wait(2)
	while oldDistanceFromTarget > distanceFromTarget or distanceFromTarget>10 do
		distanceFromTarget = math.abs(d.vectorValue() - targetDegree)
		--print("Distance from target = " .. tostring(distanceFromTarget))
		if oldDistanceFromTarget > distanceFromTarget then oldDistanceFromTarget = distanceFromTarget end
		d.rotateBy(turnTowardVector(targetDegree))
		task.wait(.2)
	end
	print("Done Turning")
	d.resetEncoder()
	print(d.getPosition())
	d.moveForward(60)
	while d.encoderValue() < 10 do
		print("EncoderValue is: " .. tostring(d.encoderValue()))
		print(d.getPosition())
		task.wait(0.2)
	end
	print("EncoderValue after loop is: " .. tostring(d.encoderValue()))
	print(d.getPosition())

end

The Complete Program For Moving in a Square Pattern

Example:
--startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage")
local s = require(ReplicatedStorage.genericShapes)
local d = require(ReplicatedStorage.drones)

local Players = game:GetService("Players")
local TweenService = game:GetService("TweenService") 

local blockSize = 64
local halfBlock = 32

local humanoidRootPart = nil
local compassParts = {}
Players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function(character)
		humanoidRootPart = character:WaitForChild("HumanoidRootPart")
		--local Character = player.Character
		--local description = humanoidRootPart:GetAppliedDescription()
		--description.Face = 128992838 -- 128992838 for Beast Mode; replace with "Face" or id-only
		--humanoidRootPart:ApplyDescriptionReset(description)
		--Character.Head.Decal.Texture = "http://www.roblox.com/asset/?id=7699174"
		--Character.Head.Color = Color3.new(0, 50, 0)

		local iteration = 1
		while humanoidRootPart do
			if d.showingCompass() and compassParts then				-- Draw compass
				d.moveTo2(compassParts, Vector3.new(humanoidRootPart.Position.X, 0.1, humanoidRootPart.Position.Z))
			end
			d.moveDrones(d.collection)
			--print("X: " .. tostring(math.round(humanoidRootPart.Position.X)) .. " Z: " .. tostring(math.round(-1*humanoidRootPart.Position.Z)))
			wait(0.3)
			iteration = iteration + 0.1
		end
	end)
end)

s.wall({x1=0, y1=0, z1=23, x2=20, y2=10, z2=23, thickness=1, material=Enum.Material.Brick, color="red"})

compassParts = d.newCompass()
--d.showingCompass(true)

-- This draws the drone(s)
local numDrones = 1
local radiusOfCircle = 10
for i=1,numDrones do
	local xCoordinate = radiusOfCircle * math.cos(360*(i/numDrones) * math.pi/180)
	local zCoordinate = radiusOfCircle * math.sin(360*(i/numDrones) * math.pi/180)
	d.collection[i] = d.new( { x=xCoordinate, y=5, z=zCoordinate, color="gray", material="metal" } )
end

--[[
turnTowardVector()
Function returns the speed value for making the turn.
The value returned is the difference between the targetVector and the observed gyro vector.
The value to be returned is 'capped' at a maximum value using the math.min() function.  
As the observed vector gets closer to the target vector the speed of the turn will decrease.
To avoid having the function return zero for the speed, the math.max() function is used to give a minimum value to be returned.
]]
function turnTowardVector(v) 
	local g = d.vectorValue()
	local turnDirection = 1
	local degreesToTurn = 0
	local distanceNotIncludingZero = math.abs(v-g)
	local distanceIncludingZeroPoint = 360 - distanceNotIncludingZero
	-- Determine which is the smaller value
	if distanceNotIncludingZero < distanceIncludingZeroPoint then
		if g < v then -- Turn counter-clockwise
			turnDirection = 1
			degreesToTurn = v - g
		else          -- Turn clockwise
			turnDirection = -1
			degreesToTurn = g - v
		end
	else
		if v < g then -- Turn counter-clockwise
			turnDirection = 1
			degreesToTurn = v + (360 - g)
		else          -- Turn clockwise
			turnDirection = -1
			degreesToTurn = g + (360 - v)
		end
	end
	return math.min(40, math.max(degreesToTurn,2)) * turnDirection  -- calculate a range of 40 to 2 (positive or negative)
end


for j=2,40 do
	local targetDegree = (j % 4) * 90
	d.stopMoving()
	local oldDistanceFromTarget = 999999
	local distanceFromTarget = 0
	print("Loop Counter: "..tostring(j).."; target: "..tostring(targetDegree).."; current: "..tostring(d.vectorValue()).."; distance from target = "..tostring(distanceFromTarget))
	task.wait(2)
	while oldDistanceFromTarget > distanceFromTarget or distanceFromTarget>10 do
		distanceFromTarget = math.abs(d.vectorValue() - targetDegree)
		--print("Distance from target = " .. tostring(distanceFromTarget))
		if oldDistanceFromTarget > distanceFromTarget then oldDistanceFromTarget = distanceFromTarget end
		d.rotateBy(turnTowardVector(targetDegree))
		task.wait(.2)
	end
	print("Done Turning")
	d.resetEncoder()
	print(d.getPosition())
	d.moveForward(60)
	while d.encoderValue() < 10 do
		print("EncoderValue is: " .. tostring(d.encoderValue()))
		print(d.getPosition())
		task.wait(0.2)
	end
	print("EncoderValue after loop is: " .. tostring(d.encoderValue()))
	print(d.getPosition())

end

Obstacle Course

Example:
--startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage")
local s = require(ReplicatedStorage.genericShapes)
local d = require(ReplicatedStorage.drones)

local Players = game:GetService("Players")
local TweenService = game:GetService("TweenService") 

local blockSize = 64
local halfBlock = 32

local humanoidRootPart = nil
local compassParts = {}
compassParts = d.newCompass()
--d.showingCompass(true)

Players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function(character)
		humanoidRootPart = character:WaitForChild("HumanoidRootPart")
		--local Character = player.Character
		--local description = humanoidRootPart:GetAppliedDescription()
		--description.Face = 128992838 -- 128992838 for Beast Mode; replace with "Face" or id-only
		--humanoidRootPart:ApplyDescriptionReset(description)
		--Character.Head.Decal.Texture = "http://www.roblox.com/asset/?id=7699174"
		--Character.Head.Color = Color3.new(0, 50, 0)

		local iteration = 1
		while humanoidRootPart do
			if d.showingCompass() and compassParts then				-- Draw compass
				d.moveTo2(compassParts, Vector3.new(humanoidRootPart.Position.X, 0.1, humanoidRootPart.Position.Z))
			end
			d.moveDrones(d.collection)
			--print("X: " .. tostring(math.round(humanoidRootPart.Position.X)) .. " Z: " .. tostring(math.round(-1*humanoidRootPart.Position.Z)))
			wait(0.3)
			iteration = iteration + 0.1
		end
	end)
end)

s.wall({x1=0, y1=0, z1=23, x2=20, y2=10, z2=23, thickness=1, material=Enum.Material.Brick, color="red"})
s.wall({x1=0, y1=0, z1=43, x2=20, y2=10, z2=43, thickness=1, material=Enum.Material.Brick, color="red"})
s.tube({ x1=-48, y1=3, z1=85, x2=-48, y2=3, z2=25, size=20, thickness=2, color="orange"})
s.circle({ x=-8, y=4, z=15, size=15, thickness=0.5, color="red", luminosityRange=1, luminosityHue=Color3.new(1,1,0)})
s.circle({ x=35, y=4, z=35, size=15, thickness=0.5, color="red", luminosityRange=1, luminosityHue=Color3.new(1,1,0), rotateBy=Vector3.new(0,45,0)})
s.circle({ x=45, y=4, z=45, size=15, thickness=0.5, color="red", luminosityRange=1, luminosityHue=Color3.new(1,1,0), rotateBy=Vector3.new(0,45,0)})
s.circle({ x=55, y=4, z=55, size=15, thickness=0.5, color="red", luminosityRange=1, luminosityHue=Color3.new(1,1,0), rotateBy=Vector3.new(0,45,0)})

-- This draws the drone(s)
local numDrones = 1
local radiusOfCircle = 10
for i=1,numDrones do
	local xCoordinate = radiusOfCircle * math.cos(360*(i/numDrones) * math.pi/180)
	local zCoordinate = radiusOfCircle * math.sin(360*(i/numDrones) * math.pi/180)
	d.collection[i] = d.new( { x=xCoordinate, y=5, z=zCoordinate, color="gray", material="metal" } )
end

--[[
turnTowardVector()
Function returns the speed value for making the turn.
The value returned is the difference between the targetVector and the observed gyro vector.
The value to be returned is 'capped' at a maximum value using the math.min() function.  
As the observed vector gets closer to the target vector the speed of the turn will decrease.
To avoid having the function return zero for the speed, the math.max() function is used to give a minimum value to be returned.
]]
function turnTowardVector(v) 
	local g = d.vectorValue()
	local turnDirection = 1
	local degreesToTurn = 0
	local distanceNotIncludingZero = math.abs(v-g)
	local distanceIncludingZeroPoint = 360 - distanceNotIncludingZero
	-- Determine which is the smaller value
	if distanceNotIncludingZero < distanceIncludingZeroPoint then
		if g < v then -- Turn counter-clockwise
			turnDirection = 1
			degreesToTurn = v - g
		else          -- Turn clockwise
			turnDirection = -1
			degreesToTurn = g - v
		end
	else
		if v < g then -- Turn counter-clockwise
			turnDirection = 1
			degreesToTurn = v + (360 - g)
		else          -- Turn clockwise
			turnDirection = -1
			degreesToTurn = g + (360 - v)
		end
	end
	return math.min(40, math.max(degreesToTurn,2)) * turnDirection  -- calculate a range of 40 to 2 (positive or negative)
end

Example Code for Obstacle Course

Example:
-- Turn
local newVector = 145
print("The new vector is: " .. tostring(newVector))
d.rotateBy(turnTowardVector(newVector))
while math.abs(newVector - (d.vectorValue() % 360) ) > 10 do
	print(math.abs(newVector - (d.vectorValue() % 360) ))
	d.rotateBy(turnTowardVector(newVector))
	task.wait(0.01)
end

-- Drive Straight
d.resetEncoder()                -- Sets the encoder to zero
d.moveForward(40)               -- Sets the speed to 40
local distance = 20
while d.encoderValue() < distance do  -- Checks the distance traveled
	if d.encoderValue() > distance-5 then
		d.moveForward(10) -- Resets the speed to 10
	end
	task.wait(0.1)          -- Allows the drone to move between checks
end                             -- Loop back
d.stopMoving()                  -- Criteria has been met, stop moving


-- Turn
newVector = 90
print("The new vector is: " .. tostring(newVector))
d.rotateBy(turnTowardVector(newVector))
while math.abs(newVector - (d.vectorValue() % 360) ) > 10 do
	print(math.abs(newVector - (d.vectorValue() % 360) ))
	d.rotateBy(turnTowardVector(newVector))
	task.wait(0.01)
end

-- Drive Straight
d.resetEncoder()                -- Sets the encoder to zero
d.moveForward(40)               -- Sets the speed to 40
distance = 80
while d.encoderValue() < distance do  -- Checks the distance traveled
	if d.encoderValue() > distance-5 then
		d.moveForward(10) -- Resets the speed to 10
	end
	task.wait(0.1)          -- Allows the drone to move between checks
end                             -- Loop back
d.stopMoving()                  -- Criteria has been met, stop moving


-- Turn
newVector = 180
print("The new vector is: " .. tostring(newVector))
d.rotateBy(turnTowardVector(newVector))
while math.abs(newVector - (d.vectorValue() % 360) ) > 10 do
	print(math.abs(newVector - (d.vectorValue() % 360) ))
	d.rotateBy(turnTowardVector(newVector))
	task.wait(0.01)
end

-- Drive Straight
d.resetEncoder()                -- Sets the encoder to zero
d.moveForward(40)               -- Sets the speed to 40
distance = 40
while d.encoderValue() < distance do  -- Checks the distance traveled
	if d.encoderValue() > distance-5 then
		d.moveForward(10) -- Resets the speed to 10
	end
	task.wait(0.1)          -- Allows the drone to move between checks
end                             -- Loop back
d.stopMoving()                  -- Criteria has been met, stop moving



-- Turn
newVector = 270
print("The new vector is: " .. tostring(newVector))
d.rotateBy(turnTowardVector(newVector))
while math.abs(newVector - (d.vectorValue() % 360) ) > 10 do
	print(math.abs(newVector - (d.vectorValue() % 360) ))
	d.rotateBy(turnTowardVector(newVector))
	task.wait(0.01)
end

-- Drive Straight
d.resetEncoder()                -- Sets the encoder to zero
d.moveForward(40)               -- Sets the speed to 40
distance = 74
while d.encoderValue() < distance do  -- Checks the distance traveled
	if d.encoderValue() > distance-5 then
		d.moveForward(10) -- Resets the speed to 10
	end
	task.wait(0.1)          -- Allows the drone to move between checks
end                             -- Loop back
d.stopMoving()                  -- Criteria has been met, stop moving
Road Race Track (https://robocatz.com/documents/RoadTrackSquare.rbxl)
Road Race Simulator

The link above should open a blank Roblox project with a pre-set road race track.

Add the genericShapes and the drones module scripts to this project.

For the startingScript, use the following:

Example:
local myDrone = 1
d.moveTo(Vector3.new(5, 4, 30))
d.resetGyroSensor(1, 90)
d.rotateBy(turnTowardVector(45))
while math.abs(45 - d.gyroSensorValue() ) > 5 do
	d.rotateBy(turnTowardVector(45))
	task.wait(0.2)
end
d.stopMoving()

while true do
	task.wait(.3)
	d.moveForward(90)
	-- attempt to stop on the line
	while d.roadSensor()>20 do
		task.wait(.2)
	end
	d.stopMoving()
	task.wait(1)
	local newVector = (d.gyroSensorValue() + (135 + math.random()*90)) % 360
	print("The new vector is: " .. tostring(newVector))
	d.rotateBy(turnTowardVector(newVector))
	while math.abs(newVector - (d.gyroSensorValue() % 360) ) > 10 do
		d.rotateBy(turnTowardVector(newVector))
		task.wait(0.2)
	end
	print("Done turning")
	d.stopMoving()
	d.moveForward(120)
	d.resetEncoder()
	while d.encoderValue() < 90 do
		task.wait(0.2)
	end
	d.stopMoving()
end

Line Following

Example:
--startingScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage")
local s = require(ReplicatedStorage.genericShapes)
local d = require(ReplicatedStorage.drones)

local Players = game:GetService("Players")
local TweenService = game:GetService("TweenService") 

local blockSize = 64
local halfBlock = 32

local humanoidRootPart = nil
local compassParts = {}
Players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function(character)
		humanoidRootPart = character:WaitForChild("HumanoidRootPart")
		--local Character = player.Character
		--local description = humanoidRootPart:GetAppliedDescription()
		--description.Face = 128992838 -- 128992838 for Beast Mode; replace with "Face" or id-only
		--humanoidRootPart:ApplyDescriptionReset(description)
		--Character.Head.Decal.Texture = "http://www.roblox.com/asset/?id=7699174"
		--Character.Head.Color = Color3.new(0, 50, 0)

		local iteration = 1
		while humanoidRootPart do
			if d.showingCompass() and compassParts then				-- Draw compass
				d.moveTo2(compassParts, Vector3.new(humanoidRootPart.Position.X, 0.1, humanoidRootPart.Position.Z))
			end
			d.moveDrones(d.collection)
			--print("X: " .. tostring(math.round(humanoidRootPart.Position.X)) .. " Z: " .. tostring(math.round(-1*humanoidRootPart.Position.Z)))
			wait(0.3)
			iteration = iteration + 0.1
		end
	end)
end)

--[[
turnTowardVector()
Function returns the speed value for making the turn.
The value returned is the difference between the targetVector and the observed gyro vector.
The value to be returned is 'capped' at a maximum value using the math.min() function.  
As the observed vector gets closer to the target vector the speed of the turn will decrease.
To avoid having the function return zero for the speed, the math.max() function is used to give a minimum value to be returned.
]]
function turnTowardVector(v) 
	local g = d.vectorValue()
	local turnDirection = 1
	local degreesToTurn = 0
	local distanceNotIncludingZero = math.abs(v-g)
	local distanceIncludingZeroPoint = 360 - distanceNotIncludingZero
	-- Determine which is the smaller value
	if distanceNotIncludingZero < distanceIncludingZeroPoint then
		if g < v then -- Turn counter-clockwise
			turnDirection = 1
			degreesToTurn = v - g
		else          -- Turn clockwise
			turnDirection = -1
			degreesToTurn = g - v
		end
	else
		if v < g then -- Turn counter-clockwise
			turnDirection = 1
			degreesToTurn = v + (360 - g)
		else          -- Turn clockwise
			turnDirection = -1
			degreesToTurn = g + (360 - v)
		end
	end
	return math.min(40, math.max(degreesToTurn,2)) * turnDirection  -- calculate a range of 40 to 2 (positive or negative)
end

local myDrone = 1
task.wait(3)
d.moveTo(Vector3.new(5, 4, 30))
d.resetGyroSensor(1, 90)
d.stopMoving()
task.wait(.3)
-- Find the center
d.moveForward(90)
-- attempt to stop on the line
while d.roadSensor()>5 do
	if d.roadSensor() < 40 then d.moveForward(30) end
	if d.roadSensor() < 20 then d.moveForward(10) end
	task.wait(.1)
end
d.stopMoving()
task.wait(1)
d.moveForward(30)
-- attempt to stop on the line
while d.roadSensor()<30 do
	if d.roadSensor() > 20 then d.moveForward(30) end
	if d.roadSensor() > 30 then d.moveForward(10) end
	task.wait(.1)
end
d.stopMoving()
task.wait(1)
d.rotateBy(turnTowardVector(0)) -- Negative values turn the drone clockwise
while math.abs(d.gyroSensorValue() ) > 5 do
	d.rotateBy(turnTowardVector(0))
	task.wait(0.2)
end
d.stopMoving()
task.wait(1)
local gain = 1/3
local targetSensorValue = 50
while true do
	d.moveForward(30)
	local sensorValue = d.roadSensor()
	print("Road Sensor: " .. tostring(sensorValue) .. "; Rotate by: " .. tostring((targetSensorValue-sensorValue)*gain) )
	d.rotateBy((targetSensorValue-sensorValue)*gain)
	task.wait(0.15 * math.abs((targetSensorValue-sensorValue)/5)) -- Adjust the amount of time for steering based on the distance from the target sensor value
	d.moveForward(20)
	task.wait(5)
end

Your Turn


End of Document