Thanks for showing interest in this project everyone! Don’t forget to leave a like
I have published important final optimizations to this project which are very important.
For those interested here is the source code! A copy and paste to your version would suffice if you needed to update to the final version. Feel free to rewrite it and use the tech for other projects.
--!native
local splashing=false
local Character=game.Players.LocalPlayer.Character
local Hum=Character.Humanoid
local Root = Character:WaitForChild("HumanoidRootPart")
local rate=.0333
local writetime=os.time()
local refreshrate=20
local params = RaycastParams.new()
task.synchronize()
params.FilterDescendantsInstances = {workspace.Terrain}--{Character, workspace:FindFirstChild("Enemys"), workspace:FindFirstChild("NPCS"), workspace:FindFirstChild("GroundItems"), workspace:FindFirstChild("Houses")}
params.FilterType=Enum.RaycastFilterType.Include
local lastpos=nil
local function multnumseq(sequence,Scale)
task.desynchronize()
local numberKeypoints2 = {}
for i = 1, #sequence.Keypoints do
local currKeypoint = sequence.Keypoints[i]
table.insert(numberKeypoints2, NumberSequenceKeypoint.new(currKeypoint.Time,currKeypoint.Value*Scale))
end
task.synchronize()
return NumberSequence.new(numberKeypoints2)
end
local function performRaycast(origin, direction, params)
return workspace:Raycast(origin, direction, params)
end
local function setupRaycastParams()
local timer=os.time()
if timer>writetime+refreshrate then
writetime=timer
params = RaycastParams.new()
task.synchronize()
params.FilterDescendantsInstances = {workspace.Terrain}--{Character, workspace:FindFirstChild("Enemys"), workspace:FindFirstChild("NPCS"), workspace:FindFirstChild("GroundItems"), workspace:FindFirstChild("Houses")}
params.FilterType=Enum.RaycastFilterType.Include
task.desynchronize()
end
return params
end
local function detecter(region)
local material = workspace.Terrain:ReadVoxels(region, 4)
local check=false
for i,v in material do
if v then
if i~="Size" then
for t,o in v do
for p,l in o do
if l==Enum.Material.Water then--and l~=Enum.Material.Air then
check=true
return check
elseif l~=Enum.Material.Water and l~=Enum.Material.Air then
return false
end
end
end
end
end
end
return check
end
local function isWaterInVoxel(position)
-- Define the region around the position
local region = Region3.new(position-Vector3.new(2,2,2), position + Vector3.new(2,2,2)):ExpandToGrid(4)
return detecter(region)
end
local Splash=script:WaitForChild("SplashObject").Value:Clone()
local Current=script:WaitForChild("CurrentObject").Value
local Ts = game:GetService('TweenService')
local PartsIN = {}
local splashs={}
local function RunWater(Splish,index,weld,scale)
if weld then
weld=weldmotor(Character.PrimaryPart,Splish)
end
local origin=Splish.Attachment.Splash.Size
local origin2=Splish.Attachment.Mist.Size
if scale~=1 then
Splish.Attachment.Splash.Size=multnumseq(origin,scale)
Splish.Attachment.Mist.Size=multnumseq(origin2,scale)
Splish.Attachment.Splash.Speed=NumberRange.new(2.5*scale)
Splish.Attachment.Splash.Lifetime=NumberRange.new(1*scale)
Splish.Attachment.Mist.Speed=NumberRange.new(5*scale)
end
Splish.Attachment.Splash.Enabled = true
Splish.Attachment.Mist.Enabled = true
splashs[index].Weld=weld
task.wait(0.35)
if weld then
weld.Enabled=false
weld:Destroy()
splashs[index].Weld=nil
Splish.Anchored=true
end
Splish.Attachment.Splash.Enabled = false
Splish.Attachment.Mist.Enabled = false
task.wait(0.25)
if scale~=1 then
Splish.Attachment.Splash.Size=origin
Splish.Attachment.Mist.Size=origin2
Splish.Attachment.Splash.Speed=NumberRange.new(2.5)
Splish.Attachment.Mist.Speed=NumberRange.new(5)
end
splashs[index].Running=false
end
local Roothalf= -(Root.Size.Y / 2)
local lengthvector=-Hum.HipHeight--(Character.LeftUpperLeg.Size.Y+Character.LeftLowerLeg.Size.Y+Character.LeftFoot.Size.Y)-3
local Debris=game:GetService("Debris")
local currents={}
Hum:GetPropertyChangedSignal("HipHeight"):ConnectParallel(function()
lengthvector=-Hum.HipHeight
end)
local trans
for i=1, 12 do
local obj=Current:Clone()
obj.Parent=game.ReplicatedStorage
trans=obj.Beam.Transparency
-- obj.Anchored=false
currents[i]={Running=false,Object=obj,}
local obj=Splash:Clone()
obj.Parent=game.ReplicatedStorage
--trans=obj.Beam.Transparency
-- obj.Anchored=false
splashs[i]={Running=false,Object=obj}
end
local function getsplash()
for i,v in splashs do
if v.Running==false then
v.Running=true
return v,i
end
end
return nil
end
local height=math.min(20,not Character.Humanoid.UseJumpPower and Character.Humanoid.JumpHeight or Character.Humanoid.JumpPower/9)
if not Character.Humanoid.UseJumpPower and Character.Humanoid.JumpHeight then
Hum:GetPropertyChangedSignal("JumpHeight"):ConnectParallel(function()
height=math.min(20,not Character.Humanoid.UseJumpPower and Character.Humanoid.JumpHeight or Character.Humanoid.JumpPower/9)
end)
else
Hum:GetPropertyChangedSignal("JumpPower"):ConnectParallel(function()
height=math.min(20,not Character.Humanoid.UseJumpPower and Character.Humanoid.JumpHeight or Character.Humanoid.JumpPower/9)
end)
end
local splashsounds={
["Small"]={"9120584650","9119481281","9117942473","9117940939"},
["Medium"]={"9119482201"},
["Large"]={"9119477732"},
}
rbxasset="rbxassetid://"
local soundeb=false
local function RunEffect(Cframe,scale,welds)
--print("Splish Splash")
local Splisher,index=getsplash()
if Splisher then
local Splish=Splisher.Object
if welds then
Splish.Anchored=false
end
Splish.Parent=workspace
local scale=scale
if scale==nil then scale=1 end
Splish.Size = Vector3.new(.01, .5, .01)
Splish.CFrame= Cframe--Vector3.new(Pos.X,Splash.Position.Y,Pos.Z)
Splish.Decal.Transparency = 0.125
coroutine.wrap(RunWater)(Splish,index,welds,scale)
--local height=math.min(20,not Character.Humanoid.UseJumpPower and Character.Humanoid.JumpHeight or Character.Humanoid.JumpPower/9)
--print(height)
if soundeb==false then
soundeb=true
local SoundId=(height<8 and splashsounds.Small[math.random(1,#splashsounds.Small)]) or (height<12 and splashsounds.Medium[1]) or splashsounds.Large[1]
local Sound=Splish.Sound
Sound.SoundId=rbxasset..SoundId
Sound.Volume = math.random(25,35)*.01
Sound.PlaybackSpeed = math.random(90,110)*.01
Sound.Pitch = math.random(80,120)*.01
Sound:Play()
task.delay(Sound.TimeLength*.25,function()
soundeb=false
end)
end
local timer=height*.1
local T1 = Ts:Create(Splish,TweenInfo.new(timer,Enum.EasingStyle.Linear),{Size=Vector3.new(height*1, .5, height*.9167)*scale})
local T2 = Ts:Create(Splish.Decal,TweenInfo.new(timer,Enum.EasingStyle.Linear),{Transparency=1})
T1:Play()
T2:Play()
end
end
local running=false
local function EffectTracker(once,jump)
--local params = setupRaycastParams()
local Root=Character.HumanoidRootPart
local Roothalf= -Root.Size.Y/2--not jump and -(Root.Size.Y or -(Root.Size.Y)
local lastpos=nil
repeat
--print("Detecting Water")
local Rootpos=Root.Position
if Rootpos~=lastpos then
--lastpos=Rootpos
task.synchronize()
local result = performRaycast(Rootpos+Vector3.new(0,Roothalf,0), Vector3.new(0,lengthvector,0), params)
task.desynchronize()
--local otherResult = performRaycast(Root.Position, Root.CFrame.LookVector * 2, params)
local Yoffset=Rootpos.Y + Roothalf
--print(result)
if result then--and not otherResult then
local hitPart = result.Instance
--print(hitPart)
if (hitPart.Name=="Terrain") then--or ((Vector3.new(0, Yoffset, 0) - Vector3.new(0, hitPart.Position.Y + hitPart.Size.Y / 2, 0)).magnitude < 2)) then
local construct=CFrame.new(result.Position)--+(result.Normal))
task.synchronize()
if isWaterInVoxel(construct.Position) then
--print("Successful Splash!")
RunEffect(CFrame.new(Character.LeftFoot.Position.X,construct.Position.Y,Character.LeftFoot.Position.Z))
task.desynchronize()
task.wait(rate)
return
--elseif once then
-- return
else
local watersurface=getWaterSurface(result.Position)
if watersurface then
RunEffect(CFrame.new(Character.LeftFoot.Position.X,watersurface,Character.LeftFoot.Position.Z))
task.desynchronize()
task.wait(rate)
end
-- print("Failed splash")
end
--return
end
end
else task.wait(rate)
end
task.wait(rate)
until splashing==false --or running)-- or Bars.Flying.Value
end
local function tweenemitter(v,Scale,duration)
task.spawn(function()
local base=v.Transparency
local frame=(2/30)
local timer=0
local numofframes=duration/frame
local dif=Scale-1
repeat
v.Transparency=multnumseq(base,1+(dif*(timer/numofframes)))
task.wait(frame)
timer=timer+frame
until timer>=duration
end)
end
function weldmotor(Root1,Root2)
local w=Instance.new("Motor6D")
w.Part0,w.Part1 = Root1,Root2
w.C0 = Root2.CFrame:toObjectSpace(Root1.CFrame):inverse()
w.Parent = Root1
w.Name=Root2.Name.."Joint"
return w
end
local function animatecurrent(tbl,index)
local currentobj=tbl.Object
local beam=currentobj.Beam
local attachment=currentobj.Attachment1
local start={Width1=math.max(2,Character.Humanoid.WalkSpeed*.5),Width0=Character.LowerTorso.Size.X}
local timer=Character.Humanoid.WalkSpeed*.125
local ti=TweenInfo.new(timer)
--tweenemitter(beam,5,.5)
--local beamorigintr=beam.Transparency
beam.Transparency=trans
beam.Transparency=multnumseq(beam.Transparency,5)
for t,property in start do
beam[t]=property
end
local vari=beam.Transparency
--task.spawn(function()
for i=1, 12 do
beam.Transparency=multnumseq(vari,math.max(.2,(15-i)/15))
task.wait(.0333)
end
--task.delay(,function()
--task.wait(math.max(.1,timer-(12*.033)))
--end)
-- tweenemitter(beam,.2,.3)
local origin=attachment.CFrame
task.delay(.25,function() Ts:Create(attachment,ti,{CFrame=attachment.CFrame-attachment.CFrame.Position}):Play()
end)
--local goal={Width1=math.min(75,Character.Humanoid.WalkSpeed*.15),Width0=1}
local goal={Width1=0,Width0=Character.LowerTorso.Size.X}
local tw=Ts:Create(beam,ti,goal)
tw:Play()
tw.Completed:Wait()
beam.Transparency=trans
for i=1, 30 do
beam.Transparency=multnumseq(trans,math.max(1,i*.2))
task.wait(.0333)
end
--currentobj.Parent=game.ReplicatedStorage
beam.Transparency=trans
attachment.CFrame=origin--attachment.CFrame:ToWorldSpace(CFrame.new(6, 0, 0))
currents[index].Running=false
-- end
end
function getWaterSurface(position)
local rayOrigin = position + Vector3.new(0, height, 0) -- Start the ray above the position
local rayDirection = Vector3.new(0, -10, 0) -- Cast the ray downwards
local result = workspace:Raycast(rayOrigin, rayDirection, params)
if result and result.Material == Enum.Material.Water then
return result.Position.Y
end
return nil -- No water surface found at the given position
end
local function getcurrent()
for i,v in currents do
if v.Running==false then
return v,i
end
end
return nil
end
local weld=false
local pos=Character.PrimaryPart.Position
local debounce2=false
local count=0
local debounce=false
local bottom=Roothalf+lengthvector
Hum.Seated:ConnectParallel(function()
running=false
splashing=false
end)
Hum.Died:Connect(function()
for t,o in currents do
o.Object:Destroy()
currents[t]=nil
end
for t,o in splashs do
o.Object:Destroy()
splashs[t]=nil
end
end)
Hum.Jumping:ConnectParallel(function()
running=false
--print("Splashing!")
splashing=true
--task.wait(rate)
swimming=false
--EffectTracker()
-- task.wait(rate)
EffectTracker()
for i,v in splashs do
if v.Weld then
task.synchronize()
v.Weld:Destroy()
v.Object.Anchored=true
--v.Parent=game.ReplicatedStorage
end
end
for i,v in currents do
if v.Weld then
task.synchronize()
v.Weld:Destroy()
v.Object.Anchored=true
--v.Parent=game.ReplicatedStorage
end
end
end)
Hum.FreeFalling:ConnectParallel(function()
splashing=true
running=false
swimming=false
EffectTracker()
end)
Hum.Swimming:ConnectParallel(function()
if swimming==false then
running=false
swimming=true
EffectTracker(true)
end
end)
Hum.Running:ConnectParallel(function()
running=true
swimming=false
if debounce==false then--and getWaterSurface(Character.PrimaryPart.Position) then
debounce=true
task.desynchronize()
repeat
--RunEffect(Current.CFrame,.35)
if pos~=Character.PrimaryPart.Position then
--count=0
pos=Character.PrimaryPart.Position
local interest=Character.PrimaryPart.CFrame
local direction=(interest:ToWorldSpace(CFrame.new(0,bottom,-2))-interest.Position).Position
task.synchronize()
local result = performRaycast(interest.Position, direction, params)
if result and result.Instance and result.Instance.Name=="Terrain" then
if isWaterInVoxel( interest:ToWorldSpace(CFrame.new(0,bottom+2,0)).Position) then
--splashing=true
task.desynchronize()
--splashing=false
local construct=CFrame.new(result.Position)--+(result.Normal))
local lookdirection=interest:ToWorldSpace(CFrame.new(0,0,-5)).Position
local Currenter,index= getcurrent()
local Currentss={}
local watersurface=getWaterSurface(result.Position)
Currentss.CFrame=CFrame.new(Vector3.new(interest.Position.X,watersurface and watersurface+.15 or construct.Position.Y+.15,interest.Position.Z),Vector3.new(lookdirection.X,construct.Position.Y,lookdirection.Z))
if Currenter and debounce2==false then
debounce2=true
if currents[index].Running==false then
local Current=Currenter.Object
task.synchronize()
if Current.Anchored then Current.Anchored=false end
Current.CFrame=Currentss.CFrame
Current.Parent=Character
if running==false then running=true
RunEffect(Current.CFrame)
end
currents[index].Running=true
local weld=weldmotor(Character.PrimaryPart,Current)
currents[index].Weld=weld
task.spawn(function()
animatecurrent(Currenter,index)
weld:Destroy()
currents[index].Weld=nil
end)
task.delay(.6,function()
debounce2=false
end)
task.wait(.1)
end
end
task.synchronize()
--if splashing==false then
RunEffect(Currentss.CFrame,.5,true)
--end
else
--print("No water")
--splashing=false
for i,v in currents do
if v.Weld then
task.synchronize()
v.Weld.Enabled=false
v.Weld:Destroy()
v.Object.Anchored=true
--v.Parent=game.ReplicatedStorage
end
end
--Current.Parent=game.ReplicatedStorage
end
else
for i,v in currents do
if v.Weld then
task.synchronize()
v.Weld.Enabled=false
v.Weld:Destroy()
v.Object.Anchored=true
--v.Parent=game.ReplicatedStorage
end
end
-- Current.Parent=game.ReplicatedStorage
end
else
--count+=1
--if count>=10 then
--splashing=false
for i,v in currents do
if v.Weld then
task.synchronize()
v.Weld.Enabled=false
v.Weld:Destroy()
v.Object.Anchored=true
--v.Parent=game.ReplicatedStorage
end
end
--end
--running=false
end
task.wait(.1)
until running==false
debounce=false
task.desynchronize()
end
splashing=false
--splashing=false
end)
--Can be in a seperate Script
local DetectTerrainWater = true
local UnderwaterReverb = true
local DetectPartWater = true
local Blur = game.Lighting:FindFirstChild("Blur") or Instance.new("BlurEffect", game.Lighting)
Blur.Name="Blur"
Blur.Size=10
Blur.Enabled = false
local ColorCorrection = game.Lighting:FindFirstChild("ColorCorrection") or Instance.new("ColorCorrectionEffect", game.Lighting)
ColorCorrection.Name="ColorCorrection"
ColorCorrection.Enabled = false
ColorCorrection.TintColor = Color3.fromRGB(99, 151, 213)
ColorCorrection.Contrast = 0.5
ColorCorrection.Brightness = .75
ColorCorrection.Saturation = 0.6
local UnderwaterAmbienceSound = game.Players.LocalPlayer.PlayerGui:FindFirstChild("UnderwaterAmbienceSound") or Instance.new("Sound", workspace.CurrentCamera)
UnderwaterAmbienceSound.Name = "UnderwaterAmbienceSound"
UnderwaterAmbienceSound.SoundId = "rbxassetid://4626145950"
if not UnderwaterAmbienceSound.IsLoaded then
UnderwaterAmbienceSound.Loaded:Wait()
end
UnderwaterAmbienceSound.Volume = 0
UnderwaterAmbienceSound.Parent=game.Players.LocalPlayer.PlayerGui
UnderwaterAmbienceSound.Looped = true
UnderwaterAmbienceSound:Play()
local AirBubbles=game.Players.LocalPlayer.Character:WaitForChild("Head"):FindFirstChild("AirBubble") or script.AirObject.Value.AirBubble:Clone()
AirBubbles.Parent=game.Players.LocalPlayer.Character:WaitForChild("Head")
AirBubbles.Bubble.Enabled=false
local state=false
local LastReverb = nil
local constant= Vector3.new(4, 4, 4)
workspace.CurrentCamera:GetPropertyChangedSignal("CFrame"):ConnectParallel(function()
local WaterFound = false
if DetectTerrainWater then
local pos=workspace.CurrentCamera.CFrame.Position+(constant*.5)
local region = Region3.new(pos, pos+constant)
local materials, occupancies = workspace.Terrain:ReadVoxels(region:ExpandToGrid(4), 4)
local size = materials.Size
for x = 1, size.X, 1 do
for y = 1, size.Y, 1 do
for z = 1, size.Z, 1 do
if materials[x][y][z].Name == "Water" then
WaterFound = true
break
end
end
end
end
end
--if WaterFound and AirBubbles.Bubble.Enabled==false then
--
-- task.delay(3,function() AirBubbles.Bubble.Enabled=true end)
-- end
if WaterFound then
local torsowater= WaterFound and AirBubbles.Bubble.Enabled==false and isWaterInVoxel(Character.HumanoidRootPart.Position)
if torsowater then
--
task.synchronize()
AirBubbles.Bubble.Enabled=true
task.desynchronize()
--elseif isWaterInVoxel(Character.HumanoidRootPart.Position) and not isWaterInVoxel(Character.HumanoidRootPart.Position+constant) then
--running=true
--swimefx(nil)
end
end
if WaterFound and state==false then
state=true
task.synchronize()
if UnderwaterReverb then
if game.SoundService.AmbientReverb ~= Enum.ReverbType.UnderWater then LastReverb = game.SoundService.AmbientReverb end
game.SoundService.AmbientReverb = Enum.ReverbType.UnderWater
end
UnderwaterAmbienceSound.Volume = math.random(25,35)*.01
UnderwaterAmbienceSound.PlaybackSpeed = math.random(90,110)*.01
UnderwaterAmbienceSound.Pitch = math.random(80,120)*.01
ColorCorrection.Enabled = true
Blur.Enabled = true
task.desynchronize()
elseif state==true and not WaterFound then
state=false
task.synchronize()
AirBubbles.Bubble.Enabled=false
if UnderwaterReverb then
game.SoundService.AmbientReverb = LastReverb or Enum.ReverbType.NoReverb
end
UnderwaterAmbienceSound.Volume = 0
ColorCorrection.Enabled = false
Blur.Enabled = false
task.desynchronize()
end
end)
The final updates are the result of implementing this into various projects and fixing any edge case issues I find so if you find any bugs please share them!