There are a few issues with the code.
The raycasting is incorrect as you’re giving the wrong vector as a direction. You also shouldn’t use if/else for checking the raycasts because if one of the rays doesn’t hit anything, all proceeding rays are ignored.
If the score is equal to the time the car exists, you should instead use os.clock() to time it instead of a while loop.
Running the car without a delay (other than the wait() every 0.1 seconds) results in way too much processing than necessary, causing the low framerate. This can be replaced with a single wait().
To speed up testing, the car should move and turn a bit faster.
Those are the only changes I made in the code to keep it as close to the original as possible. The one other thing I did was rename the front bumper of the car to “MainBumper” so you were raycasting from the correct thing. I wasn’t quite able to get the thing to turn in both directions correctly but that’s more due to the settings involved; figuring out the best network settings is as difficult as the math involved in the library.
local HttpService = game:GetService("HttpService")
local Package = game:GetService("ReplicatedStorage").NNLibrary
local Base = require(Package.BaseRedirect)
local FeedforwardNetwork = require(Package.NeuralNetwork.FeedforwardNetwork)
local ParamEvo = require(Package.GeneticAlgorithm.ParamEvo)
local Momentum = require(Package.Optimizer.Momentum)
--If the training/testing is intensive, we will want to setup automatic wait() statements
--in order to avoid a timeout. This can be done with os.clock().
local clock = os.clock()
-- Function to activate scripts inside of car
local function ActivateScripts(Model)
for _,Item in pairs(Model:GetDescendants()) do
if Item:IsA("Script") then
Item.Disabled = false
end
end
end
-- Function that casts rays from the car in five directions and returns the distances in a table
local function getRayDistances(car)
-- Setup filterTable for rays to ignore (all parts of car)
local filterTable = {}
for _, v in pairs(car:GetDescendants()) do
if v:IsA("BasePart") then
table.insert(filterTable, v)
end
end
-- Setup RayCastParams
local rayCastParams = RaycastParams.new()
rayCastParams.IgnoreWater = true
rayCastParams.FilterType = Enum.RaycastFilterType.Blacklist
rayCastParams.FilterDescendantsInstances = {car}
local distance = 50
local bumper = car.MainBumper
local bumperPos = bumper.Position
local bumperCFrame = bumper.CFrame
local function getDirection(vec)
local start = bumperPos
local finish = (bumperCFrame * CFrame.new(vec)).Position
return (finish - start).Unit * distance
end
local FrontRay = workspace:Raycast(bumperPos, getDirection(Vector3.new(0,0,-1)), rayCastParams)
local FrontLeftRay = workspace:Raycast(bumperPos, getDirection(Vector3.new(-1,0,-1)), rayCastParams)
local FrontRightRay = workspace:Raycast(bumperPos, getDirection(Vector3.new(1,0,-1)), rayCastParams)
local LeftRay = workspace:Raycast(bumperPos, getDirection(Vector3.new(-1,0,0)), rayCastParams)
local RightRay = workspace:Raycast(bumperPos, getDirection(Vector3.new(1,0,0)), rayCastParams)
local pos1 = 1
local pos2 = 1
local pos3 = 1
local pos4 = 1
local pos5 = 1
if FrontRay then
pos1 = ((FrontRay.Position - bumperPos).Magnitude) / distance
end
if FrontLeftRay then
pos2 = ((FrontLeftRay.Position - bumperPos).Magnitude) / distance
end
if FrontRightRay then
pos3 = ((FrontRightRay.Position - bumperPos).Magnitude) / distance
end
if LeftRay then
pos4 = ((LeftRay.Position - bumperPos).Magnitude) / distance
end
if RightRay then
pos5 = ((RightRay.Position - bumperPos).Magnitude) / distance
end
--print("FrontRay: " .. tostring(pos1))
--print("FrontLeftRay: " .. tostring(pos2),FrontLeftRay)
--print("FrontRightRay: " .. tostring(pos3),FrontRightRay)
--print("LeftRay: " .. tostring(pos4),LeftRay)
--print("RightRay: " .. tostring(pos5),RightRay)
return {front = pos1, frontLeft = pos2, frontRight = pos3, left = pos4, right = pos5}
end
-- Settings for genetic algorithm
local geneticSetting = {
--The function that, when given the network, will return it's score.
ScoreFunction = function(net)
local car = game:GetService("ServerStorage").Car:Clone()
car.Parent = workspace
-- Setup car
ActivateScripts(car)
car.RemoteControl.MaxSpeed = 70
car.RemoteControl.Torque = 100
car.RemoteControl.Throttle = 1
car.RemoteControl.TurnSpeed = 50
local startTime = os.clock()
local bool = true
for _, v in pairs(car:GetDescendants()) do
if v:IsA("BasePart") then
v.Touched:Connect(function(hit)
if hit.Parent == workspace.Obstacles then
bool = false
end
end)
end
end
while bool do
local distances = getRayDistances(car)
local output = net(distances)
local steeringDir = output.steerDirection
--print(output.steerDirection)
car.LeftMotor.DesiredAngle = steeringDir
car.RightMotor.DesiredAngle = steeringDir
wait()
end
local score = os.clock() - startTime
print("Exit score: "..math.floor(score*100)/100)
car:Destroy()
return score
end;
--The function that runs when a generation is complete. It is given the genetic algorithm as input.
PostFunction = function(geneticAlgo)
local info = geneticAlgo:GetInfo()
print("Generation "..info.Generation..", Best Score: "..info.BestScore)
end;
PercentageToKill = 0.8;
ParameterNoiseRange = 0.01;
ParameterMutateRange = 0.1;
}
local feedForwardSettings = {
HiddenActivationName = "LeakyReLU";
OutputActivationName = "Tanh";
}
-- Create a new network with 5 inputs, 2 layers with 4 nodes each and 1 output "steerDirection" (default settings)
local tempNet = FeedforwardNetwork.new({"front", "frontLeft", "frontRight", "left", "right"}, 2, 4, {"steerDirection"}, feedForwardSettings) --FeedforwardNetwork.newFromSave(game.ServerStorage.NetworkSave.Value)
-- Create ParanEvo with the tempNet template, population size (20) and settings
local geneticAlgo = ParamEvo.new(tempNet, 30, geneticSetting)
-- Run the algorithm one generation
geneticAlgo:ProcessGenerations(100)
-- Get the best network in the population
local net = geneticAlgo:GetBestNetwork()
local save = net:Save()
local stringSave = HttpService:JSONEncode(save)
print(stringSave)
game.ServerScriptService.NetworkSave.Value = stringSave
--Total number of test runs.
local totalRuns = 0
--The number of runs that were deemed correct.
local wins = 0
--[[
-- Run algorithm 100 times
for i = totalRuns, 100 do
local car = workspace.Car
-- Setup filterTable (all parts of car)
local filterTable = {}
for _, v in pairs(car:GetDescendants()) do
if v:IsA("BasePart") then
table.insert(filterTable, v)
end
end
-- Setup RayCastParams
local rayCastParams = RaycastParams.new()
rayCastParams.IgnoreWater = true
rayCastParams.FilterType = Enum.RaycastFilterType.Blacklist
rayCastParams.FilterDescendantsInstances = filterTable
local bumperPos = car.Bumper.Position
local FrontRay = workspace:Raycast(bumperPos, bumperPos + Vector3.new(0, 0, -100), rayCastParams)
local FrontLeftRay = workspace:Raycast(bumperPos, bumperPos + Vector3.new(-100, 0, -100), rayCastParams)
local FrontRightRay = workspace:Raycast(bumperPos, bumperPos + Vector3.new(100, 0, -100), rayCastParams)
local LeftRay = workspace:Raycast(bumperPos, bumperPos + Vector3.new(-100, 0, 0), rayCastParams)
local RightRay = workspace:Raycast(bumperPos, bumperPos + Vector3.new(100, 0, 0), rayCastParams)
local pos1 = 1
local pos2 = 1
local pos3 = 1
local pos4 = 1
local pos5 = 1
if FrontRay then
pos1 = ((FrontRay.Position - bumperPos).Magnitude)/100
elseif FrontLeftRay then
pos2 = ((FrontLeftRay.Position - bumperPos).Magnitude)/100
elseif FrontRightRay then
pos3 = ((FrontRightRay.Position - bumperPos).Magnitude)/100
elseif LeftRay then
pos4 = ((LeftRay.Position - bumperPos).Magnitude)/100
elseif RightRay then
pos5 = ((RightRay.Position - bumperPos).Magnitude)/100
end
net({front = pos1, frontLeft = pos2, frontRight = pos3, left = pos4, right = pos5})
-- Automatic wait
if os.clock()-clock >= 0.1 then
clock = os.clock()
wait()
--print("Testing... "..(x+400)/(8).."%")
end
end
print(wins/totalRuns*(100).."% correct!")]]
I think I’ll try my hand at making a self-driving car example similar to the one ScriptOn did. Would be a much better demo than the cubic function stuff.