This for example is a model that trains a car to drive on a road, with waypoints for score,
the score is assigned correctlly, the cars can freely steer, the rays work correctlly, gaining score is extremelly easy, and possible, as the first generation is often the best, anyone can try it out with this code or the place file, any help is much appreciated
-- Same seed to hopefully have the same results
math.randomseed(1)
local PhysicsService = game:GetService("PhysicsService")
local ServerScriptService = game:GetService("ServerScriptService")
local ServerStorage = game:FindFirstChild("ServerStorage")
local RunService = game:GetService("RunService")
local HttpService = game:GetService("HttpService")
local PhysicsService = game:GetService("PhysicsService")
local Debris = game:GetService("Debris")
local Package = ServerScriptService["NNLibrary-2.0"]
local Base = require(Package.BaseRedirect)
local FeedforwardNetwork = require(Package.NeuralNetwork.FeedforwardNetwork)
local ParamEvo = require(Package.GeneticAlgorithm.ParamEvo)
local Momentum = require(Package.Optimizer.Momentum)
local NetworkSave = ServerStorage.NetworkSave
----------------<<MAIN SETTINGS>>---------------------------------------------------------------
local generations = 20
local population = 50
--[[ The delay between the car inputs and outputs, the smaller, the more decisions it can make at once,
but .1 is more than sufficent ]]
local carReactionTime = .2
-- We dont want them to bump into each other, so we make a collision group for them in witch they ignore themselfs
local CarsString = "Cars"
PhysicsService:CreateCollisionGroup(CarsString)
PhysicsService:CollisionGroupSetCollidable(CarsString,CarsString,false)
if workspace:FindFirstChild("Car") then
workspace.Car.Parent = ServerStorage
end
local function FindRandomSpot(Car,Max_X,Max_Z)
local CurrentCFrame
local function Attempt()
task.wait(.1)
local RandomX = math.random(-Max_X,Max_X)
local RandomZ = math.random(-Max_Z,Max_Z)
local Height = Car.PrimaryPart.Position.Y
local Cframe,Size = Car:GetBoundingBox()
local ChosenCFrame = CFrame.new(workspace.SpawnLocation.Position+Vector3.new(RandomX,Height,RandomZ))
local OverlapInfo = OverlapParams.new()
OverlapInfo.FilterType = Enum.RaycastFilterType.Whitelist
OverlapInfo.FilterDescendantsInstances = {workspace.Map}
local Parts = workspace:GetPartBoundsInBox(Cframe,Size,OverlapInfo)
if #Parts == 0 then
CurrentCFrame = ChosenCFrame
else
Attempt()
end
end
Attempt()
return CurrentCFrame
end
--- < DEBUG > ---
local function CastBeam(Origin,Direction)
local CenterPoint = Origin + Direction/2--Center
local beam = Instance.new("Part")--Beam
beam.Parent = workspace
beam.Anchored = true
beam.CanCollide = false
beam.BrickColor = BrickColor.new("Really red")
beam.Transparency = .8
--Actually important
beam.CFrame = CFrame.new(CenterPoint,Origin)--CFrame
beam.Size = Vector3.new(.1,.1,Direction.magnitude)--Size
Debris:AddItem(beam,carReactionTime)
end
local function PlaceDot(Position)
local dot = Instance.new("Part")--Beam
dot.Parent = workspace
dot.Anchored = true
dot.CanCollide = false
dot.CanQuery = false
dot.BrickColor = BrickColor.new("Really red")
dot.Position = Position
dot.Size = Vector3.new(.5,.5,.5)
dot.Shape = Enum.PartType.Ball
Debris:AddItem(dot,carReactionTime)
end
-- < RAYCASTING > --
local function CastRay(Origin,Direction,Folder)
local Rayinfo = RaycastParams.new()
Rayinfo.FilterType = Enum.RaycastFilterType.Whitelist
Rayinfo.FilterDescendantsInstances = {Folder}
local Raycast = workspace:Raycast(Origin,Direction,Rayinfo)
if Raycast then
if Raycast.Instance then
-- Returns the distance between the origin and hit position
local Distance = (Origin-Raycast.Position).Magnitude
-- Debugging Examples:
--CastBeam(Origin,Direction.Unit*Distance)
--PlaceDot(Raycast.Position)
return Distance
end
end
--[[ If it doesnt find anything, just return the magnitude of the direction, its kinda as if the ai always had a fog around
its a way to tell the ai when there arent any obstacles in its view distance ]]
return Direction.Magnitude
end
local setting = {
HiddenActivationName = "LeakyReLU";
OutputActivationName = "Sigmoid";
PercentageToKill = 0.5;
PercentageToMutate = 0.3;
MutateBestNetwork = true;
PercentageOfCrossedToMutate = 0.5;
}
local geneticSetting = {
-- We want all cars to run at the same time, but with a small delay for performance
RunEntirePopulationAtOnce = true;
DelayBetweenPopulationRuns = 0.2;
ScoreFunction = function(net,index)
local score = 0
local steps = 0
local startTime = os.clock()
local waypoints = {}
local input = {}
local Hit = false
-- Clone the Car from our desired location (Dont put inside walls when in workspace)
local Car = ServerStorage.Car:Clone()
-- Require the automator, witch makes changed when we change the car propieties
require(Car.VehiclePropieties.Automator)()
for _,v in pairs(Car:GetChildren()) do
if v:IsA("BasePart") then
PhysicsService:SetPartCollisionGroup(v,CarsString)
end
end
-- Lets mark the index
Car.PrimaryPart.Attachment.Tag.TextLabel.Text = index
Car.Parent = workspace
-- Set the CFrame to a random spot that doesnt collide with walls with the FindRandomSpot function
-- The last 2 paramaters express the random distribution when finding a spot , the center is the workspace.SpawnLocation
local Cframe = FindRandomSpot(Car,5,5)
Car:SetPrimaryPartCFrame(CFrame.new(Cframe.Position))
local Wheels = {Car.Wheel1,Car.Wheel2,Car.Wheel3,Car.Wheel4}
for _,Wheel in pairs(Wheels) do
Wheel.Touched:Connect(function(p)
if p.Parent == workspace.Map then
Hit = true
end
end)
end
-- Stop if it hits something or if too much time has passed, the more score, the more time it can waste
while Hit == false and steps < 30 + score*5 do
steps += 1
local TurningAngle = Car.VehiclePropieties.TurningAngle
-- Directions
local Forward = Car.PrimaryPart.CFrame.LookVector
local Right = Car.PrimaryPart.CFrame.RightVector
local CloseRight = Forward + Right/4
local Left = -Car.PrimaryPart.CFrame.RightVector
local CloseLeft = Forward + Left/4
-- How far the ai's rays will see
local Amplitude = 100
local ray1 = CastRay(Car.PrimaryPart.Position,Forward*Amplitude,workspace.Map)
local ray2 = CastRay(Car.PrimaryPart.Position,Right*Amplitude,workspace.Map)
local ray3 = CastRay(Car.PrimaryPart.Position,CloseRight*Amplitude,workspace.Map)
local ray4 = CastRay(Car.PrimaryPart.Position,Left*Amplitude,workspace.Map)
local ray5 = CastRay(Car.PrimaryPart.Position,CloseLeft*Amplitude,workspace.Map)
-- Input --
input = {
ray1 = ray1;
ray2 = ray2;
ray3 = ray3;
ray4 = ray4;
ray5 = ray5;
TurningAngle = TurningAngle.Value;
Speed = Car.VehiclePropieties.Speed.Value/Car.VehiclePropieties.MaxSpeed.Value;
}
-- Output --
local output = net(input)
local SpeedOutput = output["Speed"]
if SpeedOutput >= 0.4 and SpeedOutput <= 1 then
Car.VehiclePropieties.Speed.Value = Car.VehiclePropieties.MaxSpeed.Value
elseif SpeedOutput >= 0 and SpeedOutput <= 0.4 then
Car.VehiclePropieties.Speed.Value = Car.VehiclePropieties.MaxSpeed.Value/2
end
local TurningOutput = output["TurningAngle"]
local Degrees = (TurningOutput)*360
if Degrees > 180 then
Degrees = -(360-Degrees)
end
TurningAngle.Value = Degrees
-- Collisions --
for _,v in pairs(Car:GetChildren()) do
if v:IsA("Part") then
local OverlapInfo = OverlapParams.new()
OverlapInfo.FilterType = Enum.RaycastFilterType.Whitelist
OverlapInfo.FilterDescendantsInstances = {workspace.Map}
local Parts = workspace:GetPartBoundsInBox(v.CFrame,v.Size*1.1,OverlapInfo)
if #Parts > 0 then
Hit = true
end
end
end
for _,v in pairs(Car:GetChildren()) do
if v:IsA("Part") then
local OverlapInfo = OverlapParams.new()
OverlapInfo.FilterType = Enum.RaycastFilterType.Whitelist
OverlapInfo.FilterDescendantsInstances = {workspace.Waypoints}
local Parts = workspace:GetPartsInPart(v,OverlapInfo)
if #Parts > 0 then
for _,w in pairs(Parts) do
if not table.find(waypoints,w) then
score += 1
table.insert(waypoints,w)
break
end
end
end
end
end
task.wait(carReactionTime)
end
Car:Destroy()
return score
end;
PostFunction = function(geneticAlgo)
local info = geneticAlgo:GetInfo()
warn("Generation ".. info.Generation-1 ..", Best Score: ".. (info.BestScore/#workspace.Waypoints:GetChildren())*100 .."%")
-- Lets save the best network at the end of each generation
local save = geneticAlgo:GetBestNetwork():Save()
NetworkSave.Value = save
end;
}
----------------<<END OF MAIN SETTINGS>>---------------------------------------------------------------
local tempNet
if ServerScriptService.PlaySaveFirst.Value == true and string.len(NetworkSave.Value) > 0 then
tempNet = FeedforwardNetwork.newFromSave(NetworkSave.Value)
geneticSetting.ScoreFunction(tempNet,1)
end
tempNet = FeedforwardNetwork.new({"ray1","ray2","ray3","ray4","ray5","TurningAngle","Speed"}, 3, 6, {"TurningAngle","Speed"},setting)
local geneticAlgo = ParamEvo.new(tempNet,population,geneticSetting)
geneticAlgo:ProcessGenerations(generations)
4/15/2022 Here is the final version, with a brand new look and a few improvements and more documentation, have fun!
Car NNs - for devforum.rbxl (97.5 KB)