Area Music Player Script! [Open Source]

Hi, I’m @RuizuKun_Dev, I’d like to share my creation and contribute to the community!


Showcase

Behold~ my Area Music Player Script in action!


How it works

This works by detecting when the player is in a Part then plays a Sound

Features:

  • Tweens in and out for nice effect
  • Easy to add/remove/change Sounds
  • Simple to move Area Parts
  • Handles CharacterAdded and Humanoid.Died
  • Solved reliability issue with TouchEvents
  • Light weight (only 121 lines of clean code)
  • Effortless integration

this was designed especially for Non-Programmers, meaning that this Module is easy to use for everyone


How to use?

  1. Create a Sound Instance
  2. Create a Part (this will act as an Area detector)
  3. Put that part under Sound from step #1
  • Put this Script anywhere you like but preferably somewhere this Script can only runs once

Source Code

Code

added comments and minor improvements

--| SERVICES:
local PlayerS = game:GetService("Players")
	local plr = PlayerS.LocalPlayer
		local Cha;
			local Hum;
				local RootPart;
				
local TweenS = game:GetService('TweenService')
	local TweenInfo_New = TweenInfo.new
	
--| VARIABLES:
local Musics = script.Musics
Musics.Parent = game.SoundService

local currentMusic;

--| FUNCTIONS:

-- Character Handling:

local HumDied_Connection, Humanoid_Died;

function Humanoid_Died()
	HumDied_Connection:Disconnect(); HumDied_Connection = nil
	if currentMusic then
		currentMusic()
	end
end

local function Character_Added(Character)
	--[[
		Make sure that this doesn't run twice when Character_Added() is called exclisively @line 64 if CharacterAdded:Connect Fires
		however we could create another function and call that instead (then assign nil)
		because the if statement becomes useless after the first time
	--]]
	if (not Cha) then
		Cha = Character; Hum = Cha:WaitForChild('Humanoid'); RootPart = Hum.RootPart
		HumDied_Connection = Hum.Died:Connect(Humanoid_Died)
	end
end

local function Character_Removing(Character)
	Cha:Destroy(); Hum:Destroy();
	Cha, Hum = nil
end

local function isPointInPart_Fn(Point, Part)
	local relPos = Part.CFrame:PointToObjectSpace(Point)
	return math.abs(relPos.X) < Part.Size.X*.5 and math.abs(relPos.Y) < Part.Size.Y*.5 and math.abs(relPos.Z) < Part.Size.Z*.5
end

local function isAlive(humanoid)
	return humanoid and humanoid.Health > 0
end

--| SCRIPTS:

plr.CharacterAdded:Connect(Character_Added);
--[[
	We call Character_Added() exclusively because CharacterAdded:Connect might not Fire
--]]
Character_Added(plr.Character or plr.CharacterAdded:Wait());
plr.CharacterRemoving:Connect(Character_Removing);

-- Area Music:

for _,Sound in ipairs(Musics:GetChildren()) do
	
	local isInPart
	
	local part = Sound.Part
	
	local touched_Fn, touchEnded_Fn
	local touched_Ev, touchEnded_Ev = v.Part.Touched, v.Part.TouchEnded
	local touched_Connection, touchEnded_Connection
	
	local playTween = TweenS:Create(Sound, TweenInfo_New(1,0,0), {Volume = 5,})
	local stopTween = TweenS:Create(Sound, TweenInfo_New(2,0,1), {Playing = false, Volume = 0,})
	local diedTween = TweenS:Create(Sound, TweenInfo_New(2,0,1), {PlaybackSpeed = 0, Volume = .1,})
	
	local function died_Fn() -- Handles when the player dies
		diedTween:Play()
		isInPart = nil
		touchEnded_Connection:Disconnect() -- stop detecting touched
		touched_Connection = touched_Ev:Connect(touched_Fn) -- start detecting touchEnded
	end
	
	-- Play
	function touched_Fn(touchedPart)
		-- isAlive | make sure that the Humanoid is Alive
		-- isInPart | acts as a Debounce and State
		if isAlive(Hum) and (not isInPart) and touchedPart == RootPart then
			isInPart = true
			touched_Connection:Disconnect() -- stop detecting touched
			touchEnded_Connection = touchEnded_Ev:Connect(touchEnded_Fn) -- start detecting touchEnded
			Sound.PlaybackSpeed = 1
			Sound:Play()
			stopTween:Cancel()
			playTween:Play()
			currentMusic = died_Fn
		end
	end
	
	-- Stop
	function touchEnded_Fn(touchedPart)
		if isInPart and touchedPart == RootPart then
			if (not isPointInPart_Fn(RootPart.Position,part)) then
				isInPart = nil
				touchEnded_Connection:Disconnect()  -- stop detecting touchEnded
				touched_Connection = touched_Ev:Connect(touched_Fn) -- start detecting touched
				playTween:Cancel()
				stopTween:Play()
				currentMusic = nil
			end
		end
	end
	
	touched_Connection = touched_Ev:Connect(touched_Fn)
	
	part.Parent = workspace
	
	--[[
		You might want to do
		part.Transparency = 1
		part.CanCollide = false
		part.Anchored = true
		... etc
	--]]
end

return nil

– might actually be better to do Tween:Pause() instead of Tween:Cancel()
– but not doing Tween:Pause() nor Tween:Cancel() could be better
– I haven’t done enough testing on those so let me know if you get to


Resouces

  • Uncopylocked:
  • Take as Model:


have fun developing!!

103 Likes

Nice, I really like it! Keep up bro. :+1:

5 Likes

This is actually very nice, I’ve been looking around for something similar to this and I couldn’t seem to find anyone who did anything like it, props to you man :slightly_smiling_face:.

3 Likes

as a future idea, allowing multiple audios to play for the same area, random, so that each time you enter it is a different song
also, for some reason I keep getting this error

[Requested module experienced an error while loading](rbxopenscript://www.dummy.com/dummy?scriptGuid=%7B727781D7-3228-4274-8BED-E6F29B5693FB%7D&gst=2#1)
3 Likes

I would use region3 personally because it is much easier to use.

2 Likes

@Bloxrrey, Interesting that you mentioned that, Region3 is very tedious and not Non-Programmer friendly, it’s a lot harder to set up than just using Parts, I can’t see how it’s easier to use.

  • You can’t Rotate it
  • You need to use a while loop and then go through all the parts inside the Region
  • You need to set it up correctly with a lot of restrictions
  • The API is tedious and hard to understand
  • Using TouchEvent is more Efficient and Effective in this case

if you meant using Zone+ or some other Module, I rather not share something claimed as my own creation by using someone else’s resource


I don’t know what that error means @Wertyhappy27 but the suggestions are good

If you are requiring by Id you can’t this Module isn’t meant for that.

6 Likes

Just to add on, Region3 is more and more intensive for larger and larger regions. Ideally you would use Region3 to index a small area for multiple instances when a Touched event isn’t reliable; local music playing should really be enforced by the server since it doesn’t affect other players, thus Touched is pretty safe to use.


How does your method account for rotation? All I see is a comparison of rectangular dimensions. Although I don’t really see rotation being useful for something like this anyways, better to keep it simple too. (EDIT: this is wrong, read below for clarification)

Regardless this is a very clean module, and I like the smooth transitions you implemented.

Using CFrame does account for rotation, but when you are checking if the point is in the part, you are only using the position of the point and the size of the part.

i.e., you are only using position data from relPos to check. You could expand it by checking the orientation data from the CFrame but like I said above, it’s not really useful.

Edit:

Nevermind I’m just being dumb as usual, PointToObjectSpace automatically takes into account the orientation and applies it to position (like a transformation matrix). Thanks @RedcommanderV2

1 Like

It actually does work with rotation (I just tested it), the position of the point it uses is relative to the part’s cframe (using PointToObjectSpace).

For an unrotated part the relative position would look something like this
https://gyazo.com/7a97441d883502e0bb86c1d7f77374ff
(Check the output, only the y part of the position changes)

For a rotated part it’d look something like this
https://gyazo.com/804c9d10b1d4e6c2782fc8647cfd71a0
(Both the y and z parts now change)

I’m not good with cframes though so I hope that the gifs explain

2 Likes

This actually helped me a lot. I didn’t specifically need this, but the source code taught me something about advanced scripting. Thank you.

1 Like

Better way of putting it, sound regions. This is pretty nice, well done.

Does this support non-rectangular music areas?
Can I use Unions or spheres?

Woah that death sound was super cool. How did you do that?

it is from the module script that is the parent of the script that has all of the sounds and main code

@Wertyhappy27
You can’t require a LocalScript, only ModuleScript.


@dispeller
Nope, but you can use this:

local function IsPointInPart_Fn(Point, Part) -- Cylinder
    local relPos = Part.CFrame:PointToObjectSpace(Point)
    local distFromCenter = Vector2_New(relPos.Y, relPos.Z).Magnitude
    local radius = Part.Size.Y*.5
    return Math_Abs(relPos.X) < Part.Size.X*.5 and distFromCenter < radius
end

credits to my mentor


You could also use GetTouchingParts.

Personally, the first time I made an area music system, I used Region3.

I looped this every 5 seconds. That way, each piece of music played for at least 5 seconds. Kinda prevents breaking immersion
function UpdateRegion()

	local inRegion = false
	
	for i,v in pairs(game.ReplicatedStorage.BorderBlocks:GetChildren()) do
	
		local min = v.Position - (0.5 * v.Size)
		local max = v.Position + (0.5 * v.Size)
		local region = Region3.new(min, max)
		local whitelist = {Player.Character}
		local parts = workspace:FindPartsInRegion3WithWhiteList(region,
														whitelist, 100)
		--warn(#parts)
		for partIndex, part in pairs(parts) do
			--print('PART NAME!'..v.Name)
			if part.Parent == Player.Character then
				inRegion = true
				if regionCurrentlyInside ~= v.Name then
					regionCurrentlyInside = v.Name
					print'CHANGED REGION!'
				end
			end
			
		end
	
	end
	
	if not inRegion then
		if regionCurrentlyInside ~= 'Outside' then
			regionCurrentlyInside = 'Outside'
			print'went outside'
		end
	end

end

There was also this module called Zone+ that might be useful.

Lots of ways to do this.
Cool module!

I noticed that in post #6

I also mentioned that using Region3 is tedious and not Non-Programmer friendly, you can refer back to post #6 for more details.

indeed there are multiple ways of achieving the same thing~


1 Like

If region 3 is “tedious”, isn’t it your job to make it easier? I wouldn’t recommend this to anyone, .touched event is unreliable.

If I were you, I would use region 3 and get players to define zones using parts, then calculate region3 zone for them. Not so hard is it?

Despite the comments about using region 3 and about using TouchedEvent not being reliable, I thank you for this resource that can improve my scripting abilities, continue doing what you do best :pray:.

Also is there a way to turn this into a music player where you can just insert the song id and play different types of music such as a party setting etc and how would you go about doing that?

1 Like

I added it how you had it set up in your game, i have no idea what is wrong